diff --git a/op-chain-ops/interopgen/deploy.go b/op-chain-ops/interopgen/deploy.go index e3449866290cf..b8b973b2f9228 100644 --- a/op-chain-ops/interopgen/deploy.go +++ b/op-chain-ops/interopgen/deploy.go @@ -308,7 +308,7 @@ func GenesisL2(l2Host *script.Host, cfg *L2Config, deployment *L2Deployment) err L1FeeVaultWithdrawalNetwork: big.NewInt(int64(cfg.L1FeeVaultWithdrawalNetwork.ToUint8())), GovernanceTokenOwner: cfg.GovernanceTokenOwner, Fork: big.NewInt(cfg.SolidityForkNumber(1)), - UseInterop: true, + UseInterop: cfg.UseInterop, EnableGovernance: cfg.EnableGovernance, FundDevAccounts: cfg.FundDevAccounts, }); err != nil { diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index 948007d296be8..201a8e8475c63 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -262,7 +262,8 @@ func (r *InteropDevL2Recipe) build(l1ChainID uint64, addrs devkeys.Addresses) (* L2GenesisJovianTimeOffset: nil, L1CancunTimeOffset: new(hexutil.Uint64), L1PragueTimeOffset: new(hexutil.Uint64), - UseInterop: true, + // Don't deploy interop L2 contracts if interop hard fork isn't active at genesis + UseInterop: r.InteropOffset == 0, }, L2CoreDeployConfig: genesis.L2CoreDeployConfig{ L1ChainID: l1ChainID, diff --git a/op-e2e/actions/interop/dsl/inbox.go b/op-e2e/actions/interop/dsl/inbox.go index 0a75ce7239cc8..9975de461a609 100644 --- a/op-e2e/actions/interop/dsl/inbox.go +++ b/op-e2e/actions/interop/dsl/inbox.go @@ -30,6 +30,7 @@ func NewInboxContract(t helpers.Testing) *InboxContract { type ExecuteOpts struct { Identifier *inbox.Identifier Payload *[]byte + GasLimit uint64 } func WithIdentifier(ident inbox.Identifier) func(opts *ExecuteOpts) { @@ -44,6 +45,12 @@ func WithPayload(payload []byte) func(opts *ExecuteOpts) { } } +func WithFixedGasLimit() func(opts *ExecuteOpts) { + return func(opts *ExecuteOpts) { + opts.GasLimit = 1_000_000 // Overly large to ensure the tx doesn't OOG. + } +} + func WithPendingMessage(emitter *EmitterContract, chain *Chain, number uint64, logIndex int, msg string) func(opts *ExecuteOpts) { return func(opts *ExecuteOpts) { blockTime := chain.RollupCfg.TimestampForBlock(number) @@ -87,6 +94,7 @@ func (i *InboxContract) Execute(user *DSLUser, initTx *GeneratedTransaction, arg payload = initTx.MessagePayload() } txOpts, from := user.TransactOpts(chain.ChainID.ToBig()) + txOpts.GasLimit = opts.GasLimit contract, err := inbox.NewInbox(predeploys.CrossL2InboxAddr, chain.SequencerEngine.EthClient()) require.NoError(i.t, err) id := stypes.Identifier{ diff --git a/op-e2e/actions/interop/dsl/interop.go b/op-e2e/actions/interop/dsl/interop.go index b2b755bf07aac..451466f633737 100644 --- a/op-e2e/actions/interop/dsl/interop.go +++ b/op-e2e/actions/interop/dsl/interop.go @@ -140,6 +140,18 @@ func SetInteropOffsetForAllL2s(offset uint64) setupOption { } } +func SetInteropForkScheduledButInactive() setupOption { + return func(recipe *interopgen.InteropDevRecipe) { + // Update in place to avoid making a copy and losing the change. + // Set to a year in the future. Far enough tests won't hit it + // but not so far it will overflow when added to current time. + val := uint64(365 * 24 * 60 * 60) + for key := range recipe.L2s { + recipe.L2s[key].InteropOffset = val + } + } +} + // SetupInterop creates an InteropSetup to instantiate actors on, with 2 L2 chains. func SetupInterop(t helpers.Testing, opts ...setupOption) *InteropSetup { recipe := interopgen.InteropDevRecipe{ diff --git a/op-e2e/actions/interop/dsl/transactions.go b/op-e2e/actions/interop/dsl/transactions.go index 7cdc2de7f3241..b32b6356fe69d 100644 --- a/op-e2e/actions/interop/dsl/transactions.go +++ b/op-e2e/actions/interop/dsl/transactions.go @@ -89,10 +89,29 @@ func (m *GeneratedTransaction) MessagePayload() []byte { return stypes.LogToMessagePayload(m.rcpt.Logs[0]) } -func (m *GeneratedTransaction) CheckIncluded() { +type CheckIncludedOpts struct { + ExpectRevert bool +} + +func WithRevertExpected() func(*CheckIncludedOpts) { + return func(opts *CheckIncludedOpts) { + opts.ExpectRevert = true + } +} + +func (m *GeneratedTransaction) CheckIncluded(args ...func(opts *CheckIncludedOpts)) { + opts := CheckIncludedOpts{} + for _, arg := range args { + arg(&opts) + } rcpt, err := m.chain.SequencerEngine.EthClient().TransactionReceipt(m.t.Ctx(), m.tx.Hash()) require.NoError(m.t, err, "Transaction should have been included") require.NotNil(m.t, rcpt, "No receipt found") + if opts.ExpectRevert { + require.Equal(m.t, types.ReceiptStatusFailed, rcpt.Status, "Expected tx to revert") + } else { + require.Equal(m.t, types.ReceiptStatusSuccessful, rcpt.Status, "Expected tx to be successful") + } } func (m *GeneratedTransaction) CheckNotIncluded() { diff --git a/op-e2e/actions/interop/proofs_test.go b/op-e2e/actions/interop/proofs_test.go index 6057816d8bf83..45d0bf29f86a9 100644 --- a/op-e2e/actions/interop/proofs_test.go +++ b/op-e2e/actions/interop/proofs_test.go @@ -172,6 +172,102 @@ func TestInteropFaultProofs_ConsolidateValidCrossChainMessage(gt *testing.T) { runFppAndChallengerTests(gt, system, tests) } +func TestInteropFaultProofs_PreForkActivation(gt *testing.T) { + t := helpers.NewDefaultTesting(gt) + system := dsl.NewInteropDSL(t, dsl.SetInteropForkScheduledButInactive()) + + actors := system.Actors + endTimestamp := actors.ChainA.RollupCfg.Genesis.L2Time + actors.ChainA.RollupCfg.BlockTime + startTimestamp := endTimestamp - 1 + require.False(t, actors.ChainA.RollupCfg.IsInterop(endTimestamp), "Interop should not be active") + + alice := system.CreateUser() + emitter := system.DeployEmitterContracts() + + system.AddL2Block(system.Actors.ChainA, dsl.WithL2BlockTransactions(emitter.EmitMessage(alice, "hello"))) + initMsg := emitter.LastEmittedMessage() + system.AddL2Block(system.Actors.ChainB, + dsl.WithL2BlockTransactions(system.InboxContract.Execute(alice, initMsg, + dsl.WithPayload([]byte("wrong")), + // CrossL2Inbox contract isn't deployed so the tx will revert. Need to avoid using eth_estimateGas + dsl.WithFixedGasLimit()))) + system.InboxContract.LastTransaction().CheckIncluded(dsl.WithRevertExpected()) + + // Submit batch data for each chain in separate L1 blocks so tests can have one chain safe and one unsafe + system.SubmitBatchData(func(opts *dsl.SubmitBatchDataOpts) { + opts.SetChains(system.Actors.ChainA) + }) + system.SubmitBatchData(func(opts *dsl.SubmitBatchDataOpts) { + opts.SetChains(system.Actors.ChainB) + }) + // Check that the supervisor didn't re-org out this transaction. + // Interop isn't active yet so the extra derivation rules to validate executing messages must not be active. + system.InboxContract.LastTransaction().CheckIncluded(dsl.WithRevertExpected()) + + start := system.Outputs.SuperRoot(startTimestamp) + end := system.Outputs.SuperRoot(endTimestamp) + + step1Expected := system.Outputs.TransitionState(startTimestamp, 1, + system.Outputs.OptimisticBlockAtTimestamp(actors.ChainA, endTimestamp), + ).Marshal() + + step2Expected := system.Outputs.TransitionState(startTimestamp, 2, + system.Outputs.OptimisticBlockAtTimestamp(actors.ChainA, endTimestamp), + system.Outputs.OptimisticBlockAtTimestamp(actors.ChainB, endTimestamp), + ).Marshal() + + firstPaddingStep := system.Outputs.TransitionState(startTimestamp, 3, + system.Outputs.OptimisticBlockAtTimestamp(actors.ChainA, endTimestamp), + system.Outputs.OptimisticBlockAtTimestamp(actors.ChainB, endTimestamp), + ).Marshal() + + lastPaddingStep := system.Outputs.TransitionState(startTimestamp, consolidateStep, + system.Outputs.OptimisticBlockAtTimestamp(actors.ChainA, endTimestamp), + system.Outputs.OptimisticBlockAtTimestamp(actors.ChainB, endTimestamp), + ).Marshal() + + tests := []*transitionTest{ + { + name: "FirstChainOptimisticBlock", + agreedClaim: start.Marshal(), + disputedClaim: step1Expected, + disputedTraceIndex: 0, + expectValid: true, + startTimestamp: startTimestamp, + proposalTimestamp: endTimestamp, + }, + { + name: "SecondChainOptimisticBlock", + agreedClaim: step1Expected, + disputedClaim: step2Expected, + disputedTraceIndex: 1, + expectValid: true, + startTimestamp: startTimestamp, + proposalTimestamp: endTimestamp, + }, + { + name: "FirstPaddingStep", + agreedClaim: step2Expected, + disputedClaim: firstPaddingStep, + disputedTraceIndex: 2, + expectValid: true, + startTimestamp: startTimestamp, + proposalTimestamp: endTimestamp, + }, + { + name: "Consolidate", + agreedClaim: lastPaddingStep, + disputedClaim: end.Marshal(), + disputedTraceIndex: consolidateStep, + expectValid: true, + startTimestamp: startTimestamp, + proposalTimestamp: endTimestamp, + }, + } + + runFppAndChallengerTests(gt, system, tests) +} + func TestInteropFaultProofs(gt *testing.T) { t := helpers.NewDefaultTesting(gt) system := dsl.NewInteropDSL(t)