Skip to content

Commit

Permalink
add retryable expiry system tests
Browse files Browse the repository at this point in the history
  • Loading branch information
magicxyyz committed Nov 19, 2024
1 parent 36a7ab8 commit 32b3981
Showing 1 changed file with 183 additions and 2 deletions.
185 changes: 183 additions & 2 deletions system_tests/retryable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit 32b3981

Please sign in to comment.