@@ -7,25 +7,39 @@ import (
7
7
"io"
8
8
"math/big"
9
9
_ "net/http/pprof"
10
+ "strings"
10
11
"sync"
12
+ "sync/atomic"
11
13
"time"
12
14
15
+ "github.com/ethereum-optimism/optimism/op-batcher/flags"
13
16
"github.com/ethereum-optimism/optimism/op-batcher/metrics"
14
17
"github.com/ethereum-optimism/optimism/op-node/rollup"
15
18
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
16
19
plasma "github.com/ethereum-optimism/optimism/op-plasma"
17
20
"github.com/ethereum-optimism/optimism/op-service/dial"
18
21
"github.com/ethereum-optimism/optimism/op-service/eth"
19
22
"github.com/ethereum-optimism/optimism/op-service/txmgr"
23
+ "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
20
24
"github.com/ethereum/go-ethereum/core"
21
25
"github.com/ethereum/go-ethereum/core/types"
22
26
"github.com/ethereum/go-ethereum/log"
27
+ "github.com/ethereum/go-ethereum/params"
23
28
)
24
29
30
+ const LimitLoadBlocksOneTime uint64 = 30
31
+
32
+ // Auto DA params
33
+ const DATypeSwitchThrehold int = 5
34
+ const CallDataMaxTxSize uint64 = 120000
35
+ const ApproximateGasPerCallDataTx int64 = 1934892
36
+ const MaxBlobsNumberPerTx int64 = 6
37
+
25
38
var ErrBatcherNotRunning = errors .New ("batcher is not running" )
26
39
27
40
type L1Client interface {
28
41
HeaderByNumber (ctx context.Context , number * big.Int ) (* types.Header , error )
42
+ SuggestGasTipCap (ctx context.Context ) (* big.Int , error )
29
43
}
30
44
31
45
type L2Client interface {
@@ -47,6 +61,7 @@ type DriverSetup struct {
47
61
EndpointProvider dial.L2EndpointProvider
48
62
ChannelConfig ChannelConfig
49
63
PlasmaDA * plasma.DAClient
64
+ AutoSwitchDA bool
50
65
}
51
66
52
67
// BatchSubmitter encapsulates a service responsible for submitting L2 tx
@@ -68,6 +83,9 @@ type BatchSubmitter struct {
68
83
lastStoredBlock eth.BlockID
69
84
lastL1Tip eth.L1BlockRef
70
85
86
+ // addressReservedError is recorded from L1 txpool, which may occur when switch DA type
87
+ addressReservedError atomic.Bool
88
+
71
89
state * channelManager
72
90
}
73
91
@@ -155,10 +173,15 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) error {
155
173
} else if start .Number >= end .Number {
156
174
return errors .New ("start number is >= end number" )
157
175
}
176
+ // Limit the max loaded blocks one time
177
+ endNumber := end .Number
178
+ if endNumber - start .Number > LimitLoadBlocksOneTime {
179
+ endNumber = start .Number + LimitLoadBlocksOneTime
180
+ }
158
181
159
182
var latestBlock * types.Block
160
183
// Add all blocks to "state"
161
- for i := start .Number + 1 ; i < end . Number + 1 ; i ++ {
184
+ for i := start .Number + 1 ; i < endNumber + 1 ; i ++ {
162
185
block , err := l .loadBlockIntoState (ctx , i )
163
186
if errors .Is (err , ErrReorg ) {
164
187
l .Log .Warn ("Found L2 reorg" , "block_number" , i )
@@ -272,6 +295,78 @@ func (l *BatchSubmitter) loop() {
272
295
}
273
296
}()
274
297
298
+ economicDATypeCh := make (chan flags.DataAvailabilityType )
299
+ waitSwitchDACh := make (chan struct {})
300
+ if l .AutoSwitchDA {
301
+ // start auto choose economic DA type processing loop
302
+ economicDALoopDone := make (chan struct {})
303
+ defer close (economicDALoopDone ) // shut down auto DA loop
304
+ go func () {
305
+ economicDAType := flags .BlobsType
306
+ l .Metr .RecordAutoChoosedDAType (economicDAType )
307
+ switchCount := 0
308
+ economicDATicker := time .NewTicker (5 * time .Second )
309
+ defer economicDATicker .Stop ()
310
+ addressReservedErrorTicker := time .NewTicker (time .Second )
311
+ defer addressReservedErrorTicker .Stop ()
312
+ for {
313
+ select {
314
+ case <- economicDATicker .C :
315
+ newEconomicDAType , err := l .getEconomicDAType (l .shutdownCtx )
316
+ if err != nil {
317
+ l .Log .Error ("getEconomicDAType failed: %w" , err )
318
+ continue
319
+ }
320
+ if newEconomicDAType != economicDAType {
321
+ switchCount ++
322
+ } else {
323
+ switchCount = 0
324
+ }
325
+ threhold := DATypeSwitchThrehold
326
+ if economicDAType == flags .CalldataType {
327
+ threhold = 20 * DATypeSwitchThrehold
328
+ }
329
+ if switchCount >= threhold {
330
+ l .Log .Info ("start economic switch" , "from type" , economicDAType .String (), "to type" , newEconomicDAType .String ())
331
+ start := time .Now ()
332
+ economicDAType = newEconomicDAType
333
+ switchCount = 0
334
+ economicDATypeCh <- economicDAType
335
+ <- waitSwitchDACh
336
+ l .Log .Info ("finish economic switch" , "duration" , time .Since (start ))
337
+ l .Metr .RecordAutoChoosedDAType (economicDAType )
338
+ l .Metr .RecordEconomicAutoSwitchCount ()
339
+ l .Metr .RecordAutoSwitchTimeDuration (time .Since (start ))
340
+ }
341
+ case <- addressReservedErrorTicker .C :
342
+ if l .addressReservedError .Load () {
343
+ if economicDAType == flags .BlobsType {
344
+ economicDAType = flags .CalldataType
345
+ l .Log .Info ("start resolve addressReservedError switch" , "from type" , flags .BlobsType .String (), "to type" , flags .CalldataType .String ())
346
+ } else if economicDAType == flags .CalldataType {
347
+ economicDAType = flags .BlobsType
348
+ l .Log .Info ("start resolve addressReservedError switch" , "from type" , flags .CalldataType .String (), "to type" , flags .BlobsType .String ())
349
+ } else {
350
+ l .Log .Crit ("invalid DA type in economic switch loop" , "invalid type" , economicDAType .String ())
351
+ }
352
+ switchCount = 0
353
+ start := time .Now ()
354
+ economicDATypeCh <- economicDAType
355
+ <- waitSwitchDACh
356
+ l .Log .Info ("finish resolve addressReservedError switch" , "duration" , time .Since (start ))
357
+ l .Metr .RecordAutoChoosedDAType (economicDAType )
358
+ l .Metr .RecordReservedErrorSwitchCount ()
359
+ l .Metr .RecordAutoSwitchTimeDuration (time .Since (start ))
360
+ l .addressReservedError .Store (false )
361
+ }
362
+ case <- economicDALoopDone :
363
+ l .Log .Info ("auto DA processing loop done" )
364
+ return
365
+ }
366
+ }
367
+ }()
368
+ }
369
+
275
370
ticker := time .NewTicker (l .Config .PollInterval )
276
371
defer ticker .Stop ()
277
372
@@ -302,6 +397,26 @@ func (l *BatchSubmitter) loop() {
302
397
continue
303
398
}
304
399
l .publishStateToL1 (queue , receiptsCh )
400
+ case targetDAType := <- economicDATypeCh :
401
+ l .lastStoredBlock = eth.BlockID {}
402
+ // close current state to prepare for switch
403
+ err := l .state .Close ()
404
+ if err != nil {
405
+ if errors .Is (err , ErrPendingAfterClose ) {
406
+ l .Log .Warn ("Closed channel manager to handle DA type switch with pending channel(s) remaining - submitting" )
407
+ } else {
408
+ l .Log .Error ("Error closing the channel manager to handle a DA type switch" , "err" , err )
409
+ }
410
+ }
411
+ // on DA type switch we want to publish all pending state then wait until each result clears before resetting
412
+ // the state.
413
+ publishAndWait ()
414
+ l .clearState (l .shutdownCtx )
415
+ // switch action after clear state
416
+ l .switchDAType (targetDAType )
417
+ time .Sleep (time .Minute ) // wait op-node derivation published DA data
418
+ waitSwitchDACh <- struct {}{}
419
+ continue
305
420
case <- l .shutdownCtx .Done ():
306
421
if l .Txmgr .IsClosed () {
307
422
l .Log .Info ("Txmgr is closed, remaining channel data won't be sent" )
@@ -324,6 +439,54 @@ func (l *BatchSubmitter) loop() {
324
439
}
325
440
}
326
441
442
+ func (l * BatchSubmitter ) getEconomicDAType (ctx context.Context ) (flags.DataAvailabilityType , error ) {
443
+ sCtx , sCancel := context .WithTimeout (ctx , l .Config .NetworkTimeout )
444
+ defer sCancel ()
445
+ gasPrice , err := l .L1Client .SuggestGasTipCap (sCtx )
446
+ if err != nil {
447
+ return "" , fmt .Errorf ("getEconomicDAType failed to fetch the suggested gas tip cap: %w" , err )
448
+ }
449
+ calldataCost := big .NewInt (0 ).Mul (big .NewInt (MaxBlobsNumberPerTx * ApproximateGasPerCallDataTx ), gasPrice )
450
+
451
+ hCtx , hCancel := context .WithTimeout (ctx , l .Config .NetworkTimeout )
452
+ defer hCancel ()
453
+ header , err := l .L1Client .HeaderByNumber (hCtx , nil )
454
+ if err != nil {
455
+ return "" , fmt .Errorf ("getEconomicDAType failed to fetch the latest header: %w" , err )
456
+ }
457
+ if header .ExcessBlobGas == nil {
458
+ return "" , fmt .Errorf ("getEconomicDAType fetched header with nil ExcessBlobGas: %v" , header )
459
+ }
460
+ blobGasPrice := eip4844 .CalcBlobFee (* header .ExcessBlobGas )
461
+ blobCost := big .NewInt (0 ).Add (big .NewInt (0 ).Mul (big .NewInt (int64 (params .TxGas )), gasPrice ), big .NewInt (0 ).Mul (big .NewInt (params .MaxBlobGasPerBlock ), blobGasPrice ))
462
+
463
+ l .Metr .RecordEstimatedCalldataTypeFee (calldataCost )
464
+ l .Metr .RecordEstimatedBlobTypeFee (blobCost )
465
+ if calldataCost .Cmp (blobCost ) < 0 {
466
+ l .Log .Info ("Economic DA type is calldata" , "gas price" , gasPrice , "calldata cost" , calldataCost , "blob gas price" , blobGasPrice , "blob cost" , blobCost )
467
+ return flags .CalldataType , nil
468
+ }
469
+ l .Log .Info ("Economic DA type is blobs" , "gas price" , gasPrice , "calldata cost" , calldataCost , "blob gas price" , blobGasPrice , "blob cost" , blobCost )
470
+ return flags .BlobsType , nil
471
+ }
472
+
473
+ func (l * BatchSubmitter ) switchDAType (targetDAType flags.DataAvailabilityType ) {
474
+ switch targetDAType {
475
+ case flags .BlobsType :
476
+ l .Config .UseBlobs = true
477
+ l .ChannelConfig .MaxFrameSize = eth .MaxBlobDataSize - 1
478
+ l .ChannelConfig .MultiFrameTxs = true
479
+ l .state .SwitchDAType (targetDAType )
480
+ case flags .CalldataType :
481
+ l .Config .UseBlobs = false
482
+ l .ChannelConfig .MaxFrameSize = CallDataMaxTxSize - 1
483
+ l .ChannelConfig .MultiFrameTxs = false
484
+ l .state .SwitchDAType (targetDAType )
485
+ default :
486
+ l .Log .Crit ("batch submitter switch to a invalid DA type" , "targetDAType" , targetDAType .String ())
487
+ }
488
+ }
489
+
327
490
// publishStateToL1 queues up all pending TxData to be published to the L1, returning when there is
328
491
// no more data to queue for publishing or if there was an error queing the data.
329
492
func (l * BatchSubmitter ) publishStateToL1 (queue * txmgr.Queue [txID ], receiptsCh chan txmgr.TxReceipt [txID ]) {
@@ -525,6 +688,10 @@ func (l *BatchSubmitter) recordL1Tip(l1tip eth.L1BlockRef) {
525
688
func (l * BatchSubmitter ) recordFailedTx (id txID , err error ) {
526
689
l .Log .Warn ("Transaction failed to send" , logFields (id , err )... )
527
690
l .state .TxFailed (id )
691
+ if errStringMatch (err , txmgr .ErrAlreadyReserved ) && l .AutoSwitchDA {
692
+ l .Log .Warn ("Encounter ErrAlreadyReserved" , "id" , id .String ())
693
+ l .addressReservedError .Store (true )
694
+ }
528
695
}
529
696
530
697
func (l * BatchSubmitter ) recordConfirmedTx (id txID , receipt * types.Receipt ) {
@@ -560,3 +727,12 @@ func logFields(xs ...any) (fs []any) {
560
727
}
561
728
return fs
562
729
}
730
+
731
+ func errStringMatch (err , target error ) bool {
732
+ if err == nil && target == nil {
733
+ return true
734
+ } else if err == nil || target == nil {
735
+ return false
736
+ }
737
+ return strings .Contains (err .Error (), target .Error ())
738
+ }
0 commit comments