Skip to content
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

fix(l2geth-verifier): only check last batch of the bundle #904

Merged
2 changes: 1 addition & 1 deletion params/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
const (
VersionMajor = 5 // Major version component of the current release
VersionMinor = 5 // Minor version component of the current release
VersionPatch = 12 // Patch version component of the current release
VersionPatch = 13 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string
)

Expand Down
98 changes: 64 additions & 34 deletions rollup/rollup_sync_service/rollup_sync_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ func (s *RollupSyncService) parseAndUpdateRollupEventLogs(logs []types.Log, endB
return fmt.Errorf("failed to get local node info, batch index: %v, err: %w", index, err)
}

endBlock, finalizedBatchMeta, err := validateBatch(event, parentBatchMeta, chunks, s.bc.Config(), s.stack)
endBlock, finalizedBatchMeta, err := validateBatch(index, event, parentBatchMeta, chunks, s.bc.Config(), s.stack)
Thegaram marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("fatal: validateBatch failed: finalize event: %v, err: %w", event, err)
}
Expand Down Expand Up @@ -423,42 +423,48 @@ func (s *RollupSyncService) decodeChunkBlockRanges(txData []byte) ([]*rawdb.Chun
}

// validateBatch verifies the consistency between the L1 contract and L2 node data.
// It performs the following checks:
// 1. Recalculates the batch hash locally
// 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")
//
// The function will terminate the node and exit if any consistency check fails.
// It returns the number of the end block, a finalized batch meta data, and an error if any.
func validateBatch(event *L1FinalizeBatchEvent, parentBatchMeta *rawdb.FinalizedBatchMeta, chunks []*encoding.Chunk, chainCfg *params.ChainConfig, stack *node.Node) (uint64, *rawdb.FinalizedBatchMeta, error) {
//
// Parameters:
// - batchIndex: batch index of the validated batch
// - event: L1 finalize batch event data
// - parentBatchMeta: metadata of the parent batch
// - chunks: slice of chunk data for the current batch
// - chainCfg: chain configuration to identify the codec version
// - stack: node stack to terminate the node in case of inconsistency
//
// Returns:
// - uint64: the end block height of the batch
// - *rawdb.FinalizedBatchMeta: finalized batch metadata
// - error: any error encountered during validation
//
// Note: This function is compatible with both "finalize by batch" and "finalize by bundle" methods.
// In "finalize by bundle", only the last batch of each bundle is fully verified.
0xmountaintop marked this conversation as resolved.
Show resolved Hide resolved
// This check still ensures the correctness of all batch hashes in the bundle due to the parent-child relationship between batch hashes.
func validateBatch(batchIndex uint64, event *L1FinalizeBatchEvent, parentBatchMeta *rawdb.FinalizedBatchMeta, chunks []*encoding.Chunk, chainCfg *params.ChainConfig, stack *node.Node) (uint64, *rawdb.FinalizedBatchMeta, error) {
if len(chunks) == 0 {
return 0, nil, fmt.Errorf("invalid argument: length of chunks is 0, batch index: %v", event.BatchIndex.Uint64())
return 0, nil, fmt.Errorf("invalid argument: length of chunks is 0, batch index: %v", batchIndex)
}

startChunk := chunks[0]
if len(startChunk.Blocks) == 0 {
return 0, nil, fmt.Errorf("invalid argument: block count of start chunk is 0, batch index: %v", event.BatchIndex.Uint64())
return 0, nil, fmt.Errorf("invalid argument: block count of start chunk is 0, batch index: %v", batchIndex)
}
startBlock := startChunk.Blocks[0]

endChunk := chunks[len(chunks)-1]
if len(endChunk.Blocks) == 0 {
return 0, nil, fmt.Errorf("invalid argument: block count of end chunk is 0, batch index: %v", event.BatchIndex.Uint64())
return 0, nil, fmt.Errorf("invalid argument: block count of end chunk is 0, batch index: %v", batchIndex)
}
endBlock := endChunk.Blocks[len(endChunk.Blocks)-1]

localStateRoot := endBlock.Header.Root
if localStateRoot != event.StateRoot {
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())
stack.Close()
os.Exit(1)
}

localWithdrawRoot := endBlock.WithdrawRoot
if localWithdrawRoot != event.WithdrawRoot {
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())
stack.Close()
os.Exit(1)
}

