Skip to content

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

Draft
QuentinI wants to merge 3 commits into
espresso/derivation-pipelinefrom
espresso/batcher-fallback
Draft

Espresso 3a: Fallback batcher (re-hosted)#458
QuentinI wants to merge 3 commits into
espresso/derivation-pipelinefrom
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
"(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,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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).

@QuentinI QuentinI Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@palango technically it's not necessary at all. If the scenario it tries to prevent still happens, the batcher will just detect it here:

m.Warn("sequencer did not make expected progress",

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]) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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. sendTxWithFallbackAuth checks only err after l.Txmgr.Send and never checks verificationReceipt.Status in op-batcher/batcher/fallback_auth.go:85. txmgr.Send returns receipt, nil on receipt arrival in op-service/txmgr/txmgr.go:743, and derivation ignores failed auth receipts in op-node/rollup/derive/ batch_authenticator.go:85.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed as part of the rework in 6f42f54

Comment thread op-batcher/batcher/espresso_driver.go Outdated
}
l.authGroup.Go(
func() error {
l.sendTxWithFallbackAuth(txdata, isCancel, candidate, queue, receiptsCh)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

That queue is created with MaxPendingTransactions:

op-batcher/batcher/driver.go:515

and txmgr.Queue.Send explicitly 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,
dispatchAuthenticatedSendTx starts a goroutine in authGroup:

op-batcher/batcher/espresso_driver.go:65

and that goroutine calls l.Txmgr.Send directly 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.Send is concurrency-safe, but it only preserves the order in which
callers actually reach nonce assignment. With up to fallbackAuthGroupLimit = 128 goroutines 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 to 1; 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.

@QuentinI QuentinI Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

@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 3 commits June 18, 2026 18:38
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>
@QuentinI QuentinI force-pushed the espresso/batcher-fallback branch from 1616378 to 25a6c63 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
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.

1 participant