Espresso 3a: Fallback batcher (re-hosted)#458
Conversation
| "(based on L1 tip time) and the verifier's gate (based on the containing " + | ||
| "L1 block's time). Has no effect outside the boundary window around the " + | ||
| "EspressoTime hardfork.", | ||
| Value: 5 * time.Minute, |
There was a problem hiding this comment.
Mirrored from #448 (unresolved review thread) to keep tracking it here after the re-host. Original location: op-batcher/flags/flags.go:172.
Original transcript:
@palango — 2026-06-02:
This is a nice idea, but we need to see how to set this value.
I think we either set this to something big and safe (1-2h) and accept that we're sending a couple of unused tx, or remove it at all and shutdown the batcher before the switch (causing more work for devops).
There was a problem hiding this comment.
@palango technically it's not necessary at all. If the scenario it tries to prevent still happens, the batcher will just detect it here:
optimism/op-batcher/batcher/sync_actions.go
Line 159 in 9b2017e
This will cause at most several minutes of delay before it re-submits, now through the authenticated path. So there shouldn't be any need to shut the batcher down in any case.
| // | ||
| // The contract's fallback path checks msg.sender against systemConfig.batcherHash(), so no | ||
| // separate signature is needed — the L1 transaction is already signed by the TxManager's key. | ||
| func (l *BatchSubmitter) sendTxWithFallbackAuth(txdata txData, isCancel bool, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { |
There was a problem hiding this comment.
Mirrored from #448 (unresolved review thread) to keep tracking it here after the re-host. Original location: op-batcher/batcher/fallback_auth.go:42.
Original transcript:
@palango — 2026-06-02:
Reverted auth tx can be reported as success.
sendTxWithFallbackAuthchecks only err afterl.Txmgr.Sendand never checksverificationReceipt.Statusinop-batcher/batcher/fallback_auth.go:85.txmgr.Sendreturnsreceipt, nilon receipt arrival inop-service/txmgr/txmgr.go:743, and derivation ignores failed auth receipts inop-node/rollup/derive/ batch_authenticator.go:85.
| } | ||
| l.authGroup.Go( | ||
| func() error { | ||
| l.sendTxWithFallbackAuth(txdata, isCancel, candidate, queue, receiptsCh) |
There was a problem hiding this comment.
Mirrored from #448 (unresolved review thread) to keep tracking it here after the re-host. Original location: op-batcher/batcher/espresso_driver.go:67.
Original transcript:
@palango — 2026-06-02:
The normal batch submission path sends every batch tx through
txQueue.Send:
op-batcher/batcher/driver.go:1061That queue is created with
MaxPendingTransactions:
op-batcher/batcher/driver.go:515and
txmgr.Queue.Sendexplicitly assigns nonces synchronously so transactions
confirm in the order they are sent. This is important for Holocene, where
frames for a channel must arrive in order.The fallback-auth path bypasses that queue. Once fallback auth is required,
dispatchAuthenticatedSendTxstarts a goroutine inauthGroup:
op-batcher/batcher/espresso_driver.go:65and that goroutine calls
l.Txmgr.Senddirectly for both the auth tx and the
batch inbox tx:
op-batcher/batcher/fallback_auth.go:85
op-batcher/batcher/fallback_auth.go:95
Txmgr.Sendis concurrency-safe, but it only preserves the order in which
callers actually reach nonce assignment. With up tofallbackAuthGroupLimit = 128goroutines racing, that order is no longer the publishing loop’s frame
order. As a result, batch inbox txs from the same channel can receive nonces
in a different order than the channel manager emitted them, and L1 inclusion
order follows those nonces.That can violate Holocene strict frame ordering and cause derivation to drop
later/non-contiguous frames.This is also a regression from the default config, where
max-pending-tx
defaults to1; operators who configured one-at- a-time submission no longer
get that behavior for fallback-authenticated batches.The fix should preserve the original batch order across the whole auth+inbox
pair. Simply queueing inbox txs after concurrent auth confirmation is not
sufficient, because auth confirmations can complete out of order. The
fallback-auth path should either be serialized, or use an ordered mechanism
that keeps the original frame order while still ensuring each inbox tx is
posted only after its matching auth tx succeeds.
There was a problem hiding this comment.
This was a serious oversight.
Reworked in 6f42f54: queueing is serialized, the goroutine is spawned only to convert the authentication and batch submission receipt pair to a single receipt indicating transaction status. This should both respect the number of in-flight transactions allowed and preserve Holocene ordering.
Added some tests for this behaviour as well.
a587307 to
34c9d0d
Compare
f840eee to
12b46a6
Compare
34c9d0d to
60f92ee
Compare
0f8d63c to
9330de1
Compare
8a45116 to
266fe6f
Compare
9330de1 to
1616378
Compare
Regenerated against PR #443's BatchAuthenticator.sol via forge build + abigen. Includes the new history-based API (espressoBatcherAt, espressoBatcherAtBlock, espressoBatcherHistoryLength, setEspressoBatcher) and the EspressoBatcherUpdated(address,address,uint64) event with the fromBlock parameter; drops the removed paused() function. Consumed by the fallback batcher (next commit) to read activeIsEspresso and pack authenticateBatchInfo calldata. The TEE batcher in a follow-up PR will use the same binding. Co-authored-by: OpenCode <noreply@opencode.ai>
Add the fallback (non-TEE) batcher's BatchAuthenticator integration: - op-batcher/batcher/fallback_auth.go: sendTxWithFallbackAuth path that posts authenticateBatchInfo before the batch tx, with a deadline check against the batch's L1 inclusion window. Computes the batch commitment hash from either calldata or concatenated blob versioned hashes. - op-batcher/batcher/espresso_active.go: hasBatchAuthenticator (does this rollup use BatchAuthenticator at all?) and isFallbackAuthRequired (gates fallback authentication on Config.IsEspresso(tip.Time + lead)). The Espresso hardfork predicate is consulted with the configured FallbackAuthLeadTime added to the L1 tip, so the batcher starts authenticating slightly before the verifier requires it. This absorbs worst-case L1 inclusion delay between the batcher's decision time (L1 tip) and the verifier's evaluation time (containing L1 block). - op-batcher/batcher/espresso_driver.go: the authGroup bookkeeping (initAuthGroup, waitForAuthGroup, fallbackAuthGroupLimit) and the dispatchAuthenticatedSendTx fan-out used by driver.go sendTx. Small wiring edits to upstream files: - op-batcher/flags/flags.go: register --espresso.fallback-auth-lead-time (default 5m). - op-batcher/batcher/config.go: thread the FallbackAuthLeadTime through CLIConfig. - op-batcher/batcher/service.go: BatcherConfig.FallbackAuthLeadTime field, propagated from CLIConfig in initFromCLIConfig. - op-batcher/batcher/driver.go: extend L1Client to embed bind.ContractBackend (required by the BatchAuthenticator binding), add authGroup field to BatchSubmitter, call initAuthGroup in NewBatchSubmitter, call dispatchAuthenticatedSendTx in sendTx, call waitForAuthGroup in publishingLoop's shutdown drain. - op-batcher/batcher/driver_test.go: embed bind.ContractBackend in fakeL1Client so the AltDA tests still satisfy L1Client. The fallback batcher does nothing when the rollup config has no BatchAuthenticator address, and it falls through to the upstream queue.Send path pre-EspressoTime. Cancel transactions always take the upstream path. No new external dependencies are added; the only third- party Go modules needed are already in PR #445. The TEE batcher is a separate PR stacked on top. Co-authored-by: OpenCode <noreply@opencode.ai>
The Espresso fallback-auth path previously dispatched each auth+batch pair to a separate errgroup and called Txmgr.Send directly, bypassing the operator's MaxPendingTransactions bound and assigning nonces in a nondeterministic order. Under Holocene the frame queue drops out-of-order frames instead of buffering them, so the batcher's L1 txs must land in submission order. Submit the authenticateBatchInfo tx and the batch inbox tx through the same ordered queue.Send path as the non-fallback batcher, in submission order, so the auth tx takes the lower nonce and is mined first, and both txs stay under MaxPendingTransactions. A watcher goroutine (tracked by authGroup so the publishing loop drains it before closing receiptsCh) collects both receipts on private channels, fails the pair if the auth tx reverted (a reverted authenticateBatchInfo emits no event, so the verifier would silently drop the batch), runs the lookback-window check, and emits a single synthetic receipt for the batch txData. Co-authored-by: OpenCode <noreply@opencode.ai>
1616378 to
25a6c63
Compare
266fe6f to
2782976
Compare
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).