// Note: All params of batch are calculated locally based on the block data.
batch := &encoding.Batch{
Index: event.BatchIndex.Uint64(),
Index: batchIndex,
TotalL1MessagePoppedBefore: parentBatchMeta.TotalL1MessagePopped,
ParentBatchHash: parentBatchMeta.BatchHash,
Chunks: chunks,
Expand All @@ -468,40 +474,64 @@ func validateBatch(event *L1FinalizeBatchEvent, parentBatchMeta *rawdb.Finalized
if startBlock.Header.Number.Uint64() == 0 || !chainCfg.IsBernoulli(startBlock.Header.Number) { // codecv0: genesis batch or batches before Bernoulli
daBatch, err := codecv0.NewDABatch(batch)
if err != nil {
return 0, nil, fmt.Errorf("failed to create codecv0 DA batch, batch index: %v, err: %w", event.BatchIndex.Uint64(), err)
return 0, nil, fmt.Errorf("failed to create codecv0 DA batch, batch index: %v, err: %w", batchIndex, err)
}
localBatchHash = daBatch.Hash()
} else if !chainCfg.IsCurie(startBlock.Header.Number) { // codecv1: batches after Bernoulli and before Curie
daBatch, err := codecv1.NewDABatch(batch)
if err != nil {
return 0, nil, fmt.Errorf("failed to create codecv1 DA batch, batch index: %v, err: %w", event.BatchIndex.Uint64(), err)
return 0, nil, fmt.Errorf("failed to create codecv1 DA batch, batch index: %v, err: %w", batchIndex, err)
}
localBatchHash = daBatch.Hash()
} else if !chainCfg.IsDarwin(startBlock.Header.Time) { // codecv2: batches after Curie and before Darwin
daBatch, err := codecv2.NewDABatch(batch)
if err != nil {
return 0, nil, fmt.Errorf("failed to create codecv2 DA batch, batch index: %v, err: %w", event.BatchIndex.Uint64(), err)
return 0, nil, fmt.Errorf("failed to create codecv2 DA batch, batch index: %v, err: %w", batchIndex, err)
}
localBatchHash = daBatch.Hash()
} else { // codecv3: batches after Darwin
daBatch, err := codecv3.NewDABatch(batch)
if err != nil {
return 0, nil, fmt.Errorf("failed to create codecv3 DA batch, batch index: %v, err: %w", event.BatchIndex.Uint64(), err)
return 0, nil, fmt.Errorf("failed to create codecv3 DA batch, batch index: %v, err: %w", batchIndex, err)
}
localBatchHash = daBatch.Hash()
}

// Note: If the batch headers match, this ensures the consistency of blocks and transactions
localStateRoot := endBlock.Header.Root
localWithdrawRoot := endBlock.WithdrawRoot

// Note: If the state root, withdraw root, and batch headers match, this ensures the consistency of blocks and transactions
// (including skipped transactions) between L1 and L2.
if localBatchHash != event.BatchHash {
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())
chunksJson, err := json.Marshal(chunks)
if err != nil {
log.Error("marshal chunks failed", "err", err)
//
// Only check when batch index matches the index of the event. This is compatible with both "finalize by batch" and "finalize by bundle":
// - finalize by batch: check all batches
// - finalize by bundle: check the last batch, because only one event (containing the info of the last batch) is emitted per bundle
if batchIndex == event.BatchIndex.Uint64() {
if localStateRoot != event.StateRoot {
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())
stack.Close()
os.Exit(1)
}

if localWithdrawRoot != event.WithdrawRoot {
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())
stack.Close()
os.Exit(1)
}

// Verify batch hash
// This check ensures the correctness of all batch hashes in the bundle
// due to the parent-child relationship between batch hashes
if localBatchHash != event.BatchHash {
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())
chunksJson, err := json.Marshal(chunks)
if err != nil {
log.Error("marshal chunks failed", "err", err)
}
log.Error("Chunks", "chunks", string(chunksJson))
stack.Close()
os.Exit(1)
}
log.Error("Chunks", "chunks", string(chunksJson))
stack.Close()
os.Exit(1)
}

totalL1MessagePopped := parentBatchMeta.TotalL1MessagePopped
Expand Down
69 changes: 57 additions & 12 deletions rollup/rollup_sync_service/rollup_sync_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ func TestValidateBatchCodecv0(t *testing.T) {
WithdrawRoot: chunk3.Blocks[len(chunk3.Blocks)-1].WithdrawRoot,
}

endBlock1, finalizedBatchMeta1, err := validateBatch(event1, parentBatchMeta1, []*encoding.Chunk{chunk1, chunk2, chunk3}, chainConfig, nil)
endBlock1, finalizedBatchMeta1, err := validateBatch(event1.BatchIndex.Uint64(), event1, parentBatchMeta1, []*encoding.Chunk{chunk1, chunk2, chunk3}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(13), endBlock1)

