Skip to content

Espresso 2: derivation pipeline (re-hosted)#457

Merged
piersy merged 48 commits into
celo-rebase-18from
espresso/derivation-pipeline
Jun 22, 2026
Merged

Espresso 2: derivation pipeline (re-hosted)#457
piersy merged 48 commits into
celo-rebase-18from
espresso/derivation-pipeline

Conversation

@QuentinI

@QuentinI QuentinI commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

This PR introduces the op-node changes of Espresso integration: batch authentication and caff node.

Batch authentication: derivation pipeline now reads BatchAuthenticated events emitted by the BatchAuthenticator contract introduced by #443, gated by EspressoTime hardfork timestamp.

The PR includes unit tests for the batch-authenticator event scanner, Espresso batch RLP round-trip, and the new event-based authorization path across the calldata, blob, and altDA data sources. e2e testing involving actual Espresso instance is not part of this PR because it requires batcher changes as well.

This is #445, re-hosted from an in-repo branch (now properly stacked on #455).

Comment thread op-node/rollup/derive/batch_authenticator.go Outdated
@QuentinI QuentinI force-pushed the espresso/contracts branch from c6b82ca to 5b28541 Compare June 17, 2026 16:21
@QuentinI QuentinI force-pushed the espresso/derivation-pipeline branch 2 times, most recently from 34c9d0d to 60f92ee Compare June 17, 2026 16:27
@QuentinI QuentinI force-pushed the espresso/contracts branch 3 times, most recently from a6a2298 to d5ff5d1 Compare June 17, 2026 17:10
@QuentinI QuentinI force-pushed the espresso/derivation-pipeline branch from 8a45116 to 266fe6f Compare June 18, 2026 14:25
QuentinI and others added 21 commits June 18, 2026 18:38
Adds the Espresso-introduced contracts and the minimum supporting changes
required for them to compile, test, and pass the contract checks.

New contracts and scripts:

- src/L1/BatchAuthenticator.sol and interfaces/L1/IBatchAuthenticator.sol
  (upgradeable contract that authenticates batch transactions, with switching
  between Espresso and fallback batchers)
- scripts/deploy/DeployBatchAuthenticator.s.sol and
  scripts/deploy/DeployEspresso.s.sol
- test/L1/BatchAuthenticator.t.sol and test/mocks/MockEspressoTEEVerifiers.sol
- snapshots/{abi,storageLayout}/BatchAuthenticator.json
- snapshots/semver-lock.json entry for BatchAuthenticator

New submodules:

- lib/espresso-tee-contracts (interfaces required by BatchAuthenticator)
- lib/openzeppelin-contracts-upgradeable-v5 (OZ v5 used by BatchAuthenticator
  via OwnableUpgradeable)

Supporting changes (Espresso-driven):

- foundry.toml: remappings for OZ v5 and espresso-tee-contracts; ignored
  warning codes for vendored libs; OOM-safe jobs settings; via-ir profile.
- justfile: fix-proxy-artifact recipe to handle OZ v5 shadowing Proxy/ProxyAdmin
  artifacts; build/coverage hooks.
- src/universal/Proxy.sol, src/universal/ProxyAdmin.sol: pin pragma to exact
  0.8.15 so they stay in their own compilation group and never emit PUSH0.
- src/universal/ReinitializableBase.sol: loosen pragma to ^0.8.15 so
  BatchAuthenticator (compiled with OZ v5) can import it.
