Skip to content

Espresso: enforce calldata-only DA post-fork (Least Authority Suggestion 4) - #482

Open
philippecamacho wants to merge 1 commit into
celo-rebase-18from
espresso-enforce-calldata-only-da
Open

Espresso: enforce calldata-only DA post-fork (Least Authority Suggestion 4)#482
philippecamacho wants to merge 1 commit into
celo-rebase-18from
espresso-enforce-calldata-only-da

Conversation

@philippecamacho

Copy link
Copy Markdown
Collaborator

Context

Least Authority audit Suggestion 4: the Celo fault-proof host (celo-kona) does not implement the L1Blob preimage hint, yet authenticated blob batches were still accepted by the derivation pipeline. A blob batch landing in the inbox would be accepted by full nodes but stall fault-proof execution at its L1 block. The spec addressed this with DEC-op-026 (calldata-only DA), but only as a documented operational restriction — nothing in the code enforced it.

What this PR does

Enforces the calldata-only restriction at two layers:

Derivation pipeline (consensus-level, op-node/rollup/derive)

  • From Espresso activation onward (IsEspresso(ref.Time), including the auth-enforcement grace window), dataAndHashesFromTxs drops blob-carrying inbox transactions before any authorization check — authenticated or not.
  • Post-Espresso derivation therefore never requests blob preimages, so fault-proof execution can no longer be stalled by a blob batch. An authenticated blob batch now simply contributes no data, on full nodes and in the proof program alike (op-node and the Go op-program share this code).
  • Pre-fork blocks keep upstream semantics unchanged — no historical re-derivation risk; the change activates exactly at the already-coordinated fork boundary.

Batcher startup guard (operational, op-batcher)

  • checkEspressoDataAvailability refuses to start the batcher with --data-availability-type=blobs|auto when EspressoTime is scheduled. Without this, a misconfigured (fallback) batcher would have every blob batch silently ignored by verifiers after activation, stalling the safe head.
  • This makes the blob-pair headroom check in checkFallbackAuthConfirmations and the serialized blob fallback-auth path unreachable in practice; they are kept as defense in depth (can be removed in a follow-up if preferred).

Tests

  • authenticated blob tx rejected: blob DA unsupported post-fork — a fully event-authenticated blob batch is dropped and no blob preimages are requested.
  • mixed calldata+blob block: only the calldata batch accepted.
  • Fork-boundary walk: pre-fork blob batches keep upstream sender-auth acceptance; from activation onward (incl. grace window) they are dropped, with the empty L1 mock asserting no receipt scanning pre-enforcement.
  • TestCheckEspressoDataAvailability — batcher guard matrix (scheduled/unscheduled × calldata/blobs/auto).

go test ./op-node/rollup/... ./op-batcher/... passes; op-node, op-program, op-batcher build.

Cross-repo note

celo-kona has its own Rust implementation of this derivation stage and needs the mirrored change (drop blob inbox txs post-Espresso in its data source); companion PR to follow there. The spec (the-book, n-026) can then be updated to note the restriction is code-enforced.

🤖 Generated with Claude Code

…ion 4)

The Celo fault-proof host (celo-kona) does not implement the L1Blob
preimage hint, so an authenticated blob batch — while accepted by full
nodes — would stall fault-proof execution at its L1 block. The spec
declares blob DA unsupported (DEC-op-026/n-026), but until now that was
an operational restriction only.

Enforce it in code, at two layers:

- Derivation pipeline (consensus): from Espresso activation onward
  (including the auth-enforcement grace window), dataAndHashesFromTxs
  drops blob-carrying inbox transactions before any authorization
  check, so post-Espresso derivation never requires blob preimages.
  This covers op-node and the Go op-program alike, since they share the
  derive package. Pre-fork semantics are unchanged (upstream behavior).

- Batcher startup (operational): refuse to start with a blob or auto
  data-availability type when EspressoTime is scheduled, instead of
  letting verifiers silently drop every blob batch after activation and
  stalling the safe head. This makes the existing blob-pair headroom
  check in checkFallbackAuthConfirmations unreachable in practice; it
  is kept as defense in depth.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@palango palango left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at f7e587d. go test ./op-node/rollup/derive/... ./op-batcher/... passes, go vet clean on both.

Premise holds up. celo-kona's host bails on HintType::L1Blob outright (bin/host/src/single/handler.rs:244), and types.go:401 already forces espresso_time >= ecotone_time, so everything
post-Espresso goes through BlobDataSource. No way around the new gate via the calldata source. Dropping on IsEspresso rather than isEspressoAuthEnforced is right too, since proofs run from
activation and the grace window has to be blob-free.

Approach is good. Five small things.

  1. blob_data_source.go:122-124 is stale now. It still says that before enforcement "this runs upstream Optimism semantics: filter by batch inbox + sender == batcher". Not true in the grace window
    anymore, and the paragraph you added just below says the opposite.
  2. ComputeBlobBatchHash is dead, but doesn't look dead. Blob txs get dropped at :164 whenever IsEspresso(ref.Time), so the batchHash = ComputeBlobBatchHash(...) at :173 and the blob-append block
    under it only run pre-Espresso, and pre-Espresso isBatchTxAuthorized takes the sender path and never touches batchHash. Reads like blob commitments still get authenticated after the fork. A comment
    would do.
  3. Same story in the batcher. checkEspressoDataAvailability runs at service.go:174, ahead of checkFallbackAuthConfirmations at :177, and rejects every non-calldata config when EspressoTime != nil,
    so checkFallbackAuthConfirmations can't ever fire and its test covers unreachable code. Keeping both as belt and braces is fine by me. I'd just rather that live in the code than only in the PR
    description.
  4. config.rollupCfg.IsEspresso(ref.Time) runs per tx, while isEspressoAuthEnforced(...) right above at :143 is hoisted out of the loop. Might as well do both in one place.
  5. The Warn fires before the auth check, so anyone can buy themselves a log line per blob tx sent to the inbox. Upstream warns on unauthorized senders anyway, so it's consistent. Only raising it in
    case volume matters.

Tests look good. Using an empty MockL1Source to assert no receipt scanning is a real check, not decoration, and upstream's blob_data_source_test.go passing untouched is decent evidence for the
pre-fork claim.

One you could add: the fork-boundary walk stops at espressoTime + BatchAuthEnforcementDelaySecs - 1 for blobs, so the post-enforcement blob case only gets covered sideways by
TestDataAndHashesFromTxsEventAuth (espressoTime = 0, ref.Time = delay). Three lines would make it direct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants