Skip to content

Espresso 2: derivation pipeline#445

Draft
QuentinI wants to merge 17 commits into
celo-org:celo-rebase-17from
EspressoSystems:ag/derivation-pipeline
Draft

Espresso 2: derivation pipeline#445
QuentinI wants to merge 17 commits into
celo-org:celo-rebase-17from
EspressoSystems:ag/derivation-pipeline

Conversation

@QuentinI
Copy link
Copy Markdown

This PR introduces the op-node changes of Espresso integration: batch authentication and caff node. As I can't push branches to this PR I can't create a clean stacked PR; relevant changes over #443 are in commit 08a9056

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

Caff node: op-node gains Caff mode support. When a node is configured as a Caff node (CaffNodeConfig.Enabled) and the parent L2 block has reached the configured caffeination height (and the Espresso enforcement hardfork is active), derivation pulls batches directly from the Espresso sequencer via Espresso Streamer

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.

QuentinI and others added 5 commits May 8, 2026 02:50
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>
Comment thread op-service/sources/eth_client.go Outdated
Comment thread op-node/rollup/types.go Outdated
Comment thread espresso/cli.go Outdated
QuentinI and others added 5 commits May 20, 2026 21:14
- 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>
@piersy
Copy link
Copy Markdown

piersy commented May 21, 2026

Hi @QuentinI, wondering why there are these "CaffNode" changes:

Caff node: op-node gains Caff mode support

I thought this was being handled by the espresso-rollup-node-proxy?

@QuentinI
Copy link
Copy Markdown
Author

@piersy you are right, I was out of date on how the proxy works. I'll strip the Caff node out of this PR, we are not going to need it.

QuentinI and others added 6 commits May 25, 2026 14:11
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 ethereum-optimism#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>
Adds derivation-pipeline support for the BatchAuthenticator contract
introduced in the previous PR. Stacks on the contracts PR.

Introduces an L2-timestamp hardfork (EspressoEnforcementTime) gating all
post-fork derivation semantics. Pre-fork, derivation behaves exactly as
upstream Optimism: batches are accepted based on the L1 transaction
sender matching the SystemConfig batcher address. Post-fork, batches are
authenticated via BatchInfoAuthenticated(bytes32) events emitted by the
BatchAuthenticator contract, and sender-based authorization is rejected.

Adds CollectAuthenticatedBatches which scans L1 receipts over a
configurable lookback window (default 100 blocks) to build the set of
authenticated batch commitment hashes for each L1 block being derived.
Results are cached in two reorg-safe (block-hash-keyed) LRU caches: one
for receipt-derived event sets, one for L1BlockRef resolution. For
consecutive L1 blocks the lookback windows overlap by ~99 blocks, so
only one new block's receipts need to be fetched on each call.

Adds rollup.Config fields: EspressoEnforcementTime *uint64,
BatchAuthenticatorAddress, BatchAuthLookbackWindow.

Adds unit tests for batch authentication across calldata, blob, and
altda data sources.

Co-authored-by: OpenCode <noreply@opencode.ai>
@QuentinI QuentinI force-pushed the ag/derivation-pipeline branch from 08a9056 to 51888e5 Compare May 25, 2026 14:05
@QuentinI
Copy link
Copy Markdown
Author

Rebased and dropped caff-node changes.

Matches upstream Optimism hardfork naming convention (RegolithTime,
EcotoneTime, IsthmusTime, ...). All hardforks enforce a new set of
rules, so the "Enforcement" qualifier was redundant.

Renames:
  EspressoEnforcementTime    -> EspressoTime    (rollup.Config field)
  IsEspressoEnforcement      -> IsEspresso      (rollup.Config method)
  espressoEnforcementTime    -> espressoTime    (DataSourceConfig field)
  isEspressoEnforcement      -> isEspresso      (DataSourceConfig method)
  espresso_enforcement_time  -> espresso_time   (JSON tag, forEachFork log key)
  "Espresso Enforcement"     -> "Espresso"      (forEachFork display name)

Also rewords prose docstrings: "EspressoEnforcement" -> "Espresso",
"Pre/Post-EspressoEnforcement" -> "Pre/Post-Espresso".

Addresses PR feedback: celo-org#445 (comment)

Co-authored-by: OpenCode <noreply@opencode.ai>
@jcortejoso
Copy link
Copy Markdown
Member

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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: d55b56e12a

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +288 to +290
IEspressoTEEVerifier(teeProxyAddr).setEspressoNitroTEEVerifier(
IEspressoNitroTEEVerifier(address(nitroVerifier))
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Set the Nitro verifier before transferring ownership

When proxyAdminOwner is set to an address other than the broadcaster, production Espresso deployment initializes the TEE verifier owner to proxyAdminOwner (line 270) and then broadcasts setEspressoNitroTEEVerifier from msg.sender here, so this onlyOwner call reverts and the deployment cannot complete for the standard multisig-owner flow. Either perform this call as the configured owner or wire the Nitro verifier before ownership is no longer controlled by the broadcaster.

Useful? React with 👍 / 👎.

Comment thread op-node/rollup/types.go
// accepted based on the L1 transaction sender matching the SystemConfig batcher address.
// Post-fork, batches must be authenticated via BatchInfoAuthenticated events emitted by
// the BatchAuthenticator contract; sender-based authorization is rejected.
// Active if EspressoTime != nil && L2 block timestamp >= *EspressoTime.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This doc is wrong, in the derivation it uses the time of the l1Origin

Comment thread op-node/rollup/types.go

// BatchAuthenticatorAddress is the L1 address of the BatchAuthenticator contract whose
// BatchInfoAuthenticated(bytes32) events the derivation pipeline scans post-Espresso.
BatchAuthenticatorAddress common.Address `json:"batch_authenticator_address,omitempty,omitzero"`
Copy link
Copy Markdown

@palango palango May 27, 2026

Choose a reason for hiding this comment

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

EspressoTime makes BatchInfoAuthenticated events mandatory post-fork, but the rollup config does not appear to require a non-zero BatchAuthenticatorAddress when the fork is configured.

There should be a check added that checks BatchAuthenticatorAddress is set when EspressoTime is non-nil.

Then test should be added:

  • A rollup config validation test that fails when EspressoTime != nil and BatchAuthenticatorAddress == common.Address{}.
  • A rollup config validation test that passes when both EspressoTime and a non-zero BatchAuthenticatorAddress are configured.

@palango
Copy link
Copy Markdown

palango commented May 27, 2026

@QuentinI Will the derivation changes for kona also be submitted in this PR or in a stacked one?

@QuentinI
Copy link
Copy Markdown
Author

@palango kona counterpart available at #449

Comment on lines +189 to +191
if authenticatedHashes[batchHash] {
return true
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hi @QuentinI, I realised that this opens up an attack vector in that batch transactions can be replayed once legitimately submitted. Any account can simply re-submit the same data and force the derivation of an invalid batch for the entire network. Because we use altDA it's actually pretty cheap to submit batches (about 10 cents currently). Is it reasonable to also check that the batch comes from the espresso batcher address hered?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It is quite hard to check the espresso batcher here, because we don't have access to contract storage in the derivation pipeline when running in FPVM and the espresso batcher address is mutable. The vanilla OP stack deals with this by embedding current batcher address (=fallback batcher address after our changes) in L2 state, but doing the same with espresso batcher address would require a very invasive change.

To be clear, do you worry about this as a potential DoS vector? My napkin math suggests that it would be at the absolute worst be about 1$/second to keep a node busy dropping replayed batches. If you consider this unacceptable I think the best way to work around the storage issue by making BatchAuthenticated event a pair of (hash, address) to let the derivation pipeline know who is supposed to post the authenticated batch.

Copy link
Copy Markdown
Author

@QuentinI QuentinI May 29, 2026

Choose a reason for hiding this comment

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

For reference, the estimate is from this benchmark I had Claude write that runs the derivation pipeline through the same batch with ~OP transaction numbers, ran on AWS c6i.2xlarge (Intel Xeon Platinum 8375C @ 2.9 GHz), bench is single-threaded (as is the op-node). OP number was 70ms/batch, which at 0.1$/batch comes to the 1$/second I referenced above.

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