From 32b398105c9b200790ed3a06bcf9cdff7035455d Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 19 Nov 2024 17:10:40 +0100 Subject: [PATCH] add retryable expiry system tests --- system_tests/retryable_test.go | 185 ++++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 2 deletions(-) diff --git a/system_tests/retryable_test.go b/system_tests/retryable_test.go index af5f8bf57c..bf47b9a643 100644 --- a/system_tests/retryable_test.go +++ b/system_tests/retryable_test.go @@ -16,10 +16,12 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/gasestimator" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/retryables" "github.com/offchainlabs/nitro/arbos/util" @@ -445,6 +447,186 @@ func TestGetLifetime(t *testing.T) { } } +func warpL1Time(t *testing.T, builder *NodeBuilder, ctx context.Context, currentL1time, advanceTime uint64) uint64 { + t.Log("Warping L1 time...") + l1LatestHeader, err := builder.L1.Client.HeaderByNumber(ctx, big.NewInt(int64(rpc.LatestBlockNumber))) + Require(t, err) + if currentL1time == 0 { + currentL1time = l1LatestHeader.Time + } + newL1Timestamp := currentL1time + advanceTime + timeWarpHeader := &arbostypes.L1IncomingMessageHeader{ + Kind: arbostypes.L1MessageType_L2Message, + Poster: l1pricing.BatchPosterAddress, + BlockNumber: l1LatestHeader.Number.Uint64(), + Timestamp: newL1Timestamp, + RequestId: nil, + L1BaseFee: nil, + } + hooks := arbos.NoopSequencingHooks() + tx := builder.L2Info.PrepareTx("Faucet", "User2", 300000, big.NewInt(1), nil) + _, err = builder.L2.ExecNode.ExecEngine.SequenceTransactions(timeWarpHeader, types.Transactions{tx}, hooks) + Require(t, err) + return newL1Timestamp +} + +func TestRetryableExpiry(t *testing.T) { + t.Parallel() + builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) + defer teardown() + + ownerTxOpts := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) + usertxopts := builder.L1Info.GetDefaultTransactOpts("Faucet", ctx) + usertxopts.Value = arbmath.BigMul(big.NewInt(1e12), big.NewInt(1e12)) + + simpleAddr, _ := builder.L2.DeploySimple(t, ownerTxOpts) + simpleABI, err := mocksgen.SimpleMetaData.GetAbi() + Require(t, err) + + beneficiaryAddress := builder.L2Info.GetAddress("Beneficiary") + l1tx, err := delayedInbox.CreateRetryableTicket( + &usertxopts, + simpleAddr, + common.Big0, + big.NewInt(1e16), + beneficiaryAddress, + beneficiaryAddress, + // send enough L2 gas for intrinsic but not compute + big.NewInt(int64(params.TxGas+params.TxDataNonZeroGasEIP2028*4)), + big.NewInt(l2pricing.InitialBaseFeeWei*2), + simpleABI.Methods["incrementRedeem"].ID, + ) + Require(t, err) + + l1Receipt, err := builder.L1.EnsureTxSucceeded(l1tx) + Require(t, err) + if l1Receipt.Status != types.ReceiptStatusSuccessful { + Fatal(t, "l1Receipt indicated failure") + } + + waitForL1DelayBlocks(t, builder) + + receipt, err := builder.L2.EnsureTxSucceeded(lookupL2Tx(l1Receipt)) + Require(t, err) + if len(receipt.Logs) != 2 { + Fatal(t, len(receipt.Logs)) + } + ticketId := receipt.Logs[0].Topics[1] + firstRetryTxId := receipt.Logs[1].Topics[2] + + // make sure it failed + receipt, err = WaitForTx(ctx, builder.L2.Client, firstRetryTxId, time.Second*5) + Require(t, err) + if receipt.Status != types.ReceiptStatusFailed { + Fatal(t, receipt.GasUsed) + } + + arbRetryableTx, err := precompilesgen.NewArbRetryableTx(common.HexToAddress("6e"), builder.L2.Client) + Require(t, err) + + // check that the ticket exists + _, err = arbRetryableTx.GetTimeout(&bind.CallOpts{}, ticketId) + Require(t, err) + + _ = warpL1Time(t, builder, ctx, 0, retryables.RetryableLifetimeSeconds) + + // check that the ticket no longer exists + _, err = arbRetryableTx.GetTimeout(&bind.CallOpts{}, ticketId) + if (err == nil) || (err.Error() != "execution reverted: error NoTicketWithID(): NoTicketWithID()") { + Fatal(t, "didn't get expected NoTicketWithID error") + } +} + +func TestKeepaliveAndRetryableExpiry(t *testing.T) { + t.Parallel() + builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) + defer teardown() + + ownerTxOpts := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) + usertxopts := builder.L1Info.GetDefaultTransactOpts("Faucet", ctx) + usertxopts.Value = arbmath.BigMul(big.NewInt(1e12), big.NewInt(1e12)) + + simpleAddr, _ := builder.L2.DeploySimple(t, ownerTxOpts) + simpleABI, err := mocksgen.SimpleMetaData.GetAbi() + Require(t, err) + + beneficiaryAddress := builder.L2Info.GetAddress("Beneficiary") + l1tx, err := delayedInbox.CreateRetryableTicket( + &usertxopts, + simpleAddr, + common.Big0, + big.NewInt(1e16), + beneficiaryAddress, + beneficiaryAddress, + // send enough L2 gas for intrinsic but not compute + big.NewInt(int64(params.TxGas+params.TxDataNonZeroGasEIP2028*4)), + big.NewInt(l2pricing.InitialBaseFeeWei*2), + simpleABI.Methods["incrementRedeem"].ID, + ) + Require(t, err) + + l1Receipt, err := builder.L1.EnsureTxSucceeded(l1tx) + Require(t, err) + if l1Receipt.Status != types.ReceiptStatusSuccessful { + Fatal(t, "l1Receipt indicated failure") + } + + waitForL1DelayBlocks(t, builder) + + receipt, err := builder.L2.EnsureTxSucceeded(lookupL2Tx(l1Receipt)) + Require(t, err) + if len(receipt.Logs) != 2 { + Fatal(t, len(receipt.Logs)) + } + ticketId := receipt.Logs[0].Topics[1] + firstRetryTxId := receipt.Logs[1].Topics[2] + + // make sure it failed + receipt, err = WaitForTx(ctx, builder.L2.Client, firstRetryTxId, time.Second*5) + Require(t, err) + if receipt.Status != types.ReceiptStatusFailed { + Fatal(t, receipt.GasUsed) + } + + arbRetryableTx, err := precompilesgen.NewArbRetryableTx(common.HexToAddress("6e"), builder.L2.Client) + Require(t, err) + + // checks that the ticket exists and gets current timeout + timeoutBeforeKeepalive, err := arbRetryableTx.GetTimeout(&bind.CallOpts{}, ticketId) + Require(t, err) + + // checks beneficiary + retrievedBeneficiaryAddress, err := arbRetryableTx.GetBeneficiary(&bind.CallOpts{}, ticketId) + Require(t, err) + if retrievedBeneficiaryAddress != beneficiaryAddress { + Fatal(t, "expected beneficiary to be", beneficiaryAddress, "but got", retrievedBeneficiaryAddress) + } + + // checks that keepalive increases the timeout as expected + _, err = arbRetryableTx.Keepalive(&ownerTxOpts, ticketId) + Require(t, err) + timeoutAfterKeepalive, err := arbRetryableTx.GetTimeout(&bind.CallOpts{}, ticketId) + Require(t, err) + expectedTimeoutAfterKeepAlive := arbmath.BigAdd(timeoutBeforeKeepalive, big.NewInt(retryables.RetryableLifetimeSeconds)) + if timeoutAfterKeepalive.Cmp(expectedTimeoutAfterKeepAlive) != 0 { + Fatal(t, "expected timeout after keepalive to be", expectedTimeoutAfterKeepAlive, "but got", timeoutAfterKeepalive) + } + + currentL1time := warpL1Time(t, builder, ctx, 0, retryables.RetryableLifetimeSeconds) + + // check that the ticket still exists + _, err = arbRetryableTx.GetTimeout(&bind.CallOpts{}, ticketId) + Require(t, err) + + _ = warpL1Time(t, builder, ctx, currentL1time, retryables.RetryableLifetimeSeconds) + + // check that the ticket no longer exists + _, err = arbRetryableTx.GetTimeout(&bind.CallOpts{}, ticketId) + if (err == nil) || (err.Error() != "execution reverted: error NoTicketWithID(): NoTicketWithID()") { + Fatal(t, "didn't get expected NoTicketWithID error") + } +} + func TestKeepaliveAndCancelRetryable(t *testing.T) { t.Parallel() builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) @@ -515,8 +697,7 @@ func TestKeepaliveAndCancelRetryable(t *testing.T) { Require(t, err) timeoutAfterKeepalive, err := arbRetryableTx.GetTimeout(&bind.CallOpts{}, ticketId) Require(t, err) - expectedTimeoutAfterKeepAlive := timeoutBeforeKeepalive - expectedTimeoutAfterKeepAlive.Add(expectedTimeoutAfterKeepAlive, big.NewInt(retryables.RetryableLifetimeSeconds)) + expectedTimeoutAfterKeepAlive := arbmath.BigAdd(timeoutBeforeKeepalive, big.NewInt(retryables.RetryableLifetimeSeconds)) if timeoutAfterKeepalive.Cmp(expectedTimeoutAfterKeepAlive) != 0 { Fatal(t, "expected timeout after keepalive to be", expectedTimeoutAfterKeepAlive, "but got", timeoutAfterKeepalive) }