diff --git a/op-challenger/cmd/main_test.go b/op-challenger/cmd/main_test.go index 067214436be04..fdd2cfa1dc9cf 100644 --- a/op-challenger/cmd/main_test.go +++ b/op-challenger/cmd/main_test.go @@ -52,17 +52,12 @@ func TestLogLevel(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet)) - defaultCfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, l1Beacon, datadir, config.TraceTypeAlphabet) - // Add in the extra CLI options required when using alphabet trace type - defaultCfg.RollupRpc = rollupRpc + defaultCfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, l1Beacon, rollupRpc, l2EthRpc, datadir, config.TraceTypeAlphabet) require.Equal(t, defaultCfg, cfg) } func TestDefaultConfigIsValid(t *testing.T) { - cfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, l1Beacon, datadir, config.TraceTypeAlphabet) - // Add in options that are required based on the specific trace type - // To avoid needing to specify unused options, these aren't included in the params for NewConfig - cfg.RollupRpc = rollupRpc + cfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, l1Beacon, rollupRpc, l2EthRpc, datadir, config.TraceTypeAlphabet) require.NoError(t, cfg.Check()) } @@ -114,7 +109,6 @@ func TestTraceType(t *testing.T) { func TestMultipleTraceTypes(t *testing.T) { t.Run("WithAllOptions", func(t *testing.T) { argsMap := requiredArgs(config.TraceTypeCannon) - addRequiredOutputArgs(argsMap) // Add Asterisc required flags addRequiredAsteriscArgs(argsMap) args := toArgList(argsMap) @@ -130,7 +124,6 @@ func TestMultipleTraceTypes(t *testing.T) { }) t.Run("WithSomeOptions", func(t *testing.T) { argsMap := requiredArgs(config.TraceTypeCannon) - addRequiredOutputArgs(argsMap) args := toArgList(argsMap) // Add extra trace types (cannon is already specified) args = append(args, @@ -315,14 +308,6 @@ func TestAsteriscRequiredArgs(t *testing.T) { }) t.Run(fmt.Sprintf("TestL2Rpc-%v", traceType), func(t *testing.T) { - t.Run("NotRequiredForAlphabetTraceLegacy", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-l2")) - }) - - t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--l2-eth-rpc")) - }) - t.Run("RequiredForAsteriscTrace", func(t *testing.T) { verifyArgsInvalid(t, "flag l2-eth-rpc is required", addRequiredArgsExcept(traceType, "--l2-eth-rpc")) }) @@ -438,6 +423,25 @@ func TestAsteriscRequiredArgs(t *testing.T) { }) } } + +func TestAlphabetRequiredArgs(t *testing.T) { + t.Run(fmt.Sprintf("TestL2Rpc-%v", config.TraceTypeAlphabet), func(t *testing.T) { + t.Run("RequiredForAlphabetTrace", func(t *testing.T) { + verifyArgsInvalid(t, "flag l2-eth-rpc is required", addRequiredArgsExcept(config.TraceTypeAlphabet, "--l2-eth-rpc")) + }) + + t.Run("ValidLegacy", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--l2-eth-rpc", fmt.Sprintf("--cannon-l2=%s", l2EthRpc))) + require.Equal(t, l2EthRpc, cfg.L2Rpc) + }) + + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet)) + require.Equal(t, l2EthRpc, cfg.L2Rpc) + }) + }) +} + func TestCannonRequiredArgs(t *testing.T) { for _, traceType := range []config.TraceType{config.TraceTypeCannon, config.TraceTypePermissioned} { traceType := traceType @@ -502,14 +506,6 @@ func TestCannonRequiredArgs(t *testing.T) { }) t.Run(fmt.Sprintf("TestL2Rpc-%v", traceType), func(t *testing.T) { - t.Run("NotRequiredForAlphabetTraceLegacy", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--cannon-l2")) - }) - - t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(config.TraceTypeAlphabet, "--l2-eth-rpc")) - }) - t.Run("RequiredForCannonTrace", func(t *testing.T) { verifyArgsInvalid(t, "flag l2-eth-rpc is required", addRequiredArgsExcept(traceType, "--l2-eth-rpc")) }) @@ -772,6 +768,8 @@ func requiredArgs(traceType config.TraceType) map[string]string { args := map[string]string{ "--l1-eth-rpc": l1EthRpc, "--l1-beacon": l1Beacon, + "--rollup-rpc": rollupRpc, + "--l2-eth-rpc": l2EthRpc, "--game-factory-address": gameFactoryAddressValue, "--trace-type": traceType.String(), "--datadir": datadir, @@ -781,8 +779,6 @@ func requiredArgs(traceType config.TraceType) map[string]string { addRequiredCannonArgs(args) case config.TraceTypeAsterisc: addRequiredAsteriscArgs(args) - case config.TraceTypeAlphabet: - addRequiredOutputArgs(args) } return args } @@ -793,7 +789,6 @@ func addRequiredCannonArgs(args map[string]string) { args["--cannon-server"] = cannonServer args["--cannon-prestate"] = cannonPreState args["--l2-eth-rpc"] = l2EthRpc - addRequiredOutputArgs(args) } func addRequiredAsteriscArgs(args map[string]string) { @@ -802,11 +797,6 @@ func addRequiredAsteriscArgs(args map[string]string) { args["--asterisc-server"] = asteriscServer args["--asterisc-prestate"] = asteriscPreState args["--l2-eth-rpc"] = l2EthRpc - addRequiredOutputArgs(args) -} - -func addRequiredOutputArgs(args map[string]string) { - args["--rollup-rpc"] = rollupRpc } func toArgList(req map[string]string) []string { diff --git a/op-challenger/config/config.go b/op-challenger/config/config.go index 7ed960e7682ec..3fd2dea804cc9 100644 --- a/op-challenger/config/config.go +++ b/op-challenger/config/config.go @@ -159,12 +159,16 @@ func NewConfig( gameFactoryAddress common.Address, l1EthRpc string, l1BeaconApi string, + l2RollupRpc string, + l2EthRpc string, datadir string, supportedTraceTypes ...TraceType, ) Config { return Config{ L1EthRpc: l1EthRpc, L1Beacon: l1BeaconApi, + RollupRpc: l2RollupRpc, + L2Rpc: l2EthRpc, GameFactoryAddress: gameFactoryAddress, MaxConcurrency: uint(runtime.NumCPU()), PollInterval: DefaultPollInterval, @@ -201,6 +205,9 @@ func (c Config) Check() error { if c.RollupRpc == "" { return ErrMissingRollupRpc } + if c.L2Rpc == "" { + return ErrMissingL2Rpc + } if c.GameFactoryAddress == (common.Address{}) { return ErrMissingGameFactoryAddress } @@ -244,9 +251,6 @@ func (c Config) Check() error { if c.CannonAbsolutePreState != "" && c.CannonAbsolutePreStateBaseURL != nil { return ErrCannonAbsolutePreStateAndBaseURL } - if c.L2Rpc == "" { - return ErrMissingL2Rpc - } if c.CannonSnapshotFreq == 0 { return ErrMissingCannonSnapshotFreq } @@ -285,9 +289,6 @@ func (c Config) Check() error { if c.AsteriscAbsolutePreState != "" && c.AsteriscAbsolutePreStateBaseURL != nil { return ErrAsteriscAbsolutePreStateAndBaseURL } - if c.L2Rpc == "" { - return ErrMissingL2Rpc - } if c.AsteriscSnapshotFreq == 0 { return ErrMissingAsteriscSnapshotFreq } diff --git a/op-challenger/config/config_test.go b/op-challenger/config/config_test.go index a673e9ff554f8..dc2949e41d201 100644 --- a/op-challenger/config/config_test.go +++ b/op-challenger/config/config_test.go @@ -40,7 +40,6 @@ func applyValidConfigForCannon(cfg *Config) { cfg.CannonServer = validCannonOpProgramBin cfg.CannonAbsolutePreStateBaseURL = validCannonAbsolutPreStateBaseURL cfg.CannonNetwork = validCannonNetwork - cfg.L2Rpc = validL2Rpc } func applyValidConfigForAsterisc(cfg *Config) { @@ -48,18 +47,16 @@ func applyValidConfigForAsterisc(cfg *Config) { cfg.AsteriscServer = validAsteriscOpProgramBin cfg.AsteriscAbsolutePreStateBaseURL = validAsteriscAbsolutPreStateBaseURL cfg.AsteriscNetwork = validAsteriscNetwork - cfg.L2Rpc = validL2Rpc } func validConfig(traceType TraceType) Config { - cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validL1BeaconUrl, validDatadir, traceType) + cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validL1BeaconUrl, validRollupRpc, validL2Rpc, validDatadir, traceType) if traceType == TraceTypeCannon || traceType == TraceTypePermissioned { applyValidConfigForCannon(&cfg) } if traceType == TraceTypeAsterisc { applyValidConfigForAsterisc(&cfg) } - cfg.RollupRpc = validRollupRpc return cfg } diff --git a/op-challenger/flags/flags.go b/op-challenger/flags/flags.go index ae762088df084..2ff53840623d4 100644 --- a/op-challenger/flags/flags.go +++ b/op-challenger/flags/flags.go @@ -296,10 +296,6 @@ func CheckCannonFlags(ctx *cli.Context) error { if !ctx.IsSet(CannonPreStateFlag.Name) && !ctx.IsSet(CannonPreStatesURLFlag.Name) { return fmt.Errorf("flag %s or %s is required", CannonPreStatesURLFlag.Name, CannonPreStateFlag.Name) } - // CannonL2Flag is checked because it is an alias with L2EthRpcFlag - if !ctx.IsSet(CannonL2Flag.Name) && !ctx.IsSet(L2EthRpcFlag.Name) { - return fmt.Errorf("flag %s is required", L2EthRpcFlag.Name) - } return nil } @@ -323,10 +319,6 @@ func CheckAsteriscFlags(ctx *cli.Context) error { if !ctx.IsSet(AsteriscPreStateFlag.Name) && !ctx.IsSet(AsteriscPreStatesURLFlag.Name) { return fmt.Errorf("flag %s or %s is required", AsteriscPreStatesURLFlag.Name, AsteriscPreStateFlag.Name) } - // CannonL2Flag is checked because it is an alias with L2EthRpcFlag - if !ctx.IsSet(CannonL2Flag.Name) && !ctx.IsSet(L2EthRpcFlag.Name) { - return fmt.Errorf("flag %s is required", L2EthRpcFlag.Name) - } return nil } @@ -336,6 +328,10 @@ func CheckRequired(ctx *cli.Context, traceTypes []config.TraceType) error { return fmt.Errorf("flag %s is required", f.Names()[0]) } } + // CannonL2Flag is checked because it is an alias with L2EthRpcFlag + if !ctx.IsSet(CannonL2Flag.Name) && !ctx.IsSet(L2EthRpcFlag.Name) { + return fmt.Errorf("flag %s is required", L2EthRpcFlag.Name) + } for _, traceType := range traceTypes { switch traceType { case config.TraceTypeCannon, config.TraceTypePermissioned: diff --git a/op-challenger/game/fault/agent.go b/op-challenger/game/fault/agent.go index 7cbad159c66ff..9440e0483f981 100644 --- a/op-challenger/game/fault/agent.go +++ b/op-challenger/game/fault/agent.go @@ -30,6 +30,7 @@ type Responder interface { type ClaimLoader interface { GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) + IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) } type Agent struct { @@ -84,6 +85,14 @@ func (a *Agent) Act(ctx context.Context) error { defer func() { a.metrics.RecordGameActTime(a.systemClock.Since(start).Seconds()) }() + + if challenged, err := a.loader.IsL2BlockNumberChallenged(ctx, rpcblock.Latest); err != nil { + return fmt.Errorf("failed to check if L2 block number already challenged: %w", err) + } else if challenged { + a.log.Debug("Skipping game with already challenged L2 block number") + return nil + } + game, err := a.newGameFromContracts(ctx) if err != nil { return fmt.Errorf("create game from contracts: %w", err) @@ -105,11 +114,13 @@ func (a *Agent) Act(ctx context.Context) error { func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action types.Action) { defer wg.Done() - actionLog := a.log.New("action", action.Type, "is_attack", action.IsAttack, "parent", action.ParentIdx) + actionLog := a.log.New("action", action.Type) if action.Type == types.ActionTypeStep { containsOracleData := action.OracleData != nil isLocal := containsOracleData && action.OracleData.IsLocal actionLog = actionLog.New( + "is_attack", action.IsAttack, + "parent", action.ParentIdx, "prestate", common.Bytes2Hex(action.PreState), "proof", common.Bytes2Hex(action.ProofData), "containsOracleData", containsOracleData, @@ -118,8 +129,8 @@ func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action ty if action.OracleData != nil { actionLog = actionLog.New("oracleKey", common.Bytes2Hex(action.OracleData.OracleKey)) } - } else { - actionLog = actionLog.New("value", action.Value) + } else if action.Type == types.ActionTypeMove { + actionLog = actionLog.New("is_attack", action.IsAttack, "parent", action.ParentIdx, "value", action.Value) } switch action.Type { @@ -127,6 +138,7 @@ func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action ty a.metrics.RecordGameMove() case types.ActionTypeStep: a.metrics.RecordGameStep() + // TODO(client-pod#852): Add metrics for L2 block number challenges } actionLog.Info("Performing action") err := a.responder.PerformAction(ctx, action) diff --git a/op-challenger/game/fault/agent_test.go b/op-challenger/game/fault/agent_test.go index fcbb143a93781..a3daab342c93d 100644 --- a/op-challenger/game/fault/agent_test.go +++ b/op-challenger/game/fault/agent_test.go @@ -57,6 +57,18 @@ func TestDoNotMakeMovesWhenGameIsResolvable(t *testing.T) { } } +func TestDoNotMakeMovesWhenL2BlockNumberChallenged(t *testing.T) { + ctx := context.Background() + + agent, claimLoader, responder := setupTestAgent(t) + claimLoader.blockNumChallenged = true + + require.NoError(t, agent.Act(ctx)) + + require.Equal(t, 1, responder.callResolveCount, "should check if game is resolvable") + require.Equal(t, 1, claimLoader.callCount, "should fetch claims only once for resolveClaim") +} + func createClaimsWithClaimants(t *testing.T, d types.Depth) []types.Claim { claimBuilder := test.NewClaimBuilder(t, d, alphabet.NewTraceProvider(big.NewInt(0), d)) rootClaim := claimBuilder.CreateRootClaim() @@ -180,9 +192,14 @@ func setupTestAgent(t *testing.T) (*Agent, *stubClaimLoader, *stubResponder) { } type stubClaimLoader struct { - callCount int - maxLoads int - claims []types.Claim + callCount int + maxLoads int + claims []types.Claim + blockNumChallenged bool +} + +func (s *stubClaimLoader) IsL2BlockNumberChallenged(_ context.Context, _ rpcblock.Block) (bool, error) { + return s.blockNumChallenged, nil } func (s *stubClaimLoader) GetAllClaims(_ context.Context, _ rpcblock.Block) ([]types.Claim, error) { diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index 30c0ff663f3d1..1a07ca4b82875 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -52,7 +52,10 @@ var ( methodWETH = "weth" ) -var ErrSimulationFailed = errors.New("tx simulation failed") +var ( + ErrSimulationFailed = errors.New("tx simulation failed") + ErrChallengeL2BlockNotSupported = errors.New("contract version does not support challenging L2 block number") +) type FaultDisputeGameContractLatest struct { metrics metrics.ContractMetricer @@ -410,6 +413,14 @@ func (f *FaultDisputeGameContractLatest) vm(ctx context.Context) (*VMContract, e return NewVMContract(vmAddr, f.multiCaller), nil } +func (f *FaultDisputeGameContractLatest) IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) { + return false, nil +} + +func (f *FaultDisputeGameContractLatest) ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) { + return txmgr.TxCandidate{}, ErrChallengeL2BlockNotSupported +} + func (f *FaultDisputeGameContractLatest) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) { call := f.contract.Call(methodAttack, new(big.Int).SetUint64(parentContractIndex), pivot) return call.ToTxCandidate() @@ -523,6 +534,8 @@ type FaultDisputeGameContract interface { GetClaim(ctx context.Context, idx uint64) (types.Claim, error) GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) IsResolved(ctx context.Context, block rpcblock.Block, claims ...types.Claim) ([]bool, error) + IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) + ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) diff --git a/op-challenger/game/fault/contracts/faultdisputegame_test.go b/op-challenger/game/fault/contracts/faultdisputegame_test.go index 670442029dacc..86c8b066822b5 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger/game/fault/contracts/faultdisputegame_test.go @@ -652,6 +652,30 @@ func TestFaultDisputeGame_IsResolved(t *testing.T) { } } +func TestFaultDisputeGameContractLatest_IsL2BlockNumberChallenged(t *testing.T) { + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + _, game := setupFaultDisputeGameTest(t, version) + challenged, err := game.IsL2BlockNumberChallenged(context.Background(), rpcblock.Latest) + require.NoError(t, err) + require.False(t, challenged) + }) + } +} + +func TestFaultDisputeGameContractLatest_ChallengeL2BlockNumberTx(t *testing.T) { + for _, version := range versions { + version := version + t.Run(version.version, func(t *testing.T) { + _, game := setupFaultDisputeGameTest(t, version) + tx, err := game.ChallengeL2BlockNumberTx(&faultTypes.InvalidL2BlockNumberChallenge{}) + require.ErrorIs(t, err, ErrChallengeL2BlockNotSupported) + require.Equal(t, txmgr.TxCandidate{}, tx) + }) + } +} + func setupFaultDisputeGameTest(t *testing.T, version contractVersion) (*batchingTest.AbiBasedRpc, FaultDisputeGameContract) { fdgAbi := version.loadAbi() diff --git a/op-challenger/game/fault/register.go b/op-challenger/game/fault/register.go index 120bbd2a4689e..f0c1f283ee762 100644 --- a/op-challenger/game/fault/register.go +++ b/op-challenger/game/fault/register.go @@ -67,15 +67,9 @@ func RegisterGameTypes( selective bool, claimants []common.Address, ) (CloseFunc, error) { - var closer CloseFunc - var l2Client *ethclient.Client - if cfg.TraceTypeEnabled(config.TraceTypeCannon) || cfg.TraceTypeEnabled(config.TraceTypePermissioned) || cfg.TraceTypeEnabled(config.TraceTypeAsterisc) { - l2, err := ethclient.DialContext(ctx, cfg.L2Rpc) - if err != nil { - return nil, fmt.Errorf("dial l2 client %v: %w", cfg.L2Rpc, err) - } - l2Client = l2 - closer = l2Client.Close + l2Client, err := ethclient.DialContext(ctx, cfg.L2Rpc) + if err != nil { + return nil, fmt.Errorf("dial l2 client %v: %w", cfg.L2Rpc, err) } syncValidator := newSyncStatusValidator(rollupClient) @@ -95,11 +89,11 @@ func RegisterGameTypes( } } if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) { - if err := registerAlphabet(registry, oracles, ctx, systemClock, l1Clock, logger, m, syncValidator, rollupClient, txSender, gameFactory, caller, l1HeaderSource, selective, claimants); err != nil { + if err := registerAlphabet(registry, oracles, ctx, systemClock, l1Clock, logger, m, syncValidator, rollupClient, l2Client, txSender, gameFactory, caller, l1HeaderSource, selective, claimants); err != nil { return nil, fmt.Errorf("failed to register alphabet game type: %w", err) } } - return closer, nil + return l2Client.Close, nil } func registerAlphabet( @@ -112,6 +106,7 @@ func registerAlphabet( m metrics.Metricer, syncValidator SyncValidator, rollupClient RollupClient, + l2Client utils.L2HeaderSource, txSender TxSender, gameFactory *contracts.DisputeGameFactoryContract, caller *batching.MultiCaller, @@ -143,7 +138,7 @@ func registerAlphabet( } prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) creator := func(ctx context.Context, logger log.Logger, gameDepth faultTypes.Depth, dir string) (faultTypes.TraceAccessor, error) { - accessor, err := outputs.NewOutputAlphabetTraceAccessor(logger, m, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) + accessor, err := outputs.NewOutputAlphabetTraceAccessor(logger, m, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock) if err != nil { return nil, err } diff --git a/op-challenger/game/fault/responder/responder.go b/op-challenger/game/fault/responder/responder.go index 4a5b36f3bde44..9acbff1b6c1cd 100644 --- a/op-challenger/game/fault/responder/responder.go +++ b/op-challenger/game/fault/responder/responder.go @@ -11,7 +11,6 @@ import ( gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" ) @@ -23,6 +22,7 @@ type GameContract interface { AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) + ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error) } @@ -130,6 +130,8 @@ func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) candidate.Value = bondValue case types.ActionTypeStep: candidate, err = r.contract.StepTx(uint64(action.ParentIdx), action.IsAttack, action.PreState, action.ProofData) + case types.ActionTypeChallengeL2BlockNumber: + candidate, err = r.contract.ChallengeL2BlockNumberTx(action.InvalidL2BlockNumberChallenge) } if err != nil { return err diff --git a/op-challenger/game/fault/responder/responder_test.go b/op-challenger/game/fault/responder/responder_test.go index fd35b552e8b0f..76d743c6e4a3b 100644 --- a/op-challenger/game/fault/responder/responder_test.go +++ b/op-challenger/game/fault/responder/responder_test.go @@ -284,6 +284,19 @@ func TestPerformAction(t *testing.T) { require.Equal(t, 0, uploader.updates) require.Equal(t, 1, oracle.existCalls) }) + + t.Run("challengeL2Block", func(t *testing.T) { + responder, mockTxMgr, contract, _, _ := newTestFaultResponder(t) + challenge := &types.InvalidL2BlockNumberChallenge{} + action := types.Action{ + Type: types.ActionTypeChallengeL2BlockNumber, + InvalidL2BlockNumberChallenge: challenge, + } + err := responder.PerformAction(context.Background(), action) + require.NoError(t, err) + require.Len(t, mockTxMgr.sent, 1) + require.Equal(t, []interface{}{challenge}, contract.challengeArgs) + }) } func newTestFaultResponder(t *testing.T) (*FaultResponder, *mockTxManager, *mockContract, *mockPreimageUploader, *mockOracle) { @@ -359,6 +372,7 @@ type mockContract struct { attackArgs []interface{} defendArgs []interface{} stepArgs []interface{} + challengeArgs []interface{} updateOracleClaimIdx uint64 updateOracleArgs *types.PreimageOracleData } @@ -387,6 +401,11 @@ func (m *mockContract) ResolveClaimTx(_ uint64) (txmgr.TxCandidate, error) { return txmgr.TxCandidate{}, nil } +func (m *mockContract) ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) { + m.challengeArgs = []interface{}{challenge} + return txmgr.TxCandidate{TxData: ([]byte)("challenge")}, nil +} + func (m *mockContract) AttackTx(parentClaimId uint64, claim common.Hash) (txmgr.TxCandidate, error) { m.attackArgs = []interface{}{parentClaimId, claim} return txmgr.TxCandidate{TxData: ([]byte)("attack")}, nil diff --git a/op-challenger/game/fault/solver/game_solver.go b/op-challenger/game/fault/solver/game_solver.go index 7c10d005c2e33..812936cc4f840 100644 --- a/op-challenger/game/fault/solver/game_solver.go +++ b/op-challenger/game/fault/solver/game_solver.go @@ -2,6 +2,7 @@ package solver import ( "context" + "errors" "fmt" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" @@ -27,6 +28,25 @@ func (s *GameSolver) CalculateNextActions(ctx context.Context, game types.Game) if err != nil { return nil, fmt.Errorf("failed to determine if root claim is correct: %w", err) } + + // Challenging the L2 block number will only work if we have the same output root as the claim + // Otherwise our output root preimage won't match. We can just proceed and invalidate the output root by disputing claims instead. + if agreeWithRootClaim { + if challenge, err := s.claimSolver.trace.GetL2BlockNumberChallenge(ctx, game); errors.Is(err, types.ErrL2BlockNumberValid) { + // We agree with the L2 block number, proceed to processing claims + } else if err != nil { + // Failed to check L2 block validity + return nil, fmt.Errorf("failed to determine L2 block validity: %w", err) + } else { + return []types.Action{ + { + Type: types.ActionTypeChallengeL2BlockNumber, + InvalidL2BlockNumberChallenge: challenge, + }, + }, nil + } + } + var actions []types.Action agreedClaims := newHonestClaimTracker() if agreeWithRootClaim { diff --git a/op-challenger/game/fault/solver/game_solver_test.go b/op-challenger/game/fault/solver/game_solver_test.go index c567d20e737c1..b0586a335ab2f 100644 --- a/op-challenger/game/fault/solver/game_solver_test.go +++ b/op-challenger/game/fault/solver/game_solver_test.go @@ -10,10 +10,36 @@ import ( faulttest "github.com/ethereum-optimism/optimism/op-challenger/game/fault/test" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) +func TestCalculateNextActions_ChallengeL2BlockNumber(t *testing.T) { + startingBlock := big.NewInt(5) + maxDepth := types.Depth(6) + challenge := &types.InvalidL2BlockNumberChallenge{ + Output: ð.OutputResponse{OutputRoot: eth.Bytes32{0xbb}}, + } + claimBuilder := faulttest.NewAlphabetClaimBuilder(t, startingBlock, maxDepth) + traceProvider := faulttest.NewAlphabetWithProofProvider(t, startingBlock, maxDepth, nil) + solver := NewGameSolver(maxDepth, trace.NewSimpleTraceAccessor(traceProvider)) + + // Do not challenge when provider returns error indicating l2 block is valid + actions, err := solver.CalculateNextActions(context.Background(), claimBuilder.GameBuilder().Game) + require.NoError(t, err) + require.Len(t, actions, 0) + + // Do challenge when the provider returns a challenge + traceProvider.L2BlockChallenge = challenge + actions, err = solver.CalculateNextActions(context.Background(), claimBuilder.GameBuilder().Game) + require.NoError(t, err) + require.Len(t, actions, 1) + action := actions[0] + require.Equal(t, types.ActionTypeChallengeL2BlockNumber, action.Type) + require.Equal(t, challenge, action.InvalidL2BlockNumberChallenge) +} + func TestCalculateNextActions(t *testing.T) { maxDepth := types.Depth(6) startingL2BlockNumber := big.NewInt(0) @@ -31,8 +57,6 @@ func TestCalculateNextActions(t *testing.T) { }, }, { - // Note: The fault dispute game contract should prevent a correct root claim from actually being posted - // But for completeness, test we ignore it so we don't get sucked into playing an unwinnable game. name: "DoNotAttackCorrectRootClaim_AgreeWithOutputRoot", rootClaimCorrect: true, setupGame: func(builder *faulttest.GameBuilder) {}, diff --git a/op-challenger/game/fault/test/alphabet.go b/op-challenger/game/fault/test/alphabet.go index 9aa129d221c81..96e00e90c6546 100644 --- a/op-challenger/game/fault/test/alphabet.go +++ b/op-challenger/game/fault/test/alphabet.go @@ -9,11 +9,12 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" ) -func NewAlphabetWithProofProvider(t *testing.T, startingL2BlockNumber *big.Int, maxDepth types.Depth, oracleError error) *alphabetWithProofProvider { - return &alphabetWithProofProvider{ +func NewAlphabetWithProofProvider(t *testing.T, startingL2BlockNumber *big.Int, maxDepth types.Depth, oracleError error) *AlphabetWithProofProvider { + return &AlphabetWithProofProvider{ alphabet.NewTraceProvider(startingL2BlockNumber, maxDepth), maxDepth, oracleError, + nil, } } @@ -22,13 +23,14 @@ func NewAlphabetClaimBuilder(t *testing.T, startingL2BlockNumber *big.Int, maxDe return NewClaimBuilder(t, maxDepth, alphabetProvider) } -type alphabetWithProofProvider struct { +type AlphabetWithProofProvider struct { *alphabet.AlphabetTraceProvider - depth types.Depth - OracleError error + depth types.Depth + OracleError error + L2BlockChallenge *types.InvalidL2BlockNumberChallenge } -func (a *alphabetWithProofProvider) GetStepData(ctx context.Context, i types.Position) ([]byte, []byte, *types.PreimageOracleData, error) { +func (a *AlphabetWithProofProvider) GetStepData(ctx context.Context, i types.Position) ([]byte, []byte, *types.PreimageOracleData, error) { preimage, _, _, err := a.AlphabetTraceProvider.GetStepData(ctx, i) if err != nil { return nil, nil, nil, err @@ -37,3 +39,11 @@ func (a *alphabetWithProofProvider) GetStepData(ctx context.Context, i types.Pos data := types.NewPreimageOracleData([]byte{byte(traceIndex)}, []byte{byte(traceIndex - 1)}, uint32(traceIndex-1)) return preimage, []byte{byte(traceIndex - 1)}, data, nil } + +func (c *AlphabetWithProofProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) { + if c.L2BlockChallenge != nil { + return c.L2BlockChallenge, nil + } else { + return nil, types.ErrL2BlockNumberValid + } +} diff --git a/op-challenger/game/fault/trace/access.go b/op-challenger/game/fault/trace/access.go index be42b6d81232e..4afe6d5f9b6df 100644 --- a/op-challenger/game/fault/trace/access.go +++ b/op-challenger/game/fault/trace/access.go @@ -40,4 +40,12 @@ func (t *Accessor) GetStepData(ctx context.Context, game types.Game, ref types.C return provider.GetStepData(ctx, pos) } +func (t *Accessor) GetL2BlockNumberChallenge(ctx context.Context, game types.Game) (*types.InvalidL2BlockNumberChallenge, error) { + provider, err := t.selector(ctx, game, game.Claims()[0], types.RootPosition) + if err != nil { + return nil, err + } + return provider.GetL2BlockNumberChallenge(ctx) +} + var _ types.TraceAccessor = (*Accessor)(nil) diff --git a/op-challenger/game/fault/trace/access_test.go b/op-challenger/game/fault/trace/access_test.go index df63cabcf587b..04c3ea3e6e7c2 100644 --- a/op-challenger/game/fault/trace/access_test.go +++ b/op-challenger/game/fault/trace/access_test.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/test" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/stretchr/testify/require" ) @@ -67,4 +68,32 @@ func TestAccessor_UsesSelector(t *testing.T) { require.Equal(t, expectedProofData, actualProofData) require.Equal(t, expectedPreimageData, actualPreimageData) }) + + t.Run("GetL2BlockNumberChallenge", func(t *testing.T) { + provider := &ChallengeTraceProvider{ + TraceProvider: provider1, + } + accessor := &Accessor{ + selector: func(ctx context.Context, actualGame types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) { + require.Equal(t, game, actualGame) + require.Equal(t, game.Claims()[0], ref) + require.Equal(t, types.RootPosition, pos) + return provider, nil + }, + } + challenge, err := accessor.GetL2BlockNumberChallenge(ctx, game) + require.NoError(t, err) + require.NotNil(t, challenge) + require.Equal(t, eth.Bytes32{0xaa, 0xbb}, challenge.Output.OutputRoot) + }) +} + +type ChallengeTraceProvider struct { + types.TraceProvider +} + +func (c *ChallengeTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) { + return &types.InvalidL2BlockNumberChallenge{ + Output: ð.OutputResponse{OutputRoot: eth.Bytes32{0xaa, 0xbb}}, + }, nil } diff --git a/op-challenger/game/fault/trace/alphabet/provider.go b/op-challenger/game/fault/trace/alphabet/provider.go index 6cae7154ed768..f14a8e94f48ae 100644 --- a/op-challenger/game/fault/trace/alphabet/provider.go +++ b/op-challenger/game/fault/trace/alphabet/provider.go @@ -75,6 +75,10 @@ func (ap *AlphabetTraceProvider) Get(ctx context.Context, i types.Position) (com return alphabetStateHash(claimBytes), nil } +func (ap *AlphabetTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) { + return nil, types.ErrL2BlockNumberValid +} + // BuildAlphabetPreimage constructs the claim bytes for the index and claim. func BuildAlphabetPreimage(traceIndex *big.Int, claim *big.Int) []byte { return append(traceIndex.FillBytes(make([]byte, 32)), claim.FillBytes(make([]byte, 32))...) diff --git a/op-challenger/game/fault/trace/asterisc/executor_test.go b/op-challenger/game/fault/trace/asterisc/executor_test.go index 4bfb537c9c752..93e6a68fde8cd 100644 --- a/op-challenger/game/fault/trace/asterisc/executor_test.go +++ b/op-challenger/game/fault/trace/asterisc/executor_test.go @@ -20,7 +20,7 @@ func TestGenerateProof(t *testing.T) { input := "starting.json" tempDir := t.TempDir() dir := filepath.Join(tempDir, "gameDir") - cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", "http://localhost:9000", tempDir, config.TraceTypeAsterisc) + cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", "http://localhost:9000", "http://localhost:9096", "http://localhost:9095", tempDir, config.TraceTypeAsterisc) cfg.L2Rpc = "http://localhost:9999" cfg.AsteriscAbsolutePreState = "pre.json" cfg.AsteriscBin = "./bin/asterisc" diff --git a/op-challenger/game/fault/trace/asterisc/provider.go b/op-challenger/game/fault/trace/asterisc/provider.go index d2fe316b5cfb8..40fd1720abd3a 100644 --- a/op-challenger/game/fault/trace/asterisc/provider.go +++ b/op-challenger/game/fault/trace/asterisc/provider.go @@ -96,6 +96,10 @@ func (p *AsteriscTraceProvider) GetStepData(ctx context.Context, pos types.Posit return value, data, oracleData, nil } +func (p *AsteriscTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) { + return nil, types.ErrL2BlockNumberValid +} + // loadProof will attempt to load or generate the proof data at the specified index // If the requested index is beyond the end of the actual trace it is extended with no-op instructions. func (p *AsteriscTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) { diff --git a/op-challenger/game/fault/trace/cannon/executor_test.go b/op-challenger/game/fault/trace/cannon/executor_test.go index c755d50031c65..8df1e9cc5c27c 100644 --- a/op-challenger/game/fault/trace/cannon/executor_test.go +++ b/op-challenger/game/fault/trace/cannon/executor_test.go @@ -25,7 +25,7 @@ func TestGenerateProof(t *testing.T) { input := "starting.json" tempDir := t.TempDir() dir := filepath.Join(tempDir, "gameDir") - cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", "http://localhost:9000", tempDir, config.TraceTypeCannon) + cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", "http://localhost:9000", "http://localhost:9096", "http://localhost:9095", tempDir, config.TraceTypeCannon) cfg.L2Rpc = "http://localhost:9999" cfg.CannonAbsolutePreState = "pre.json" cfg.CannonBin = "./bin/cannon" diff --git a/op-challenger/game/fault/trace/cannon/provider.go b/op-challenger/game/fault/trace/cannon/provider.go index 791b7c7a2cddf..dca71d4e2ede7 100644 --- a/op-challenger/game/fault/trace/cannon/provider.go +++ b/op-challenger/game/fault/trace/cannon/provider.go @@ -94,6 +94,10 @@ func (p *CannonTraceProvider) GetStepData(ctx context.Context, pos types.Positio return value, data, oracleData, nil } +func (p *CannonTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) { + return nil, types.ErrL2BlockNumberValid +} + // loadProof will attempt to load or generate the proof data at the specified index // If the requested index is beyond the end of the actual trace it is extended with no-op instructions. func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) { diff --git a/op-challenger/game/fault/trace/outputs/output_alphabet.go b/op-challenger/game/fault/trace/outputs/output_alphabet.go index 22b0fc762f831..72fdad7e0b802 100644 --- a/op-challenger/game/fault/trace/outputs/output_alphabet.go +++ b/op-challenger/game/fault/trace/outputs/output_alphabet.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/alphabet" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -19,12 +20,13 @@ func NewOutputAlphabetTraceAccessor( m metrics.Metricer, prestateProvider types.PrestateProvider, rollupClient OutputRollupClient, + l2Client utils.L2HeaderSource, l1Head eth.BlockID, splitDepth types.Depth, prestateBlock uint64, poststateBlock uint64, ) (*trace.Accessor, error) { - outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) + outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock) alphabetCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) { provider := alphabet.NewTraceProvider(agreed.L2BlockNumber, depth) return provider, nil diff --git a/op-challenger/game/fault/trace/outputs/output_asterisc.go b/op-challenger/game/fault/trace/outputs/output_asterisc.go index b33662d901cb3..45aa94a794692 100644 --- a/op-challenger/game/fault/trace/outputs/output_asterisc.go +++ b/op-challenger/game/fault/trace/outputs/output_asterisc.go @@ -31,7 +31,7 @@ func NewOutputAsteriscTraceAccessor( prestateBlock uint64, poststateBlock uint64, ) (*trace.Accessor, error) { - outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) + outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock) asteriscCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) { logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext) subdir := filepath.Join(dir, localContext.Hex()) diff --git a/op-challenger/game/fault/trace/outputs/output_cannon.go b/op-challenger/game/fault/trace/outputs/output_cannon.go index 86d6df8c140ff..47eff2818124a 100644 --- a/op-challenger/game/fault/trace/outputs/output_cannon.go +++ b/op-challenger/game/fault/trace/outputs/output_cannon.go @@ -31,7 +31,7 @@ func NewOutputCannonTraceAccessor( prestateBlock uint64, poststateBlock uint64, ) (*trace.Accessor, error) { - outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) + outputProvider := NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock) cannonCreator := func(ctx context.Context, localContext common.Hash, depth types.Depth, agreed contracts.Proposal, claimed contracts.Proposal) (types.TraceProvider, error) { logger := logger.New("pre", agreed.OutputRoot, "post", claimed.OutputRoot, "localContext", localContext) subdir := filepath.Join(dir, localContext.Hex()) diff --git a/op-challenger/game/fault/trace/outputs/provider.go b/op-challenger/game/fault/trace/outputs/provider.go index d2da7fd45a526..410b7b15d02d0 100644 --- a/op-challenger/game/fault/trace/outputs/provider.go +++ b/op-challenger/game/fault/trace/outputs/provider.go @@ -4,7 +4,9 @@ import ( "context" "errors" "fmt" + "math/big" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/common" @@ -29,17 +31,19 @@ type OutputTraceProvider struct { types.PrestateProvider logger log.Logger rollupProvider OutputRollupClient + l2Client utils.L2HeaderSource prestateBlock uint64 poststateBlock uint64 l1Head eth.BlockID gameDepth types.Depth } -func NewTraceProvider(logger log.Logger, prestateProvider types.PrestateProvider, rollupProvider OutputRollupClient, l1Head eth.BlockID, gameDepth types.Depth, prestateBlock, poststateBlock uint64) *OutputTraceProvider { +func NewTraceProvider(logger log.Logger, prestateProvider types.PrestateProvider, rollupProvider OutputRollupClient, l2Client utils.L2HeaderSource, l1Head eth.BlockID, gameDepth types.Depth, prestateBlock, poststateBlock uint64) *OutputTraceProvider { return &OutputTraceProvider{ PrestateProvider: prestateProvider, logger: logger, rollupProvider: rollupProvider, + l2Client: l2Client, prestateBlock: prestateBlock, poststateBlock: poststateBlock, l1Head: l1Head, @@ -94,6 +98,29 @@ func (o *OutputTraceProvider) GetStepData(_ context.Context, _ types.Position) ( return nil, nil, nil, ErrGetStepData } +func (o *OutputTraceProvider) GetL2BlockNumberChallenge(ctx context.Context) (*types.InvalidL2BlockNumberChallenge, error) { + outputBlock, err := o.HonestBlockNumber(ctx, types.RootPosition) + if err != nil { + return nil, err + } + claimedBlock, err := o.ClaimedBlockNumber(types.RootPosition) + if err != nil { + return nil, err + } + if claimedBlock == outputBlock { + return nil, types.ErrL2BlockNumberValid + } + output, err := o.rollupProvider.OutputAtBlock(ctx, outputBlock) + if err != nil { + return nil, err + } + header, err := o.l2Client.HeaderByNumber(ctx, new(big.Int).SetUint64(outputBlock)) + if err != nil { + return nil, fmt.Errorf("failed to retrieve L2 block header %v: %w", outputBlock, err) + } + return types.NewInvalidL2BlockNumberProof(output, header), nil +} + func (o *OutputTraceProvider) outputAtBlock(ctx context.Context, block uint64) (common.Hash, error) { output, err := o.rollupProvider.OutputAtBlock(ctx, block) if err != nil { diff --git a/op-challenger/game/fault/trace/outputs/provider_test.go b/op-challenger/game/fault/trace/outputs/provider_test.go index c4e154ff17c3c..daaddaaad4abe 100644 --- a/op-challenger/game/fault/trace/outputs/provider_test.go +++ b/op-challenger/game/fault/trace/outputs/provider_test.go @@ -11,7 +11,9 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) @@ -29,34 +31,34 @@ var ( func TestGet(t *testing.T) { t.Run("ErrorsTraceIndexOutOfBounds", func(t *testing.T) { deepGame := types.Depth(164) - provider, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame) + provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame) pos := types.NewPosition(0, big.NewInt(0)) _, err := provider.Get(context.Background(), pos) require.ErrorIs(t, err, ErrIndexTooBig) }) t.Run("FirstBlockAfterPrestate", func(t *testing.T) { - provider, _ := setupWithTestData(t, prestateBlock, poststateBlock) + provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock) value, err := provider.Get(context.Background(), types.NewPosition(gameDepth, big.NewInt(0))) require.NoError(t, err) require.Equal(t, firstOutputRoot, value) }) t.Run("MissingOutputAtBlock", func(t *testing.T) { - provider, _ := setupWithTestData(t, prestateBlock, poststateBlock) + provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock) _, err := provider.Get(context.Background(), types.NewPosition(gameDepth, big.NewInt(1))) require.ErrorIs(t, err, errNoOutputAtBlock) }) t.Run("PostStateBlock", func(t *testing.T) { - provider, _ := setupWithTestData(t, prestateBlock, poststateBlock) + provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock) value, err := provider.Get(context.Background(), types.NewPositionFromGIndex(big.NewInt(228))) require.NoError(t, err) require.Equal(t, value, poststateOutputRoot) }) t.Run("AfterPostStateBlock", func(t *testing.T) { - provider, _ := setupWithTestData(t, prestateBlock, poststateBlock) + provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock) value, err := provider.Get(context.Background(), types.NewPositionFromGIndex(big.NewInt(229))) require.NoError(t, err) require.Equal(t, value, poststateOutputRoot) @@ -87,7 +89,7 @@ func TestHonestBlockNumber(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - provider, stubRollupClient := setupWithTestData(t, prestateBlock, poststateBlock) + provider, stubRollupClient, _ := setupWithTestData(t, prestateBlock, poststateBlock) stubRollupClient.maxSafeHead = test.maxSafeHead actual, err := provider.HonestBlockNumber(context.Background(), test.pos) require.NoError(t, err) @@ -97,13 +99,55 @@ func TestHonestBlockNumber(t *testing.T) { t.Run("ErrorsTraceIndexOutOfBounds", func(t *testing.T) { deepGame := types.Depth(164) - provider, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame) + provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame) pos := types.NewPosition(0, big.NewInt(0)) _, err := provider.HonestBlockNumber(context.Background(), pos) require.ErrorIs(t, err, ErrIndexTooBig) }) } +func TestGetL2BlockNumberChallenge(t *testing.T) { + tests := []struct { + name string + maxSafeHead uint64 + expectChallenge bool + }{ + {"NoChallengeWhenMaxHeadNotLimited", math.MaxUint64, false}, + {"NoChallengeWhenBeforeMaxHead", poststateBlock + 1, false}, + {"NoChallengeWhenAtMaxHead", poststateBlock, false}, + {"ChallengeWhenBeforeMaxHead", poststateBlock - 1, true}, + } + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + provider, stubRollupClient, stubL2Client := setupWithTestData(t, prestateBlock, poststateBlock) + stubRollupClient.maxSafeHead = test.maxSafeHead + if test.expectChallenge { + stubRollupClient.outputs[test.maxSafeHead] = ð.OutputResponse{ + OutputRoot: eth.Bytes32{0xaa}, + BlockRef: eth.L2BlockRef{ + Number: test.maxSafeHead, + }, + } + stubL2Client.headers[test.maxSafeHead] = ðTypes.Header{ + Number: new(big.Int).SetUint64(test.maxSafeHead), + Root: common.Hash{0xcc}, + } + } + actual, err := provider.GetL2BlockNumberChallenge(context.Background()) + if test.expectChallenge { + require.NoError(t, err) + require.Equal(t, &types.InvalidL2BlockNumberChallenge{ + Output: stubRollupClient.outputs[test.maxSafeHead], + Header: stubL2Client.headers[test.maxSafeHead], + }, actual) + } else { + require.ErrorIs(t, err, types.ErrL2BlockNumberValid) + } + }) + } +} + func TestClaimedBlockNumber(t *testing.T) { tests := []struct { name string @@ -128,7 +172,7 @@ func TestClaimedBlockNumber(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - provider, stubRollupClient := setupWithTestData(t, prestateBlock, poststateBlock) + provider, stubRollupClient, _ := setupWithTestData(t, prestateBlock, poststateBlock) stubRollupClient.maxSafeHead = test.maxSafeHead actual, err := provider.ClaimedBlockNumber(test.pos) require.NoError(t, err) @@ -138,7 +182,7 @@ func TestClaimedBlockNumber(t *testing.T) { t.Run("ErrorsTraceIndexOutOfBounds", func(t *testing.T) { deepGame := types.Depth(164) - provider, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame) + provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock, deepGame) pos := types.NewPosition(0, big.NewInt(0)) _, err := provider.ClaimedBlockNumber(pos) require.ErrorIs(t, err, ErrIndexTooBig) @@ -146,12 +190,12 @@ func TestClaimedBlockNumber(t *testing.T) { } func TestGetStepData(t *testing.T) { - provider, _ := setupWithTestData(t, prestateBlock, poststateBlock) + provider, _, _ := setupWithTestData(t, prestateBlock, poststateBlock) _, _, _, err := provider.GetStepData(context.Background(), types.NewPosition(1, common.Big0)) require.ErrorIs(t, err, ErrGetStepData) } -func setupWithTestData(t *testing.T, prestateBlock, poststateBlock uint64, customGameDepth ...types.Depth) (*OutputTraceProvider, *stubRollupClient) { +func setupWithTestData(t *testing.T, prestateBlock, poststateBlock uint64, customGameDepth ...types.Depth) (*OutputTraceProvider, *stubRollupClient, *stubL2HeaderSource) { rollupClient := &stubRollupClient{ outputs: map[uint64]*eth.OutputResponse{ prestateBlock: { @@ -166,6 +210,9 @@ func setupWithTestData(t *testing.T, prestateBlock, poststateBlock uint64, custo }, maxSafeHead: math.MaxUint64, } + l2Client := &stubL2HeaderSource{ + headers: make(map[uint64]*ethTypes.Header), + } inputGameDepth := gameDepth if len(customGameDepth) > 0 { inputGameDepth = customGameDepth[0] @@ -173,10 +220,11 @@ func setupWithTestData(t *testing.T, prestateBlock, poststateBlock uint64, custo return &OutputTraceProvider{ logger: testlog.Logger(t, log.LevelInfo), rollupProvider: rollupClient, + l2Client: l2Client, prestateBlock: prestateBlock, poststateBlock: poststateBlock, gameDepth: inputGameDepth, - }, rollupClient + }, rollupClient, l2Client } type stubRollupClient struct { @@ -201,3 +249,15 @@ func (s *stubRollupClient) SafeHeadAtL1Block(_ context.Context, l1BlockNum uint6 }, }, nil } + +type stubL2HeaderSource struct { + headers map[uint64]*ethTypes.Header +} + +func (s *stubL2HeaderSource) HeaderByNumber(_ context.Context, num *big.Int) (*ethTypes.Header, error) { + header, ok := s.headers[num.Uint64()] + if !ok { + return nil, ethereum.NotFound + } + return header, nil +} diff --git a/op-challenger/game/fault/trace/outputs/split_adapter_test.go b/op-challenger/game/fault/trace/outputs/split_adapter_test.go index 8eabc520def7c..b791df7ee63f3 100644 --- a/op-challenger/game/fault/trace/outputs/split_adapter_test.go +++ b/op-challenger/game/fault/trace/outputs/split_adapter_test.go @@ -139,7 +139,7 @@ func setupAdapterTest(t *testing.T, topDepth types.Depth) (split.ProviderCreator prestateProvider := &stubPrestateProvider{ absolutePrestate: prestateOutputRoot, } - topProvider := NewTraceProvider(testlog.Logger(t, log.LevelInfo), prestateProvider, rollupClient, l1Head, topDepth, prestateBlock, poststateBlock) + topProvider := NewTraceProvider(testlog.Logger(t, log.LevelInfo), prestateProvider, rollupClient, nil, l1Head, topDepth, prestateBlock, poststateBlock) adapter := OutputRootSplitAdapter(topProvider, creator.Create) return adapter, creator } diff --git a/op-challenger/game/fault/trace/translate.go b/op-challenger/game/fault/trace/translate.go index 38d0435b6dc12..0295e10db4c1f 100644 --- a/op-challenger/game/fault/trace/translate.go +++ b/op-challenger/game/fault/trace/translate.go @@ -46,4 +46,8 @@ func (p *TranslatingProvider) AbsolutePreStateCommitment(ctx context.Context) (h return p.provider.AbsolutePreStateCommitment(ctx) } +func (p *TranslatingProvider) GetL2BlockNumberChallenge(ctx context.Context) (*types.InvalidL2BlockNumberChallenge, error) { + return p.provider.GetL2BlockNumberChallenge(ctx) +} + var _ types.TraceProvider = (*TranslatingProvider)(nil) diff --git a/op-challenger/game/fault/types/actions.go b/op-challenger/game/fault/types/actions.go index c6631f80e40af..a0d3588e50df4 100644 --- a/op-challenger/game/fault/types/actions.go +++ b/op-challenger/game/fault/types/actions.go @@ -9,12 +9,15 @@ func (a ActionType) String() string { } const ( - ActionTypeMove ActionType = "move" - ActionTypeStep ActionType = "step" + ActionTypeMove ActionType = "move" + ActionTypeStep ActionType = "step" + ActionTypeChallengeL2BlockNumber ActionType = "challenge-l2-block-number" ) type Action struct { - Type ActionType + Type ActionType + + // Moves and Steps ParentIdx int ParentPosition Position IsAttack bool @@ -26,4 +29,7 @@ type Action struct { PreState []byte ProofData []byte OracleData *PreimageOracleData + + // Challenge L2 Block Number + InvalidL2BlockNumberChallenge *InvalidL2BlockNumberChallenge } diff --git a/op-challenger/game/fault/types/position.go b/op-challenger/game/fault/types/position.go index 308f79498f511..c684780d78a25 100644 --- a/op-challenger/game/fault/types/position.go +++ b/op-challenger/game/fault/types/position.go @@ -10,6 +10,8 @@ import ( var ( ErrPositionDepthTooSmall = errors.New("position depth is too small") + + RootPosition = NewPositionFromGIndex(big.NewInt(1)) ) // Depth is the depth of a position in a game tree where the root level has diff --git a/op-challenger/game/fault/types/position_test.go b/op-challenger/game/fault/types/position_test.go index c7bb210a62028..535c01a7e6bfe 100644 --- a/op-challenger/game/fault/types/position_test.go +++ b/op-challenger/game/fault/types/position_test.go @@ -13,6 +13,10 @@ func bi(i int) *big.Int { return big.NewInt(int64(i)) } +func TestRootPosition(t *testing.T) { + require.True(t, RootPosition.IsRootPosition()) +} + func TestBigMSB(t *testing.T) { large, ok := new(big.Int).SetString("18446744073709551615", 10) require.True(t, ok) diff --git a/op-challenger/game/fault/types/types.go b/op-challenger/game/fault/types/types.go index d2efe5e3e27ae..85029f223f920 100644 --- a/op-challenger/game/fault/types/types.go +++ b/op-challenger/game/fault/types/types.go @@ -7,12 +7,15 @@ import ( "time" preimage "github.com/ethereum-optimism/optimism/op-preimage" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" ) var ( - ErrGameDepthReached = errors.New("game depth reached") + ErrGameDepthReached = errors.New("game depth reached") + ErrL2BlockNumberValid = errors.New("l2 block number is valid") ) const ( @@ -103,6 +106,10 @@ type TraceAccessor interface { // GetStepData returns the data required to execute the step at the specified position, // evaluated in the context of the specified claim (ref). GetStepData(ctx context.Context, game Game, ref Claim, pos Position) (prestate []byte, proofData []byte, preimageData *PreimageOracleData, err error) + + // GetL2BlockNumberChallenge returns the data required to prove the correct L2 block number of the root claim. + // Returns ErrL2BlockNumberValid if the root claim is known to come from the same block as the claimed L2 block. + GetL2BlockNumberChallenge(ctx context.Context, game Game) (*InvalidL2BlockNumberChallenge, error) } // PrestateProvider defines an interface to request the absolute prestate. @@ -124,6 +131,10 @@ type TraceProvider interface { // and any pre-image data that needs to be loaded into the oracle prior to execution (may be nil) // The prestate returned from GetStepData for trace 10 should be the pre-image of the claim from trace 9 GetStepData(ctx context.Context, i Position) (prestate []byte, proofData []byte, preimageData *PreimageOracleData, err error) + + // GetL2BlockNumberChallenge returns the data required to prove the correct L2 block number of the root claim. + // Returns ErrL2BlockNumberValid if the root claim is known to come from the same block as the claimed L2 block. + GetL2BlockNumberChallenge(ctx context.Context) (*InvalidL2BlockNumberChallenge, error) } // ClaimData is the core of a claim. It must be unique inside a specific game. @@ -201,3 +212,15 @@ func NewClock(duration time.Duration, timestamp time.Time) Clock { Timestamp: timestamp, } } + +type InvalidL2BlockNumberChallenge struct { + Output *eth.OutputResponse + Header *ethTypes.Header +} + +func NewInvalidL2BlockNumberProof(output *eth.OutputResponse, header *ethTypes.Header) *InvalidL2BlockNumberChallenge { + return &InvalidL2BlockNumberChallenge{ + Output: output, + Header: header, + } +} diff --git a/op-e2e/e2eutils/challenger/helper.go b/op-e2e/e2eutils/challenger/helper.go index 39dbeda65589e..ae96a99ac382e 100644 --- a/op-e2e/e2eutils/challenger/helper.go +++ b/op-e2e/e2eutils/challenger/helper.go @@ -29,6 +29,7 @@ import ( type EndpointProvider interface { NodeEndpoint(name string) string + RollupEndpoint(name string) string L1BeaconEndpoint() string } @@ -95,15 +96,8 @@ func FindMonorepoRoot(t *testing.T) string { return "" } -func applyCannonConfig( - c *config.Config, - t *testing.T, - rollupCfg *rollup.Config, - l2Genesis *core.Genesis, - l2Endpoint string, -) { +func applyCannonConfig(c *config.Config, t *testing.T, rollupCfg *rollup.Config, l2Genesis *core.Genesis) { require := require.New(t) - c.L2Rpc = l2Endpoint root := FindMonorepoRoot(t) c.CannonBin = root + "cannon/bin/cannon" c.CannonServer = root + "op-program/bin/op-program" @@ -123,31 +117,23 @@ func applyCannonConfig( c.CannonRollupConfigPath = rollupFile } -func WithCannon( - t *testing.T, - rollupCfg *rollup.Config, - l2Genesis *core.Genesis, - rollupEndpoint string, - l2Endpoint string, -) Option { +func WithCannon(t *testing.T, rollupCfg *rollup.Config, l2Genesis *core.Genesis) Option { return func(c *config.Config) { c.TraceTypes = append(c.TraceTypes, config.TraceTypeCannon) - c.RollupRpc = rollupEndpoint - applyCannonConfig(c, t, rollupCfg, l2Genesis, l2Endpoint) + applyCannonConfig(c, t, rollupCfg, l2Genesis) } } -func WithAlphabet(rollupEndpoint string) Option { +func WithAlphabet() Option { return func(c *config.Config) { c.TraceTypes = append(c.TraceTypes, config.TraceTypeAlphabet) - c.RollupRpc = rollupEndpoint } } func NewChallenger(t *testing.T, ctx context.Context, sys EndpointProvider, name string, options ...Option) *Helper { log := testlog.Logger(t, log.LevelDebug).New("role", name) log.Info("Creating challenger") - cfg := NewChallengerConfig(t, sys, options...) + cfg := NewChallengerConfig(t, sys, "sequencer", options...) chl, err := challenger.Main(ctx, log, cfg) require.NoError(t, err, "must init challenger") require.NoError(t, chl.Start(ctx), "must start challenger") @@ -161,11 +147,11 @@ func NewChallenger(t *testing.T, ctx context.Context, sys EndpointProvider, name } } -func NewChallengerConfig(t *testing.T, sys EndpointProvider, options ...Option) *config.Config { +func NewChallengerConfig(t *testing.T, sys EndpointProvider, l2NodeName string, options ...Option) *config.Config { // Use the NewConfig method to ensure we pick up any defaults that are set. l1Endpoint := sys.NodeEndpoint("l1") l1Beacon := sys.L1BeaconEndpoint() - cfg := config.NewConfig(common.Address{}, l1Endpoint, l1Beacon, t.TempDir()) + cfg := config.NewConfig(common.Address{}, l1Endpoint, l1Beacon, sys.RollupEndpoint(l2NodeName), sys.NodeEndpoint(l2NodeName), t.TempDir()) // The devnet can't set the absolute prestate output root because the contracts are deployed in L1 genesis // before the L2 genesis is known. cfg.AllowInvalidPrestate = true diff --git a/op-e2e/e2eutils/disputegame/alphabet_helper.go b/op-e2e/e2eutils/disputegame/alphabet_helper.go index 228dc52f4ba29..68f61481ad779 100644 --- a/op-e2e/e2eutils/disputegame/alphabet_helper.go +++ b/op-e2e/e2eutils/disputegame/alphabet_helper.go @@ -17,7 +17,7 @@ func (g *AlphabetGameHelper) StartChallenger(ctx context.Context, sys challenger opts := []challenger.Option{ challenger.WithFactoryAddress(g.factoryAddr), challenger.WithGameAddress(g.addr), - challenger.WithAlphabet(g.system.RollupEndpoint("sequencer")), + challenger.WithAlphabet(), } opts = append(opts, options...) c := challenger.NewChallenger(g.t, ctx, sys, name, opts...) diff --git a/op-e2e/e2eutils/disputegame/game_helper.go b/op-e2e/e2eutils/disputegame/game_helper.go index d4480d642d572..3bb5ade9fe931 100644 --- a/op-e2e/e2eutils/disputegame/game_helper.go +++ b/op-e2e/e2eutils/disputegame/game_helper.go @@ -25,7 +25,6 @@ type FaultGameHelper struct { game *bindings.FaultDisputeGame factoryAddr common.Address addr common.Address - system DisputeSystem } func (g *FaultGameHelper) Addr() common.Address { diff --git a/op-e2e/e2eutils/disputegame/helper.go b/op-e2e/e2eutils/disputegame/helper.go index 22d7d4ad77fb7..91c201778c34f 100644 --- a/op-e2e/e2eutils/disputegame/helper.go +++ b/op-e2e/e2eutils/disputegame/helper.go @@ -172,6 +172,7 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string cfg := NewGameCfg(opts...) logger := testlog.Logger(h.T, log.LevelInfo).New("role", "OutputCannonGameHelper") rollupClient := h.System.RollupClient(l2Node) + l2Client := h.System.NodeClient(l2Node) extraData := h.CreateBisectionGameExtraData(l2Node, l2BlockNumber, cfg) @@ -200,7 +201,7 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, l2Node string l1Head := h.GetL1Head(ctx, game) prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock.Uint64()) - provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, faultTypes.Depth(splitDepth.Uint64()), prestateBlock.Uint64(), poststateBlock.Uint64()) + provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, faultTypes.Depth(splitDepth.Uint64()), prestateBlock.Uint64(), poststateBlock.Uint64()) return &OutputCannonGameHelper{ OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System), @@ -228,6 +229,7 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri cfg := NewGameCfg(opts...) logger := testlog.Logger(h.T, log.LevelInfo).New("role", "OutputAlphabetGameHelper") rollupClient := h.System.RollupClient(l2Node) + l2Client := h.System.NodeClient(l2Node) extraData := h.CreateBisectionGameExtraData(l2Node, l2BlockNumber, cfg) @@ -256,7 +258,7 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri l1Head := h.GetL1Head(ctx, game) prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock.Uint64()) - provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, faultTypes.Depth(splitDepth.Uint64()), prestateBlock.Uint64(), poststateBlock.Uint64()) + provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, faultTypes.Depth(splitDepth.Uint64()), prestateBlock.Uint64(), poststateBlock.Uint64()) return &OutputAlphabetGameHelper{ OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System), diff --git a/op-e2e/e2eutils/disputegame/output_alphabet_helper.go b/op-e2e/e2eutils/disputegame/output_alphabet_helper.go index bcdcc68970cb4..c5d877c2d2649 100644 --- a/op-e2e/e2eutils/disputegame/output_alphabet_helper.go +++ b/op-e2e/e2eutils/disputegame/output_alphabet_helper.go @@ -24,7 +24,7 @@ func (g *OutputAlphabetGameHelper) StartChallenger( options ...challenger.Option, ) *challenger.Helper { opts := []challenger.Option{ - challenger.WithAlphabet(g.System.RollupEndpoint(l2Node)), + challenger.WithAlphabet(), challenger.WithFactoryAddress(g.FactoryAddr), challenger.WithGameAddress(g.Addr), } @@ -46,8 +46,9 @@ func (g *OutputAlphabetGameHelper) CreateHonestActor(ctx context.Context, l2Node splitDepth := g.SplitDepth(ctx) l1Head := g.GetL1Head(ctx) rollupClient := g.System.RollupClient(l2Node) + l2Client := g.System.NodeClient(l2Node) prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) - correctTrace, err := outputs.NewOutputAlphabetTraceAccessor(logger, metrics.NoopMetrics, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) + correctTrace, err := outputs.NewOutputAlphabetTraceAccessor(logger, metrics.NoopMetrics, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock) g.Require.NoError(err, "Create trace accessor") return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper, contract, correctTrace) } diff --git a/op-e2e/e2eutils/disputegame/output_cannon_helper.go b/op-e2e/e2eutils/disputegame/output_cannon_helper.go index e0eedcadf6322..27c4ef76a61c7 100644 --- a/op-e2e/e2eutils/disputegame/output_cannon_helper.go +++ b/op-e2e/e2eutils/disputegame/output_cannon_helper.go @@ -34,16 +34,9 @@ type OutputCannonGameHelper struct { OutputGameHelper } -func (g *OutputCannonGameHelper) StartChallenger( - ctx context.Context, - l2Node string, - name string, - options ...challenger.Option, -) *challenger.Helper { - rollupEndpoint := g.System.RollupEndpoint(l2Node) - l2Endpoint := g.System.NodeEndpoint(l2Node) +func (g *OutputCannonGameHelper) StartChallenger(ctx context.Context, name string, options ...challenger.Option) *challenger.Helper { opts := []challenger.Option{ - challenger.WithCannon(g.T, g.System.RollupCfg(), g.System.L2Genesis(), rollupEndpoint, l2Endpoint), + challenger.WithCannon(g.T, g.System.RollupCfg(), g.System.L2Genesis()), challenger.WithFactoryAddress(g.FactoryAddr), challenger.WithGameAddress(g.Addr), } @@ -56,9 +49,9 @@ func (g *OutputCannonGameHelper) StartChallenger( } func (g *OutputCannonGameHelper) CreateHonestActor(ctx context.Context, l2Node string, options ...challenger.Option) *OutputHonestHelper { - opts := g.defaultChallengerOptions(l2Node) + opts := g.defaultChallengerOptions() opts = append(opts, options...) - cfg := challenger.NewChallengerConfig(g.T, g.System, opts...) + cfg := challenger.NewChallengerConfig(g.T, g.System, l2Node, opts...) logger := testlog.Logger(g.T, log.LevelInfo).New("role", "HonestHelper", "game", g.Addr) l2Client := g.System.NodeClient(l2Node) @@ -283,9 +276,9 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, g.Require.EqualValues(outputRootClaim.Depth(), splitDepth+1, "outputRootClaim must be the root of an execution game") logger := testlog.Logger(g.T, log.LevelInfo).New("role", "CannonTraceProvider", "game", g.Addr) - opt := g.defaultChallengerOptions(l2Node) + opt := g.defaultChallengerOptions() opt = append(opt, options...) - cfg := challenger.NewChallengerConfig(g.T, g.System, opt...) + cfg := challenger.NewChallengerConfig(g.T, g.System, l2Node, opt...) caller := batching.NewMultiCaller(g.System.NodeClient("l1").Client(), batching.DefaultBatchSize) l2Client := g.System.NodeClient(l2Node) @@ -297,7 +290,7 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, rollupClient := g.System.RollupClient(l2Node) prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) l1Head := g.GetL1Head(ctx) - outputProvider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l1Head, splitDepth, prestateBlock, poststateBlock) + outputProvider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock) var localContext common.Hash selector := split.NewSplitProviderSelector(outputProvider, splitDepth, func(ctx context.Context, depth types.Depth, pre types.Claim, post types.Claim) (types.TraceProvider, error) { @@ -322,9 +315,9 @@ func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, return translatingProvider.Original().(*cannon.CannonTraceProviderForTest), localContext } -func (g *OutputCannonGameHelper) defaultChallengerOptions(l2Node string) []challenger.Option { +func (g *OutputCannonGameHelper) defaultChallengerOptions() []challenger.Option { return []challenger.Option{ - challenger.WithCannon(g.T, g.System.RollupCfg(), g.System.L2Genesis(), g.System.RollupEndpoint(l2Node), g.System.NodeEndpoint(l2Node)), + challenger.WithCannon(g.T, g.System.RollupCfg(), g.System.L2Genesis()), challenger.WithFactoryAddress(g.FactoryAddr), challenger.WithGameAddress(g.Addr), } diff --git a/op-e2e/faultproofs/challenge_preimage_test.go b/op-e2e/faultproofs/challenge_preimage_test.go index 445bcf50c8077..226f85c825c15 100644 --- a/op-e2e/faultproofs/challenge_preimage_test.go +++ b/op-e2e/faultproofs/challenge_preimage_test.go @@ -20,7 +20,7 @@ func TestChallengeLargePreimages_ChallengeFirst(t *testing.T) { disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory.StartChallenger(ctx, "Challenger", - challenger.WithAlphabet(sys.RollupEndpoint("sequencer")), + challenger.WithAlphabet(), challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) preimageHelper := disputeGameFactory.PreimageHelper(ctx) ident := preimageHelper.UploadLargePreimage(ctx, preimage.MinPreimageSize, @@ -38,7 +38,7 @@ func TestChallengeLargePreimages_ChallengeMiddle(t *testing.T) { t.Cleanup(sys.Close) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory.StartChallenger(ctx, "Challenger", - challenger.WithAlphabet(sys.RollupEndpoint("sequencer")), + challenger.WithAlphabet(), challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) preimageHelper := disputeGameFactory.PreimageHelper(ctx) ident := preimageHelper.UploadLargePreimage(ctx, preimage.MinPreimageSize, @@ -56,7 +56,7 @@ func TestChallengeLargePreimages_ChallengeLast(t *testing.T) { t.Cleanup(sys.Close) disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys) disputeGameFactory.StartChallenger(ctx, "Challenger", - challenger.WithAlphabet(sys.RollupEndpoint("sequencer")), + challenger.WithAlphabet(), challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) preimageHelper := disputeGameFactory.PreimageHelper(ctx) ident := preimageHelper.UploadLargePreimage(ctx, preimage.MinPreimageSize, diff --git a/op-e2e/faultproofs/multi_test.go b/op-e2e/faultproofs/multi_test.go index a2a171adc875c..8073c3e7596b2 100644 --- a/op-e2e/faultproofs/multi_test.go +++ b/op-e2e/faultproofs/multi_test.go @@ -26,8 +26,8 @@ func TestMultipleGameTypes(t *testing.T) { // Start a challenger with both cannon and alphabet support gameFactory.StartChallenger(ctx, "TowerDefense", - challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.RollupEndpoint("sequencer"), sys.NodeEndpoint("sequencer")), - challenger.WithAlphabet(sys.RollupEndpoint("sequencer")), + challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg), + challenger.WithAlphabet(), challenger.WithPrivKey(sys.Cfg.Secrets.Alice), ) diff --git a/op-e2e/faultproofs/output_alphabet_test.go b/op-e2e/faultproofs/output_alphabet_test.go index 0d5911dffd691..7893d69e0c475 100644 --- a/op-e2e/faultproofs/output_alphabet_test.go +++ b/op-e2e/faultproofs/output_alphabet_test.go @@ -185,7 +185,7 @@ func TestChallengerCompleteExhaustiveDisputeGame(t *testing.T) { // Start honest challenger game.StartChallenger(ctx, "sequencer", "Challenger", - challenger.WithAlphabet(sys.RollupEndpoint("sequencer")), + challenger.WithAlphabet(), challenger.WithPrivKey(sys.Cfg.Secrets.Alice), // Ensures the challenger responds to all claims before test timeout challenger.WithPollInterval(time.Millisecond*400), diff --git a/op-e2e/faultproofs/output_cannon_test.go b/op-e2e/faultproofs/output_cannon_test.go index 9003f68b1d6ca..27a64d996a7a3 100644 --- a/op-e2e/faultproofs/output_cannon_test.go +++ b/op-e2e/faultproofs/output_cannon_test.go @@ -28,7 +28,7 @@ func TestOutputCannonGame(t *testing.T) { game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 4, common.Hash{0x01}) game.LogGameData(ctx) - game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) game.LogGameData(ctx) @@ -85,7 +85,7 @@ func TestOutputCannon_ChallengeAllZeroClaim(t *testing.T) { game.LogGameData(ctx) claim := game.DisputeLastBlock(ctx) - game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) game.DefendClaim(ctx, claim, func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { if parent.IsBottomGameRoot(ctx) { @@ -123,7 +123,7 @@ func TestOutputCannon_PublishCannonRootClaim(t *testing.T) { game.DisputeLastBlock(ctx) game.LogGameData(ctx) - game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) splitDepth := game.SplitDepth(ctx) game.WaitForClaimAtDepth(ctx, splitDepth+1) @@ -158,7 +158,7 @@ func TestOutputCannonDisputeGame(t *testing.T) { outputClaim := game.DisputeLastBlock(ctx) splitDepth := game.SplitDepth(ctx) - game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) game.DefendClaim( ctx, @@ -193,7 +193,7 @@ func TestOutputCannonDefendStep(t *testing.T) { outputRootClaim := game.DisputeLastBlock(ctx) game.LogGameData(ctx) - game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) @@ -240,7 +240,7 @@ func TestOutputCannonStepWithLargePreimage(t *testing.T) { outputRootClaim := game.DisputeBlock(ctx, l2BlockNumber) game.LogGameData(ctx) - game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) // Wait for the honest challenger to dispute the outputRootClaim. // This creates a root of an execution game that we challenge by @@ -272,7 +272,7 @@ func TestOutputCannonStepWithPreimage(t *testing.T) { outputRootClaim := game.DisputeLastBlock(ctx) game.LogGameData(ctx) - game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) // Wait for the honest challenger to dispute the outputRootClaim. This creates a root of an execution game that we challenge by coercing // a step at a preimage trace index. @@ -325,7 +325,7 @@ func TestOutputCannonStepWithKZGPointEvaluation(t *testing.T) { outputRootClaim := game.DisputeLastBlock(ctx) game.LogGameData(ctx) - game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) // Wait for the honest challenger to dispute the outputRootClaim. This creates a root of an execution game that we challenge by coercing // a step at a preimage trace index. @@ -413,7 +413,7 @@ func TestOutputCannonProposedOutputRootValid(t *testing.T) { game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", 1) correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) - game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) // Now maliciously play the game and it should be impossible to win game.ChallengeClaim(ctx, @@ -462,7 +462,7 @@ func TestOutputCannonPoisonedPostState(t *testing.T) { game.LogGameData(ctx) // Start the honest challenger - game.StartChallenger(ctx, "sequencer", "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) + game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) // Start dishonest challenger that posts correct claims for { @@ -512,7 +512,7 @@ func TestDisputeOutputRootBeyondProposedBlock_ValidOutputRoot(t *testing.T) { game := disputeGameFactory.StartOutputCannonGameWithCorrectRoot(ctx, "sequencer", 1) correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) // Start the honest challenger - game.StartChallenger(ctx, "sequencer", "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) + game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) claim := game.RootClaim(ctx) // Attack the output root @@ -563,7 +563,7 @@ func TestDisputeOutputRootBeyondProposedBlock_InvalidOutputRoot(t *testing.T) { correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) // Start the honest challenger - game.StartChallenger(ctx, "sequencer", "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) + game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) claim := game.RootClaim(ctx) // Wait for the honest challenger to counter the root @@ -614,7 +614,7 @@ func TestDisputeOutputRoot_ChangeClaimedOutputRoot(t *testing.T) { correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) // Start the honest challenger - game.StartChallenger(ctx, "sequencer", "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) + game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) claim := game.RootClaim(ctx) // Wait for the honest challenger to counter the root @@ -704,7 +704,7 @@ func TestInvalidateUnsafeProposal(t *testing.T) { correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) // Start the honest challenger - game.StartChallenger(ctx, "sequencer", "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) + game.StartChallenger(ctx, "Challenger", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.DefendClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { if parent.IsBottomGameRoot(ctx) { @@ -766,7 +766,7 @@ func TestInvalidateProposalForFutureBlock(t *testing.T) { correctTrace := game.CreateHonestActor(ctx, "sequencer", challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) // Start the honest challenger - game.StartChallenger(ctx, "sequencer", "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) + game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob)) game.DefendClaim(ctx, game.RootClaim(ctx), func(parent *disputegame.ClaimHelper) *disputegame.ClaimHelper { if parent.IsBottomGameRoot(ctx) { diff --git a/op-e2e/faultproofs/precompile_test.go b/op-e2e/faultproofs/precompile_test.go index dfd9f5d2c14e1..183d560e614fc 100644 --- a/op-e2e/faultproofs/precompile_test.go +++ b/op-e2e/faultproofs/precompile_test.go @@ -138,10 +138,12 @@ func TestPrecompiles(t *testing.T) { func runCannon(t *testing.T, ctx context.Context, sys *op_e2e.System, inputs utils.LocalGameInputs, l2Node string) { l1Endpoint := sys.NodeEndpoint("l1") l1Beacon := sys.L1BeaconEndpoint() - cannonOpts := challenger.WithCannon(t, sys.RollupCfg(), sys.L2Genesis(), sys.RollupEndpoint(l2Node), sys.NodeEndpoint(l2Node)) + rollupEndpoint := sys.RollupEndpoint("sequencer") + l2Endpoint := sys.NodeEndpoint("sequencer") + cannonOpts := challenger.WithCannon(t, sys.RollupCfg(), sys.L2Genesis()) dir := t.TempDir() proofsDir := filepath.Join(dir, "cannon-proofs") - cfg := config.NewConfig(common.Address{}, l1Endpoint, l1Beacon, dir) + cfg := config.NewConfig(common.Address{}, l1Endpoint, l1Beacon, rollupEndpoint, l2Endpoint, dir) cannonOpts(&cfg) logger := testlog.Logger(t, log.LevelInfo).New("role", "cannon") diff --git a/op-e2e/system_tob_test.go b/op-e2e/system_tob_test.go index 7fa0b2d51f375..7505c35c341a5 100644 --- a/op-e2e/system_tob_test.go +++ b/op-e2e/system_tob_test.go @@ -658,7 +658,7 @@ func TestMixedWithdrawalValidity(t *testing.T) { // Start a challenger to resolve claims and games once the clock expires factoryHelper := disputegame.NewFactoryHelper(t, ctx, sys) factoryHelper.StartChallenger(ctx, "Challenger", - challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.RollupEndpoint("sequencer"), sys.NodeEndpoint("sequencer")), + challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg), challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) } receipt, err = wait.ForReceiptOK(ctx, l1Client, tx.Hash())