go-ethereum/rollup/da_syncer/batch_queue.go
Jonas Theis 40ebbd6491
feat(L1 follower): adjust to recent CodecV7 and contract changes (#1120)
* port changes from #1013

* port changes from #1068

* go.mod tidy

* fix compile error

* fix goimports

* fix log

* address review comments

* upgrade golang.org/x/net to 0.23.0

* port changes from #1018

* fix tests and linter errors

* address review comments

* refactor rollup sync service / verifier to use CalldataBlobSource to retrieve data from L1

* add configuration and initialize blob clients

* fix unit tests

* remove unused code

* address review comments

* address more review comments

* implement first version of new da-codec and to handle multiple batches submitted in one transaction

* add CommitBatchDAV7 and handle multiple commit events submitted in a single transactions

* fix bug due to previous batch being empty when processing the first batch within a set of batches

* Allow using MPT

* update to latest da-codec

* add field to CommittedBatchMeta to store LastL1MessageQueueHash for CodecV7 batches

* adjust rollup verifier to support CodecV7 batches

* address review comments

* fix issues after merge

* go mod tidy

* fix unit tests

* update da-codec

* add test TestValidateBatchCodecV7

* go mod tidy

* do not log error on shutdown

* add sanity check for version to deserialization of committedBatchMetaV7

* port changes from #1073

* chore: auto version bump [bot]

* address review comments

* add more logs

* disable ENRUpdater if DA sync mode is enabled

* exit pipeline if context is cancelled

* correctly handle override by setting the head of the chain to the parent's height so that created blocks will always become part of canonical chain

* fix error with genesis event being nil

* chore: auto version bump [bot]

* chore: auto version bump [bot]

* adjust to renaming in CodecV7

* implement carrying forward of L1 MessageQueue index

* fix issue after upgrading from old storage to new format where batchIndex was 0 and all batches would be skipped

* add new RevertBatch event

* add commitBatches to be able to read calldata of CodecV7/EuclidV2 committed batches

* implement finding of L1 message queue height for initial batch in recovery mode

* add sanity checks for computed batches from events and batch hashes given via calldata from commit transaction

* update ScrollChain ABI

* chore: auto version bump [bot]

* remove initial batch form DAQueue

* go mod tidy

* fix underflow bug when l1DeploymentBlock==0

* fix bug with wrong parentBatchHash of first batch of batches submitted in a single tx

* update to latest da-codec

* address review comments

* address review comments

* fix bug when l1MessageV2StartIndex==0 serialized to [] (empty slice) which would always be decoded to non-existing instead of 0

* chore: auto version bump [bot]

* cache go dependencies in Dockerfile.mockccc

* chore: auto version bump [bot]

* add INFO log when reverting rollup events in L1 follower mode

* change LastProcessedMessageQueueIndex of finalize event to  totalL1MessagesPoppedOverall

* cleanup

* chore: auto version bump [bot]

---------

Co-authored-by: Ömer Faruk Irmak <omerfirmak@gmail.com>
Co-authored-by: Thegaram <Thegaram@users.noreply.github.com>
Co-authored-by: jonastheis <jonastheis@users.noreply.github.com>
2025-03-03 13:14:32 +01:00

179 lines
6.2 KiB
Go

package da_syncer
import (
"context"
"fmt"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/ethdb"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rollup/da_syncer/da"
"github.com/scroll-tech/go-ethereum/rollup/l1"
)
// BatchQueue is a pipeline stage that reads all batch events from DAQueue and provides only finalized batches to the next stage.
type BatchQueue struct {
DAQueue *DAQueue
db ethdb.Database
lastFinalizedBatchIndex uint64
batches *common.Heap[da.Entry]
batchesMap *common.ShrinkingMap[uint64, *common.HeapElement[da.Entry]]
previousBatch *rawdb.DAProcessedBatchMeta
}
func NewBatchQueue(DAQueue *DAQueue, db ethdb.Database, lastProcessedBatch *rawdb.DAProcessedBatchMeta) *BatchQueue {
return &BatchQueue{
DAQueue: DAQueue,
db: db,
lastFinalizedBatchIndex: lastProcessedBatch.BatchIndex,
batches: common.NewHeap[da.Entry](),
batchesMap: common.NewShrinkingMap[uint64, *common.HeapElement[da.Entry]](1000),
previousBatch: lastProcessedBatch,
}
}
// NextBatch finds next finalized batch and returns data, that was committed in that batch
func (bq *BatchQueue) NextBatch(ctx context.Context) (da.EntryWithBlocks, error) {
if batch := bq.nextFinalizedBatch(); batch != nil {
return batch, nil
}
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
daEntry, err := bq.DAQueue.NextDA(ctx)
if err != nil {
return nil, err
}
switch daEntry.Type() {
case da.CommitBatchV0Type, da.CommitBatchWithBlobType:
bq.addBatch(daEntry)
case da.RevertBatchType:
if err = bq.handleRevertEvent(daEntry.Event()); err != nil {
return nil, fmt.Errorf("failed to handle revert event: %w", err)
}
case da.FinalizeBatchType:
if daEntry.BatchIndex() > bq.lastFinalizedBatchIndex {
bq.lastFinalizedBatchIndex = daEntry.BatchIndex()
}
if batch := bq.nextFinalizedBatch(); batch != nil {
return batch, nil
}
default:
return nil, fmt.Errorf("unexpected type of daEntry: %T", daEntry)
}
}
}
// nextFinalizedBatch returns next finalized batch if there is available
func (bq *BatchQueue) nextFinalizedBatch() da.EntryWithBlocks {
if bq.batches.Len() == 0 {
return nil
}
batch := bq.batches.Peek().Value()
// we process all batches smaller or equal to the last finalized batch index -> this reflects bundles of multiple batches
// where we only receive the finalize event for the last batch of the bundle.
if batch.BatchIndex() <= bq.lastFinalizedBatchIndex {
return bq.processAndDeleteBatch(batch)
} else {
return nil
}
}
func (bq *BatchQueue) addBatch(batch da.Entry) {
heapElement := bq.batches.Push(batch)
bq.batchesMap.Set(batch.BatchIndex(), heapElement)
}
func (bq *BatchQueue) handleRevertEvent(event l1.RollupEvent) error {
switch event.Type() {
case l1.RevertEventV0Type:
revertBatch, ok := event.(*l1.RevertBatchEventV0)
if !ok {
return fmt.Errorf("unexpected type of revert event: %T, expected RevertEventV0Type", event)
}
log.Info("reverting batch due to RevertEventV0Type", "batchIndex", revertBatch.BatchIndex())
bq.deleteBatch(revertBatch.BatchIndex().Uint64())
case l1.RevertEventV7Type:
revertBatch, ok := event.(*l1.RevertBatchEventV7)
if !ok {
return fmt.Errorf("unexpected type of revert event: %T, expected RevertEventV7Type", event)
}
// delete all batches from revertBatch.StartBatchIndex (inclusive) to revertBatch.FinishBatchIndex (inclusive)
for i := revertBatch.StartBatchIndex().Uint64(); i <= revertBatch.FinishBatchIndex().Uint64(); i++ {
log.Info("reverting batch due to RevertEventV7Type", "batchIndex", i)
bq.deleteBatch(i)
}
default:
return fmt.Errorf("unexpected type of revert event: %T", event)
}
return nil
}
func (bq *BatchQueue) deleteBatch(batchIndex uint64) (deleted bool) {
batchHeapElement, exists := bq.batchesMap.Get(batchIndex)
if !exists {
return false
}
bq.batchesMap.Delete(batchIndex)
bq.batches.Remove(batchHeapElement)
return true
}
// processAndDeleteBatch processes a batch and deletes the batch from map. Stores the syncing progress on disk.
func (bq *BatchQueue) processAndDeleteBatch(batch da.Entry) da.EntryWithBlocks {
if !bq.deleteBatch(batch.BatchIndex()) {
return nil
}
entryWithBlocks, ok := batch.(da.EntryWithBlocks)
if !ok {
// this should only happen if we delete a reverted batch
return nil
}
// sanity check that the next batch is the one we expect. If not, we skip the batch.
if bq.previousBatch.BatchIndex > 0 && bq.previousBatch.BatchIndex+1 != entryWithBlocks.BatchIndex() {
log.Info("BatchQueue: skipping batch ", "currentBatch", entryWithBlocks.BatchIndex(), "previousBatch", bq.previousBatch.BatchIndex)
return nil
}
// carry forward the total L1 messages popped from the previous batch
entryWithBlocks.SetParentTotalL1MessagePopped(bq.previousBatch.TotalL1MessagesPopped)
// we store the previous batch as it has been completely processed which we know because the next batch is requested within the pipeline.
// In case of a restart or crash we can continue from the last processed batch (and its metadata).
rawdb.WriteDAProcessedBatchMeta(bq.db, bq.previousBatch)
log.Info("processing batch", "batchIndex", entryWithBlocks.BatchIndex(), "L1BlockNumber", entryWithBlocks.L1BlockNumber(), "totalL1MessagesPopped", entryWithBlocks.TotalL1MessagesPopped(), "previousBatch", bq.previousBatch.BatchIndex, "previousL1BlockNumber", bq.previousBatch.L1BlockNumber, "previous TotalL1MessagesPopped", bq.previousBatch.TotalL1MessagesPopped)
bq.previousBatch = &rawdb.DAProcessedBatchMeta{
L1BlockNumber: entryWithBlocks.L1BlockNumber(),
BatchIndex: entryWithBlocks.BatchIndex(),
TotalL1MessagesPopped: entryWithBlocks.TotalL1MessagesPopped(),
}
return entryWithBlocks
}
func (bq *BatchQueue) Reset(lastProcessedBatchMeta *rawdb.DAProcessedBatchMeta) {
bq.batches.Clear()
bq.batchesMap.Clear()
bq.lastFinalizedBatchIndex = lastProcessedBatchMeta.BatchIndex
bq.previousBatch = lastProcessedBatchMeta
bq.DAQueue.Reset(lastProcessedBatchMeta)
}