Skip to content

Espresso 3a: Fallback batcher (re-hosted) - #458

Merged
piersy merged 71 commits into
celo-rebase-18from
espresso/batcher-fallback
Jul 28, 2026
Merged

Espresso 3a: Fallback batcher (re-hosted)#458
piersy merged 71 commits into
celo-rebase-18from
espresso/batcher-fallback

Conversation

@QuentinI

@QuentinI QuentinI commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Relatively small change based on #445 - makes fallback batcher authenticate its transactions.

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

Comment thread op-batcher/flags/flags.go Outdated
Comment thread op-batcher/batcher/fallback_auth.go
Comment thread op-batcher/batcher/espresso_driver.go Outdated
@QuentinI
QuentinI force-pushed the espresso/derivation-pipeline branch from a587307 to 34c9d0d Compare June 17, 2026 16:21
@QuentinI
QuentinI force-pushed the espresso/batcher-fallback branch from f840eee to 12b46a6 Compare June 17, 2026 16:22
@QuentinI
QuentinI force-pushed the espresso/derivation-pipeline branch from 34c9d0d to 60f92ee Compare June 17, 2026 16:27
@QuentinI
QuentinI force-pushed the espresso/batcher-fallback branch 4 times, most recently from 0f8d63c to 9330de1 Compare June 18, 2026 13:25
@QuentinI
QuentinI force-pushed the espresso/derivation-pipeline branch from 8a45116 to 266fe6f Compare June 18, 2026 14:25
@QuentinI
QuentinI force-pushed the espresso/batcher-fallback branch from 9330de1 to 1616378 Compare June 18, 2026 14:25
QuentinI and others added 17 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>

@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: 25c5086252