Expand All @@ -572,7 +572,7 @@ func TestValidateBatchCodecv0(t *testing.T) {
StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root,
WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot,
}
endBlock2, finalizedBatchMeta2, err := validateBatch(event2, parentBatchMeta2, []*encoding.Chunk{chunk4}, chainConfig, nil)
endBlock2, finalizedBatchMeta2, err := validateBatch(event2.BatchIndex.Uint64(), event2, parentBatchMeta2, []*encoding.Chunk{chunk4}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(17), endBlock2)

Expand Down Expand Up @@ -605,7 +605,7 @@ func TestValidateBatchCodecv1(t *testing.T) {
WithdrawRoot: chunk3.Blocks[len(chunk3.Blocks)-1].WithdrawRoot,
}

endBlock1, finalizedBatchMeta1, err := validateBatch(event1, parentBatchMeta1, []*encoding.Chunk{chunk1, chunk2, chunk3}, chainConfig, nil)
endBlock1, finalizedBatchMeta1, err := validateBatch(event1.BatchIndex.Uint64(), event1, parentBatchMeta1, []*encoding.Chunk{chunk1, chunk2, chunk3}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(13), endBlock1)

Expand All @@ -625,7 +625,7 @@ func TestValidateBatchCodecv1(t *testing.T) {
StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root,
WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot,
}
endBlock2, finalizedBatchMeta2, err := validateBatch(event2, parentBatchMeta2, []*encoding.Chunk{chunk4}, chainConfig, nil)
endBlock2, finalizedBatchMeta2, err := validateBatch(event2.BatchIndex.Uint64(), event2, parentBatchMeta2, []*encoding.Chunk{chunk4}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(17), endBlock2)

Expand Down Expand Up @@ -658,7 +658,7 @@ func TestValidateBatchCodecv2(t *testing.T) {
WithdrawRoot: chunk3.Blocks[len(chunk3.Blocks)-1].WithdrawRoot,
}

endBlock1, finalizedBatchMeta1, err := validateBatch(event1, parentBatchMeta1, []*encoding.Chunk{chunk1, chunk2, chunk3}, chainConfig, nil)
endBlock1, finalizedBatchMeta1, err := validateBatch(event1.BatchIndex.Uint64(), event1, parentBatchMeta1, []*encoding.Chunk{chunk1, chunk2, chunk3}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(13), endBlock1)

Expand All @@ -678,7 +678,7 @@ func TestValidateBatchCodecv2(t *testing.T) {
StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root,
WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot,
}
endBlock2, finalizedBatchMeta2, err := validateBatch(event2, parentBatchMeta2, []*encoding.Chunk{chunk4}, chainConfig, nil)
endBlock2, finalizedBatchMeta2, err := validateBatch(event2.BatchIndex.Uint64(), event2, parentBatchMeta2, []*encoding.Chunk{chunk4}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(17), endBlock2)

Expand Down Expand Up @@ -711,7 +711,7 @@ func TestValidateBatchCodecv3(t *testing.T) {
WithdrawRoot: chunk3.Blocks[len(chunk3.Blocks)-1].WithdrawRoot,
}

endBlock1, finalizedBatchMeta1, err := validateBatch(event1, parentBatchMeta1, []*encoding.Chunk{chunk1, chunk2, chunk3}, chainConfig, nil)
endBlock1, finalizedBatchMeta1, err := validateBatch(event1.BatchIndex.Uint64(), event1, parentBatchMeta1, []*encoding.Chunk{chunk1, chunk2, chunk3}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(13), endBlock1)

Expand All @@ -731,7 +731,7 @@ func TestValidateBatchCodecv3(t *testing.T) {
StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root,
WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot,
}
endBlock2, finalizedBatchMeta2, err := validateBatch(event2, parentBatchMeta2, []*encoding.Chunk{chunk4}, chainConfig, nil)
endBlock2, finalizedBatchMeta2, err := validateBatch(event2.BatchIndex.Uint64(), event2, parentBatchMeta2, []*encoding.Chunk{chunk4}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(17), endBlock2)

Expand All @@ -758,7 +758,7 @@ func TestValidateBatchUpgrades(t *testing.T) {
WithdrawRoot: chunk1.Blocks[len(chunk1.Blocks)-1].WithdrawRoot,
}

endBlock1, finalizedBatchMeta1, err := validateBatch(event1, parentBatchMeta1, []*encoding.Chunk{chunk1}, chainConfig, nil)
endBlock1, finalizedBatchMeta1, err := validateBatch(event1.BatchIndex.Uint64(), event1, parentBatchMeta1, []*encoding.Chunk{chunk1}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(2), endBlock1)

