diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index 5c1d4ffac940a..6cf3265925c4d 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -14,11 +14,13 @@ import ( type InteropDevRecipe struct { L1ChainID uint64 - L2ChainIDs []uint64 + L2s []InteropDevL2Recipe GenesisTimestamp uint64 } -func (r *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) { +func (recipe *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) { + r := recipe.hydrated() + // L1 genesis l1Cfg := &L1Config{ ChainID: new(big.Int).SetUint64(r.L1ChainID), @@ -88,19 +90,41 @@ func (r *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) Superchain: superchainCfg, L2s: make(map[string]*L2Config), } - for _, l2ChainID := range r.L2ChainIDs { - l2Cfg, err := InteropL2DevConfig(r.L1ChainID, l2ChainID, addrs) + for _, l2 := range r.L2s { + l2Cfg, err := l2.build(r.L1ChainID, addrs) if err != nil { - return nil, fmt.Errorf("failed to generate L2 config for chain %d: %w", l2ChainID, err) + return nil, fmt.Errorf("failed to generate L2 config for chain %d: %w", l2.ChainID, err) } if err := prefundL2Accounts(l1Cfg, l2Cfg, addrs); err != nil { - return nil, fmt.Errorf("failed to prefund addresses on L1 for L2 chain %d: %w", l2ChainID, err) + return nil, fmt.Errorf("failed to prefund addresses on L1 for L2 chain %d: %w", l2.ChainID, err) } - world.L2s[fmt.Sprintf("%d", l2ChainID)] = l2Cfg + world.L2s[fmt.Sprintf("%d", l2.ChainID)] = l2Cfg } return world, nil } +func (r *InteropDevRecipe) hydrated() InteropDevRecipe { + out := InteropDevRecipe{ + L1ChainID: r.L1ChainID, + L2s: make([]InteropDevL2Recipe, len(r.L2s)), + GenesisTimestamp: r.GenesisTimestamp, + } + for i, l := range r.L2s { + out.L2s[i] = l + if l.BlockTime == 0 { + out.L2s[i].BlockTime = defaultBlockTime + } + } + return out +} + +const defaultBlockTime = 2 + +type InteropDevL2Recipe struct { + ChainID uint64 + BlockTime uint64 +} + func prefundL2Accounts(l1Cfg *L1Config, l2Cfg *L2Config, addrs devkeys.Addresses) error { l1Cfg.Prefund[l2Cfg.BatchSenderAddress] = Ether(10_000_000) l1Cfg.Prefund[l2Cfg.Deployer] = Ether(10_000_000) @@ -125,10 +149,10 @@ func prefundL2Accounts(l1Cfg *L1Config, l2Cfg *L2Config, addrs devkeys.Addresses return nil } -func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (*L2Config, error) { +func (r *InteropDevL2Recipe) build(l1ChainID uint64, addrs devkeys.Addresses) (*L2Config, error) { // Padded chain ID, hex encoded, prefixed with 0xff like inboxes, then 0x02 to signify devnet. - batchInboxAddress := common.HexToAddress(fmt.Sprintf("0xff02%016x", l2ChainID)) - chainOps := devkeys.ChainOperatorKeys(new(big.Int).SetUint64(l2ChainID)) + batchInboxAddress := common.HexToAddress(fmt.Sprintf("0xff02%016x", r.ChainID)) + chainOps := devkeys.ChainOperatorKeys(new(big.Int).SetUint64(r.ChainID)) deployer, err := addrs.Address(chainOps(devkeys.DeployerRole)) if err != nil { @@ -238,8 +262,8 @@ func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (* }, L2CoreDeployConfig: genesis.L2CoreDeployConfig{ L1ChainID: l1ChainID, - L2ChainID: l2ChainID, - L2BlockTime: 2, + L2ChainID: r.ChainID, + L2BlockTime: r.BlockTime, FinalizationPeriodSeconds: 2, // instant output finalization MaxSequencerDrift: 300, SequencerWindowSize: 200, @@ -262,7 +286,7 @@ func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (* DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds) } - l2Users := devkeys.ChainUserKeys(new(big.Int).SetUint64(l2ChainID)) + l2Users := devkeys.ChainUserKeys(new(big.Int).SetUint64(r.ChainID)) for i := uint64(0); i < 20; i++ { userAddr, err := addrs.Address(l2Users(i)) if err != nil { diff --git a/op-e2e/actions/interop/dsl/dsl.go b/op-e2e/actions/interop/dsl/dsl.go index 6c5ca36a3c4b8..cf123c13cdea9 100644 --- a/op-e2e/actions/interop/dsl/dsl.go +++ b/op-e2e/actions/interop/dsl/dsl.go @@ -62,8 +62,8 @@ type InteropDSL struct { createdUsers uint64 } -func NewInteropDSL(t helpers.Testing) *InteropDSL { - setup := SetupInterop(t) +func NewInteropDSL(t helpers.Testing, opts ...setupOption) *InteropDSL { + setup := SetupInterop(t, opts...) actors := setup.CreateActors() actors.PrepareChainState(t) diff --git a/op-e2e/actions/interop/dsl/interop.go b/op-e2e/actions/interop/dsl/interop.go index 48bdde8e68257..dec5aff6300f9 100644 --- a/op-e2e/actions/interop/dsl/interop.go +++ b/op-e2e/actions/interop/dsl/interop.go @@ -103,15 +103,32 @@ func (actors *InteropActors) PrepareChainState(t helpers.Testing) { // At a 2 second block time, this should be small enough to cover all events buffered in the supervisor event queue. const messageExpiryTime = 120 // 2 minutes -// SetupInterop creates an InteropSetup to instantiate actors on, with 2 L2 chains. -func SetupInterop(t helpers.Testing) *InteropSetup { - logger := testlog.Logger(t, log.LevelDebug) +type setupOption func(*interopgen.InteropDevRecipe) + +func SetBlockTimeForChainA(blockTime uint64) setupOption { + return func(recipe *interopgen.InteropDevRecipe) { + recipe.L2s[0].BlockTime = blockTime + } +} + +func SetBlockTimeForChainB(blockTime uint64) setupOption { + return func(recipe *interopgen.InteropDevRecipe) { + recipe.L2s[1].BlockTime = blockTime + } +} +// SetupInterop creates an InteropSetup to instantiate actors on, with 2 L2 chains. +func SetupInterop(t helpers.Testing, opts ...setupOption) *InteropSetup { recipe := interopgen.InteropDevRecipe{ L1ChainID: 900100, - L2ChainIDs: []uint64{900200, 900201}, + L2s: []interopgen.InteropDevL2Recipe{{ChainID: 900200}, {ChainID: 900201}}, GenesisTimestamp: uint64(time.Now().Unix() + 3), } + for _, opt := range opts { + opt(&recipe) + } + + logger := testlog.Logger(t, log.LevelDebug) hdWallet, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) require.NoError(t, err) worldCfg, err := recipe.Build(hdWallet) diff --git a/op-e2e/actions/interop/proofs_test.go b/op-e2e/actions/interop/proofs_test.go index cc6b88881a86b..dc700c8972c6c 100644 --- a/op-e2e/actions/interop/proofs_test.go +++ b/op-e2e/actions/interop/proofs_test.go @@ -769,6 +769,19 @@ func TestInteropFaultProofsInvalidBlock(gt *testing.T) { runFppAndChallengerTests(gt, system, tests) } +func TestInteropFaultProofs_VariedBlockTimes(gt *testing.T) { + t := helpers.NewDefaultTesting(gt) + system := dsl.NewInteropDSL(t, dsl.SetBlockTimeForChainA(1), dsl.SetBlockTimeForChainB(2)) + actors := system.Actors + + system.AddL2Block(system.Actors.ChainA) + system.AddL2Block(system.Actors.ChainB) + + assertTime(t, actors.ChainA, 1, 1, 0, 0) + assertTime(t, actors.ChainB, 2, 2, 0, 0) + // TODO(#14479): Complete test case +} + func runFppAndChallengerTests(gt *testing.T, system *dsl.InteropDSL, tests []*transitionTest) { for _, test := range tests { test := test @@ -873,6 +886,15 @@ func WithInteropEnabled(t helpers.StatefulTesting, actors *dsl.InteropActors, de } } +func assertTime(t helpers.Testing, chain *dsl.Chain, unsafe, crossUnsafe, localSafe, safe uint64) { + start := chain.L2Genesis.Timestamp + status := chain.Sequencer.SyncStatus() + require.Equal(t, start+unsafe, status.UnsafeL2.Time, "Unsafe") + require.Equal(t, start+crossUnsafe, status.CrossUnsafeL2.Time, "Cross Unsafe") + require.Equal(t, start+localSafe, status.LocalSafeL2.Time, "Local safe") + require.Equal(t, start+safe, status.SafeL2.Time, "Safe") +} + type transitionTest struct { name string agreedClaim []byte diff --git a/op-e2e/faultproofs/util_interop.go b/op-e2e/faultproofs/util_interop.go index 29afb3bc6d099..0f9aa3b7b1fa2 100644 --- a/op-e2e/faultproofs/util_interop.go +++ b/op-e2e/faultproofs/util_interop.go @@ -21,7 +21,7 @@ func StartInteropFaultDisputeSystem(t *testing.T, opts ...faultDisputeConfigOpts } recipe := interopgen.InteropDevRecipe{ L1ChainID: 900100, - L2ChainIDs: []uint64{900200, 900201}, + L2s: []interopgen.InteropDevL2Recipe{{ChainID: 900200}, {ChainID: 900201}}, GenesisTimestamp: uint64(time.Now().Unix() + 3), // start chain 3 seconds from now } worldResources := interop.WorldResourcePaths{ diff --git a/op-e2e/interop/interop_recipe_test.go b/op-e2e/interop/interop_recipe_test.go index 05a2e056c39b7..038460668c61c 100644 --- a/op-e2e/interop/interop_recipe_test.go +++ b/op-e2e/interop/interop_recipe_test.go @@ -18,7 +18,7 @@ import ( func TestInteropDevRecipe(t *testing.T) { rec := interopgen.InteropDevRecipe{ L1ChainID: 900100, - L2ChainIDs: []uint64{900200, 900201}, + L2s: []interopgen.InteropDevL2Recipe{{ChainID: 900200}, {ChainID: 900201}}, GenesisTimestamp: uint64(1234567), } hd, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) diff --git a/op-e2e/interop/interop_test.go b/op-e2e/interop/interop_test.go index 67013947f2780..6ae812f1609a8 100644 --- a/op-e2e/interop/interop_test.go +++ b/op-e2e/interop/interop_test.go @@ -34,7 +34,7 @@ import ( func setupAndRun(t *testing.T, config SuperSystemConfig, fn func(*testing.T, SuperSystem)) { recipe := interopgen.InteropDevRecipe{ L1ChainID: 900100, - L2ChainIDs: []uint64{900200, 900201}, + L2s: []interopgen.InteropDevL2Recipe{{ChainID: 900200}, {ChainID: 900201}}, GenesisTimestamp: uint64(time.Now().Unix() + 3), // start chain 3 seconds from now } worldResources := WorldResourcePaths{ diff --git a/op-node/cmd/interop/interop.go b/op-node/cmd/interop/interop.go index 3e6f75d530bb0..7b71940ff8102 100644 --- a/op-node/cmd/interop/interop.go +++ b/op-node/cmd/interop/interop.go @@ -96,9 +96,14 @@ var InteropDevSetup = &cli.Command{ logCfg := oplog.ReadCLIConfig(cliCtx) logger := oplog.NewLogger(cliCtx.App.Writer, logCfg) + l2ChainIDs := cliCtx.Uint64Slice(l2ChainIDsFlag.Name) + l2Recipes := make([]interopgen.InteropDevL2Recipe, len(l2ChainIDs)) + for i, id := range l2ChainIDs { + l2Recipes[i] = interopgen.InteropDevL2Recipe{ChainID: id} + } recipe := &interopgen.InteropDevRecipe{ L1ChainID: cliCtx.Uint64(l1ChainIDFlag.Name), - L2ChainIDs: cliCtx.Uint64Slice(l2ChainIDsFlag.Name), + L2s: l2Recipes, GenesisTimestamp: cliCtx.Uint64(timestampFlag.Name), } if recipe.GenesisTimestamp == 0 {