- scripts/* and test/*: disambiguate Proxy artifact lookups to
  src/universal/Proxy.sol:Proxy (avoids OZ v5 proxy/Proxy.sol shadow).
- scripts/checks: bypass interface checks for artifacts originating from lib/;
  add Espresso-related contract names to exclude lists; pragma exclusions for
  Proxy/ProxyAdmin/BatchAuthenticator.
- test/vendor/Initializable.t.sol: exclude BatchAuthenticator (deployed by a
  separate Espresso script).

Co-authored-by: OpenCode <noreply@opencode.ai>
Co-authored-by: piersy <pierspowlesland@gmail.com>
- strict-pragma: remove unneeded exclusions for src/universal/Proxy.sol
  and src/universal/ProxyAdmin.sol — both already use strict
  'pragma solidity 0.8.15;', so the entries (and their misleading
  comment claiming '^') were dead.
- interfaces: move the Espresso excludeContracts block out of the
  upstream-shared area and down next to the Celo block, with one
  entry per line to match the surrounding style. Localizes future
  rebase deltas.

Co-authored-by: OpenCode <noreply@opencode.ai>
Inline the EspressoTEEVerifier deployment in DeployEspresso.s.sol so it
no longer imports lib/espresso-tee-contracts/scripts/DeployTEEVerifier.s.sol
or DeployNitroTEEVerifier.s.sol. The upstream scripts pulled OZ v5's
TransparentUpgradeableProxy (and its auto-deployed ProxyAdmin) into the
OP artifact tree, shadowing src/universal/ProxyAdmin.sol and forcing a
~90-line fix-proxy-artifact justfile recipe.

The TEEVerifier is now deployed behind src/universal/Proxy.sol +
src/universal/ProxyAdmin.sol, matching how BatchAuthenticator is
deployed in the same script. ERC-1967 slots are unchanged, so external
callers see no difference.

The raw vm.getCode("ProxyAdmin") lookups in the deploy scripts and
BatchAuthenticator tests are switched to the explicit artifact path
vm.getCode("forge-artifacts/ProxyAdmin.sol/ProxyAdmin.json") to
deterministically resolve the default compilation profile's bytecode
(the dispute profile transitively compiles ProxyAdmin at optimizer_runs=5000,
creating a second artifact that broke unqualified lookups).

The fix-proxy-artifact recipe and its 5 callsites are removed.
Cherry-picked from piersy's commit 5d0a803 on PR #443.

Walks the dual-batcher state machine: Espresso path → switchBatcher →
fallback path → switchBatcher → Espresso path. Asserts every transition
emits the expected event, that signer registration survives the
round-trip, and that re-issuing the same call after a mode flip changes
the outcome (the previously-valid Espresso signature is no longer
consulted on the fallback path).

Co-authored-by: Piers Powlesland <pierspowlesland@gmail.com>
Co-authored-by: OpenCode <noreply@opencode.ai>
Replace the hand-rolled `EspressoBatcherEntry[]` history + binary search
with OpenZeppelin's `Checkpoints.Trace160` (`(uint96 key, uint160 value)`).
`uint160` is exactly an address with no waste, and `uint96` easily covers
L1 block numbers. `upperLookupRecent` replaces the custom binary search
and the same-block-overwrite branch is now handled inside `_insert`.

Co-authored-by: OpenCode <noreply@opencode.ai>
…sed warning codes

Drop the two impl imports (EspressoTEEVerifier, EspressoNitroTEEVerifier) from
DeployEspresso.s.sol and replace direct instantiation with vm.getCode + assembly
create, reading bytecode from the submodule's own out/ directory.

This removes the impl closure (TEEHelper, JournalValidation, and the
aws-nitro-enclave-attestation chain) from OP's solc invocations. The impls
are still parsed/ABI-checked by forge via libs=['lib'], but they no longer
require bytecode emission or the optimizer backend.

Since OP's build no longer compiles the submodule's impl files, the three
error codes those files triggered (6321 unnamed return, 5667 unused param,
1878 missing SPDX) can be removed from ignored_error_codes. OP's own code
does not trigger any of them. The lint_on_build=false workaround is also
removed for the same reason — with the impl closure gone, forge lint reports
283 warnings (all from OP's own code), none of which cause a build failure.

Adds fs_permissions read access for lib/espresso-tee-contracts/out/ so
vm.getCode can locate the pre-built artifacts. The submodule must be built
(forge build --root lib/espresso-tee-contracts) before OP's main build.

Co-authored-by: OpenCode <noreply@opencode.ai>
piersy and others added 9 commits June 18, 2026 18:38
Two follow-ups required after cherry-picking the batch-auth cache
dependency-injection and rollup.Config-in-DataSourceConfig changes:

- batch_authenticator_test.go: thread the BatchAuthCaches argument
  through the duplicate-authentication caller test, which post-dates the
  cache-DI change and so was not updated by it.
- IsEspresso: guard against a nil Config receiver. Holding the
  rollup.Config in DataSourceConfig means IsEspresso is now reached via
  DataSourceConfig.rollupCfg, which is nil for non-Espresso data-source
  tests; the previous DataSourceConfig.isEspresso method was nil-safe.

Co-authored-by: OpenCode <noreply@opencode.ai>
Adds derivation tests that close two gaps in the Espresso batch-auth
coverage. Both data sources gate event-based authentication on
IsEspresso(ref.Time), and each implements that gate separately, but no
test exercised the gate flipping across activation or verified that
multiple batches are matched to their own commitments.

Fork-boundary tests (TestDataFromEVMTransactionsForkBoundary for the
calldata source, TestDataAndHashesFromTxsForkBoundary for the blob
source) reuse a single DataSourceConfig with EspressoTime set and vary
only ref.Time across the activation boundary:

  - pre-fork (ref.Time < EspressoTime): the batcher tx is accepted via
    upstream sender-based auth, and an empty L1 mock asserts that zero
    receipt scanning occurs;
  - non-batcher senders remain rejected pre-fork;
  - activation block (ref.Time == EspressoTime): the same batcher tx is
    rejected without a BatchInfoAuthenticated event and accepted with one.

This pins the ">=" gate in both directions: a regression to ">" makes the
activation block either accept an unauthenticated batch or skip the event
scan, failing the test. The blob-source copy drives a type-2 calldata tx,
the shape an Ecotone-active, calldata-batching chain (e.g. Celo) submits
through the blob source.

TestDataFromEVMTransactionsEventAuth gains a "multiple authenticated txs
each accepted for their own commitment" case: two distinct batches, each
authenticated by its own commitment, must both be accepted in order and
mapped to their own data, verifying each tx is matched against its own
commitment rather than to "some" authenticated entry.

Test-only change; no production code is modified.
Moves the Espresso batch-auth tests out of the upstream calldata/blob
data-source test files into new espresso_calldata_source_test.go and
espresso_blob_data_source_test.go, and renames batch_authenticator_test.go
to espresso_batch_authenticator_test.go.

Pure test relocation: no production code and no test logic, names,
comments, or assertions change. The goal is to keep all Espresso-specific
tests in espresso_-prefixed files so upstream changes to the shared
data-source test files cannot conflict with them on rebase.

Moved into espresso_calldata_source_test.go (from calldata_source_test.go):
the mockAuthEvents helper, TestDataFromEVMTransactionsEventAuth (including
the "multiple authenticated txs each accepted for their own commitment"
subtest), and TestDataFromEVMTransactionsForkBoundary.

Moved into espresso_blob_data_source_test.go (from blob_data_source_test.go):
TestDataAndHashesFromTxsEventAuth and TestDataAndHashesFromTxsForkBoundary.

The upstream tests (TestDataFromEVMTransactions, TestDataAndHashesFromTxs,
TestFillBlobPointers, TestBlobDataSourceL1FetcherErrors) stay in their
original files. The only import adjustment is dropping io, common/hexutil,
and op-service/txmgr from the new blob file (used only by the kept
TestBlobDataSourceL1FetcherErrors); both original import blocks are
unchanged.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@QuentinI QuentinI force-pushed the espresso/contracts branch from d5ff5d1 to 27f75a7 Compare June 18, 2026 16:40
@QuentinI QuentinI force-pushed the espresso/derivation-pipeline branch from 266fe6f to 2782976 Compare June 18, 2026 16:40
@piersy piersy marked this pull request as ready for review June 18, 2026 17:49
Base automatically changed from espresso/contracts to celo-rebase-18 June 18, 2026 21:42
@piersy

piersy commented Jun 18, 2026

Copy link
Copy Markdown

Hey @QuentinI, I spotted one last thing, see PR here - #462.

After that I think It's ready to merge!

…nd (#462)

CollectAuthenticatedBatches walks the L1 parent chain over the
BatchAuthLookbackWindow, calling FetchReceipts and L1BlockRefByHash on
each block. Both error sites wrapped every error as a TemporaryError,
including ethereum.NotFound.

A TemporaryError makes the pipeline retry the same step at the same L1
origin with capped exponential backoff and no give-up, so a persistently
unavailable in-window ancestor (e.g. an L1 reorg orphaned it) causes a
silent derivation stall. The independent reorg detection in L1Traversal
only validates the single forward parent-hash link to the next origin,
not the [ref-window, ref-1] range the lookback walks, so it does not
rescue this case.

The sibling data sources already classify a NotFound ref block as a
ResetError (calldata_source.go, blob_data_source.go); a reset rewinds to
a canonical origin and re-derives, which is the correct response to a
missing L1 block. Match that here: NotFound -> ResetError, other errors
stay TemporaryError.

Adds TestCollectAuthenticatedBatchesResetOnNotFound covering NotFound on
FetchReceipts and on L1BlockRefByHash (both -> ResetError) and a
non-NotFound error (stays TemporaryError).
@QuentinI

Copy link
Copy Markdown
Collaborator Author

@piersy that made total sense, merged, thanks!

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7785e75b0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread op-node/rollup/derive/batch_authenticator.go

@piersy piersy 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.

Looking good, thank you @QuentinI !

@piersy piersy merged commit 689e3d3 into celo-rebase-18 Jun 22, 2026
41 checks passed
@piersy piersy deleted the espresso/derivation-pipeline branch June 22, 2026 09:01
QuentinI added a commit to celo-org/celo-kona that referenced this pull request Jun 30, 2026
…lldataSource

The pre-ecotone calldata data source is unreachable on Celo: every Celo
chain sets ecotone_time = 0 (active from the L2/Cel2 transition), so
CeloEthereumDataSource always routes to the blob source. Espresso
(espresso_time) activates strictly after ecotone, so the Espresso-specific
batch-authentication branch folded into CeloCalldataSource could never fire.

Remove CeloCalldataSource entirely and wire ethereum.rs to upstream kona's
CalldataSource for the (still byte-identical, still unreachable) pre-ecotone
path. The blob source retains the Espresso event-based authentication and
also handles plain calldata-carrying batcher txs post-ecotone, so no
behaviour is lost.

Addresses piersy's review thread on calldata.rs. The matching Go-side
removal in celo-org/optimism#457 is out of scope for this change.

Co-authored-by: OpenCode <noreply@opencode.ai>
piersy added a commit that referenced this pull request Jul 1, 2026
The calldata data source is only reached pre-ecotone: OpenData routes to the
blob source whenever ecotone is active (ref.Time >= ecotone_time). Every Celo
chain sets ecotone_time = 0, and Espresso (espresso_time) activates strictly
later, so the Espresso event-based batch-authentication branch that #457 folded
into DataFromEVMTransactions could never fire on a real chain.

Revert DataFromEVMTransactions / CalldataSource to the upstream sender-based
path (no lookback-window event scan, no L1Fetcher): the calldata source now uses
isValidBatchTx + isAuthorizedBatchSender only. The blob source keeps Espresso
event-based authentication and also handles plain calldata-carrying batcher txs
post-ecotone, so no behaviour is lost. Delete the now-dead Espresso calldata
test and move its shared mockAuthEvents helper into the blob data source test,
its only user.

Mirrors the celo-kona removal (drop CeloCalldataSource, use upstream CalldataSource).
piersy added a commit that referenced this pull request Jul 1, 2026
The calldata data source is only reached pre-ecotone: OpenData routes to the
blob source whenever ecotone is active (ref.Time >= ecotone_time). Every Celo
chain sets ecotone_time = 0, and Espresso (espresso_time) activates strictly
later, so the Espresso event-based batch-authentication branch that #457 folded
into DataFromEVMTransactions could never fire on a real chain.

Restore the calldata data source to its pre-Espresso form, byte-identical to
before #457. #457 split isValidBatchTx into a type/To-only check plus
isAuthorizedBatchSender to serve the blob source's event-based path, which
rippled a needless change into the calldata source. Reinstate the original
5-arg isValidBatchTx (composed from isBatchTxToInbox + isAuthorizedBatchSender)
so the calldata source is untouched, and point the blob source at
isBatchTxToInbox. The blob source keeps Espresso event-based authentication and
also handles plain calldata-carrying batcher txs post-ecotone, so no behaviour
is lost. Delete the now-dead Espresso calldata test and move its shared
mockAuthEvents helper into the blob data source test, its only user.

Mirrors the celo-kona removal (drop CeloCalldataSource, use upstream CalldataSource).
piersy added a commit that referenced this pull request Jul 2, 2026
The calldata data source is only reached pre-ecotone: OpenData routes to the
blob source whenever ecotone is active (ref.Time >= ecotone_time). Every Celo
chain sets ecotone_time = 0, and Espresso (espresso_time) activates strictly
later, so the Espresso event-based batch-authentication branch that #457 folded
into DataFromEVMTransactions could never fire on a real chain.

Restore the calldata data source to its pre-Espresso form. calldata_source.go is
byte-identical to before #457; #457 had split isValidBatchTx into a type/To-only
check plus isAuthorizedBatchSender to serve the blob source's event-based path,
which rippled a needless change into the calldata source. Reinstate the original
5-arg isValidBatchTx (composed from isBatchTxToInbox + isAuthorizedBatchSender)
and point the blob source at isBatchTxToInbox. calldata_source_test.go is also
restored except for one line: DataSourceConfig gained fields (rollupCfg,
batchAuthCaches), so its literal must use named fields instead of positional.

The blob source keeps Espresso event-based authentication and also handles plain
calldata-carrying batcher txs post-ecotone, so no behaviour is lost. Delete the
now-dead Espresso calldata test and move its shared mockAuthEvents helper into
the blob data source test, its only user.

Mirrors the celo-kona removal (drop CeloCalldataSource, use upstream CalldataSource).
piersy pushed a commit to celo-org/celo-kona that referenced this pull request Jul 2, 2026
Rust counterpart of celo-org/optimism#457. Adds an Espresso-aware L1
derivation path in which batches are authorized by on-chain
`BatchInfoAuthenticated` events rather than by batcher-transaction sender.
Pre-Espresso behaviour is byte-for-byte identical to upstream OP Stack; the
event path activates only once Espresso is active for the L1 origin time.

New crate `celo-derive` (crates/kona/derive):
- batch_auth.rs: the only genuinely new logic. Walks a fixed lookback
  window of L1 blocks, collects `BatchInfoAuthenticated` events into a
  commitment -> caller map, and gates each batch by its blob/calldata
  commitment hash. Receipts and headers are cached in reorg-safe,
  block-hash-keyed LRUs so consecutive origins refetch only one block.
- blobs.rs / blob_data.rs / ethereum.rs: CeloBlobSource, CeloBlobData and
  CeloEthereumDataSource are duplicated from kona because the upstream
  BlobData fields and its decode/fill helpers are pub(crate) and celo-kona
  wraps kona rather than patching it. The batch-auth branch is folded into
  the duplicated blob source; the calldata path delegates to upstream.

Config plumbing:
- genesis (rollup.rs): add Espresso settings to CeloRollupConfig with
  flattened serialization; reject an espresso_time scheduled before
  ecotone_time.
- proof (boot.rs) / bin/host / bin/client: thread the Espresso config
  through the rollup-config preimage and boot so host and client agree,
  and bake activation for the Celo registry chains (Chaos).

Provider-polymorphic error handling is preserved: fetch failures collapse
via each provider's Into<PipelineErrorKind>, so the lookback walk resets
under the online provider and stays critical under the fault-proof oracle,
matching the existing block/blob paths.
palango added a commit that referenced this pull request Jul 3, 2026
…ime >= ecotone_time (#466)

* refactor(derive): drop dead Espresso calldata authentication path

The calldata data source is only reached pre-ecotone: OpenData routes to the
blob source whenever ecotone is active (ref.Time >= ecotone_time). Every Celo
chain sets ecotone_time = 0, and Espresso (espresso_time) activates strictly
later, so the Espresso event-based batch-authentication branch that #457 folded
into DataFromEVMTransactions could never fire on a real chain.

Restore the calldata data source to its pre-Espresso form. calldata_source.go is
byte-identical to before #457; #457 had split isValidBatchTx into a type/To-only
check plus isAuthorizedBatchSender to serve the blob source's event-based path,
which rippled a needless change into the calldata source. Reinstate the original
5-arg isValidBatchTx (composed from isBatchTxToInbox + isAuthorizedBatchSender)
and point the blob source at isBatchTxToInbox. calldata_source_test.go is also
restored except for one line: DataSourceConfig gained fields (rollupCfg,
batchAuthCaches), so its literal must use named fields instead of positional.

The blob source keeps Espresso event-based authentication and also handles plain
calldata-carrying batcher txs post-ecotone, so no behaviour is lost. Delete the
now-dead Espresso calldata test and move its shared mockAuthEvents helper into
the blob data source test, its only user.

Mirrors the celo-kona removal (drop CeloCalldataSource, use upstream CalldataSource).

* feat(rollup): reject espresso_time scheduled before ecotone_time

Dropping the Espresso-aware calldata source made "espresso_time >= ecotone_time"
a silent correctness invariant: Espresso event-based authentication now lives
only on the post-ecotone blob path, so a block in the [espresso_time,
ecotone_time) window would route to the pre-ecotone calldata source and silently
fall back to sender-based authorization.

Guard the invariant in Config.Check(): error with ErrEspressoBeforeEcotone when
espresso_time is set but ecotone_time is unset or scheduled later than
espresso_time. Every real Celo chain sets ecotone_time = 0, so this only fires on
a misconfiguration -- but now it fails fast instead of corrupting derivation.

Mirrors the celo-kona validate_espresso guard (EspressoBeforeEcotone).

* test(derive): restore multi-tx event-auth coverage; review cleanups

- add mixed-block and multi-commitment subtests to
  TestDataAndHashesFromTxsEventAuth, restoring per-tx commitment
  matching coverage lost with espresso_calldata_source_test.go
- drop stale comment reference to the deleted
  TestDataFromEVMTransactionsForkBoundary
- reuse u64ptr instead of a local ptr closure in
  TestConfig_Check_EspressoBeforeEcotone

---------

Co-authored-by: Paul Lange <palango@gmx.de>
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.

4 participants