@@ -242,26 +242,31 @@ func (s *RollupSyncService) parseAndUpdateRollupEventLogs(logs []types.Log, endB
242
242
}
243
243
244
244
var highestFinalizedBlockNumber uint64
245
+ batchWriter := s .db .NewBatch ()
245
246
for index := startBatchIndex ; index <= batchIndex ; index ++ {
246
247
parentBatchMeta , chunks , err := s .getLocalInfoForBatch (index )
247
248
if err != nil {
248
249
return fmt .Errorf ("failed to get local node info, batch index: %v, err: %w" , index , err )
249
250
}
250
251
251
- endBlock , finalizedBatchMeta , err := validateBatch (event , parentBatchMeta , chunks , s .bc .Config (), s .stack )
252
+ endBlock , finalizedBatchMeta , err := validateBatch (index , event , parentBatchMeta , chunks , s .bc .Config (), s .stack )
252
253
if err != nil {
253
254
return fmt .Errorf ("fatal: validateBatch failed: finalize event: %v, err: %w" , event , err )
254
255
}
255
256
256
- rawdb .WriteFinalizedBatchMeta (s .db , index , finalizedBatchMeta )
257
+ rawdb .WriteFinalizedBatchMeta (batchWriter , index , finalizedBatchMeta )
258
+ highestFinalizedBlockNumber = endBlock
257
259
258
260
if index % 100 == 0 {
259
261
log .Info ("finalized batch progress" , "batch index" , index , "finalized l2 block height" , endBlock )
260
262
}
261
-
262
- highestFinalizedBlockNumber = endBlock
263
263
}
264
264
265
+ if err := batchWriter .Write (); err != nil {
266
+ log .Error ("fatal: failed to batch write finalized batch meta to database" , "startBatchIndex" , startBatchIndex , "endBatchIndex" , batchIndex ,
267
+ "batchCount" , batchIndex - startBatchIndex + 1 , "highestFinalizedBlockNumber" , highestFinalizedBlockNumber , "err" , err )
268
+ return fmt .Errorf ("failed to batch write finalized batch meta to database: %w" , err )
269
+ }
265
270
rawdb .WriteFinalizedL2BlockNumber (s .db , highestFinalizedBlockNumber )
266
271
rawdb .WriteLastFinalizedBatchIndex (s .db , batchIndex )
267
272
log .Debug ("write finalized l2 block number" , "batch index" , batchIndex , "finalized l2 block height" , highestFinalizedBlockNumber )
@@ -423,42 +428,48 @@ func (s *RollupSyncService) decodeChunkBlockRanges(txData []byte) ([]*rawdb.Chun
423
428
}
424
429
425
430
// validateBatch verifies the consistency between the L1 contract and L2 node data.
431
+ // It performs the following checks:
432
+ // 1. Recalculates the batch hash locally
433
+ // 2. Compares local state root, local withdraw root, and locally calculated batch hash with L1 data (for the last batch only when "finalize by bundle")
434
+ //
426
435
// The function will terminate the node and exit if any consistency check fails.
427
- // It returns the number of the end block, a finalized batch meta data, and an error if any.
428
- func validateBatch (event * L1FinalizeBatchEvent , parentBatchMeta * rawdb.FinalizedBatchMeta , chunks []* encoding.Chunk , chainCfg * params.ChainConfig , stack * node.Node ) (uint64 , * rawdb.FinalizedBatchMeta , error ) {
436
+ //
437
+ // Parameters:
438
+ // - batchIndex: batch index of the validated batch
439
+ // - event: L1 finalize batch event data
440
+ // - parentBatchMeta: metadata of the parent batch
441
+ // - chunks: slice of chunk data for the current batch
442
+ // - chainCfg: chain configuration to identify the codec version
443
+ // - stack: node stack to terminate the node in case of inconsistency
444
+ //
445
+ // Returns:
446
+ // - uint64: the end block height of the batch
447
+ // - *rawdb.FinalizedBatchMeta: finalized batch metadata
448
+ // - error: any error encountered during validation
449
+ //
450
+ // Note: This function is compatible with both "finalize by batch" and "finalize by bundle" methods.
451
+ // In "finalize by bundle", only the last batch of each bundle is fully verified.
452
+ // This check still ensures the correctness of all batch hashes in the bundle due to the parent-child relationship between batch hashes.
453
+ func validateBatch (batchIndex uint64 , event * L1FinalizeBatchEvent , parentBatchMeta * rawdb.FinalizedBatchMeta , chunks []* encoding.Chunk , chainCfg * params.ChainConfig , stack * node.Node ) (uint64 , * rawdb.FinalizedBatchMeta , error ) {
429
454
if len (chunks ) == 0 {
430
- return 0 , nil , fmt .Errorf ("invalid argument: length of chunks is 0, batch index: %v" , event . BatchIndex . Uint64 () )
455
+ return 0 , nil , fmt .Errorf ("invalid argument: length of chunks is 0, batch index: %v" , batchIndex )
431
456
}
432
457
433
458
startChunk := chunks [0 ]
434
459
if len (startChunk .Blocks ) == 0 {
435
- return 0 , nil , fmt .Errorf ("invalid argument: block count of start chunk is 0, batch index: %v" , event . BatchIndex . Uint64 () )
460
+ return 0 , nil , fmt .Errorf ("invalid argument: block count of start chunk is 0, batch index: %v" , batchIndex )
436
461
}
437
462
startBlock := startChunk .Blocks [0 ]
438
463
439
464
endChunk := chunks [len (chunks )- 1 ]
440
465
if len (endChunk .Blocks ) == 0 {
441
- return 0 , nil , fmt .Errorf ("invalid argument: block count of end chunk is 0, batch index: %v" , event . BatchIndex . Uint64 () )
466
+ return 0 , nil , fmt .Errorf ("invalid argument: block count of end chunk is 0, batch index: %v" , batchIndex )
442
467
}
443
468
endBlock := endChunk .Blocks [len (endChunk .Blocks )- 1 ]
444
469
445
- localStateRoot := endBlock .Header .Root
446
- if localStateRoot != event .StateRoot {
447
- log .Error ("State root mismatch" , "batch index" , event .BatchIndex .Uint64 (), "start block" , startBlock .Header .Number .Uint64 (), "end block" , endBlock .Header .Number .Uint64 (), "parent batch hash" , parentBatchMeta .BatchHash .Hex (), "l1 finalized state root" , event .StateRoot .Hex (), "l2 state root" , localStateRoot .Hex ())
448
- stack .Close ()
449
- os .Exit (1 )
450
- }
451
-
452
- localWithdrawRoot := endBlock .WithdrawRoot
453
- if localWithdrawRoot != event .WithdrawRoot {
454
- log .Error ("Withdraw root mismatch" , "batch index" , event .BatchIndex .Uint64 (), "start block" , startBlock .Header .Number .Uint64 (), "end block" , endBlock .Header .Number .Uint64 (), "parent batch hash" , parentBatchMeta .BatchHash .Hex (), "l1 finalized withdraw root" , event .WithdrawRoot .Hex (), "l2 withdraw root" , localWithdrawRoot .Hex ())
455
- stack .Close ()
456
- os .Exit (1 )
457
- }
458
-
459
470
// Note: All params of batch are calculated locally based on the block data.
460
471
batch := & encoding.Batch {
461
- Index : event . BatchIndex . Uint64 () ,
472
+ Index : batchIndex ,
462
473
TotalL1MessagePoppedBefore : parentBatchMeta .TotalL1MessagePopped ,
463
474
ParentBatchHash : parentBatchMeta .BatchHash ,
464
475
Chunks : chunks ,
@@ -468,40 +479,64 @@ func validateBatch(event *L1FinalizeBatchEvent, parentBatchMeta *rawdb.Finalized
468
479
if startBlock .Header .Number .Uint64 () == 0 || ! chainCfg .IsBernoulli (startBlock .Header .Number ) { // codecv0: genesis batch or batches before Bernoulli
469
480
daBatch , err := codecv0 .NewDABatch (batch )
470
481
if err != nil {
471
- return 0 , nil , fmt .Errorf ("failed to create codecv0 DA batch, batch index: %v, err: %w" , event . BatchIndex . Uint64 () , err )
482
+ return 0 , nil , fmt .Errorf ("failed to create codecv0 DA batch, batch index: %v, err: %w" , batchIndex , err )
472
483
}
473
484
localBatchHash = daBatch .Hash ()
474
485
} else if ! chainCfg .IsCurie (startBlock .Header .Number ) { // codecv1: batches after Bernoulli and before Curie
475
486
daBatch , err := codecv1 .NewDABatch (batch )
476
487
if err != nil {
477
- return 0 , nil , fmt .Errorf ("failed to create codecv1 DA batch, batch index: %v, err: %w" , event . BatchIndex . Uint64 () , err )
488
+ return 0 , nil , fmt .Errorf ("failed to create codecv1 DA batch, batch index: %v, err: %w" , batchIndex , err )
478
489
}
479
490
localBatchHash = daBatch .Hash ()
480
491
} else if ! chainCfg .IsDarwin (startBlock .Header .Time ) { // codecv2: batches after Curie and before Darwin
481
492
daBatch , err := codecv2 .NewDABatch (batch )
482
493
if err != nil {
483
- return 0 , nil , fmt .Errorf ("failed to create codecv2 DA batch, batch index: %v, err: %w" , event . BatchIndex . Uint64 () , err )
494
+ return 0 , nil , fmt .Errorf ("failed to create codecv2 DA batch, batch index: %v, err: %w" , batchIndex , err )
484
495
}
485
496
localBatchHash = daBatch .Hash ()
486
497
} else { // codecv3: batches after Darwin
487
498
daBatch , err := codecv3 .NewDABatch (batch )
488
499
if err != nil {
489
- return 0 , nil , fmt .Errorf ("failed to create codecv3 DA batch, batch index: %v, err: %w" , event . BatchIndex . Uint64 () , err )
500
+ return 0 , nil , fmt .Errorf ("failed to create codecv3 DA batch, batch index: %v, err: %w" , batchIndex , err )
490
501
}
491
502
localBatchHash = daBatch .Hash ()
492
503
}
493
504
494
- // Note: If the batch headers match, this ensures the consistency of blocks and transactions
505
+ localStateRoot := endBlock .Header .Root
506
+ localWithdrawRoot := endBlock .WithdrawRoot
507
+
508
+ // Note: If the state root, withdraw root, and batch headers match, this ensures the consistency of blocks and transactions
495
509
// (including skipped transactions) between L1 and L2.
496
- if localBatchHash != event .BatchHash {
497
- log .Error ("Batch hash mismatch" , "batch index" , event .BatchIndex .Uint64 (), "start block" , startBlock .Header .Number .Uint64 (), "end block" , endBlock .Header .Number .Uint64 (), "parent batch hash" , parentBatchMeta .BatchHash .Hex (), "parent TotalL1MessagePopped" , parentBatchMeta .TotalL1MessagePopped , "l1 finalized batch hash" , event .BatchHash .Hex (), "l2 batch hash" , localBatchHash .Hex ())
498
- chunksJson , err := json .Marshal (chunks )
499
- if err != nil {
500
- log .Error ("marshal chunks failed" , "err" , err )
510
+ //
511
+ // Only check when batch index matches the index of the event. This is compatible with both "finalize by batch" and "finalize by bundle":
512
+ // - finalize by batch: check all batches
513
+ // - finalize by bundle: check the last batch, because only one event (containing the info of the last batch) is emitted per bundle
514
+ if batchIndex == event .BatchIndex .Uint64 () {
515
+ if localStateRoot != event .StateRoot {
516
+ log .Error ("State root mismatch" , "batch index" , event .BatchIndex .Uint64 (), "start block" , startBlock .Header .Number .Uint64 (), "end block" , endBlock .Header .Number .Uint64 (), "parent batch hash" , parentBatchMeta .BatchHash .Hex (), "l1 finalized state root" , event .StateRoot .Hex (), "l2 state root" , localStateRoot .Hex ())
517
+ stack .Close ()
518
+ os .Exit (1 )
519
+ }
520
+
521
+ if localWithdrawRoot != event .WithdrawRoot {
522
+ log .Error ("Withdraw root mismatch" , "batch index" , event .BatchIndex .Uint64 (), "start block" , startBlock .Header .Number .Uint64 (), "end block" , endBlock .Header .Number .Uint64 (), "parent batch hash" , parentBatchMeta .BatchHash .Hex (), "l1 finalized withdraw root" , event .WithdrawRoot .Hex (), "l2 withdraw root" , localWithdrawRoot .Hex ())
523
+ stack .Close ()
524
+ os .Exit (1 )
525
+ }
526
+
527
+ // Verify batch hash
528
+ // This check ensures the correctness of all batch hashes in the bundle
529
+ // due to the parent-child relationship between batch hashes
530
+ if localBatchHash != event .BatchHash {
531
+ log .Error ("Batch hash mismatch" , "batch index" , event .BatchIndex .Uint64 (), "start block" , startBlock .Header .Number .Uint64 (), "end block" , endBlock .Header .Number .Uint64 (), "parent batch hash" , parentBatchMeta .BatchHash .Hex (), "parent TotalL1MessagePopped" , parentBatchMeta .TotalL1MessagePopped , "l1 finalized batch hash" , event .BatchHash .Hex (), "l2 batch hash" , localBatchHash .Hex ())
532
+ chunksJson , err := json .Marshal (chunks )
533
+ if err != nil {
534
+ log .Error ("marshal chunks failed" , "err" , err )
535
+ }
536
+ log .Error ("Chunks" , "chunks" , string (chunksJson ))
537
+ stack .Close ()
538
+ os .Exit (1 )
501
539
}
502
- log .Error ("Chunks" , "chunks" , string (chunksJson ))
503
- stack .Close ()
504
- os .Exit (1 )
505
540
}
506
541
507
542
totalL1MessagePopped := parentBatchMeta .TotalL1MessagePopped
0 commit comments