From 8c41b0b86bb3324d1484197f9a40391812bcfd38 Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Thu, 7 Sep 2023 13:29:08 +0800 Subject: [PATCH] test(rollup-relayer): add chunk and batch proposer limit tests (#932) Co-authored-by: colinlyguo --- common/version/version.go | 2 +- .../controller/watcher/batch_proposer.go | 4 +- .../controller/watcher/batch_proposer_test.go | 176 ++++++++++++-- .../controller/watcher/chunk_proposer.go | 3 +- .../controller/watcher/chunk_proposer_test.go | 216 +++++++++++++----- .../controller/watcher/watcher_test.go | 10 +- 6 files changed, 332 insertions(+), 79 deletions(-) diff --git a/common/version/version.go b/common/version/version.go index 3d5d12233..fa8aae6e4 100644 --- a/common/version/version.go +++ b/common/version/version.go @@ -5,7 +5,7 @@ import ( "runtime/debug" ) -var tag = "v4.3.1" +var tag = "v4.3.2" var commit = func() string { if info, ok := debug.ReadBuildInfo(); ok { diff --git a/rollup/internal/controller/watcher/batch_proposer.go b/rollup/internal/controller/watcher/batch_proposer.go index 7b99c2c4c..d66786c69 100644 --- a/rollup/internal/controller/watcher/batch_proposer.go +++ b/rollup/internal/controller/watcher/batch_proposer.go @@ -48,7 +48,8 @@ func NewBatchProposer(ctx context.Context, cfg *config.BatchProposerConfig, db * "maxChunkNumPerBatch", cfg.MaxChunkNumPerBatch, "maxL1CommitGasPerBatch", cfg.MaxL1CommitGasPerBatch, "maxL1CommitCalldataSizePerBatch", cfg.MaxL1CommitCalldataSizePerBatch, - "batchTimeoutSec", cfg.BatchTimeoutSec) + "batchTimeoutSec", cfg.BatchTimeoutSec, + "gasCostIncreaseMultiplier", cfg.GasCostIncreaseMultiplier) return &BatchProposer{ ctx: ctx, @@ -178,6 +179,7 @@ func (p *BatchProposer) proposeBatchChunks() ([]*orm.Chunk, *types.BatchMeta, er // Add extra gas costs totalL1CommitGas += 4 * 2100 // 4 one-time cold sload for commitBatch totalL1CommitGas += 20000 // 1 time sstore + totalL1CommitGas += 21000 // base fee for tx totalL1CommitGas += types.CalldataNonZeroByteGas // version in calldata // adjusting gas: diff --git a/rollup/internal/controller/watcher/batch_proposer_test.go b/rollup/internal/controller/watcher/batch_proposer_test.go index a8dd81aed..0d0e23e0b 100644 --- a/rollup/internal/controller/watcher/batch_proposer_test.go +++ b/rollup/internal/controller/watcher/batch_proposer_test.go @@ -13,8 +13,139 @@ import ( "scroll-tech/rollup/internal/orm" ) -// TODO: Add unit tests that the limits are enforced correctly. -func testBatchProposer(t *testing.T) { +func testBatchProposerLimits(t *testing.T) { + tests := []struct { + name string + maxChunkNum uint64 + maxL1CommitGas uint64 + maxL1CommitCalldataSize uint32 + batchTimeoutSec uint64 + expectedBatchesLen int + expectedChunksInFirstBatch uint64 // only be checked when expectedBatchesLen > 0 + }{ + { + name: "NoLimitReached", + maxChunkNum: 10, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 1000000, + batchTimeoutSec: 1000000000000, + expectedBatchesLen: 0, + }, + { + name: "Timeout", + maxChunkNum: 10, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 1000000, + batchTimeoutSec: 0, + expectedBatchesLen: 1, + expectedChunksInFirstBatch: 2, + }, + { + name: "MaxL1CommitGasPerBatchIs0", + maxChunkNum: 10, + maxL1CommitGas: 0, + maxL1CommitCalldataSize: 1000000, + batchTimeoutSec: 1000000000000, + expectedBatchesLen: 0, + }, + { + name: "MaxL1CommitCalldataSizePerBatchIs0", + maxChunkNum: 10, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 0, + batchTimeoutSec: 1000000000000, + expectedBatchesLen: 0, + }, + { + name: "MaxChunkNumPerBatchIs1", + maxChunkNum: 1, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 1000000, + batchTimeoutSec: 1000000000000, + expectedBatchesLen: 1, + expectedChunksInFirstBatch: 1, + }, + { + name: "MaxL1CommitGasPerBatchIsFirstChunk", + maxChunkNum: 10, + maxL1CommitGas: 100000, + maxL1CommitCalldataSize: 1000000, + batchTimeoutSec: 1000000000000, + expectedBatchesLen: 1, + expectedChunksInFirstBatch: 1, + }, + { + name: "MaxL1CommitCalldataSizePerBatchIsFirstChunk", + maxChunkNum: 10, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 298, + batchTimeoutSec: 1000000000000, + expectedBatchesLen: 1, + expectedChunksInFirstBatch: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := setupDB(t) + defer database.CloseDB(db) + + l2BlockOrm := orm.NewL2Block(db) + err := l2BlockOrm.InsertL2Blocks(context.Background(), []*types.WrappedBlock{wrappedBlock1, wrappedBlock2}) + assert.NoError(t, err) + + cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{ + MaxBlockNumPerChunk: 1, + MaxTxNumPerChunk: 10000, + MaxL1CommitGasPerChunk: 50000000000, + MaxL1CommitCalldataSizePerChunk: 1000000, + MaxRowConsumptionPerChunk: 1000000, + ChunkTimeoutSec: 300, + GasCostIncreaseMultiplier: 1.2, + }, db, nil) + cp.TryProposeChunk() // chunk1 contains block1 + cp.TryProposeChunk() // chunk2 contains block2 + + chunkOrm := orm.NewChunk(db) + chunks, err := chunkOrm.GetChunksInRange(context.Background(), 0, 1) + assert.NoError(t, err) + assert.Equal(t, uint64(6006), chunks[0].TotalL1CommitGas) + assert.Equal(t, uint32(298), chunks[0].TotalL1CommitCalldataSize) + assert.Equal(t, uint64(93982), chunks[1].TotalL1CommitGas) + assert.Equal(t, uint32(5735), chunks[1].TotalL1CommitCalldataSize) + + bp := NewBatchProposer(context.Background(), &config.BatchProposerConfig{ + MaxChunkNumPerBatch: tt.maxChunkNum, + MaxL1CommitGasPerBatch: tt.maxL1CommitGas, + MaxL1CommitCalldataSizePerBatch: tt.maxL1CommitCalldataSize, + BatchTimeoutSec: tt.batchTimeoutSec, + GasCostIncreaseMultiplier: 1.2, + }, db, nil) + bp.TryProposeBatch() + + batchOrm := orm.NewBatch(db) + batches, err := batchOrm.GetBatches(context.Background(), map[string]interface{}{}, []string{}, 0) + assert.NoError(t, err) + assert.Len(t, batches, tt.expectedBatchesLen) + if tt.expectedBatchesLen > 0 { + assert.Equal(t, uint64(0), batches[0].StartChunkIndex) + assert.Equal(t, tt.expectedChunksInFirstBatch-1, batches[0].EndChunkIndex) + assert.Equal(t, types.RollupPending, types.RollupStatus(batches[0].RollupStatus)) + assert.Equal(t, types.ProvingTaskUnassigned, types.ProvingStatus(batches[0].ProvingStatus)) + + dbChunks, err := chunkOrm.GetChunksInRange(context.Background(), 0, tt.expectedChunksInFirstBatch-1) + assert.NoError(t, err) + assert.Len(t, dbChunks, int(tt.expectedChunksInFirstBatch)) + for _, chunk := range dbChunks { + assert.Equal(t, batches[0].Hash, chunk.BatchHash) + assert.Equal(t, types.ProvingTaskUnassigned, types.ProvingStatus(chunk.ProvingStatus)) + } + } + }) + } +} + +func testBatchCommitGasAndCalldataSizeEstimation(t *testing.T) { db := setupDB(t) defer database.CloseDB(db) @@ -23,44 +154,51 @@ func testBatchProposer(t *testing.T) { assert.NoError(t, err) cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{ - MaxBlockNumPerChunk: 100, + MaxBlockNumPerChunk: 1, MaxTxNumPerChunk: 10000, MaxL1CommitGasPerChunk: 50000000000, MaxL1CommitCalldataSizePerChunk: 1000000, - MaxRowConsumptionPerChunk: 1048319, + MaxRowConsumptionPerChunk: 1000000, ChunkTimeoutSec: 300, + GasCostIncreaseMultiplier: 1.2, }, db, nil) - cp.TryProposeChunk() + cp.TryProposeChunk() // chunk1 contains block1 + cp.TryProposeChunk() // chunk2 contains block2 + + chunkOrm := orm.NewChunk(db) + chunks, err := chunkOrm.GetChunksInRange(context.Background(), 0, 1) + assert.NoError(t, err) + assert.Equal(t, uint64(6006), chunks[0].TotalL1CommitGas) + assert.Equal(t, uint32(298), chunks[0].TotalL1CommitCalldataSize) + assert.Equal(t, uint64(93982), chunks[1].TotalL1CommitGas) + assert.Equal(t, uint32(5735), chunks[1].TotalL1CommitCalldataSize) bp := NewBatchProposer(context.Background(), &config.BatchProposerConfig{ MaxChunkNumPerBatch: 10, MaxL1CommitGasPerBatch: 50000000000, MaxL1CommitCalldataSizePerBatch: 1000000, - BatchTimeoutSec: 300, + BatchTimeoutSec: 0, + GasCostIncreaseMultiplier: 1.2, }, db, nil) bp.TryProposeBatch() batchOrm := orm.NewBatch(db) - // get all batches. batches, err := batchOrm.GetBatches(context.Background(), map[string]interface{}{}, []string{}, 0) assert.NoError(t, err) assert.Len(t, batches, 1) assert.Equal(t, uint64(0), batches[0].StartChunkIndex) - assert.Equal(t, uint64(0), batches[0].EndChunkIndex) + assert.Equal(t, uint64(1), batches[0].EndChunkIndex) assert.Equal(t, types.RollupPending, types.RollupStatus(batches[0].RollupStatus)) assert.Equal(t, types.ProvingTaskUnassigned, types.ProvingStatus(batches[0].ProvingStatus)) - chunkOrm := orm.NewChunk(db) - dbChunks, err := chunkOrm.GetChunksInRange(context.Background(), 0, 0) + dbChunks, err := chunkOrm.GetChunksInRange(context.Background(), 0, 1) assert.NoError(t, err) - assert.Len(t, batches, 1) - assert.Equal(t, batches[0].Hash, dbChunks[0].BatchHash) - assert.Equal(t, types.ProvingTaskUnassigned, types.ProvingStatus(dbChunks[0].ProvingStatus)) + assert.Len(t, dbChunks, 2) + for _, chunk := range dbChunks { + assert.Equal(t, batches[0].Hash, chunk.BatchHash) + assert.Equal(t, types.ProvingTaskUnassigned, types.ProvingStatus(chunk.ProvingStatus)) + } - blockOrm := orm.NewL2Block(db) - blocks, err := blockOrm.GetL2Blocks(context.Background(), map[string]interface{}{}, []string{}, 0) - assert.NoError(t, err) - assert.Len(t, blocks, 2) - assert.Equal(t, dbChunks[0].Hash, blocks[0].ChunkHash) - assert.Equal(t, dbChunks[0].Hash, blocks[1].ChunkHash) + assert.Equal(t, uint64(153916), batches[0].TotalL1CommitGas) + assert.Equal(t, uint32(6033), batches[0].TotalL1CommitCalldataSize) } diff --git a/rollup/internal/controller/watcher/chunk_proposer.go b/rollup/internal/controller/watcher/chunk_proposer.go index 6c9be6fd3..92d707c8b 100644 --- a/rollup/internal/controller/watcher/chunk_proposer.go +++ b/rollup/internal/controller/watcher/chunk_proposer.go @@ -80,7 +80,8 @@ func NewChunkProposer(ctx context.Context, cfg *config.ChunkProposerConfig, db * "maxL1CommitGasPerChunk", cfg.MaxL1CommitGasPerChunk, "maxL1CommitCalldataSizePerChunk", cfg.MaxL1CommitCalldataSizePerChunk, "maxRowConsumptionPerChunk", cfg.MaxRowConsumptionPerChunk, - "chunkTimeoutSec", cfg.ChunkTimeoutSec) + "chunkTimeoutSec", cfg.ChunkTimeoutSec, + "gasCostIncreaseMultiplier", cfg.GasCostIncreaseMultiplier) return &ChunkProposer{ ctx: ctx, diff --git a/rollup/internal/controller/watcher/chunk_proposer_test.go b/rollup/internal/controller/watcher/chunk_proposer_test.go index 1fff539e7..bef35323b 100644 --- a/rollup/internal/controller/watcher/chunk_proposer_test.go +++ b/rollup/internal/controller/watcher/chunk_proposer_test.go @@ -13,58 +13,170 @@ import ( "scroll-tech/rollup/internal/orm" ) -// TODO: Add unit tests that the limits are enforced correctly. -func testChunkProposer(t *testing.T) { - db := setupDB(t) - defer database.CloseDB(db) - - l2BlockOrm := orm.NewL2Block(db) - err := l2BlockOrm.InsertL2Blocks(context.Background(), []*types.WrappedBlock{wrappedBlock1, wrappedBlock2}) - assert.NoError(t, err) - - cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{ - MaxBlockNumPerChunk: 100, - MaxTxNumPerChunk: 10000, - MaxL1CommitGasPerChunk: 50000000000, - MaxL1CommitCalldataSizePerChunk: 1000000, - MaxRowConsumptionPerChunk: 1048319, - ChunkTimeoutSec: 300, - }, db, nil) - cp.TryProposeChunk() - - expectedChunk := &types.Chunk{ - Blocks: []*types.WrappedBlock{wrappedBlock1, wrappedBlock2}, +func testChunkProposerLimits(t *testing.T) { + tests := []struct { + name string + maxBlockNum uint64 + maxTxNum uint64 + maxL1CommitGas uint64 + maxL1CommitCalldataSize uint64 + maxRowConsumption uint64 + chunkTimeoutSec uint64 + expectedChunksLen int + expectedBlocksInFirstChunk int // only be checked when expectedChunksLen > 0 + }{ + { + name: "NoLimitReached", + maxBlockNum: 100, + maxTxNum: 10000, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 1000000, + maxRowConsumption: 1000000, + chunkTimeoutSec: 1000000000000, + expectedChunksLen: 0, + }, + { + name: "Timeout", + maxBlockNum: 100, + maxTxNum: 10000, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 1000000, + maxRowConsumption: 1000000, + chunkTimeoutSec: 0, + expectedChunksLen: 1, + expectedBlocksInFirstChunk: 2, + }, + { + name: "MaxTxNumPerChunkIs0", + maxBlockNum: 10, + maxTxNum: 0, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 1000000, + maxRowConsumption: 1000000, + chunkTimeoutSec: 1000000000000, + expectedChunksLen: 0, + }, + { + name: "MaxL1CommitGasPerChunkIs0", + maxBlockNum: 10, + maxTxNum: 10000, + maxL1CommitGas: 0, + maxL1CommitCalldataSize: 1000000, + maxRowConsumption: 1000000, + chunkTimeoutSec: 1000000000000, + expectedChunksLen: 0, + }, + { + name: "MaxL1CommitCalldataSizePerChunkIs0", + maxBlockNum: 10, + maxTxNum: 10000, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 0, + maxRowConsumption: 1000000, + chunkTimeoutSec: 1000000000000, + expectedChunksLen: 0, + }, + { + name: "MaxRowConsumptionPerChunkIs0", + maxBlockNum: 100, + maxTxNum: 10000, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 1000000, + maxRowConsumption: 0, + chunkTimeoutSec: 1000000000000, + expectedChunksLen: 0, + }, + { + name: "MaxBlockNumPerChunkIs1", + maxBlockNum: 1, + maxTxNum: 10000, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 1000000, + maxRowConsumption: 1000000, + chunkTimeoutSec: 1000000000000, + expectedChunksLen: 1, + expectedBlocksInFirstChunk: 1, + }, + { + name: "MaxTxNumPerChunkIsFirstBlock", + maxBlockNum: 10, + maxTxNum: 2, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 1000000, + maxRowConsumption: 1000000, + chunkTimeoutSec: 1000000000000, + expectedChunksLen: 1, + expectedBlocksInFirstChunk: 1, + }, + { + name: "MaxL1CommitGasPerChunkIsFirstBlock", + maxBlockNum: 10, + maxTxNum: 10000, + maxL1CommitGas: 60, + maxL1CommitCalldataSize: 1000000, + maxRowConsumption: 1000000, + chunkTimeoutSec: 1000000000000, + expectedChunksLen: 1, + expectedBlocksInFirstChunk: 1, + }, + { + name: "MaxL1CommitCalldataSizePerChunkIsFirstBlock", + maxBlockNum: 10, + maxTxNum: 10000, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 298, + maxRowConsumption: 1000000, + chunkTimeoutSec: 1000000000000, + expectedChunksLen: 1, + expectedBlocksInFirstChunk: 1, + }, + { + name: "MaxRowConsumptionPerChunkIs1", + maxBlockNum: 10, + maxTxNum: 10000, + maxL1CommitGas: 50000000000, + maxL1CommitCalldataSize: 1000000, + maxRowConsumption: 1, + chunkTimeoutSec: 1000000000000, + expectedChunksLen: 1, + expectedBlocksInFirstChunk: 1, + }, } - expectedHash, err := expectedChunk.Hash(0) - assert.NoError(t, err) - chunkOrm := orm.NewChunk(db) - chunks, err := chunkOrm.GetChunksGEIndex(context.Background(), 0, 0) - assert.NoError(t, err) - assert.Len(t, chunks, 1) - assert.Equal(t, expectedHash.Hex(), chunks[0].Hash) -} - -func testChunkProposerRowConsumption(t *testing.T) { - db := setupDB(t) - defer database.CloseDB(db) - - l2BlockOrm := orm.NewL2Block(db) - err := l2BlockOrm.InsertL2Blocks(context.Background(), []*types.WrappedBlock{wrappedBlock1, wrappedBlock2}) - assert.NoError(t, err) - - cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{ - MaxBlockNumPerChunk: 100, - MaxTxNumPerChunk: 10000, - MaxL1CommitGasPerChunk: 50000000000, - MaxL1CommitCalldataSizePerChunk: 1000000, - MaxRowConsumptionPerChunk: 0, // ! - ChunkTimeoutSec: 300, - }, db, nil) - cp.TryProposeChunk() - - chunkOrm := orm.NewChunk(db) - chunks, err := chunkOrm.GetChunksGEIndex(context.Background(), 0, 0) - assert.NoError(t, err) - assert.Len(t, chunks, 0) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := setupDB(t) + defer database.CloseDB(db) + + l2BlockOrm := orm.NewL2Block(db) + err := l2BlockOrm.InsertL2Blocks(context.Background(), []*types.WrappedBlock{wrappedBlock1, wrappedBlock2}) + assert.NoError(t, err) + + cp := NewChunkProposer(context.Background(), &config.ChunkProposerConfig{ + MaxBlockNumPerChunk: tt.maxBlockNum, + MaxTxNumPerChunk: tt.maxTxNum, + MaxL1CommitGasPerChunk: tt.maxL1CommitGas, + MaxL1CommitCalldataSizePerChunk: tt.maxL1CommitCalldataSize, + MaxRowConsumptionPerChunk: tt.maxRowConsumption, + ChunkTimeoutSec: tt.chunkTimeoutSec, + GasCostIncreaseMultiplier: 1.2, + }, db, nil) + cp.TryProposeChunk() + + chunkOrm := orm.NewChunk(db) + chunks, err := chunkOrm.GetChunksGEIndex(context.Background(), 0, 0) + assert.NoError(t, err) + assert.Len(t, chunks, tt.expectedChunksLen) + + if len(chunks) > 0 { + blockOrm := orm.NewL2Block(db) + blocks, err := blockOrm.GetL2Blocks(context.Background(), map[string]interface{}{}, []string{"number ASC"}, tt.expectedBlocksInFirstChunk) + assert.NoError(t, err) + assert.Len(t, blocks, tt.expectedBlocksInFirstChunk) + for _, block := range blocks { + assert.Equal(t, chunks[0].Hash, block.ChunkHash) + } + } + }) + } } diff --git a/rollup/internal/controller/watcher/watcher_test.go b/rollup/internal/controller/watcher/watcher_test.go index 85d89c698..0bae139de 100644 --- a/rollup/internal/controller/watcher/watcher_test.go +++ b/rollup/internal/controller/watcher/watcher_test.go @@ -110,10 +110,10 @@ func TestFunction(t *testing.T) { t.Run("TestParseBridgeEventLogsL2RelayedMessageEventSignature", testParseBridgeEventLogsL2RelayedMessageEventSignature) t.Run("TestParseBridgeEventLogsL2FailedRelayedMessageEventSignature", testParseBridgeEventLogsL2FailedRelayedMessageEventSignature) - // Run chunk-proposer test cases. - t.Run("TestChunkProposer", testChunkProposer) - t.Run("TestChunkProposerRowConsumption", testChunkProposerRowConsumption) + // Run chunk proposer test cases. + t.Run("TestChunkProposerLimits", testChunkProposerLimits) - // Run batch-proposer test cases. - t.Run("TestBatchProposer", testBatchProposer) + // Run chunk proposer test cases. + t.Run("TestBatchProposerLimits", testBatchProposerLimits) + t.Run("TestBatchCommitGasAndCalldataSizeEstimation", testBatchCommitGasAndCalldataSizeEstimation) }