ℹ️ 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-batcher/batcher/fallback_auth.go Outdated
lukeiannucci and others added 3 commits July 13, 2026 10:07
…ide enforcement delay (#468)

* address comment

* Update op-node/rollup/derive/batch_authenticator.go

Co-authored-by: piersy <pierspowlesland@gmail.com>

* lint

* fix documentation

---------

Co-authored-by: piersy <pierspowlesland@gmail.com>
Fixes the M3 nonce-gap hazard raised in the audit review: #458 (comment)

A fallback batch is two L1 transactions that must land in order — an `authenticateBatchInfo` call followed by the batch-data tx. Previously both were submitted back-to-back at consecutive nonces. If the auth tx failed asynchronously, txmgr reset its cached nonce, the in-flight batch tx was left sitting on a gapped nonce, and the next pair collided with it, forcing channel rewinds.

The fix keeps the two txs paired but makes a failed pair leave no gap — without serializing the hot path:

- Calldata pairs (today's fallback DA) pipeline through a new txmgr primitive, `SendPair`. Both legs are signed synchronously at consecutive nonces and broadcast back-to-back under a single `MaxPendingTransactions` slot, so throughput is unchanged and `max-pending-tx` stays meaningful. If the auth leg fails permanently, the batch leg's nonce is consumed by a fee-bumped no-op at that exact nonce (`cancelPairNonce`) instead of a global nonce reset — no gap, no orphaned batch tx, and higher pending nonces are left undisturbed.
- Blob pairs can't share one slot (geth won't hold a calldata auth tx and a blob batch tx reserved for the same account at the same time), so they serialize: the auth tx is sent and confirmed first, then the batch tx (`sendFallbackAuthSerialized`).

Also in this PR:

- Auth-tx failures are reported under a calldata-typed `txRef`, so an `ErrAlreadyReserved` receipt can't make `cancelBlockingTx` cancel the wrong pool and wedge the blobpool.
- On parent-context cancellation (shutdown, or a sibling pair failing the queue's error group) the pair resets the cached nonce and leaves in-flight legs to resolve in the pool, so the next send refills any gap.
- New metric `fallback_auth_window_exceeded_total`, incremented when a batch tx lands more than `BatchAuthLookbackWindow` blocks after its auth tx.
- Startup check that `NumConfirmations` leaves enough headroom inside `BatchAuthLookbackWindow` for the batch to land, applied only when the BatchAuthenticator is configured and the DA type can serialize (blobs or auto).

The receipt-watcher goroutine and `authGroup` are retained — they drive the pipelined calldata path (the earlier plan to delete them applied only to the fully-serialized design).

---------

Co-authored-by: piersy <pierspowlesland@gmail.com>
The interface doc claimed a failed pair never resets the nonce, but the
pair code resets it in three cases: a leg failing during preparation, a
cancelled parent context (the repair branch calls resetNonce), and a
failed no-op repair falling back to resetNonce. Reword to state that only
in-flight leg failures are repaired with a no-op and no reset, and that
preparation failures, parent-ctx cancellation, and failed repairs fall
back to a nonce reset.

@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: 229eea2f1f

ℹ️ 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/data_source.go
Comment thread op-batcher/batcher/espresso_active.go Outdated
Comment thread op-batcher/batcher/espresso_active.go Outdated
Comment thread op-batcher/batcher/espresso_active.go Outdated

@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: 8957155392

ℹ️ 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-batcher/batcher/fallback_auth.go
@piersy
piersy force-pushed the espresso/batcher-fallback branch from 155ea47 to 7c1715c Compare July 21, 2026 20:01
piersy added 2 commits July 21, 2026 21:02
leg's methods were one-line wrappers, legResult duplicated SendResponse,
and pairSend existed only to hold sendPair/repair's arguments. Replace
all three with plain SimpleTxManager methods taking explicit args,
matching the SendAsync idiom. No behavior change.
@piersy
piersy force-pushed the espresso/batcher-fallback branch from 7c1715c to 1cc52fc Compare July 21, 2026 20:02
@piersy

piersy commented Jul 21, 2026

Copy link
Copy Markdown

Hi @lukeiannucci, I pushed a commit just simplifying the pair and leg code just drops around 50 LOC, makes it a bit easier to follow.

@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: 1da0b588b1

ℹ️ 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-batcher/batcher/service.go
@lukeiannucci

Copy link
Copy Markdown
Collaborator

Hi @lukeiannucci, I pushed a commit just simplifying the pair and leg code just drops around 50 LOC, makes it a bit easier to follow.

Thanks @piersy just took a look, and it looks nice and clean to me!

Comment on lines +33 to +66
// sendPair drives the pair to resolution on its own goroutine: it broadcasts
// both legs concurrently, cancels the second leg if the first fails permanently,
// and delivers exactly one response per channel. A second leg cancelled this way
// has its error wrapped in ErrPairLegCancelled so callers can tell it did not
// fail on its own.
//
// Before any response is delivered, the pair repairs the nonce of each leg that
// resolved with an error (repairPair): the nonce is consumed on chain by a
// fee-bumped no-op, and sendPair blocks until that settles. Why: an errored leg
// is in an unknown state — possibly never broadcast, possibly in the pool at
// heavily bumped fees, possibly even mined unseen. Stock recovery (resetNonce,
// then re-signing the same nonces) recovers by collision with those orphans,
// which is safe for self-contained txs (a stale winner is at worst a duplicate)
// but not for pairs: a failure makes the batcher rewind and re-pack, so resent
// legs carry a different commitment than the orphans they contest. A stale leg
// winning one nonce while a fresh leg wins the adjacent one mines a torn pair —
// auth and batch with mismatched commitments, ignored by derivation — and the
// recovery round has itself half-failed, leaving fresh orphans behind.
//
// Repairing before responding keeps the failure self-contained: the error
// cannot cancel the queue's error group, and so cannot trigger resubmission,
// until this pair's nonces are already consumed — the resubmission's nonce
// query starts above everything the pair left behind.
//
// Aborted siblings get no such guarantee. A pair cancelled by the group abort
// (parent context already dead) only resets the nonce — see repairPair's
// parent-context branch — leaving its legs wherever cancellation caught them:
// That spoilage is self-limiting: a stale leg can only cause damage by
// contesting a slot, and a contested slot that fails a round becomes an
// errored leg of that round's pair — so repair settles the very slots the
// damage occurred at, and each abort's leftovers are cleaned up by the first
// round they hurt. It is accepted because settling every aborted sibling
// eagerly would stall the queue's drain barrier a block per abort, scaling
// with the pipeline depth.

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 @lukeiannucci, it took me a while to get my head around how repair and recovery work. I've extended the comment for sendPair to include my understanding. I'd appreciate you checking it to make sure it aligns with your understanding.

I think it's Ok to go ahead with this as it is, the alternatives I see to this approach would be:

  • To serialize (we already tried this and it reduced the potential throughput too much, although if instead of waiting for 10 confirmations on the auth we just waited for inclusion then the throughput could be aceptable for now (up to 36MiB/h, but might struggle with future load)
  • Use a single tx for auth and batch, I think this was ruled out initially by espresso's investigations, but might be worth revisiting if the current approach does not work well
  • Use 2 accounts for sending auth and batch txs, this would allow reusing plain txmgr and queue constructs but feels like it would introduce quite a lot of changes elsewhere.

@lukeiannucci lukeiannucci Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hey @piersy,

Thanks for cleaning up the doc comment it does look good to me now.

Regarding your bullet points:

  • Serialize: We could also have support for this way (might get a bit messy), and enable it if the SendPairAsync proves problematic.
  • Single tx: Yea, it may be worth revisiting, however I would imagine the cancellation logic would not get triggered that frequently (at least i hope)
  • Two accounts: Yes this might be quite a large overhaul, as we also use this key currently to sign espresso transactions. So I would need to then rethink the system and which keys to use when.

With a BatchAuthenticator address configured but no EspressoTime
scheduled, fallback auth is unreachable, so the NumConfirmations
headroom bound does not apply and must not block startup. A scheduled
future activation still enforces it, since activation switches the send
path mid-run.

Addresses #458 (comment)
@lukeiannucci

Copy link
Copy Markdown
Collaborator

@piersy Seems I cannot merge this, do you mind doing it if all looks good?

@piersy
piersy merged commit 28b3d45 into celo-rebase-18 Jul 28, 2026
41 checks passed
@piersy
piersy deleted the espresso/batcher-fallback branch July 28, 2026 16:45
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.

6 participants