Skip to content

Commit 3753f35

Browse files
committed
restore pipelined fallback-auth submission
1 parent 47cb2a1 commit 3753f35

4 files changed

Lines changed: 75 additions & 22 deletions

File tree

op-batcher/batcher/driver.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,15 @@ type BatchSubmitter struct {
136136
throttleController *throttler.ThrottleController
137137

138138
publishSignal chan pubInfo
139+
140+
// authGroup tracks the fallback batcher's receipt-watcher goroutines (one
141+
// per auth+batch pair) so the publishing loop can drain them via
142+
// waitForAuthGroup before closing receiptsCh. New watchers are back-pressured
143+
// (not hard-bounded) by the txmgr Queue: queue.Send blocks at
144+
// MaxPendingTransactions, so watchers are created no faster than txs drain,
145+
// though a slow receipts loop can briefly leave more than that parked on their
146+
// final receiptsCh send.
147+
authGroup sync.WaitGroup
139148
}
140149

141150
// NewBatchSubmitter initializes the BatchSubmitter driver from a preconfigured DriverSetup
@@ -528,6 +537,12 @@ func (l *BatchSubmitter) publishingLoop(ctx context.Context, wg *sync.WaitGroup,
528537
}
529538
}
530539

540+
// Wait for all in-flight fallback-auth submissions to complete to prevent
541+
// new transactions being queued. No-op when the rollup is not configured
542+
// with a BatchAuthenticator or when the EspressoTime hardfork has not
543+
// activated.
544+
l.waitForAuthGroup()
545+
531546
// We _must_ wait for all senders on receiptsCh to finish before we can close it.
532547
if err := txQueue.Wait(); err != nil {
533548
if !errors.Is(err, context.Canceled) {

op-batcher/batcher/espresso_driver.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,20 @@ import (
66
"github.com/ethereum-optimism/optimism/op-service/txmgr"
77
)
88

9+
// waitForAuthGroup blocks until all in-flight fallback-auth watcher goroutines
10+
// have finished. publishingLoop calls it before closing receiptsCh: each watcher
11+
// is a sender on receiptsCh, so the receipts loop must still be draining
12+
// receiptsCh at this point or a watcher's final send would block forever. Each
13+
// watcher always terminates because the txmgr Queue emits exactly one receipt per
14+
// Send, even on context cancellation.
15+
func (l *BatchSubmitter) waitForAuthGroup() {
16+
l.authGroup.Wait()
17+
}
18+
919
// dispatchAuthenticatedSendTx routes sendTx through the fallback-batcher
10-
// post-fork auth path, returning true when the tx has been fully handled
11-
// (sendTxWithFallbackAuth blocks until the pair is resolved and a receipt has
12-
// been forwarded). Returns false to mean "fall through to the upstream
13-
// queue.Send path" — pre-fork operation and any cancel tx.
20+
// post-fork auth path, returning true when the tx has been handed off to
21+
// authGroup. Returns false to mean "fall through to the upstream queue.Send
22+
// path" — pre-fork operation and any cancel tx.
1423
//
1524
// The fallback batcher consults isFallbackAuthRequired to gate authentication
1625
// behind the EspressoTime hardfork: pre-fork the verifier accepts plain

op-batcher/batcher/fallback_auth.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,43 @@ func (l *BatchSubmitter) sendTxWithFallbackAuth(txdata txData, isCancel bool, ca
8686
To: &l.RollupConfig.BatchAuthenticatorAddress,
8787
}
8888

89+
// Private buffered channels: queue.Send forwards exactly one receipt to each, so the watcher
90+
// reads exactly once per channel (even on context cancellation, the queue still emits a
91+
// ctx-error receipt). These never reach handleReceipt; only the synthetic receipt below does.
92+
authReceiptCh := make(chan txmgr.TxReceipt[txRef], 1)
93+
batchReceiptCh := make(chan txmgr.TxReceipt[txRef], 1)
94+
8995
l.Log.Debug(
9096
"Sending fallback authenticateBatchInfo transaction",
9197
"txRef", transactionReference,
9298
"commitment", hexutil.Encode(commitment[:]),
9399
"address", l.RollupConfig.BatchAuthenticatorAddress.String(),
94100
)
95-
// Submit the auth tx and wait for its receipt, then send the batch tx. Each Send
96-
// blocks here when the queue is at its MaxPendingTransactions limit.
97-
authReceiptCh := make(chan txmgr.TxReceipt[txRef], 1)
101+
// Submit the auth tx then the batch tx, in order, on the publishing-loop goroutine so their
102+
// nonces are assigned in submission order (auth = N, batch = N+1), guaranteeing the auth tx
103+
// precedes the batch tx on chain. Each Send blocks here when the queue is at its
104+
// MaxPendingTransactions limit, so the pair pipelines with subsequent pairs rather than
105+
// stalling the loop for a full confirmation cycle. The auth tx uses authReference so its
106+
// failures are typed as calldata (see above).
98107
queue.Send(authReference, verifyCandidate, authReceiptCh)
108+
queue.Send(transactionReference, *candidate, batchReceiptCh)
109+
110+
l.authGroup.Add(1)
111+
go func() {
112+
defer l.authGroup.Done()
113+
l.watchFallbackAuthReceipts(transactionReference, authReference, authReceiptCh, batchReceiptCh, receiptsCh)
114+
}()
115+
}
116+
117+
// watchFallbackAuthReceipts collects the auth and batch receipts for a fallback-auth pair,
118+
// validates that the batch tx landed within the lookback window of the auth tx, and forwards a
119+
// single synthetic receipt keyed to the batch txData onto receiptsCh. Any failure produces an
120+
// error receipt so the channel manager rewinds and resubmits the frame set. Auth failures are
121+
// reported under authReference so cancelBlockingTx targets the calldata pool, while the batch
122+
// txData id is preserved so the right frames are requeued.
123+
func (l *BatchSubmitter) watchFallbackAuthReceipts(transactionReference, authReference txRef, authReceiptCh, batchReceiptCh chan txmgr.TxReceipt[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) {
99124
authResult := <-authReceiptCh
125+
batchResult := <-batchReceiptCh
100126

101127
if authResult.Err != nil {
102128
l.Log.Error("Failed to send fallback authenticateBatchInfo transaction", "txRef", transactionReference, "err", authResult.Err)
@@ -121,11 +147,6 @@ func (l *BatchSubmitter) sendTxWithFallbackAuth(txdata txData, isCancel bool, ca
121147
return
122148
}
123149

124-
// The auth tx is confirmed on L1. Now send the batch tx
125-
batchReceiptCh := make(chan txmgr.TxReceipt[txRef], 1)
126-
queue.Send(transactionReference, *candidate, batchReceiptCh)
127-
batchResult := <-batchReceiptCh
128-
129150
if batchResult.Err != nil {
130151
l.Log.Error("Failed to send batch inbox transaction", "txRef", transactionReference, "err", batchResult.Err)
131152
receiptsCh <- txmgr.TxReceipt[txRef]{

op-batcher/batcher/fallback_auth_test.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,20 @@ func TestFallbackAuth_OrderingAndSuccess(t *testing.T) {
104104
require.Equal(t, txdata.ID().String(), got.ID.id.String())
105105
}
106106

107+
// TestFallbackAuth_AuthFailureRetried verifies that an auth-tx send failure produces an error
108+
// receipt keyed to the batch txData so the frames are re-queued. The pair is submitted
109+
// back-to-back (pipelined), so the batch tx is also sent; if its auth never lands the batch is
110+
// simply dropped by derivation — its commitment has no matching auth event in the lookback
111+
// window — so the orphaned send is harmless to safety.
107112
func TestFallbackAuth_AuthFailureRetried(t *testing.T) {
108113
l := newFallbackAuthSubmitter(t)
109114
txdata := testFallbackTxData(t)
110115
candidate := &txmgr.TxCandidate{TxData: []byte("batch-calldata")}
111116

112117
queue := &fakeTxSender{
113118
responses: []txmgr.TxReceipt[txRef]{
114-
{Err: errSendFailed}, // auth fails. No batch response, it must never be sent
119+
{Err: errSendFailed}, // auth fails
120+
{Receipt: receiptWithBlock(101)}, // batch send (result discarded on auth failure)
115121
},
116122
}
117123
receiptsCh := make(chan txmgr.TxReceipt[txRef], 1)
@@ -121,9 +127,8 @@ func TestFallbackAuth_AuthFailureRetried(t *testing.T) {
121127
got := <-receiptsCh
122128
require.Error(t, got.Err)
123129
require.Equal(t, txdata.ID().String(), got.ID.id.String())
124-
// The batch tx must not be sent after an auth failure: it would be crafted at the
125-
// next nonce while the failed auth tx resets the txmgr nonce, leaving a nonce gap.
126-
require.Len(t, queue.sends, 1)
130+
// Both txs are submitted back-to-back; the auth failure does not gate the batch send.
131+
require.Len(t, queue.sends, 2)
127132
}
128133

129134
// TestFallbackAuth_AuthFailureTxRefType verifies that an auth-tx failure is
@@ -140,7 +145,8 @@ func TestFallbackAuth_AuthFailureTxRefType(t *testing.T) {
140145

141146
queue := &fakeTxSender{
142147
responses: []txmgr.TxReceipt[txRef]{
143-
{Err: errSendFailed}, // auth fails. No batch response, it must never be sent
148+
{Err: errSendFailed}, // auth fails
149+
{Receipt: receiptWithBlock(101)}, // batch send (result discarded on auth failure)
144150
},
145151
}
146152
receiptsCh := make(chan txmgr.TxReceipt[txRef], 1)
@@ -150,7 +156,7 @@ func TestFallbackAuth_AuthFailureTxRefType(t *testing.T) {
150156
got := <-receiptsCh
151157
require.Error(t, got.Err)
152158
require.Equal(t, txdata.ID().String(), got.ID.id.String())
153-
require.Len(t, queue.sends, 1)
159+
require.Len(t, queue.sends, 2)
154160
require.False(t, got.ID.isBlob)
155161
require.Equal(t, DaTypeCalldata, got.ID.daType)
156162
}
@@ -211,7 +217,8 @@ func TestFallbackAuth_AuthRevertedRetried(t *testing.T) {
211217

212218
queue := &fakeTxSender{
213219
responses: []txmgr.TxReceipt[txRef]{
214-
{Receipt: revertedReceiptWithBlock(100)}, // auth mined but reverted. Batch must never be sent
220+
{Receipt: revertedReceiptWithBlock(100)}, // auth mined but reverted
221+
{Receipt: receiptWithBlock(101)}, // batch send (result discarded on auth revert)
215222
},
216223
}
217224
receiptsCh := make(chan txmgr.TxReceipt[txRef], 1)
@@ -221,9 +228,10 @@ func TestFallbackAuth_AuthRevertedRetried(t *testing.T) {
221228
got := <-receiptsCh
222229
require.Error(t, got.Err)
223230
require.Equal(t, txdata.ID().String(), got.ID.id.String())
224-
// A reverted auth tx emits no BatchInfoAuthenticated event, so the batch data would be
225-
// unverifiable so the batch tx must not be submitted at all.
226-
require.Len(t, queue.sends, 1)
231+
// A reverted auth tx emits no BatchInfoAuthenticated event, so the batch is unverifiable and
232+
// an error receipt is forwarded to re-queue the frames. Both txs are still submitted
233+
// (pipelined); the orphaned batch is dropped by derivation for lack of a matching auth event.
234+
require.Len(t, queue.sends, 2)
227235
}
228236

229237
// TestFallbackAuth_WindowViolationRetried verifies that a batch tx landing

0 commit comments

Comments
 (0)