diff --git a/tests/supervisor/pre_interop/post_test.go b/tests/supervisor/pre_interop/post_test.go index f68acc9a03..849d5818eb 100644 --- a/tests/supervisor/pre_interop/post_test.go +++ b/tests/supervisor/pre_interop/post_test.go @@ -1,72 +1,216 @@ package preinterop import ( + "math/rand" "testing" "time" + "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack/match" "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/predeploys" stypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" ) -func TestPostInteropStatusSync(gt *testing.T) { - t := devtest.ParallelT(gt) - sys := presets.NewSimpleInterop(t) +func testSupervisorActivationBlock(t devtest.T, sys *presets.SimpleInterop, net *dsl.L2Network, activationBlock eth.BlockID) { require := t.Require() - devtest.RunParallel(t, sys.L2Networks(), func(t devtest.T, net *dsl.L2Network) { - t.Logger().Info("Awaiting activation block for chain", "chainID", net.ChainID()) - activationBlock := net.AwaitActivation(t, rollup.Interop) - require.NotNil(activationBlock, "ActivationBlock should return a valid block number") + // wait for some time to ensure the interop activation block is become cross-safe + t.Logger().Info("Waiting for interop activation block to be cross-safe") + sys.Supervisor.WaitForL2HeadToAdvanceTo(net.ChainID(), stypes.CrossSafe, activationBlock) - t.Logger().Info("Activation block found", "blockNumber", activationBlock.Number) - time.Sleep(10 * time.Second) + interopTime := net.Escape().ChainConfig().InteropTime + pre := net.LatestBlockBeforeTimestamp(t, *interopTime) + require.NotNil(pre, "Pre-interop block should be found before interop time") - // wait for some time to ensure the interop activation block is become cross-safe - t.Logger().Info("Waiting for interop activation block to be cross-safe") - sys.Supervisor.WaitForL2HeadToAdvanceTo(net.ChainID(), stypes.CrossSafe, activationBlock) + // make sure pre-interop block is parent of activation block + require.Equal(pre.Number, activationBlock.Number-1, "Activation block should be one after pre-interop block") + + // fetching the source for the pre-interop block should return the error + // this is to make sure that we only store the blocks after interop + _, err := sys.Supervisor.Escape().QueryAPI().CrossDerivedToSource(t.Ctx(), net.ChainID(), pre.ID()) + require.Error(err, "CrossDerivedToSource should error before interop") + + // fetch the source for the activation block + derivedFrom, err := sys.Supervisor.Escape().QueryAPI().CrossDerivedToSource(t.Ctx(), net.ChainID(), activationBlock) + require.NoError(err, "CrossDerivedToSource should not error after interop") + require.NotNil(derivedFrom, "CrossDerivedToSource should return a valid source block") +} + +// Acceptance Test: https://github.com/ethereum-optimism/optimism/blob/develop/op-acceptance-tests/tests/interop/upgrade/post_test.go +// test case modified to check the correctness of the supervisor activation block as well +func TestPostInbox(gt *testing.T) { + t := devtest.ParallelT(gt) + sys := presets.NewSimpleInterop(t) + devtest.RunParallel(t, sys.L2Networks(), func(t devtest.T, net *dsl.L2Network) { + require := t.Require() + activationBlock := net.AwaitActivation(t, rollup.Interop) - status, err := sys.Supervisor.Escape().QueryAPI().SyncStatus(t.Ctx()) - require.NoError(err, "SyncStatus should not error after interop") - require.NotNil(status, "SyncStatus should return a valid status") + el := net.Escape().L2ELNode(match.FirstL2EL) + implAddrBytes, err := el.EthClient().GetStorageAt(t.Ctx(), predeploys.CrossL2InboxAddr, + genesis.ImplementationSlot, activationBlock.Hash.String()) + require.NoError(err) + implAddr := common.BytesToAddress(implAddrBytes[:]) + require.NotEqual(common.Address{}, implAddr) + code, err := el.EthClient().CodeAtHash(t.Ctx(), implAddr, activationBlock.Hash) + require.NoError(err) + require.NotEmpty(code) + + testSupervisorActivationBlock(t, sys, net, activationBlock) }) } -func TestSupervisorActivationBlock(gt *testing.T) { +// Acceptance Test: https://github.com/ethereum-optimism/optimism/blob/develop/op-acceptance-tests/tests/interop/upgrade/post_test.go +func TestPostInteropUpgradeComprehensive(gt *testing.T) { t := devtest.ParallelT(gt) sys := presets.NewSimpleInterop(t) require := t.Require() + logger := t.Logger() + + // Wait for networks to be online by waiting for blocks + sys.L1Network.WaitForBlock() + sys.L2ChainA.WaitForBlock() + sys.L2ChainB.WaitForBlock() + + // Get interop activation time + interopTime := sys.L2ChainA.Escape().ChainConfig().InteropTime + require.NotNil(interopTime, "InteropTime must be set") + + logger.Info("Starting comprehensive post-interop upgrade tests", "interopTime", *interopTime) + + // 1. Check that anchor block of supervisor matches the activation block + logger.Info("Checking supervisor anchor block matches activation block") + testSupervisorAnchorBlock(t, sys) + + // 2. Check that the supervisor has safety progression for each level + logger.Info("Checking supervisor safety progression") + testSupervisorSafetyProgression(t, sys) + + // 3. Confirms that interop message can be included + logger.Info("Testing interop message inclusion") + testInteropMessageInclusion(t, sys) + + logger.Info("All comprehensive post-interop upgrade tests completed successfully") +} + +// testSupervisorAnchorBlock checks that the supervisor's anchor block has been set and matches the upgrade timestamp +func testSupervisorAnchorBlock(t devtest.T, sys *presets.SimpleInterop) { + logger := t.Logger() + + // Use the DSL helper for anchor block validation + logger.Info("Testing supervisor anchor block functionality") + + // Phase 1: Wait for L2 chains to reach interop activation time + logger.Info("Phase 1: Waiting for L2 chains to reach interop activation time") devtest.RunParallel(t, sys.L2Networks(), func(t devtest.T, net *dsl.L2Network) { - t.Logger().Info("Awaiting activation block for chain", "chainID", net.ChainID()) + + // Gate test to not time out before upgrade happens + forkTimestamp := net.Escape().ChainConfig().InteropTime + t.Gate().NotNil(forkTimestamp, "Must have fork configured") + t.Gate().Greater(*forkTimestamp, uint64(0), "Must not start fork at genesis") + upgradeTime := time.Unix(int64(*forkTimestamp), 0) + if deadline, hasDeadline := t.Deadline(); hasDeadline { + t.Gate().True(upgradeTime.Before(deadline), "test must not time out before upgrade happens") + } activationBlock := net.AwaitActivation(t, rollup.Interop) - require.NotNil(activationBlock, "ActivationBlock should return a valid block number") + sys.Supervisor.WaitForL2HeadToAdvanceTo(net.ChainID(), stypes.CrossSafe, activationBlock) - t.Logger().Info("Activation block found", "blockNumber", activationBlock.Number) - time.Sleep(10 * time.Second) + logger.Info("Validating anchor block timing", + "chainID", net.ChainID(), + "derivedBlockNumber", activationBlock.Number, + "interopTime", *forkTimestamp) + }) - // wait for some time to ensure the interop activation block is become cross-safe - t.Logger().Info("Waiting for interop activation block to be cross-safe") - sys.Supervisor.WaitForL2HeadToAdvanceTo(net.ChainID(), stypes.CrossSafe, activationBlock) + logger.Info("Supervisor anchor block validation completed successfully") +} - interopTime := net.Escape().ChainConfig().InteropTime - pre := net.LatestBlockBeforeTimestamp(t, *interopTime) - require.NotNil(pre, "Pre-interop block should be found before interop time") +// testSupervisorSafetyProgression checks that supervisor has safety progression for each level +func testSupervisorSafetyProgression(t devtest.T, sys *presets.SimpleInterop) { + logger := t.Logger() + logger.Info("Testing supervisor safety progression") - // make sure pre-interop block is parent of activation block - require.Equal(pre.Number, activationBlock.Number-1, "Activation block should be one after pre-interop block") + delta := uint64(3) // Minimum blocks of progression expected + dsl.CheckAll(t, + sys.L2CLA.AdvancedFn(stypes.LocalUnsafe, delta, 30), + sys.L2CLB.AdvancedFn(stypes.LocalUnsafe, delta, 30), - // fetching the source for the pre-interop block should return the error - // this is to make sure that we only store the blocks after interop - _, err := sys.Supervisor.Escape().QueryAPI().CrossDerivedToSource(t.Ctx(), net.ChainID(), pre.ID()) - require.Error(err, "CrossDerivedToSource should error before interop") + sys.L2CLA.AdvancedFn(stypes.LocalSafe, delta, 30), + sys.L2CLB.AdvancedFn(stypes.LocalSafe, delta, 30), - // fetch the source for the activation block - derivedFrom, err := sys.Supervisor.Escape().QueryAPI().CrossDerivedToSource(t.Ctx(), net.ChainID(), activationBlock) - require.NoError(err, "CrossDerivedToSource should not error after interop") - require.NotNil(derivedFrom, "CrossDerivedToSource should return a valid source block") - }) + sys.L2CLA.AdvancedFn(stypes.CrossUnsafe, delta, 30), + sys.L2CLB.AdvancedFn(stypes.CrossUnsafe, delta, 30), + + sys.L2CLA.AdvancedFn(stypes.CrossSafe, delta, 60), + sys.L2CLB.AdvancedFn(stypes.CrossSafe, delta, 60), + ) + + logger.Info("Supervisor safety progression validation completed successfully") +} + +// testInteropMessageInclusion confirms that interop messages can be included post-upgrade +func testInteropMessageInclusion(t devtest.T, sys *presets.SimpleInterop) { + logger := t.Logger() + logger.Info("Starting interop message inclusion test") + + // Phase 1: Setup test accounts and contracts + alice, bob, eventLoggerAddress := setupInteropTestEnvironment(sys) + + // Phase 2: Send init message on chain A + rng := rand.New(rand.NewSource(1234)) + initIntent, initReceipt := alice.SendInitMessage(interop.RandomInitTrigger(rng, eventLoggerAddress, rng.Intn(5), rng.Intn(30))) + + // Make sure supervisor indexes block which includes init message + sys.Supervisor.WaitForUnsafeHeadToAdvance(alice.ChainID(), 2) + + // Single event in tx so index is 0 + _, execReceipt := bob.SendExecMessage(initIntent, 0) + + // Phase 5: Verify cross-safe progression + verifyInteropMessagesProgression(t, sys, initReceipt, execReceipt) + + logger.Info("Interop message inclusion test completed successfully") +} + +// setupInteropTestEnvironment creates test accounts and deploys necessary contracts +func setupInteropTestEnvironment(sys *presets.SimpleInterop) (alice, bob *dsl.EOA, eventLoggerAddress common.Address) { + + // Create EOAs for interop messaging + alice = sys.FunderA.NewFundedEOA(eth.OneHundredthEther) + bob = sys.FunderB.NewFundedEOA(eth.OneHundredthEther) + + // Deploy event logger contract on chain A + eventLoggerAddress = alice.DeployEventLogger() + + // Wait for chains to catch up + sys.L2ChainB.CatchUpTo(sys.L2ChainA) + + return alice, bob, eventLoggerAddress +} + +// verifyInteropMessagesProgression verifies cross-safe progression for both init and exec messages +func verifyInteropMessagesProgression(t devtest.T, sys *presets.SimpleInterop, initReceipt, execReceipt *types.Receipt) { + logger := t.Logger() + + // Verify cross-safe progression for both messages + dsl.CheckAll(t, + sys.L2CLA.ReachedRefFn(stypes.CrossSafe, eth.BlockID{ + Number: initReceipt.BlockNumber.Uint64(), + Hash: initReceipt.BlockHash, + }, 60), + sys.L2CLB.ReachedRefFn(stypes.CrossSafe, eth.BlockID{ + Number: execReceipt.BlockNumber.Uint64(), + Hash: execReceipt.BlockHash, + }, 60), + ) + + logger.Info("Cross-safe progression verified for both init and exec messages") } diff --git a/tests/supervisor/pre_interop/pre_test.go b/tests/supervisor/pre_interop/pre_test.go index 7b4146cceb..188bfeef60 100644 --- a/tests/supervisor/pre_interop/pre_test.go +++ b/tests/supervisor/pre_interop/pre_test.go @@ -5,23 +5,35 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-devstack/devtest" "github.com/ethereum-optimism/optimism/op-devstack/dsl" "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-devstack/stack/match" "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum-optimism/optimism/op-service/txintent" stypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" - gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types" ) -func TestPreInteropNoSyncStatus(gt *testing.T) { +func TestPreInteropComprehensive(gt *testing.T) { t := devtest.ParallelT(gt) sys := presets.NewSimpleInterop(t) - require := t.Require() - t.Logger().Info("Starting") + testPreInteropNoSyncStatus(t, sys) + + testPreInteropCheckAccessList(t, sys) +} + +func testPreInteropNoSyncStatus(t devtest.T, sys *presets.SimpleInterop) { + t.Logger().Info("Starting pre-interop no sync status test") + require := t.Require() devtest.RunParallel(t, sys.L2Networks(), func(t devtest.T, net *dsl.L2Network) { interopTime := net.Escape().ChainConfig().InteropTime @@ -33,13 +45,11 @@ func TestPreInteropNoSyncStatus(gt *testing.T) { _, err := sys.Supervisor.Escape().QueryAPI().SyncStatus(t.Ctx()) require.ErrorContains(err, "chain database is not initialized") }) - - t.Logger().Info("Done") + t.Logger().Info("Done pre-interop no sync status test") } -func TestPreInteropCheckAccessList(gt *testing.T) { - t := devtest.ParallelT(gt) - sys := presets.NewSimpleInterop(t) +func testPreInteropCheckAccessList(t devtest.T, sys *presets.SimpleInterop) { + t.Logger().Info("Starting pre-interop check access list test") require := t.Require() alice := sys.FunderA.NewFundedEOA(eth.OneHundredthEther) @@ -53,7 +63,7 @@ func TestPreInteropCheckAccessList(gt *testing.T) { interop.RandomInitTrigger(rng, eventLoggerAddress, rng.Intn(3), rng.Intn(10)), ) - logToAccess := func(chainID eth.ChainID, log *gethTypes.Log, timestamp uint64) stypes.Access { + logToAccess := func(chainID eth.ChainID, log *types.Log, timestamp uint64) stypes.Access { msgPayload := make([]byte, 0) for _, topic := range log.Topics { msgPayload = append(msgPayload, topic.Bytes()...) @@ -89,4 +99,98 @@ func TestPreInteropCheckAccessList(gt *testing.T) { err := sys.Supervisor.Escape().QueryAPI().CheckAccessList(t.Ctx(), accessList, stypes.LocalUnsafe, ed) require.Error(err, "CheckAccessList should fail before interop") + t.Logger().Info("Done pre-interop check access list test") +} + +// Acceptance Test: https://github.com/ethereum-optimism/optimism/blob/develop/op-acceptance-tests/tests/interop/upgrade/pre_test.go +func TestPreNoInbox(gt *testing.T) { + gt.Skip("This test requires op_contract_deployer_params setup in the kurtosis network, which is not available in the devnet setup.") + t := devtest.ParallelT(gt) + sys := presets.NewSimpleInterop(t) + require := t.Require() + + t.Logger().Info("Starting") + + devtest.RunParallel(t, sys.L2Networks(), func(t devtest.T, net *dsl.L2Network) { + interopTime := net.Escape().ChainConfig().InteropTime + t.Require().NotNil(interopTime) + pre := net.LatestBlockBeforeTimestamp(t, *interopTime) + el := net.Escape().L2ELNode(match.FirstL2EL) + codeAddr := common.HexToAddress("0xC0D3C0d3C0D3C0d3c0d3c0D3c0D3C0d3C0D30022") + implCode, err := el.EthClient().CodeAtHash(t.Ctx(), codeAddr, pre.Hash) + require.NoError(err) + require.Len(implCode, 0, "needs to be empty") + implAddrBytes, err := el.EthClient().GetStorageAt(t.Ctx(), predeploys.CrossL2InboxAddr, + genesis.ImplementationSlot, pre.Hash.String()) + require.NoError(err) + require.Equal(common.Address{}, common.BytesToAddress(implAddrBytes[:])) + }) + + // try access the sync-status of the supervisor, assert that the sync-status returns the expected error + devtest.RunParallel(t, sys.L2Networks(), func(t devtest.T, net *dsl.L2Network) { + interopTime := net.Escape().ChainConfig().InteropTime + + _, err := sys.Supervisor.Escape().QueryAPI().SyncStatus(t.Ctx()) + require.ErrorContains(err, "supervisor status tracker not ready") + + // confirm we are still pre-interop + require.False(net.IsActivated(*interopTime)) + t.Logger().Info("Timestamps", "interopTime", *interopTime, "now", time.Now().Unix()) + }) + + var initReceipt *types.Receipt + var initTx *txintent.IntentTx[*txintent.InitTrigger, *txintent.InteropOutput] + // try interop before the upgrade, confirm that messages do not get included + { + // two EOAs for triggering the init and exec interop txs + alice := sys.FunderA.NewFundedEOA(eth.OneHundredthEther) + bob := sys.FunderB.NewFundedEOA(eth.OneHundredthEther) + + interopTimeA := sys.L2ChainA.Escape().ChainConfig().InteropTime + interopTimeB := sys.L2ChainB.Escape().ChainConfig().InteropTime + + eventLoggerAddress := alice.DeployEventLogger() + + // wait for chain B to catch up to chain A if necessary + sys.L2ChainB.CatchUpTo(sys.L2ChainA) + + // send initiating message on chain A + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + initTx, initReceipt = alice.SendInitMessage(interop.RandomInitTrigger(rng, eventLoggerAddress, rng.Intn(3), rng.Intn(10))) + + // at least one block between the init tx on chain A and the exec tx on chain B + sys.L2ChainB.WaitForBlock() + + // send executing message on chain B and confirm we got an error + execTx := txintent.NewIntent[*txintent.ExecTrigger, *txintent.InteropOutput](bob.Plan()) + execTx.Content.DependOn(&initTx.Result) + execTx.Content.Fn(txintent.ExecuteIndexed(constants.CrossL2Inbox, &initTx.Result, 0)) + execReceipt, err := execTx.PlannedTx.Included.Eval(sys.T.Ctx()) + require.ErrorContains(err, "implementation not initialized", "error did not contain expected string") + require.Nil(execReceipt) + + t.Logger().Info("initReceipt", "blocknum", initReceipt.BlockNumber, "txhash", initReceipt.TxHash) + + // confirm we are still pre-interop + require.False(sys.L2ChainA.IsActivated(*interopTimeA)) + require.False(sys.L2ChainB.IsActivated(*interopTimeB)) + t.Logger().Info("Timestamps", "interopTimeA", *interopTimeA, "interopTimeB", *interopTimeB, "now", time.Now().Unix()) + } + + // check that log events from a block before activation, when converted into an access-list, fail the check-access-list RPC check + { + ctx := sys.T.Ctx() + + execTrigger, err := txintent.ExecuteIndexed(constants.CrossL2Inbox, &initTx.Result, 0)(ctx) + require.NoError(err) + + ed := stypes.ExecutingDescriptor{Timestamp: uint64(time.Now().Unix())} + accessEntries := []stypes.Access{execTrigger.Msg.Access()} + accessList := stypes.EncodeAccessList(accessEntries) + + err = sys.Supervisor.Escape().QueryAPI().CheckAccessList(ctx, accessList, stypes.CrossSafe, ed) + require.ErrorContains(err, "conflicting data") + } + + t.Logger().Info("Done") }