diff --git a/system_tests/batch_poster_test.go b/system_tests/batch_poster_test.go index 0ec03e84c4..6b602d38a8 100644 --- a/system_tests/batch_poster_test.go +++ b/system_tests/batch_poster_test.go @@ -14,6 +14,7 @@ import ( "github.com/andybalholm/brotli" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -363,3 +364,53 @@ func TestAllowPostingFirstBatchWhenSequencerMessageCountMismatchEnabled(t *testi func TestAllowPostingFirstBatchWhenSequencerMessageCountMismatchDisabled(t *testing.T) { testAllowPostingFirstBatchWhenSequencerMessageCountMismatch(t, false) } + +func TestBatchPosterDelayBuffer(t *testing.T) { + const messagesPerBatch = 10 + const threshold = 100 + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).WithDelayBuffer(threshold) + builder.nodeConfig.BatchPoster.MaxDelay = time.Hour // set high max-delay so we can test the delay buffer + cleanup := builder.Build(t) + defer cleanup() + builder.L2Info.GenerateAccount("User2") + + testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{}) + defer cleanupB() + + sequenceInbox, err := bridgegen.NewSequencerInbox(builder.L1Info.GetAddress("SequencerInbox"), builder.L1.Client) + Require(t, err) + getBatchCount := func() uint64 { + batchCount, err := sequenceInbox.BatchCount(&bind.CallOpts{Context: ctx}) + Require(t, err) + return batchCount.Uint64() + } + + t.Run("SendsDelayedMessages", func(t *testing.T) { + previousBatchCount := getBatchCount() + const numBatches = 3 + for batch := uint64(0); batch < numBatches; batch++ { + txs := make(types.Transactions, messagesPerBatch) + for i := range txs { + txs[i] = builder.L2Info.PrepareTx("Owner", "User2", builder.L2Info.TransferGas, common.Big1, nil) + } + SendSignedTxesInBatchViaL1(t, ctx, builder.L1Info, builder.L1.Client, builder.L2.Client, txs) + time.Sleep(time.Second) + if currBatchCount := getBatchCount(); currBatchCount != previousBatchCount+batch { + t.Fatalf("expected batch count %v; got %v", previousBatchCount+batch, currBatchCount) + } + // Advance L1 to force the delay buffer + AdvanceL1(t, ctx, builder.L1.Client, builder.L1Info, threshold) + if currBatchCount := getBatchCount(); currBatchCount != previousBatchCount+batch+1 { + t.Fatalf("expected batch count %v; got %v", previousBatchCount+batch+1, currBatchCount) + } + for _, tx := range txs { + _, err := testClientB.EnsureTxSucceeded(tx) + Require(t, err, "tx not found on second node") + } + } + }) +} diff --git a/system_tests/common_test.go b/system_tests/common_test.go index f4ea03a105..4bbf7c473c 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -76,7 +76,6 @@ import ( "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" - "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/solgen/go/upgrade_executorgen" "github.com/offchainlabs/nitro/statetransfer" "github.com/offchainlabs/nitro/util/testhelpers" @@ -253,7 +252,7 @@ type NodeBuilder struct { l3InitMessage *arbostypes.ParsedInitMessage withProdConfirmPeriodBlocks bool wasmCacheTag uint32 - isDelayBufferable bool + delayBufferThreshold uint64 // Created nodes L1 *TestClient @@ -366,8 +365,11 @@ func (b *NodeBuilder) WithStylusLongTermCache(enabled bool) *NodeBuilder { return b } -func (b *NodeBuilder) WithDelayBuffer(enabled bool) *NodeBuilder { - b.isDelayBufferable = enabled +// WithDelayBuffer sets the delay-buffer threshold, which is the number of blocks the batch-poster +// is allowed to delay a batch with a delayed message. +// Setting the threshold to zero disabled the delay buffer (default behaviour). +func (b *NodeBuilder) WithDelayBuffer(threshold uint64) *NodeBuilder { + b.delayBufferThreshold = threshold return b } @@ -420,7 +422,7 @@ func (b *NodeBuilder) BuildL1(t *testing.T) { locator.LatestWasmModuleRoot(), b.withProdConfirmPeriodBlocks, true, - b.isDelayBufferable, + b.delayBufferThreshold, ) b.L1.cleanup = func() { requireClose(t, b.L1.Stack) } } @@ -524,7 +526,7 @@ func (b *NodeBuilder) BuildL3OnL2(t *testing.T) func() { locator.LatestWasmModuleRoot(), b.l3Config.withProdConfirmPeriodBlocks, false, - false, + 0, ) b.L3 = buildOnParentChain( @@ -882,6 +884,21 @@ func BridgeBalance( return tx, res } +// AdvanceL1 sends dummy transactions to L1 to create blocks. +func AdvanceL1( + t *testing.T, + ctx context.Context, + l1client *ethclient.Client, + l1info *BlockchainTestInfo, + numBlocks int, +) { + for i := 0; i < numBlocks; i++ { + SendWaitTestTransactions(t, ctx, l1client, []*types.Transaction{ + l1info.PrepareTx("Faucet", "Faucet", 30000, big.NewInt(1e12), nil), + }) + } +} + func SendSignedTxesInBatchViaL1( t *testing.T, ctx context.Context, @@ -901,12 +918,7 @@ func SendSignedTxesInBatchViaL1( _, err = EnsureTxSucceeded(ctx, l1client, l1tx) Require(t, err) - // sending l1 messages creates l1 blocks.. make enough to get that delayed inbox message in - for i := 0; i < 30; i++ { - SendWaitTestTransactions(t, ctx, l1client, []*types.Transaction{ - l1info.PrepareTx("Faucet", "Faucet", 30000, big.NewInt(1e12), nil), - }) - } + AdvanceL1(t, ctx, l1client, l1info, 30) var receipts types.Receipts for _, tx := range delayedTxes { receipt, err := EnsureTxSucceeded(ctx, l2client, tx) @@ -953,12 +965,7 @@ func SendSignedTxViaL1( _, err = EnsureTxSucceeded(ctx, l1client, l1tx) Require(t, err) - // sending l1 messages creates l1 blocks.. make enough to get that delayed inbox message in - for i := 0; i < 30; i++ { - SendWaitTestTransactions(t, ctx, l1client, []*types.Transaction{ - l1info.PrepareTx("Faucet", "Faucet", 30000, big.NewInt(1e12), nil), - }) - } + AdvanceL1(t, ctx, l1client, l1info, 30) receipt, err := EnsureTxSucceeded(ctx, l2client, delayedTx) Require(t, err) return receipt @@ -1004,12 +1011,7 @@ func SendUnsignedTxViaL1( _, err = EnsureTxSucceeded(ctx, l1client, l1tx) Require(t, err) - // sending l1 messages creates l1 blocks.. make enough to get that delayed inbox message in - for i := 0; i < 30; i++ { - SendWaitTestTransactions(t, ctx, l1client, []*types.Transaction{ - l1info.PrepareTx("Faucet", "Faucet", 30000, big.NewInt(1e12), nil), - }) - } + AdvanceL1(t, ctx, l1client, l1info, 30) receipt, err := EnsureTxSucceeded(ctx, l2client, unsignedTx) Require(t, err) return receipt @@ -1268,7 +1270,7 @@ func deployOnParentChain( wasmModuleRoot common.Hash, prodConfirmPeriodBlocks bool, chainSupportsBlobs bool, - isDelayBufferable bool, + delayBufferThreshold uint64, ) (*chaininfo.RollupAddresses, *arbostypes.ParsedInitMessage) { parentChainInfo.GenerateAccount("RollupOwner") parentChainInfo.GenerateAccount("Sequencer") @@ -1291,10 +1293,8 @@ func deployOnParentChain( parentChainReader.Start(ctx) defer parentChainReader.StopAndWait() - var bufferConfig rollupgen.BufferConfig - if isDelayBufferable { - bufferConfig = arbnode.DefaultBufferConfig() - } + bufferConfig := arbnode.DefaultBufferConfig() + bufferConfig.Threshold = delayBufferThreshold nativeToken := common.Address{} maxDataSize := big.NewInt(117964)