Expand All @@ -778,7 +778,7 @@ func TestValidateBatchUpgrades(t *testing.T) {
StateRoot: chunk2.Blocks[len(chunk2.Blocks)-1].Header.Root,
WithdrawRoot: chunk2.Blocks[len(chunk2.Blocks)-1].WithdrawRoot,
}
endBlock2, finalizedBatchMeta2, err := validateBatch(event2, parentBatchMeta2, []*encoding.Chunk{chunk2}, chainConfig, nil)
endBlock2, finalizedBatchMeta2, err := validateBatch(event2.BatchIndex.Uint64(), event2, parentBatchMeta2, []*encoding.Chunk{chunk2}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(3), endBlock2)

Expand All @@ -798,7 +798,7 @@ func TestValidateBatchUpgrades(t *testing.T) {
StateRoot: chunk3.Blocks[len(chunk3.Blocks)-1].Header.Root,
WithdrawRoot: chunk3.Blocks[len(chunk3.Blocks)-1].WithdrawRoot,
}
endBlock3, finalizedBatchMeta3, err := validateBatch(event3, parentBatchMeta3, []*encoding.Chunk{chunk3}, chainConfig, nil)
endBlock3, finalizedBatchMeta3, err := validateBatch(event3.BatchIndex.Uint64(), event3, parentBatchMeta3, []*encoding.Chunk{chunk3}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(13), endBlock3)

Expand All @@ -818,7 +818,7 @@ func TestValidateBatchUpgrades(t *testing.T) {
StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root,
WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot,
}
endBlock4, finalizedBatchMeta4, err := validateBatch(event4, parentBatchMeta4, []*encoding.Chunk{chunk4}, chainConfig, nil)
endBlock4, finalizedBatchMeta4, err := validateBatch(event4.BatchIndex.Uint64(), event4, parentBatchMeta4, []*encoding.Chunk{chunk4}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(17), endBlock4)

Expand All @@ -831,6 +831,51 @@ func TestValidateBatchUpgrades(t *testing.T) {
assert.Equal(t, parentBatchMeta5, finalizedBatchMeta4)
}

func TestValidateBatchInFinalizeByBundle(t *testing.T) {
chainConfig := &params.ChainConfig{BernoulliBlock: big.NewInt(0), CurieBlock: big.NewInt(0), DarwinTime: func() *uint64 { t := uint64(0); return &t }()}

block1 := readBlockFromJSON(t, "./testdata/blockTrace_02.json")
block2 := readBlockFromJSON(t, "./testdata/blockTrace_03.json")
block3 := readBlockFromJSON(t, "./testdata/blockTrace_04.json")
block4 := readBlockFromJSON(t, "./testdata/blockTrace_05.json")

chunk1 := &encoding.Chunk{Blocks: []*encoding.Block{block1}}
chunk2 := &encoding.Chunk{Blocks: []*encoding.Block{block2}}
chunk3 := &encoding.Chunk{Blocks: []*encoding.Block{block3}}
chunk4 := &encoding.Chunk{Blocks: []*encoding.Block{block4}}

event := &L1FinalizeBatchEvent{
BatchIndex: big.NewInt(3),
BatchHash: common.HexToHash("0xaa6dc7cc432c8d46a9373e1e96d829a1e24e52fe0468012ff062793ea8f5b55e"),
StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root,
WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot,
}

endBlock1, finalizedBatchMeta1, err := validateBatch(0, event, &rawdb.FinalizedBatchMeta{}, []*encoding.Chunk{chunk1}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(2), endBlock1)

endBlock2, finalizedBatchMeta2, err := validateBatch(1, event, finalizedBatchMeta1, []*encoding.Chunk{chunk2}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(3), endBlock2)

endBlock3, finalizedBatchMeta3, err := validateBatch(2, event, finalizedBatchMeta2, []*encoding.Chunk{chunk3}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(13), endBlock3)

endBlock4, finalizedBatchMeta4, err := validateBatch(3, event, finalizedBatchMeta3, []*encoding.Chunk{chunk4}, chainConfig, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(17), endBlock4)

parentBatchMeta5 := &rawdb.FinalizedBatchMeta{
BatchHash: event.BatchHash,
TotalL1MessagePopped: 42,
StateRoot: event.StateRoot,
WithdrawRoot: event.WithdrawRoot,
}
assert.Equal(t, parentBatchMeta5, finalizedBatchMeta4)
}

func readBlockFromJSON(t *testing.T, filename string) *encoding.Block {
data, err := os.ReadFile(filename)
assert.NoError(t, err)
Expand Down
Loading