-
Notifications
You must be signed in to change notification settings - Fork 13
Send Fallback Batch tx only after Auth tx Confirms (m3)
#467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: espresso/batcher-fallback
Are you sure you want to change the base?
Changes from 6 commits
37e22f8
9eab3cb
4bd6fa5
9d3bb77
266241f
26e7ff1
7ecbfdc
54896cd
6a1030d
ecddaf8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,13 @@ func computeCommitment(candidate *txmgr.TxCandidate) ([32]byte, error) { | |
| // 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]) { | ||
| transactionReference := newTxRef(txdata, isCancel) | ||
| // The auth tx shares the batch txdata's identity (so a failure requeues the right frames) | ||
| // but is always a calldata tx. Auth failures must carry its real type: an ErrAlreadyReserved | ||
| // receipt labeled with the batch's blob type would make cancelBlockingTx cancel the wrong | ||
| // pool, leaving the reserving tx stuck. | ||
| authReference := transactionReference | ||
| authReference.isBlob = false | ||
| authReference.daType = DaTypeCalldata | ||
| l.Log.Debug("Sending fallback-authenticated L1 transaction", "txRef", transactionReference) | ||
|
|
||
| commitment, err := computeCommitment(candidate) | ||
|
|
@@ -79,58 +86,96 @@ func (l *BatchSubmitter) sendTxWithFallbackAuth(txdata txData, isCancel bool, ca | |
| To: &l.RollupConfig.BatchAuthenticatorAddress, | ||
| } | ||
|
|
||
| // Private buffered channels: queue.Send forwards exactly one receipt to each, so the watcher | ||
| // reads exactly once per channel (even on context cancellation, the queue still emits a | ||
| // ctx-error receipt). These never reach handleReceipt; only the synthetic receipt below does. | ||
| authReceiptCh := make(chan txmgr.TxReceipt[txRef], 1) | ||
| batchReceiptCh := make(chan txmgr.TxReceipt[txRef], 1) | ||
|
|
||
| l.Log.Debug( | ||
| "Sending fallback authenticateBatchInfo transaction", | ||
| "txRef", transactionReference, | ||
| "commitment", hexutil.Encode(commitment[:]), | ||
| "address", l.RollupConfig.BatchAuthenticatorAddress.String(), | ||
| ) | ||
| // Submit the auth tx then the batch tx, in order, on the publishing-loop goroutine so their | ||
| // nonces are assigned in submission order. Each Send blocks here when the queue is at its | ||
| // MaxPendingTransactions limit. | ||
| queue.Send(transactionReference, verifyCandidate, authReceiptCh) | ||
| queue.Send(transactionReference, *candidate, batchReceiptCh) | ||
|
|
||
| if len(candidate.Blobs) > 0 { | ||
| // SendPair doesn't support blobs. | ||
| l.sendFallbackAuthSerialized(transactionReference, authReference, verifyCandidate, candidate, queue, receiptsCh) | ||
| return | ||
| } | ||
|
|
||
| // Private buffered channels — Queue.SendPair forwards exactly one receipt to | ||
| // each, so the watcher reads exactly once per channel. These never reach | ||
| // handleReceipt; only the synthetic receipt from the watcher does. | ||
| authReceiptCh := make(chan txmgr.TxReceipt[txRef], 1) | ||
| batchReceiptCh := make(chan txmgr.TxReceipt[txRef], 1) | ||
|
|
||
| // Calldata pair: pipelined, auth leg first; see TxManager.SendPairAsync for the | ||
| // contract (ordering, slot accounting, cancellation of the batch leg on failure). | ||
| queue.SendPair(authReference, verifyCandidate, authReceiptCh, transactionReference, *candidate, batchReceiptCh) | ||
|
|
||
| l.authGroup.Add(1) | ||
| go func() { | ||
| defer l.authGroup.Done() | ||
| l.watchFallbackAuthReceipts(transactionReference, authReceiptCh, batchReceiptCh, receiptsCh) | ||
| l.watchFallbackAuthReceipts(transactionReference, authReference, authReceiptCh, batchReceiptCh, receiptsCh) | ||
| }() | ||
| } | ||
|
|
||
| // watchFallbackAuthReceipts collects the auth and batch receipts for a fallback-auth pair, | ||
| // validates that the batch tx landed within the lookback window of the auth tx, and forwards a | ||
| // single synthetic receipt keyed to the batch txData onto receiptsCh. Any failure produces an | ||
| // error receipt so the channel manager rewinds and resubmits the frame set. | ||
| func (l *BatchSubmitter) watchFallbackAuthReceipts(transactionReference txRef, authReceiptCh, batchReceiptCh chan txmgr.TxReceipt[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { | ||
| // sendFallbackAuthSerialized submits an auth+batch pair serially: the auth tx is sent | ||
| // and confirmed first, then the batch tx. It runs on the publishing-loop goroutine and | ||
| // blocks it for the pair's full confirmation cycle, so consecutive pairs never overlap. | ||
| func (l *BatchSubmitter) sendFallbackAuthSerialized(transactionReference, authReference txRef, verifyCandidate txmgr.TxCandidate, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { | ||
| authReceiptCh := make(chan txmgr.TxReceipt[txRef], 1) | ||
| queue.Send(authReference, verifyCandidate, authReceiptCh) | ||
| authResult := <-authReceiptCh | ||
| batchResult := <-batchReceiptCh | ||
|
|
||
| if authResult.Err != nil { | ||
| l.Log.Error("Failed to send fallback authenticateBatchInfo transaction", "txRef", transactionReference, "err", authResult.Err) | ||
| if err := fallbackAuthResultError(authResult); err != nil { | ||
| l.Log.Error("Fallback authenticateBatchInfo transaction failed", "txRef", transactionReference, "err", err) | ||
| receiptsCh <- txmgr.TxReceipt[txRef]{ | ||
| ID: transactionReference, | ||
| Err: fmt.Errorf("failed to send fallback authenticateBatchInfo transaction: %w", authResult.Err), | ||
| ID: authReference, | ||
| Err: err, | ||
| } | ||
| return | ||
| } | ||
|
|
||
| // txmgr returns a receipt as soon as the tx is mined, regardless of execution status. A | ||
| // reverted authenticateBatchInfo call emits no BatchInfoAuthenticated event, so the verifier | ||
| // drops the batch and the safe head stalls; report failure so the frames are re-queued. The | ||
| // batch inbox tx needs no such check: derivation reads its data by L1 inclusion, not by | ||
| // execution status. | ||
| // The auth tx is confirmed on L1, releasing the account's reservation in the | ||
| // calldata pool. Now send the blob batch tx. | ||
| batchReceiptCh := make(chan txmgr.TxReceipt[txRef], 1) | ||
| queue.Send(transactionReference, *candidate, batchReceiptCh) | ||
| batchResult := <-batchReceiptCh | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In post-Espresso fallback-auth mode, Useful? React with 👍 / 👎.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this is now the intended design after addressing |
||
| l.resolveFallbackAuthPair(transactionReference, authReference, authResult, batchResult, receiptsCh) | ||
| } | ||
|
|
||
| // watchFallbackAuthReceipts collects the auth and batch receipts of a pipelined fallback-auth | ||
| // pair and resolves them into a single synthetic receipt (see resolveFallbackAuthPair). | ||
| func (l *BatchSubmitter) watchFallbackAuthReceipts(transactionReference, authReference txRef, authReceiptCh, batchReceiptCh chan txmgr.TxReceipt[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { | ||
| authResult := <-authReceiptCh | ||
| batchResult := <-batchReceiptCh | ||
| l.resolveFallbackAuthPair(transactionReference, authReference, authResult, batchResult, receiptsCh) | ||
| } | ||
|
|
||
| // fallbackAuthResultError interprets the auth leg's receipt: a send failure or a | ||
| // mined-but-reverted auth tx both fail the pair, since a reverted authenticateBatchInfo call | ||
| // emits no BatchInfoAuthenticated event and the verifier would drop the batch (txmgr returns a | ||
| // receipt as soon as the tx is mined, regardless of execution status). The batch inbox tx | ||
| // needs no such status check: derivation reads its data by L1 inclusion, not by execution | ||
| // status. | ||
| func fallbackAuthResultError(authResult txmgr.TxReceipt[txRef]) error { | ||
| if authResult.Err != nil { | ||
| return fmt.Errorf("failed to send fallback authenticateBatchInfo transaction: %w", authResult.Err) | ||
| } | ||
| if authResult.Receipt.Status != types.ReceiptStatusSuccessful { | ||
| l.Log.Error("Fallback authenticateBatchInfo transaction reverted", "txRef", transactionReference, "txHash", authResult.Receipt.TxHash) | ||
| return fmt.Errorf("fallback authenticateBatchInfo transaction reverted: %s", authResult.Receipt.TxHash) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // resolveFallbackAuthPair validates the receipts of an auth+batch pair — auth success, batch | ||
| // success, and that the batch tx landed within the lookback window of the auth tx — and | ||
| // forwards a single synthetic receipt keyed to the batch txData onto receiptsCh. Any failure | ||
| // produces an error receipt so the channel manager rewinds and resubmits the frame set. Auth | ||
| // failures are reported under authReference (see its construction in sendTxWithFallbackAuth | ||
| // for why the calldata typing matters). | ||
| func (l *BatchSubmitter) resolveFallbackAuthPair(transactionReference, authReference txRef, authResult, batchResult txmgr.TxReceipt[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { | ||
| if err := fallbackAuthResultError(authResult); err != nil { | ||
| l.Log.Error("Fallback authenticateBatchInfo transaction failed", "txRef", transactionReference, "err", err) | ||
| receiptsCh <- txmgr.TxReceipt[txRef]{ | ||
| ID: transactionReference, | ||
| Err: fmt.Errorf("fallback authenticateBatchInfo transaction reverted: %s", authResult.Receipt.TxHash), | ||
| ID: authReference, | ||
| Err: err, | ||
| } | ||
| return | ||
| } | ||
|
|
@@ -147,6 +192,7 @@ func (l *BatchSubmitter) watchFallbackAuthReceipts(transactionReference txRef, a | |
| distance := new(big.Int).Sub(batchResult.Receipt.BlockNumber, authResult.Receipt.BlockNumber) | ||
| lookbackWindow := new(big.Int).SetUint64(derive.BatchAuthLookbackWindow) | ||
| if distance.Sign() < 0 || distance.Cmp(lookbackWindow) > 0 { | ||
| l.Metr.RecordFallbackAuthWindowExceeded() | ||
| l.Log.Error("authenticateBatchInfo transaction too far from batch inbox transaction", "txRef", transactionReference, "distance", distance) | ||
| receiptsCh <- txmgr.TxReceipt[txRef]{ | ||
| ID: transactionReference, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When any deployment sets
--num-confirmationsabove 25, this new check rejects the CLI config before the batcher has loaded the rollup config, so it also blocks chains that have noBatchAuthenticatorAddressor Espresso fallback-auth path at all. The lookback bound only matters forsendTxWithFallbackAuth; non-Espresso or pre-Espresso batchers can still safely wait for more confirmations, so this should be applied only when fallback auth is actually configured/active, or after rollup config is available.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lukeiannucci
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
7ecbfdc