Commit 382c1051 authored by Joshua Gutow's avatar Joshua Gutow Committed by GitHub

Merge pull request #8338 from ethereum-optimism/aj/remove-agree-with-proposed

op-challenger: Remove --agree-with-proposed-output option
parents b0424bd5 93454816
...@@ -29,7 +29,6 @@ var ( ...@@ -29,7 +29,6 @@ var (
cannonL2 = "http://example.com:9545" cannonL2 = "http://example.com:9545"
rollupRpc = "http://example.com:8555" rollupRpc = "http://example.com:8555"
alphabetTrace = "abcdefghijz" alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true"
) )
func TestLogLevel(t *testing.T) { func TestLogLevel(t *testing.T) {
...@@ -49,14 +48,14 @@ func TestLogLevel(t *testing.T) { ...@@ -49,14 +48,14 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet)) cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet))
defaultCfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, true, datadir, config.TraceTypeAlphabet) defaultCfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, datadir, config.TraceTypeAlphabet)
// Add in the extra CLI options required when using alphabet trace type // Add in the extra CLI options required when using alphabet trace type
defaultCfg.AlphabetTrace = alphabetTrace defaultCfg.AlphabetTrace = alphabetTrace
require.Equal(t, defaultCfg, cfg) require.Equal(t, defaultCfg, cfg)
} }
func TestDefaultConfigIsValid(t *testing.T) { func TestDefaultConfigIsValid(t *testing.T) {
cfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, true, datadir, config.TraceTypeAlphabet) cfg := config.NewConfig(common.HexToAddress(gameFactoryAddressValue), l1EthRpc, datadir, config.TraceTypeAlphabet)
// Add in options that are required based on the specific trace type // 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 // To avoid needing to specify unused options, these aren't included in the params for NewConfig
cfg.AlphabetTrace = alphabetTrace cfg.AlphabetTrace = alphabetTrace
...@@ -168,24 +167,6 @@ func TestTxManagerFlagsSupported(t *testing.T) { ...@@ -168,24 +167,6 @@ func TestTxManagerFlagsSupported(t *testing.T) {
require.Equal(t, uint64(7), cfg.TxMgrConfig.NumConfirmations) require.Equal(t, uint64(7), cfg.TxMgrConfig.NumConfirmations)
} }
func TestAgreeWithProposedOutput(t *testing.T) {
t.Run("MustBeProvided", func(t *testing.T) {
verifyArgsInvalid(t, "flag agree-with-proposed-output is required", addRequiredArgsExcept(config.TraceTypeAlphabet, "--agree-with-proposed-output"))
})
t.Run("Enabled", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--agree-with-proposed-output"))
require.True(t, cfg.AgreeWithProposedOutput)
})
t.Run("EnabledWithArg", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--agree-with-proposed-output=true"))
require.True(t, cfg.AgreeWithProposedOutput)
})
t.Run("Disabled", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs(config.TraceTypeAlphabet, "--agree-with-proposed-output=false"))
require.False(t, cfg.AgreeWithProposedOutput)
})
}
func TestMaxConcurrency(t *testing.T) { func TestMaxConcurrency(t *testing.T) {
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
expected := uint(345) expected := uint(345)
...@@ -475,11 +456,10 @@ func addRequiredArgsExcept(traceType config.TraceType, name string, optionalArgs ...@@ -475,11 +456,10 @@ func addRequiredArgsExcept(traceType config.TraceType, name string, optionalArgs
func requiredArgs(traceType config.TraceType) map[string]string { func requiredArgs(traceType config.TraceType) map[string]string {
args := map[string]string{ args := map[string]string{
"--agree-with-proposed-output": agreeWithProposedOutput, "--l1-eth-rpc": l1EthRpc,
"--l1-eth-rpc": l1EthRpc, "--game-factory-address": gameFactoryAddressValue,
"--game-factory-address": gameFactoryAddressValue, "--trace-type": traceType.String(),
"--trace-type": traceType.String(), "--datadir": datadir,
"--datadir": datadir,
} }
switch traceType { switch traceType {
case config.TraceTypeAlphabet: case config.TraceTypeAlphabet:
......
...@@ -101,14 +101,13 @@ const ( ...@@ -101,14 +101,13 @@ const (
// This also contains config options for auxiliary services. // This also contains config options for auxiliary services.
// It is used to initialize the challenger. // It is used to initialize the challenger.
type Config struct { type Config struct {
L1EthRpc string // L1 RPC Url L1EthRpc string // L1 RPC Url
GameFactoryAddress common.Address // Address of the dispute game factory GameFactoryAddress common.Address // Address of the dispute game factory
GameAllowlist []common.Address // Allowlist of fault game addresses GameAllowlist []common.Address // Allowlist of fault game addresses
GameWindow time.Duration // Maximum time duration to look for games to progress GameWindow time.Duration // Maximum time duration to look for games to progress
AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output Datadir string // Data Directory
Datadir string // Data Directory MaxConcurrency uint // Maximum number of threads to use when progressing games
MaxConcurrency uint // Maximum number of threads to use when progressing games PollInterval time.Duration // Polling interval for latest-block subscription when using an HTTP RPC provider
PollInterval time.Duration // Polling interval for latest-block subscription when using an HTTP RPC provider
TraceTypes []TraceType // Type of traces supported TraceTypes []TraceType // Type of traces supported
...@@ -137,7 +136,6 @@ type Config struct { ...@@ -137,7 +136,6 @@ type Config struct {
func NewConfig( func NewConfig(
gameFactoryAddress common.Address, gameFactoryAddress common.Address,
l1EthRpc string, l1EthRpc string,
agreeWithProposedOutput bool,
datadir string, datadir string,
supportedTraceTypes ...TraceType, supportedTraceTypes ...TraceType,
) Config { ) Config {
...@@ -147,8 +145,6 @@ func NewConfig( ...@@ -147,8 +145,6 @@ func NewConfig(
MaxConcurrency: uint(runtime.NumCPU()), MaxConcurrency: uint(runtime.NumCPU()),
PollInterval: DefaultPollInterval, PollInterval: DefaultPollInterval,
AgreeWithProposedOutput: agreeWithProposedOutput,
TraceTypes: supportedTraceTypes, TraceTypes: supportedTraceTypes,
TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc, txmgr.DefaultChallengerFlagValues), TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc, txmgr.DefaultChallengerFlagValues),
......
...@@ -21,11 +21,10 @@ var ( ...@@ -21,11 +21,10 @@ var (
validDatadir = "/tmp/data" validDatadir = "/tmp/data"
validCannonL2 = "http://localhost:9545" validCannonL2 = "http://localhost:9545"
validRollupRpc = "http://localhost:8555" validRollupRpc = "http://localhost:8555"
agreeWithProposedOutput = true
) )
func validConfig(traceType TraceType) Config { func validConfig(traceType TraceType) Config {
cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, agreeWithProposedOutput, validDatadir, traceType) cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validDatadir, traceType)
switch traceType { switch traceType {
case TraceTypeAlphabet: case TraceTypeAlphabet:
cfg.AlphabetTrace = validAlphabetTrace cfg.AlphabetTrace = validAlphabetTrace
......
...@@ -50,11 +50,6 @@ var ( ...@@ -50,11 +50,6 @@ var (
Usage: "The trace types to support. Valid options: " + openum.EnumString(config.TraceTypes), Usage: "The trace types to support. Valid options: " + openum.EnumString(config.TraceTypes),
EnvVars: prefixEnvVars("TRACE_TYPE"), EnvVars: prefixEnvVars("TRACE_TYPE"),
} }
AgreeWithProposedOutputFlag = &cli.BoolFlag{
Name: "agree-with-proposed-output",
Usage: "Temporary hardcoded flag if we agree or disagree with the proposed output.",
EnvVars: prefixEnvVars("AGREE_WITH_PROPOSED_OUTPUT"),
}
DatadirFlag = &cli.StringFlag{ DatadirFlag = &cli.StringFlag{
Name: "datadir", Name: "datadir",
Usage: "Directory to store data generated as part of responding to games", Usage: "Directory to store data generated as part of responding to games",
...@@ -146,7 +141,6 @@ var requiredFlags = []cli.Flag{ ...@@ -146,7 +141,6 @@ var requiredFlags = []cli.Flag{
L1EthRpcFlag, L1EthRpcFlag,
FactoryAddressFlag, FactoryAddressFlag,
TraceTypeFlag, TraceTypeFlag,
AgreeWithProposedOutputFlag,
DatadirFlag, DatadirFlag,
} }
...@@ -285,28 +279,27 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { ...@@ -285,28 +279,27 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
} }
return &config.Config{ return &config.Config{
// Required Flags // Required Flags
L1EthRpc: ctx.String(L1EthRpcFlag.Name), L1EthRpc: ctx.String(L1EthRpcFlag.Name),
TraceTypes: traceTypes, TraceTypes: traceTypes,
GameFactoryAddress: gameFactoryAddress, GameFactoryAddress: gameFactoryAddress,
GameAllowlist: allowedGames, GameAllowlist: allowedGames,
GameWindow: ctx.Duration(GameWindowFlag.Name), GameWindow: ctx.Duration(GameWindowFlag.Name),
MaxConcurrency: maxConcurrency, MaxConcurrency: maxConcurrency,
PollInterval: ctx.Duration(HTTPPollInterval.Name), PollInterval: ctx.Duration(HTTPPollInterval.Name),
RollupRpc: ctx.String(RollupRpcFlag.Name), RollupRpc: ctx.String(RollupRpcFlag.Name),
AlphabetTrace: ctx.String(AlphabetFlag.Name), AlphabetTrace: ctx.String(AlphabetFlag.Name),
CannonNetwork: ctx.String(CannonNetworkFlag.Name), CannonNetwork: ctx.String(CannonNetworkFlag.Name),
CannonRollupConfigPath: ctx.String(CannonRollupConfigFlag.Name), CannonRollupConfigPath: ctx.String(CannonRollupConfigFlag.Name),
CannonL2GenesisPath: ctx.String(CannonL2GenesisFlag.Name), CannonL2GenesisPath: ctx.String(CannonL2GenesisFlag.Name),
CannonBin: ctx.String(CannonBinFlag.Name), CannonBin: ctx.String(CannonBinFlag.Name),
CannonServer: ctx.String(CannonServerFlag.Name), CannonServer: ctx.String(CannonServerFlag.Name),
CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name), CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name),
Datadir: ctx.String(DatadirFlag.Name), Datadir: ctx.String(DatadirFlag.Name),
CannonL2: ctx.String(CannonL2Flag.Name), CannonL2: ctx.String(CannonL2Flag.Name),
CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name),
CannonInfoFreq: ctx.Uint(CannonInfoFreqFlag.Name), CannonInfoFreq: ctx.Uint(CannonInfoFreqFlag.Name),
AgreeWithProposedOutput: ctx.Bool(AgreeWithProposedOutputFlag.Name), TxMgrConfig: txMgrConfig,
TxMgrConfig: txMgrConfig, MetricsConfig: metricsConfig,
MetricsConfig: metricsConfig, PprofConfig: pprofConfig,
PprofConfig: pprofConfig,
}, nil }, nil
} }
...@@ -29,24 +29,22 @@ type ClaimLoader interface { ...@@ -29,24 +29,22 @@ type ClaimLoader interface {
} }
type Agent struct { type Agent struct {
metrics metrics.Metricer metrics metrics.Metricer
solver *solver.GameSolver solver *solver.GameSolver
loader ClaimLoader loader ClaimLoader
responder Responder responder Responder
maxDepth int maxDepth int
agreeWithProposedOutput bool log log.Logger
log log.Logger
} }
func NewAgent(m metrics.Metricer, loader ClaimLoader, maxDepth int, trace types.TraceAccessor, responder Responder, agreeWithProposedOutput bool, log log.Logger) *Agent { func NewAgent(m metrics.Metricer, loader ClaimLoader, maxDepth int, trace types.TraceAccessor, responder Responder, log log.Logger) *Agent {
return &Agent{ return &Agent{
metrics: m, metrics: m,
solver: solver.NewGameSolver(maxDepth, trace), solver: solver.NewGameSolver(maxDepth, trace),
loader: loader, loader: loader,
responder: responder, responder: responder,
maxDepth: maxDepth, maxDepth: maxDepth,
agreeWithProposedOutput: agreeWithProposedOutput, log: log,
log: log,
} }
} }
...@@ -90,19 +88,6 @@ func (a *Agent) Act(ctx context.Context) error { ...@@ -90,19 +88,6 @@ func (a *Agent) Act(ctx context.Context) error {
return nil return nil
} }
// shouldResolve returns true if the agent should resolve the game.
// This method will return false if the game is still in progress.
func (a *Agent) shouldResolve(status gameTypes.GameStatus) bool {
expected := gameTypes.GameStatusDefenderWon
if a.agreeWithProposedOutput {
expected = gameTypes.GameStatusChallengerWon
}
if expected != status {
a.log.Warn("Game will be lost", "expected", expected, "actual", status)
}
return expected == status
}
// tryResolve resolves the game if it is in a winning state // tryResolve resolves the game if it is in a winning state
// Returns true if the game is resolvable (regardless of whether it was actually resolved) // Returns true if the game is resolvable (regardless of whether it was actually resolved)
func (a *Agent) tryResolve(ctx context.Context) bool { func (a *Agent) tryResolve(ctx context.Context) bool {
...@@ -114,9 +99,6 @@ func (a *Agent) tryResolve(ctx context.Context) bool { ...@@ -114,9 +99,6 @@ func (a *Agent) tryResolve(ctx context.Context) bool {
if err != nil || status == gameTypes.GameStatusInProgress { if err != nil || status == gameTypes.GameStatusInProgress {
return false return false
} }
if !a.shouldResolve(status) {
return true
}
a.log.Info("Resolving game") a.log.Info("Resolving game")
if err := a.responder.Resolve(ctx); err != nil { if err := a.responder.Resolve(ctx); err != nil {
a.log.Error("Failed to resolve the game", "err", err) a.log.Error("Failed to resolve the game", "err", err)
...@@ -187,6 +169,6 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (types.Game, error) { ...@@ -187,6 +169,6 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (types.Game, error) {
if len(claims) == 0 { if len(claims) == 0 {
return nil, errors.New("no claims") return nil, errors.New("no claims")
} }
game := types.NewGameState(a.agreeWithProposedOutput, claims, uint64(a.maxDepth)) game := types.NewGameState(claims, uint64(a.maxDepth))
return game, nil return game, nil
} }
...@@ -18,62 +18,27 @@ import ( ...@@ -18,62 +18,27 @@ import (
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
) )
// TestShouldResolve tests the resolution logic.
func TestShouldResolve(t *testing.T) {
t.Run("AgreeWithProposedOutput", func(t *testing.T) {
agent, _, _ := setupTestAgent(t, true)
require.False(t, agent.shouldResolve(gameTypes.GameStatusDefenderWon))
require.True(t, agent.shouldResolve(gameTypes.GameStatusChallengerWon))
require.False(t, agent.shouldResolve(gameTypes.GameStatusInProgress))
})
t.Run("DisagreeWithProposedOutput", func(t *testing.T) {
agent, _, _ := setupTestAgent(t, false)
require.True(t, agent.shouldResolve(gameTypes.GameStatusDefenderWon))
require.False(t, agent.shouldResolve(gameTypes.GameStatusChallengerWon))
require.False(t, agent.shouldResolve(gameTypes.GameStatusInProgress))
})
}
func TestDoNotMakeMovesWhenGameIsResolvable(t *testing.T) { func TestDoNotMakeMovesWhenGameIsResolvable(t *testing.T) {
ctx := context.Background() ctx := context.Background()
tests := []struct { tests := []struct {
name string name string
agreeWithProposedOutput bool callResolveStatus gameTypes.GameStatus
callResolveStatus gameTypes.GameStatus
shouldResolve bool
}{ }{
{ {
name: "Agree_Losing", name: "DefenderWon",
agreeWithProposedOutput: true, callResolveStatus: gameTypes.GameStatusDefenderWon,
callResolveStatus: gameTypes.GameStatusDefenderWon,
shouldResolve: false,
},
{
name: "Agree_Winning",
agreeWithProposedOutput: true,
callResolveStatus: gameTypes.GameStatusChallengerWon,
shouldResolve: true,
},
{
name: "Disagree_Losing",
agreeWithProposedOutput: false,
callResolveStatus: gameTypes.GameStatusChallengerWon,
shouldResolve: false,
}, },
{ {
name: "Disagree_Winning", name: "ChallengerWon",
agreeWithProposedOutput: false, callResolveStatus: gameTypes.GameStatusChallengerWon,
callResolveStatus: gameTypes.GameStatusDefenderWon,
shouldResolve: true,
}, },
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
agent, claimLoader, responder := setupTestAgent(t, test.agreeWithProposedOutput) agent, claimLoader, responder := setupTestAgent(t)
responder.callResolveStatus = test.callResolveStatus responder.callResolveStatus = test.callResolveStatus
require.NoError(t, agent.Act(ctx)) require.NoError(t, agent.Act(ctx))
...@@ -81,18 +46,14 @@ func TestDoNotMakeMovesWhenGameIsResolvable(t *testing.T) { ...@@ -81,18 +46,14 @@ func TestDoNotMakeMovesWhenGameIsResolvable(t *testing.T) {
require.Equal(t, 1, responder.callResolveCount, "should check if game is resolvable") require.Equal(t, 1, responder.callResolveCount, "should check if game is resolvable")
require.Equal(t, 1, claimLoader.callCount, "should fetch claims once for resolveClaim") require.Equal(t, 1, claimLoader.callCount, "should fetch claims once for resolveClaim")
if test.shouldResolve { require.EqualValues(t, 1, responder.resolveCount, "should resolve winning game")
require.EqualValues(t, 1, responder.resolveCount, "should resolve winning game")
} else {
require.Zero(t, responder.resolveCount, "should not resolve losing game")
}
}) })
} }
} }
func TestLoadClaimsWhenGameNotResolvable(t *testing.T) { func TestLoadClaimsWhenGameNotResolvable(t *testing.T) {
// Checks that if the game isn't resolvable, that the agent continues on to start checking claims // Checks that if the game isn't resolvable, that the agent continues on to start checking claims
agent, claimLoader, responder := setupTestAgent(t, false) agent, claimLoader, responder := setupTestAgent(t)
responder.callResolveErr = errors.New("game is not resolvable") responder.callResolveErr = errors.New("game is not resolvable")
responder.callResolveClaimErr = errors.New("claim is not resolvable") responder.callResolveClaimErr = errors.New("claim is not resolvable")
depth := 4 depth := 4
...@@ -109,13 +70,13 @@ func TestLoadClaimsWhenGameNotResolvable(t *testing.T) { ...@@ -109,13 +70,13 @@ func TestLoadClaimsWhenGameNotResolvable(t *testing.T) {
require.Zero(t, responder.resolveClaimCount, "should not send resolveClaim") require.Zero(t, responder.resolveClaimCount, "should not send resolveClaim")
} }
func setupTestAgent(t *testing.T, agreeWithProposedOutput bool) (*Agent, *stubClaimLoader, *stubResponder) { func setupTestAgent(t *testing.T) (*Agent, *stubClaimLoader, *stubResponder) {
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
claimLoader := &stubClaimLoader{} claimLoader := &stubClaimLoader{}
depth := 4 depth := 4
provider := alphabet.NewTraceProvider("abcd", uint64(depth)) provider := alphabet.NewTraceProvider("abcd", uint64(depth))
responder := &stubResponder{} responder := &stubResponder{}
agent := NewAgent(metrics.NoopMetrics, claimLoader, depth, trace.NewSimpleTraceAccessor(provider), responder, agreeWithProposedOutput, logger) agent := NewAgent(metrics.NoopMetrics, claimLoader, depth, trace.NewSimpleTraceAccessor(provider), responder, logger)
return agent, claimLoader, responder return agent, claimLoader, responder
} }
......
...@@ -5,7 +5,6 @@ import ( ...@@ -5,7 +5,6 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/responder" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/responder"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
...@@ -30,11 +29,10 @@ type GameInfo interface { ...@@ -30,11 +29,10 @@ type GameInfo interface {
type gameValidator func(ctx context.Context, gameContract *contracts.FaultDisputeGameContract) error type gameValidator func(ctx context.Context, gameContract *contracts.FaultDisputeGameContract) error
type GamePlayer struct { type GamePlayer struct {
act actor act actor
agreeWithProposedOutput bool loader GameInfo
loader GameInfo logger log.Logger
logger log.Logger status gameTypes.GameStatus
status gameTypes.GameStatus
} }
type resourceCreator func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (types.TraceAccessor, gameValidator, error) type resourceCreator func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (types.TraceAccessor, gameValidator, error)
...@@ -43,7 +41,6 @@ func NewGamePlayer( ...@@ -43,7 +41,6 @@ func NewGamePlayer(
ctx context.Context, ctx context.Context,
logger log.Logger, logger log.Logger,
m metrics.Metricer, m metrics.Metricer,
cfg *config.Config,
dir string, dir string,
addr common.Address, addr common.Address,
txMgr txmgr.TxManager, txMgr txmgr.TxManager,
...@@ -65,10 +62,9 @@ func NewGamePlayer( ...@@ -65,10 +62,9 @@ func NewGamePlayer(
logger.Info("Game already resolved", "status", status) logger.Info("Game already resolved", "status", status)
// Game is already complete so skip creating the trace provider, loading game inputs etc. // Game is already complete so skip creating the trace provider, loading game inputs etc.
return &GamePlayer{ return &GamePlayer{
logger: logger, logger: logger,
loader: loader, loader: loader,
agreeWithProposedOutput: cfg.AgreeWithProposedOutput, status: status,
status: status,
// Act function does nothing because the game is already complete // Act function does nothing because the game is already complete
act: func(ctx context.Context) error { act: func(ctx context.Context) error {
return nil return nil
...@@ -95,13 +91,12 @@ func NewGamePlayer( ...@@ -95,13 +91,12 @@ func NewGamePlayer(
return nil, fmt.Errorf("failed to create the responder: %w", err) return nil, fmt.Errorf("failed to create the responder: %w", err)
} }
agent := NewAgent(m, loader, int(gameDepth), accessor, responder, cfg.AgreeWithProposedOutput, logger) agent := NewAgent(m, loader, int(gameDepth), accessor, responder, logger)
return &GamePlayer{ return &GamePlayer{
act: agent.Act, act: agent.Act,
agreeWithProposedOutput: cfg.AgreeWithProposedOutput, loader: loader,
loader: loader, logger: logger,
logger: logger, status: status,
status: status,
}, nil }, nil
} }
...@@ -139,17 +134,7 @@ func (g *GamePlayer) logGameStatus(ctx context.Context, status gameTypes.GameSta ...@@ -139,17 +134,7 @@ func (g *GamePlayer) logGameStatus(ctx context.Context, status gameTypes.GameSta
g.logger.Info("Game info", "claims", claimCount, "status", status) g.logger.Info("Game info", "claims", claimCount, "status", status)
return return
} }
var expectedStatus gameTypes.GameStatus g.logger.Info("Game resolved", "status", status)
if g.agreeWithProposedOutput {
expectedStatus = gameTypes.GameStatusChallengerWon
} else {
expectedStatus = gameTypes.GameStatusDefenderWon
}
if expectedStatus == status {
g.logger.Info("Game won", "status", status)
} else {
g.logger.Error("Game lost", "status", status)
}
} }
type PrestateLoader interface { type PrestateLoader interface {
......
...@@ -22,7 +22,7 @@ var ( ...@@ -22,7 +22,7 @@ var (
) )
func TestProgressGame_LogErrorFromAct(t *testing.T) { func TestProgressGame_LogErrorFromAct(t *testing.T) {
handler, game, actor := setupProgressGameTest(t, true) handler, game, actor := setupProgressGameTest(t)
actor.actErr = errors.New("boom") actor.actErr = errors.New("boom")
status := game.ProgressGame(context.Background()) status := game.ProgressGame(context.Background())
require.Equal(t, gameTypes.GameStatusInProgress, status) require.Equal(t, gameTypes.GameStatusInProgress, status)
...@@ -39,58 +39,36 @@ func TestProgressGame_LogErrorFromAct(t *testing.T) { ...@@ -39,58 +39,36 @@ func TestProgressGame_LogErrorFromAct(t *testing.T) {
func TestProgressGame_LogGameStatus(t *testing.T) { func TestProgressGame_LogGameStatus(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
status gameTypes.GameStatus status gameTypes.GameStatus
agreeWithOutput bool logMsg string
logLevel log.Lvl
logMsg string
}{ }{
{ {
name: "GameLostAsDefender", name: "ChallengerWon",
status: gameTypes.GameStatusChallengerWon, status: gameTypes.GameStatusChallengerWon,
agreeWithOutput: false, logMsg: "Game resolved",
logLevel: log.LvlError,
logMsg: "Game lost",
}, },
{ {
name: "GameLostAsChallenger", name: "DefenderWon",
status: gameTypes.GameStatusDefenderWon, status: gameTypes.GameStatusDefenderWon,
agreeWithOutput: true, logMsg: "Game resolved",
logLevel: log.LvlError,
logMsg: "Game lost",
}, },
{ {
name: "GameWonAsDefender", name: "GameInProgress",
status: gameTypes.GameStatusDefenderWon, status: gameTypes.GameStatusInProgress,
agreeWithOutput: false, logMsg: "Game info",
logLevel: log.LvlInfo,
logMsg: "Game won",
},
{
name: "GameWonAsChallenger",
status: gameTypes.GameStatusChallengerWon,
agreeWithOutput: true,
logLevel: log.LvlInfo,
logMsg: "Game won",
},
{
name: "GameInProgress",
status: gameTypes.GameStatusInProgress,
agreeWithOutput: true,
logLevel: log.LvlInfo,
logMsg: "Game info",
}, },
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
handler, game, gameState := setupProgressGameTest(t, test.agreeWithOutput) handler, game, gameState := setupProgressGameTest(t)
gameState.status = test.status gameState.status = test.status
status := game.ProgressGame(context.Background()) status := game.ProgressGame(context.Background())
require.Equal(t, 1, gameState.callCount, "should perform next actions") require.Equal(t, 1, gameState.callCount, "should perform next actions")
require.Equal(t, test.status, status) require.Equal(t, test.status, status)
errLog := handler.FindLog(test.logLevel, test.logMsg) errLog := handler.FindLog(log.LvlInfo, test.logMsg)
require.NotNil(t, errLog, "should log game result") require.NotNil(t, errLog, "should log game result")
require.Equal(t, test.status, errLog.GetContextValue("status")) require.Equal(t, test.status, errLog.GetContextValue("status"))
}) })
...@@ -100,7 +78,7 @@ func TestProgressGame_LogGameStatus(t *testing.T) { ...@@ -100,7 +78,7 @@ func TestProgressGame_LogGameStatus(t *testing.T) {
func TestDoNotActOnCompleteGame(t *testing.T) { func TestDoNotActOnCompleteGame(t *testing.T) {
for _, status := range []gameTypes.GameStatus{gameTypes.GameStatusChallengerWon, gameTypes.GameStatusDefenderWon} { for _, status := range []gameTypes.GameStatus{gameTypes.GameStatusChallengerWon, gameTypes.GameStatusDefenderWon} {
t.Run(status.String(), func(t *testing.T) { t.Run(status.String(), func(t *testing.T) {
_, game, gameState := setupProgressGameTest(t, true) _, game, gameState := setupProgressGameTest(t)
gameState.status = status gameState.status = status
fetched := game.ProgressGame(context.Background()) fetched := game.ProgressGame(context.Background())
...@@ -152,7 +130,7 @@ func TestValidateAbsolutePrestate(t *testing.T) { ...@@ -152,7 +130,7 @@ func TestValidateAbsolutePrestate(t *testing.T) {
}) })
} }
func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.CapturingHandler, *GamePlayer, *stubGameState) { func setupProgressGameTest(t *testing.T) (*testlog.CapturingHandler, *GamePlayer, *stubGameState) {
logger := testlog.Logger(t, log.LvlDebug) logger := testlog.Logger(t, log.LvlDebug)
handler := &testlog.CapturingHandler{ handler := &testlog.CapturingHandler{
Delegate: logger.GetHandler(), Delegate: logger.GetHandler(),
...@@ -160,10 +138,9 @@ func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.C ...@@ -160,10 +138,9 @@ func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.C
logger.SetHandler(handler) logger.SetHandler(handler)
gameState := &stubGameState{claimCount: 1} gameState := &stubGameState{claimCount: 1}
game := &GamePlayer{ game := &GamePlayer{
act: gameState.Act, act: gameState.Act,
agreeWithProposedOutput: agreeWithProposedRoot, loader: gameState,
loader: gameState, logger: logger,
logger: logger,
} }
return handler, game, gameState return handler, game, gameState
} }
......
...@@ -90,7 +90,7 @@ func registerOutputCannon( ...@@ -90,7 +90,7 @@ func registerOutputCannon(
return accessor, noopValidator, nil return accessor, noopValidator, nil
} }
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator) return NewGamePlayer(ctx, logger, m, dir, game.Proxy, txMgr, client, resourceCreator)
} }
registry.RegisterGameType(outputCannonGameType, playerCreator) registry.RegisterGameType(outputCannonGameType, playerCreator)
} }
...@@ -117,7 +117,7 @@ func registerCannon( ...@@ -117,7 +117,7 @@ func registerCannon(
return trace.NewSimpleTraceAccessor(provider), validator, nil return trace.NewSimpleTraceAccessor(provider), validator, nil
} }
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator) return NewGamePlayer(ctx, logger, m, dir, game.Proxy, txMgr, client, resourceCreator)
} }
registry.RegisterGameType(cannonGameType, playerCreator) registry.RegisterGameType(cannonGameType, playerCreator)
} }
...@@ -138,7 +138,7 @@ func registerAlphabet( ...@@ -138,7 +138,7 @@ func registerAlphabet(
return trace.NewSimpleTraceAccessor(provider), validator, nil return trace.NewSimpleTraceAccessor(provider), validator, nil
} }
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client, resourceCreator) return NewGamePlayer(ctx, logger, m, dir, game.Proxy, txMgr, client, resourceCreator)
} }
registry.RegisterGameType(alphabetGameType, playerCreator) registry.RegisterGameType(alphabetGameType, playerCreator)
} }
...@@ -18,16 +18,24 @@ func NewGameSolver(gameDepth int, trace types.TraceAccessor) *GameSolver { ...@@ -18,16 +18,24 @@ func NewGameSolver(gameDepth int, trace types.TraceAccessor) *GameSolver {
} }
} }
func (s *GameSolver) AgreeWithRootClaim(ctx context.Context, game types.Game) (bool, error) {
return s.claimSolver.agreeWithClaim(ctx, game, game.Claims()[0])
}
func (s *GameSolver) CalculateNextActions(ctx context.Context, game types.Game) ([]types.Action, error) { func (s *GameSolver) CalculateNextActions(ctx context.Context, game types.Game) ([]types.Action, error) {
agreeWithRootClaim, err := s.AgreeWithRootClaim(ctx, game)
if err != nil {
return nil, fmt.Errorf("failed to determine if root claim is correct: %w", err)
}
var errs []error var errs []error
var actions []types.Action var actions []types.Action
for _, claim := range game.Claims() { for _, claim := range game.Claims() {
var action *types.Action var action *types.Action
var err error var err error
if uint64(claim.Depth()) == game.MaxDepth() { if uint64(claim.Depth()) == game.MaxDepth() {
action, err = s.calculateStep(ctx, game, claim) action, err = s.calculateStep(ctx, game, agreeWithRootClaim, claim)
} else { } else {
action, err = s.calculateMove(ctx, game, claim) action, err = s.calculateMove(ctx, game, agreeWithRootClaim, claim)
} }
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
...@@ -41,11 +49,11 @@ func (s *GameSolver) CalculateNextActions(ctx context.Context, game types.Game) ...@@ -41,11 +49,11 @@ func (s *GameSolver) CalculateNextActions(ctx context.Context, game types.Game)
return actions, errors.Join(errs...) return actions, errors.Join(errs...)
} }
func (s *GameSolver) calculateStep(ctx context.Context, game types.Game, claim types.Claim) (*types.Action, error) { func (s *GameSolver) calculateStep(ctx context.Context, game types.Game, agreeWithRootClaim bool, claim types.Claim) (*types.Action, error) {
if claim.Countered { if claim.Countered {
return nil, nil return nil, nil
} }
if game.AgreeWithClaimLevel(claim) { if game.AgreeWithClaimLevel(claim, agreeWithRootClaim) {
return nil, nil return nil, nil
} }
step, err := s.claimSolver.AttemptStep(ctx, game, claim) step, err := s.claimSolver.AttemptStep(ctx, game, claim)
...@@ -65,8 +73,8 @@ func (s *GameSolver) calculateStep(ctx context.Context, game types.Game, claim t ...@@ -65,8 +73,8 @@ func (s *GameSolver) calculateStep(ctx context.Context, game types.Game, claim t
}, nil }, nil
} }
func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, claim types.Claim) (*types.Action, error) { func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, agreeWithRootClaim bool, claim types.Claim) (*types.Action, error) {
if game.AgreeWithClaimLevel(claim) { if game.AgreeWithClaimLevel(claim, agreeWithRootClaim) {
return nil, nil return nil, nil
} }
move, err := s.claimSolver.NextMove(ctx, claim, game) move, err := s.claimSolver.NextMove(ctx, claim, game)
......
...@@ -16,50 +16,32 @@ func TestCalculateNextActions(t *testing.T) { ...@@ -16,50 +16,32 @@ func TestCalculateNextActions(t *testing.T) {
claimBuilder := faulttest.NewAlphabetClaimBuilder(t, maxDepth) claimBuilder := faulttest.NewAlphabetClaimBuilder(t, maxDepth)
tests := []struct { tests := []struct {
name string name string
agreeWithOutputRoot bool rootClaimCorrect bool
rootClaimCorrect bool setupGame func(builder *faulttest.GameBuilder)
setupGame func(builder *faulttest.GameBuilder)
}{ }{
{ {
name: "AttackRootClaim", name: "AttackRootClaim",
agreeWithOutputRoot: true,
setupGame: func(builder *faulttest.GameBuilder) { setupGame: func(builder *faulttest.GameBuilder) {
builder.Seq().ExpectAttack() builder.Seq().ExpectAttack()
}, },
}, },
{
name: "DoNotAttackRootClaimWhenDisagreeWithOutputRoot",
agreeWithOutputRoot: false,
setupGame: func(builder *faulttest.GameBuilder) {},
},
{
// 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",
agreeWithOutputRoot: true,
rootClaimCorrect: true,
setupGame: func(builder *faulttest.GameBuilder) {},
},
{ {
// Note: The fault dispute game contract should prevent a correct root claim from actually being posted // 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. // But for completeness, test we ignore it so we don't get sucked into playing an unwinnable game.
name: "DoNotAttackCorrectRootClaim_DisagreeWithOutputRoot", name: "DoNotAttackCorrectRootClaim_AgreeWithOutputRoot",
agreeWithOutputRoot: false, rootClaimCorrect: true,
rootClaimCorrect: true, setupGame: func(builder *faulttest.GameBuilder) {},
setupGame: func(builder *faulttest.GameBuilder) {},
}, },
{ {
name: "DoNotPerformDuplicateMoves", name: "DoNotPerformDuplicateMoves",
agreeWithOutputRoot: true,
setupGame: func(builder *faulttest.GameBuilder) { setupGame: func(builder *faulttest.GameBuilder) {
// Expected move has already been made. // Expected move has already been made.
builder.Seq().AttackCorrect() builder.Seq().AttackCorrect()
}, },
}, },
{ {
name: "RespondToAllClaimsAtDisagreeingLevel", name: "RespondToAllClaimsAtDisagreeingLevel",
agreeWithOutputRoot: true,
setupGame: func(builder *faulttest.GameBuilder) { setupGame: func(builder *faulttest.GameBuilder) {
honestClaim := builder.Seq().AttackCorrect() honestClaim := builder.Seq().AttackCorrect()
honestClaim.AttackCorrect().ExpectDefend() honestClaim.AttackCorrect().ExpectDefend()
...@@ -71,8 +53,7 @@ func TestCalculateNextActions(t *testing.T) { ...@@ -71,8 +53,7 @@ func TestCalculateNextActions(t *testing.T) {
}, },
}, },
{ {
name: "StepAtMaxDepth", name: "StepAtMaxDepth",
agreeWithOutputRoot: true,
setupGame: func(builder *faulttest.GameBuilder) { setupGame: func(builder *faulttest.GameBuilder) {
lastHonestClaim := builder.Seq(). lastHonestClaim := builder.Seq().
AttackCorrect(). AttackCorrect().
...@@ -83,8 +64,7 @@ func TestCalculateNextActions(t *testing.T) { ...@@ -83,8 +64,7 @@ func TestCalculateNextActions(t *testing.T) {
}, },
}, },
{ {
name: "PoisonedPreState", name: "PoisonedPreState",
agreeWithOutputRoot: true,
setupGame: func(builder *faulttest.GameBuilder) { setupGame: func(builder *faulttest.GameBuilder) {
// A claim hash that has no pre-image // A claim hash that has no pre-image
maliciousStateHash := common.Hash{0x01, 0xaa} maliciousStateHash := common.Hash{0x01, 0xaa}
...@@ -106,7 +86,7 @@ func TestCalculateNextActions(t *testing.T) { ...@@ -106,7 +86,7 @@ func TestCalculateNextActions(t *testing.T) {
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
builder := claimBuilder.GameBuilder(test.agreeWithOutputRoot, test.rootClaimCorrect) builder := claimBuilder.GameBuilder(test.rootClaimCorrect)
test.setupGame(builder) test.setupGame(builder)
game := builder.Game game := builder.Game
for i, claim := range game.Claims() { for i, claim := range game.Claims() {
......
...@@ -160,7 +160,7 @@ func TestAttemptStep(t *testing.T) { ...@@ -160,7 +160,7 @@ func TestAttemptStep(t *testing.T) {
for _, tableTest := range tests { for _, tableTest := range tests {
tableTest := tableTest tableTest := tableTest
t.Run(tableTest.name, func(t *testing.T) { t.Run(tableTest.name, func(t *testing.T) {
builder := claimBuilder.GameBuilder(tableTest.agreeWithOutputRoot, !tableTest.agreeWithOutputRoot) builder := claimBuilder.GameBuilder(!tableTest.agreeWithOutputRoot)
tableTest.setupGame(builder) tableTest.setupGame(builder)
alphabetSolver := newClaimSolver(maxDepth, trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider())) alphabetSolver := newClaimSolver(maxDepth, trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()))
game := builder.Game game := builder.Game
......
...@@ -8,17 +8,15 @@ import ( ...@@ -8,17 +8,15 @@ import (
) )
type GameBuilder struct { type GameBuilder struct {
builder *ClaimBuilder builder *ClaimBuilder
Game types.Game Game types.Game
ExpectedActions []types.Action ExpectedActions []types.Action
agreeWithOutputRoot bool
} }
func (c *ClaimBuilder) GameBuilder(agreeWithOutputRoot bool, rootCorrect bool) *GameBuilder { func (c *ClaimBuilder) GameBuilder(rootCorrect bool) *GameBuilder {
return &GameBuilder{ return &GameBuilder{
builder: c, builder: c,
agreeWithOutputRoot: agreeWithOutputRoot, Game: types.NewGameState([]types.Claim{c.CreateRootClaim(rootCorrect)}, uint64(c.maxDepth)),
Game: types.NewGameState(agreeWithOutputRoot, []types.Claim{c.CreateRootClaim(rootCorrect)}, uint64(c.maxDepth)),
} }
} }
...@@ -45,7 +43,7 @@ func (g *GameBuilder) SeqFrom(claim types.Claim) *GameBuilderSeq { ...@@ -45,7 +43,7 @@ func (g *GameBuilder) SeqFrom(claim types.Claim) *GameBuilderSeq {
func (s *GameBuilderSeq) addClaimToGame(claim *types.Claim) { func (s *GameBuilderSeq) addClaimToGame(claim *types.Claim) {
claim.ContractIndex = len(s.gameBuilder.Game.Claims()) claim.ContractIndex = len(s.gameBuilder.Game.Claims())
claims := append(s.gameBuilder.Game.Claims(), *claim) claims := append(s.gameBuilder.Game.Claims(), *claim)
s.gameBuilder.Game = types.NewGameState(s.gameBuilder.agreeWithOutputRoot, claims, uint64(s.builder.maxDepth)) s.gameBuilder.Game = types.NewGameState(claims, uint64(s.builder.maxDepth))
} }
func (s *GameBuilderSeq) AttackCorrect() *GameBuilderSeq { func (s *GameBuilderSeq) AttackCorrect() *GameBuilderSeq {
......
...@@ -18,7 +18,7 @@ func TestAccessor_UsesSelector(t *testing.T) { ...@@ -18,7 +18,7 @@ func TestAccessor_UsesSelector(t *testing.T) {
provider1 := test.NewAlphabetWithProofProvider(t, int(depth), nil) provider1 := test.NewAlphabetWithProofProvider(t, int(depth), nil)
provider2 := alphabet.NewTraceProvider("qrstuv", depth) provider2 := alphabet.NewTraceProvider("qrstuv", depth)
claim := types.Claim{} claim := types.Claim{}
game := types.NewGameState(true, []types.Claim{claim}, depth) game := types.NewGameState([]types.Claim{claim}, depth)
pos1 := types.NewPositionFromGIndex(big.NewInt(4)) pos1 := types.NewPositionFromGIndex(big.NewInt(4))
pos2 := types.NewPositionFromGIndex(big.NewInt(6)) pos2 := types.NewPositionFromGIndex(big.NewInt(6))
......
...@@ -24,7 +24,7 @@ func TestGenerateProof(t *testing.T) { ...@@ -24,7 +24,7 @@ func TestGenerateProof(t *testing.T) {
input := "starting.json" input := "starting.json"
tempDir := t.TempDir() tempDir := t.TempDir()
dir := filepath.Join(tempDir, "gameDir") dir := filepath.Join(tempDir, "gameDir")
cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", true, tempDir, config.TraceTypeCannon) cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", tempDir, config.TraceTypeCannon)
cfg.CannonAbsolutePreState = "pre.json" cfg.CannonAbsolutePreState = "pre.json"
cfg.CannonBin = "./bin/cannon" cfg.CannonBin = "./bin/cannon"
cfg.CannonServer = "./bin/op-program" cfg.CannonServer = "./bin/op-program"
......
...@@ -314,7 +314,7 @@ func setupAlphabetSplitSelector(t *testing.T) (*alphabet.AlphabetTraceProvider, ...@@ -314,7 +314,7 @@ func setupAlphabetSplitSelector(t *testing.T) (*alphabet.AlphabetTraceProvider,
selector := NewSplitProviderSelector(top, topDepth, bottomCreator) selector := NewSplitProviderSelector(top, topDepth, bottomCreator)
claimBuilder := test.NewAlphabetClaimBuilder(t, topDepth+bottomDepth) claimBuilder := test.NewAlphabetClaimBuilder(t, topDepth+bottomDepth)
gameBuilder := claimBuilder.GameBuilder(true, true) gameBuilder := claimBuilder.GameBuilder(true)
return top, selector, gameBuilder return top, selector, gameBuilder
} }
......
...@@ -9,9 +9,6 @@ import ( ...@@ -9,9 +9,6 @@ import (
) )
var ( var (
// ErrClaimExists is returned when a claim already exists in the game state.
ErrClaimExists = errors.New("claim exists in game state")
// ErrClaimNotFound is returned when a claim does not exist in the game state. // ErrClaimNotFound is returned when a claim does not exist in the game state.
ErrClaimNotFound = errors.New("claim not found in game state") ErrClaimNotFound = errors.New("claim not found in game state")
) )
...@@ -33,7 +30,7 @@ type Game interface { ...@@ -33,7 +30,7 @@ type Game interface {
IsDuplicate(claim Claim) bool IsDuplicate(claim Claim) bool
// AgreeWithClaimLevel returns if the game state agrees with the provided claim level. // AgreeWithClaimLevel returns if the game state agrees with the provided claim level.
AgreeWithClaimLevel(claim Claim) bool AgreeWithClaimLevel(claim Claim, agreeWithRootClaim bool) bool
MaxDepth() uint64 MaxDepth() uint64
} }
...@@ -51,7 +48,6 @@ func computeClaimID(claim Claim) claimID { ...@@ -51,7 +48,6 @@ func computeClaimID(claim Claim) claimID {
// gameState is a struct that represents the state of a dispute game. // gameState is a struct that represents the state of a dispute game.
// The game state implements the [Game] interface. // The game state implements the [Game] interface.
type gameState struct { type gameState struct {
agreeWithProposedOutput bool
// claims is the list of claims in the same order as the contract // claims is the list of claims in the same order as the contract
claims []Claim claims []Claim
claimIDs map[claimID]bool claimIDs map[claimID]bool
...@@ -60,28 +56,27 @@ type gameState struct { ...@@ -60,28 +56,27 @@ type gameState struct {
// NewGameState returns a new game state. // NewGameState returns a new game state.
// The provided [Claim] is used as the root node. // The provided [Claim] is used as the root node.
func NewGameState(agreeWithProposedOutput bool, claims []Claim, depth uint64) *gameState { func NewGameState(claims []Claim, depth uint64) *gameState {
claimIDs := make(map[claimID]bool) claimIDs := make(map[claimID]bool)
for _, claim := range claims { for _, claim := range claims {
claimIDs[computeClaimID(claim)] = true claimIDs[computeClaimID(claim)] = true
} }
return &gameState{ return &gameState{
agreeWithProposedOutput: agreeWithProposedOutput, claims: claims,
claims: claims, claimIDs: claimIDs,
claimIDs: claimIDs, depth: depth,
depth: depth,
} }
} }
// AgreeWithClaimLevel returns if the game state agrees with the provided claim level. // AgreeWithClaimLevel returns if the game state agrees with the provided claim level.
func (g *gameState) AgreeWithClaimLevel(claim Claim) bool { func (g *gameState) AgreeWithClaimLevel(claim Claim, agreeWithRootClaim bool) bool {
isOddLevel := claim.Depth()%2 == 1 isOddLevel := claim.Depth()%2 == 1
// If we agree with the proposed output, we agree with odd levels // If we agree with the proposed output, we agree with odd levels
// If we disagree with the proposed output, we agree with the root claim level & even levels // If we disagree with the proposed output, we agree with the root claim level & even levels
if g.agreeWithProposedOutput { if agreeWithRootClaim {
return isOddLevel
} else {
return !isOddLevel return !isOddLevel
} else {
return isOddLevel
} }
} }
......
...@@ -51,7 +51,7 @@ func createTestClaims() (Claim, Claim, Claim, Claim) { ...@@ -51,7 +51,7 @@ func createTestClaims() (Claim, Claim, Claim, Claim) {
func TestIsDuplicate(t *testing.T) { func TestIsDuplicate(t *testing.T) {
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(false, []Claim{root, top}, testMaxDepth) g := NewGameState([]Claim{root, top}, testMaxDepth)
// Root + Top should be duplicates // Root + Top should be duplicates
require.True(t, g.IsDuplicate(root)) require.True(t, g.IsDuplicate(root))
...@@ -66,7 +66,7 @@ func TestGame_Claims(t *testing.T) { ...@@ -66,7 +66,7 @@ func TestGame_Claims(t *testing.T) {
// Setup the game state. // Setup the game state.
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
expected := []Claim{root, top, middle, bottom} expected := []Claim{root, top, middle, bottom}
g := NewGameState(false, expected, testMaxDepth) g := NewGameState(expected, testMaxDepth)
// Validate claim pairs. // Validate claim pairs.
actual := g.Claims() actual := g.Claims()
...@@ -111,7 +111,7 @@ func TestGame_DefendsParent(t *testing.T) { ...@@ -111,7 +111,7 @@ func TestGame_DefendsParent(t *testing.T) {
}, },
{ {
name: "RootDoesntDefend", name: "RootDoesntDefend",
game: NewGameState(false, []Claim{ game: NewGameState([]Claim{
{ {
ClaimData: ClaimData{ ClaimData: ClaimData{
Position: NewPositionFromGIndex(big.NewInt(0)), Position: NewPositionFromGIndex(big.NewInt(0)),
...@@ -145,5 +145,5 @@ func buildGameWithClaim(claimGIndex *big.Int, parentGIndex *big.Int) *gameState ...@@ -145,5 +145,5 @@ func buildGameWithClaim(claimGIndex *big.Int, parentGIndex *big.Int) *gameState
ContractIndex: 1, ContractIndex: 1,
ParentContractIndex: 0, ParentContractIndex: 0,
} }
return NewGameState(false, []Claim{parentClaim, claim}, testMaxDepth) return NewGameState([]Claim{parentClaim, claim}, testMaxDepth)
} }
...@@ -54,12 +54,6 @@ func WithPrivKey(key *ecdsa.PrivateKey) Option { ...@@ -54,12 +54,6 @@ func WithPrivKey(key *ecdsa.PrivateKey) Option {
} }
} }
func WithAgreeProposedOutput(agree bool) Option {
return func(c *config.Config) {
c.AgreeWithProposedOutput = agree
}
}
func WithAlphabet(alphabet string) Option { func WithAlphabet(alphabet string) Option {
return func(c *config.Config) { return func(c *config.Config) {
c.TraceTypes = append(c.TraceTypes, config.TraceTypeAlphabet) c.TraceTypes = append(c.TraceTypes, config.TraceTypeAlphabet)
...@@ -144,7 +138,7 @@ func NewChallenger(t *testing.T, ctx context.Context, l1Endpoint string, name st ...@@ -144,7 +138,7 @@ func NewChallenger(t *testing.T, ctx context.Context, l1Endpoint string, name st
func NewChallengerConfig(t *testing.T, l1Endpoint string, options ...Option) *config.Config { func NewChallengerConfig(t *testing.T, l1Endpoint string, options ...Option) *config.Config {
// Use the NewConfig method to ensure we pick up any defaults that are set. // Use the NewConfig method to ensure we pick up any defaults that are set.
cfg := config.NewConfig(common.Address{}, l1Endpoint, true, t.TempDir()) cfg := config.NewConfig(common.Address{}, l1Endpoint, t.TempDir())
cfg.TxMgrConfig.NumConfirmations = 1 cfg.TxMgrConfig.NumConfirmations = 1
cfg.TxMgrConfig.ReceiptQueryInterval = 1 * time.Second cfg.TxMgrConfig.ReceiptQueryInterval = 1 * time.Second
if cfg.MaxConcurrency > 4 { if cfg.MaxConcurrency > 4 {
......
...@@ -16,10 +16,7 @@ func (g *AlphabetGameHelper) StartChallenger(ctx context.Context, l1Endpoint str ...@@ -16,10 +16,7 @@ func (g *AlphabetGameHelper) StartChallenger(ctx context.Context, l1Endpoint str
opts := []challenger.Option{ opts := []challenger.Option{
challenger.WithFactoryAddress(g.factoryAddr), challenger.WithFactoryAddress(g.factoryAddr),
challenger.WithGameAddress(g.addr), challenger.WithGameAddress(g.addr),
// By default the challenger agrees with the root claim (thus disagrees with the proposed output)
// This can be overridden by passing in options
challenger.WithAlphabet(g.claimedAlphabet), challenger.WithAlphabet(g.claimedAlphabet),
challenger.WithAgreeProposedOutput(false),
} }
opts = append(opts, options...) opts = append(opts, options...)
c := challenger.NewChallenger(g.t, ctx, l1Endpoint, name, opts...) c := challenger.NewChallenger(g.t, ctx, l1Endpoint, name, opts...)
......
...@@ -43,17 +43,20 @@ func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration { ...@@ -43,17 +43,20 @@ func (g *FaultGameHelper) GameDuration(ctx context.Context) time.Duration {
// This does not check that the number of claims is exactly the specified count to avoid intermittent failures // This does not check that the number of claims is exactly the specified count to avoid intermittent failures
// where a challenger posts an additional claim before this method sees the number of claims it was waiting for. // where a challenger posts an additional claim before this method sees the number of claims it was waiting for.
func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) { func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
ctx, cancel := context.WithTimeout(ctx, defaultTimeout) timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel() defer cancel()
err := wait.For(ctx, time.Second, func() (bool, error) { err := wait.For(timedCtx, time.Second, func() (bool, error) {
actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx}) actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: timedCtx})
if err != nil { if err != nil {
return false, err return false, err
} }
g.t.Log("Waiting for claim count", "current", actual, "expected", count, "game", g.addr) g.t.Log("Waiting for claim count", "current", actual, "expected", count, "game", g.addr)
return actual.Cmp(big.NewInt(count)) >= 0, nil return actual.Cmp(big.NewInt(count)) >= 0, nil
}) })
g.require.NoErrorf(err, "Did not find expected claim count %v", count) if err != nil {
g.LogGameData(ctx)
g.require.NoErrorf(err, "Did not find expected claim count %v", count)
}
} }
type ContractClaim struct { type ContractClaim struct {
......
...@@ -94,13 +94,10 @@ func TestChallengerCompleteDisputeGame(t *testing.T) { ...@@ -94,13 +94,10 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
gameDuration := game.GameDuration(ctx) gameDuration := game.GameDuration(ctx)
game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Defender", game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Defender",
challenger.WithAgreeProposedOutput(false),
challenger.WithPrivKey(sys.Cfg.Secrets.Mallory), challenger.WithPrivKey(sys.Cfg.Secrets.Mallory),
) )
game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Challenger", game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Challenger",
// Agree with the proposed output, so disagree with the root claim
challenger.WithAgreeProposedOutput(true),
challenger.WithAlphabet(test.otherAlphabet), challenger.WithAlphabet(test.otherAlphabet),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice), challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
) )
...@@ -137,7 +134,6 @@ func TestChallengerCompleteExhaustiveDisputeGame(t *testing.T) { ...@@ -137,7 +134,6 @@ func TestChallengerCompleteExhaustiveDisputeGame(t *testing.T) {
// Start honest challenger // Start honest challenger
game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Challenger", game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Challenger",
challenger.WithAgreeProposedOutput(!isRootCorrect),
challenger.WithAlphabet(disputegame.CorrectAlphabet), challenger.WithAlphabet(disputegame.CorrectAlphabet),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice), challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
// Ensures the challenger responds to all claims before test timeout // Ensures the challenger responds to all claims before test timeout
......
...@@ -38,8 +38,6 @@ func TestCannonDisputeGame(t *testing.T) { ...@@ -38,8 +38,6 @@ func TestCannonDisputeGame(t *testing.T) {
game.LogGameData(ctx) game.LogGameData(ctx)
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("l1"), sys.NodeEndpoint("sequencer"), "Challenger", game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("l1"), sys.NodeEndpoint("sequencer"), "Challenger",
// Agree with the proposed output, so disagree with the root claim
challenger.WithAgreeProposedOutput(true),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice), challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
) )
...@@ -78,8 +76,6 @@ func TestCannonDefendStep(t *testing.T) { ...@@ -78,8 +76,6 @@ func TestCannonDefendStep(t *testing.T) {
l1Endpoint := sys.NodeEndpoint("l1") l1Endpoint := sys.NodeEndpoint("l1")
l2Endpoint := sys.NodeEndpoint("sequencer") l2Endpoint := sys.NodeEndpoint("sequencer")
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, "Challenger", game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, "Challenger",
// Agree with the proposed output, so disagree with the root claim
challenger.WithAgreeProposedOutput(true),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice), challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
) )
...@@ -214,8 +210,6 @@ func TestCannonPoisonedPostState(t *testing.T) { ...@@ -214,8 +210,6 @@ func TestCannonPoisonedPostState(t *testing.T) {
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, "Honest", game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, "Honest",
// Agree with the proposed output, so disagree with the root claim
challenger.WithAgreeProposedOutput(true),
challenger.WithPrivKey(sys.Cfg.Secrets.Bob), challenger.WithPrivKey(sys.Cfg.Secrets.Bob),
) )
...@@ -272,8 +266,6 @@ func TestCannonChallengeWithCorrectRoot(t *testing.T) { ...@@ -272,8 +266,6 @@ func TestCannonChallengeWithCorrectRoot(t *testing.T) {
game.LogGameData(ctx) game.LogGameData(ctx)
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, "Challenger", game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, "Challenger",
// Agree with the proposed output, so disagree with the root claim
challenger.WithAgreeProposedOutput(true),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice), challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
) )
......
...@@ -27,7 +27,6 @@ func TestMultipleCannonGames(t *testing.T) { ...@@ -27,7 +27,6 @@ func TestMultipleCannonGames(t *testing.T) {
challenger := gameFactory.StartChallenger(ctx, sys.NodeEndpoint("l1"), "TowerDefense", challenger := gameFactory.StartChallenger(ctx, sys.NodeEndpoint("l1"), "TowerDefense",
challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("sequencer")), challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("sequencer")),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice), challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
challenger.WithAgreeProposedOutput(true),
) )
game1 := gameFactory.StartCannonGame(ctx, common.Hash{0x01, 0xaa}) game1 := gameFactory.StartCannonGame(ctx, common.Hash{0x01, 0xaa})
...@@ -88,7 +87,6 @@ func TestMultipleGameTypes(t *testing.T) { ...@@ -88,7 +87,6 @@ func TestMultipleGameTypes(t *testing.T) {
challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("sequencer")), challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("sequencer")),
challenger.WithAlphabet(disputegame.CorrectAlphabet), challenger.WithAlphabet(disputegame.CorrectAlphabet),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice), challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
challenger.WithAgreeProposedOutput(true),
) )
game1 := gameFactory.StartCannonGame(ctx, common.Hash{0x01, 0xaa}) game1 := gameFactory.StartCannonGame(ctx, common.Hash{0x01, 0xaa})
......
...@@ -27,8 +27,6 @@ func TestOutputCannonGame(t *testing.T) { ...@@ -27,8 +27,6 @@ func TestOutputCannonGame(t *testing.T) {
game.LogGameData(ctx) game.LogGameData(ctx)
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, rollupEndpoint, l1Endpoint, l2Endpoint, "Challenger", game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, rollupEndpoint, l1Endpoint, l2Endpoint, "Challenger",
// Agree with the proposed output, so disagree with the root claim
challenger.WithAgreeProposedOutput(true),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice), challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
) )
......
...@@ -57,8 +57,6 @@ func setupDisputeGameForInvalidOutputRoot(t *testing.T, outputRoot common.Hash) ...@@ -57,8 +57,6 @@ func setupDisputeGameForInvalidOutputRoot(t *testing.T, outputRoot common.Hash)
// Start the honest challenger // Start the honest challenger
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, "Defender", game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, l1Endpoint, l2Endpoint, "Defender",
// Disagree with the proposed output, so agree with the (correct) root claim
challenger.WithAgreeProposedOutput(false),
challenger.WithPrivKey(sys.Cfg.Secrets.Mallory), challenger.WithPrivKey(sys.Cfg.Secrets.Mallory),
) )
return sys, l1Client, game, correctTrace return sys, l1Client, game, correctTrace
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment