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 (
cannonL2 = "http://example.com:9545"
rollupRpc = "http://example.com:8555"
alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true"
)
func TestLogLevel(t *testing.T) {
......@@ -49,14 +48,14 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
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
defaultCfg.AlphabetTrace = alphabetTrace
require.Equal(t, defaultCfg, cfg)
}
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
// To avoid needing to specify unused options, these aren't included in the params for NewConfig
cfg.AlphabetTrace = alphabetTrace
......@@ -168,24 +167,6 @@ func TestTxManagerFlagsSupported(t *testing.T) {
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) {
t.Run("Valid", func(t *testing.T) {
expected := uint(345)
......@@ -475,11 +456,10 @@ func addRequiredArgsExcept(traceType config.TraceType, name string, optionalArgs
func requiredArgs(traceType config.TraceType) map[string]string {
args := map[string]string{
"--agree-with-proposed-output": agreeWithProposedOutput,
"--l1-eth-rpc": l1EthRpc,
"--game-factory-address": gameFactoryAddressValue,
"--trace-type": traceType.String(),
"--datadir": datadir,
"--l1-eth-rpc": l1EthRpc,
"--game-factory-address": gameFactoryAddressValue,
"--trace-type": traceType.String(),
"--datadir": datadir,
}
switch traceType {
case config.TraceTypeAlphabet:
......
......@@ -101,14 +101,13 @@ const (
// This also contains config options for auxiliary services.
// It is used to initialize the challenger.
type Config struct {
L1EthRpc string // L1 RPC Url
GameFactoryAddress common.Address // Address of the dispute game factory
GameAllowlist []common.Address // Allowlist of fault game addresses
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
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
L1EthRpc string // L1 RPC Url
GameFactoryAddress common.Address // Address of the dispute game factory
GameAllowlist []common.Address // Allowlist of fault game addresses
GameWindow time.Duration // Maximum time duration to look for games to progress
Datadir string // Data Directory
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
TraceTypes []TraceType // Type of traces supported
......@@ -137,7 +136,6 @@ type Config struct {
func NewConfig(
gameFactoryAddress common.Address,
l1EthRpc string,
agreeWithProposedOutput bool,
datadir string,
supportedTraceTypes ...TraceType,
) Config {
......@@ -147,8 +145,6 @@ func NewConfig(
MaxConcurrency: uint(runtime.NumCPU()),
PollInterval: DefaultPollInterval,
AgreeWithProposedOutput: agreeWithProposedOutput,
TraceTypes: supportedTraceTypes,
TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc, txmgr.DefaultChallengerFlagValues),
......
......@@ -21,11 +21,10 @@ var (
validDatadir = "/tmp/data"
validCannonL2 = "http://localhost:9545"
validRollupRpc = "http://localhost:8555"
agreeWithProposedOutput = true
)
func validConfig(traceType TraceType) Config {
cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, agreeWithProposedOutput, validDatadir, traceType)
cfg := NewConfig(validGameFactoryAddress, validL1EthRpc, validDatadir, traceType)
switch traceType {
case TraceTypeAlphabet:
cfg.AlphabetTrace = validAlphabetTrace
......
......@@ -50,11 +50,6 @@ var (
Usage: "The trace types to support. Valid options: " + openum.EnumString(config.TraceTypes),
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{
Name: "datadir",
Usage: "Directory to store data generated as part of responding to games",
......@@ -146,7 +141,6 @@ var requiredFlags = []cli.Flag{
L1EthRpcFlag,
FactoryAddressFlag,
TraceTypeFlag,
AgreeWithProposedOutputFlag,
DatadirFlag,
}
......@@ -285,28 +279,27 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
}
return &config.Config{
// Required Flags
L1EthRpc: ctx.String(L1EthRpcFlag.Name),
TraceTypes: traceTypes,
GameFactoryAddress: gameFactoryAddress,
GameAllowlist: allowedGames,
GameWindow: ctx.Duration(GameWindowFlag.Name),
MaxConcurrency: maxConcurrency,
PollInterval: ctx.Duration(HTTPPollInterval.Name),
RollupRpc: ctx.String(RollupRpcFlag.Name),
AlphabetTrace: ctx.String(AlphabetFlag.Name),
CannonNetwork: ctx.String(CannonNetworkFlag.Name),
CannonRollupConfigPath: ctx.String(CannonRollupConfigFlag.Name),
CannonL2GenesisPath: ctx.String(CannonL2GenesisFlag.Name),
CannonBin: ctx.String(CannonBinFlag.Name),
CannonServer: ctx.String(CannonServerFlag.Name),
CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name),
Datadir: ctx.String(DatadirFlag.Name),
CannonL2: ctx.String(CannonL2Flag.Name),
CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name),
CannonInfoFreq: ctx.Uint(CannonInfoFreqFlag.Name),
AgreeWithProposedOutput: ctx.Bool(AgreeWithProposedOutputFlag.Name),
TxMgrConfig: txMgrConfig,
MetricsConfig: metricsConfig,
PprofConfig: pprofConfig,
L1EthRpc: ctx.String(L1EthRpcFlag.Name),
TraceTypes: traceTypes,
GameFactoryAddress: gameFactoryAddress,
GameAllowlist: allowedGames,
GameWindow: ctx.Duration(GameWindowFlag.Name),
MaxConcurrency: maxConcurrency,
PollInterval: ctx.Duration(HTTPPollInterval.Name),
RollupRpc: ctx.String(RollupRpcFlag.Name),
AlphabetTrace: ctx.String(AlphabetFlag.Name),
CannonNetwork: ctx.String(CannonNetworkFlag.Name),
CannonRollupConfigPath: ctx.String(CannonRollupConfigFlag.Name),
CannonL2GenesisPath: ctx.String(CannonL2GenesisFlag.Name),
CannonBin: ctx.String(CannonBinFlag.Name),
CannonServer: ctx.String(CannonServerFlag.Name),
CannonAbsolutePreState: ctx.String(CannonPreStateFlag.Name),
Datadir: ctx.String(DatadirFlag.Name),
CannonL2: ctx.String(CannonL2Flag.Name),
CannonSnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name),
CannonInfoFreq: ctx.Uint(CannonInfoFreqFlag.Name),
TxMgrConfig: txMgrConfig,
MetricsConfig: metricsConfig,
PprofConfig: pprofConfig,
}, nil
}
......@@ -29,24 +29,22 @@ type ClaimLoader interface {
}
type Agent struct {
metrics metrics.Metricer
solver *solver.GameSolver
loader ClaimLoader
responder Responder
maxDepth int
agreeWithProposedOutput bool
log log.Logger
metrics metrics.Metricer
solver *solver.GameSolver
loader ClaimLoader
responder Responder
maxDepth int
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{
metrics: m,
solver: solver.NewGameSolver(maxDepth, trace),
loader: loader,
responder: responder,
maxDepth: maxDepth,
agreeWithProposedOutput: agreeWithProposedOutput,
log: log,
metrics: m,
solver: solver.NewGameSolver(maxDepth, trace),
loader: loader,
responder: responder,
maxDepth: maxDepth,
log: log,
}
}
......@@ -90,19 +88,6 @@ func (a *Agent) Act(ctx context.Context) error {
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
// Returns true if the game is resolvable (regardless of whether it was actually resolved)
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 {
return false
}
if !a.shouldResolve(status) {
return true
}
a.log.Info("Resolving game")
if err := a.responder.Resolve(ctx); err != nil {
a.log.Error("Failed to resolve the game", "err", err)
......@@ -187,6 +169,6 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (types.Game, error) {
if len(claims) == 0 {
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
}
......@@ -18,62 +18,27 @@ import (
"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) {
ctx := context.Background()
tests := []struct {
name string
agreeWithProposedOutput bool
callResolveStatus gameTypes.GameStatus
shouldResolve bool
name string
callResolveStatus gameTypes.GameStatus
}{
{
name: "Agree_Losing",
agreeWithProposedOutput: true,
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: "DefenderWon",
callResolveStatus: gameTypes.GameStatusDefenderWon,
},
{
name: "Disagree_Winning",
agreeWithProposedOutput: false,
callResolveStatus: gameTypes.GameStatusDefenderWon,
shouldResolve: true,
name: "ChallengerWon",
callResolveStatus: gameTypes.GameStatusChallengerWon,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
agent, claimLoader, responder := setupTestAgent(t, test.agreeWithProposedOutput)
agent, claimLoader, responder := setupTestAgent(t)
responder.callResolveStatus = test.callResolveStatus
require.NoError(t, agent.Act(ctx))
......@@ -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, claimLoader.callCount, "should fetch claims once for resolveClaim")
if test.shouldResolve {
require.EqualValues(t, 1, responder.resolveCount, "should resolve winning game")
} else {
require.Zero(t, responder.resolveCount, "should not resolve losing game")
}
require.EqualValues(t, 1, responder.resolveCount, "should resolve winning game")
})
}
}
func TestLoadClaimsWhenGameNotResolvable(t *testing.T) {
// 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.callResolveClaimErr = errors.New("claim is not resolvable")
depth := 4
......@@ -109,13 +70,13 @@ func TestLoadClaimsWhenGameNotResolvable(t *testing.T) {
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)
claimLoader := &stubClaimLoader{}
depth := 4
provider := alphabet.NewTraceProvider("abcd", uint64(depth))
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
}
......
......@@ -5,7 +5,6 @@ import (
"context"
"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/responder"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
......@@ -30,11 +29,10 @@ type GameInfo interface {
type gameValidator func(ctx context.Context, gameContract *contracts.FaultDisputeGameContract) error
type GamePlayer struct {
act actor
agreeWithProposedOutput bool
loader GameInfo
logger log.Logger
status gameTypes.GameStatus
act actor
loader GameInfo
logger log.Logger
status gameTypes.GameStatus
}
type resourceCreator func(addr common.Address, contract *contracts.FaultDisputeGameContract, gameDepth uint64, dir string) (types.TraceAccessor, gameValidator, error)
......@@ -43,7 +41,6 @@ func NewGamePlayer(
ctx context.Context,
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
dir string,
addr common.Address,
txMgr txmgr.TxManager,
......@@ -65,10 +62,9 @@ func NewGamePlayer(
logger.Info("Game already resolved", "status", status)
// Game is already complete so skip creating the trace provider, loading game inputs etc.
return &GamePlayer{
logger: logger,
loader: loader,
agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
status: status,
logger: logger,
loader: loader,
status: status,
// Act function does nothing because the game is already complete
act: func(ctx context.Context) error {
return nil
......@@ -95,13 +91,12 @@ func NewGamePlayer(
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{
act: agent.Act,
agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
loader: loader,
logger: logger,
status: status,
act: agent.Act,
loader: loader,
logger: logger,
status: status,
}, nil
}
......@@ -139,17 +134,7 @@ func (g *GamePlayer) logGameStatus(ctx context.Context, status gameTypes.GameSta
g.logger.Info("Game info", "claims", claimCount, "status", status)
return
}
var expectedStatus gameTypes.GameStatus
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)
}
g.logger.Info("Game resolved", "status", status)
}
type PrestateLoader interface {
......
......@@ -22,7 +22,7 @@ var (
)
func TestProgressGame_LogErrorFromAct(t *testing.T) {
handler, game, actor := setupProgressGameTest(t, true)
handler, game, actor := setupProgressGameTest(t)
actor.actErr = errors.New("boom")
status := game.ProgressGame(context.Background())
require.Equal(t, gameTypes.GameStatusInProgress, status)
......@@ -39,58 +39,36 @@ func TestProgressGame_LogErrorFromAct(t *testing.T) {
func TestProgressGame_LogGameStatus(t *testing.T) {
tests := []struct {
name string
status gameTypes.GameStatus
agreeWithOutput bool
logLevel log.Lvl
logMsg string
name string
status gameTypes.GameStatus
logMsg string
}{
{
name: "GameLostAsDefender",
status: gameTypes.GameStatusChallengerWon,
agreeWithOutput: false,
logLevel: log.LvlError,
logMsg: "Game lost",
name: "ChallengerWon",
status: gameTypes.GameStatusChallengerWon,
logMsg: "Game resolved",
},
{
name: "GameLostAsChallenger",
status: gameTypes.GameStatusDefenderWon,
agreeWithOutput: true,
logLevel: log.LvlError,
logMsg: "Game lost",
name: "DefenderWon",
status: gameTypes.GameStatusDefenderWon,
logMsg: "Game resolved",
},
{
name: "GameWonAsDefender",
status: gameTypes.GameStatusDefenderWon,
agreeWithOutput: false,
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",
name: "GameInProgress",
status: gameTypes.GameStatusInProgress,
logMsg: "Game info",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
handler, game, gameState := setupProgressGameTest(t, test.agreeWithOutput)
handler, game, gameState := setupProgressGameTest(t)
gameState.status = test.status
status := game.ProgressGame(context.Background())
require.Equal(t, 1, gameState.callCount, "should perform next actions")
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.Equal(t, test.status, errLog.GetContextValue("status"))
})
......@@ -100,7 +78,7 @@ func TestProgressGame_LogGameStatus(t *testing.T) {
func TestDoNotActOnCompleteGame(t *testing.T) {
for _, status := range []gameTypes.GameStatus{gameTypes.GameStatusChallengerWon, gameTypes.GameStatusDefenderWon} {
t.Run(status.String(), func(t *testing.T) {
_, game, gameState := setupProgressGameTest(t, true)
_, game, gameState := setupProgressGameTest(t)
gameState.status = status
fetched := game.ProgressGame(context.Background())
......@@ -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)
handler := &testlog.CapturingHandler{
Delegate: logger.GetHandler(),
......@@ -160,10 +138,9 @@ func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.C
logger.SetHandler(handler)
gameState := &stubGameState{claimCount: 1}
game := &GamePlayer{
act: gameState.Act,
agreeWithProposedOutput: agreeWithProposedRoot,
loader: gameState,
logger: logger,
act: gameState.Act,
loader: gameState,
logger: logger,
}
return handler, game, gameState
}
......
......@@ -90,7 +90,7 @@ func registerOutputCannon(
return accessor, noopValidator, nil
}
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)
}
......@@ -117,7 +117,7 @@ func registerCannon(
return trace.NewSimpleTraceAccessor(provider), validator, nil
}
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)
}
......@@ -138,7 +138,7 @@ func registerAlphabet(
return trace.NewSimpleTraceAccessor(provider), validator, nil
}
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)
}
......@@ -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) {
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 actions []types.Action
for _, claim := range game.Claims() {
var action *types.Action
var err error
if uint64(claim.Depth()) == game.MaxDepth() {
action, err = s.calculateStep(ctx, game, claim)
action, err = s.calculateStep(ctx, game, agreeWithRootClaim, claim)
} else {
action, err = s.calculateMove(ctx, game, claim)
action, err = s.calculateMove(ctx, game, agreeWithRootClaim, claim)
}
if err != nil {
errs = append(errs, err)
......@@ -41,11 +49,11 @@ func (s *GameSolver) CalculateNextActions(ctx context.Context, game types.Game)
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 {
return nil, nil
}
if game.AgreeWithClaimLevel(claim) {
if game.AgreeWithClaimLevel(claim, agreeWithRootClaim) {
return nil, nil
}
step, err := s.claimSolver.AttemptStep(ctx, game, claim)
......@@ -65,8 +73,8 @@ func (s *GameSolver) calculateStep(ctx context.Context, game types.Game, claim t
}, nil
}
func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, claim types.Claim) (*types.Action, error) {
if game.AgreeWithClaimLevel(claim) {
func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, agreeWithRootClaim bool, claim types.Claim) (*types.Action, error) {
if game.AgreeWithClaimLevel(claim, agreeWithRootClaim) {
return nil, nil
}
move, err := s.claimSolver.NextMove(ctx, claim, game)
......
......@@ -16,50 +16,32 @@ func TestCalculateNextActions(t *testing.T) {
claimBuilder := faulttest.NewAlphabetClaimBuilder(t, maxDepth)
tests := []struct {
name string
agreeWithOutputRoot bool
rootClaimCorrect bool
setupGame func(builder *faulttest.GameBuilder)
name string
rootClaimCorrect bool
setupGame func(builder *faulttest.GameBuilder)
}{
{
name: "AttackRootClaim",
agreeWithOutputRoot: true,
name: "AttackRootClaim",
setupGame: func(builder *faulttest.GameBuilder) {
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
// But for completeness, test we ignore it so we don't get sucked into playing an unwinnable game.
name: "DoNotAttackCorrectRootClaim_DisagreeWithOutputRoot",
agreeWithOutputRoot: false,
rootClaimCorrect: true,
setupGame: func(builder *faulttest.GameBuilder) {},
name: "DoNotAttackCorrectRootClaim_AgreeWithOutputRoot",
rootClaimCorrect: true,
setupGame: func(builder *faulttest.GameBuilder) {},
},
{
name: "DoNotPerformDuplicateMoves",
agreeWithOutputRoot: true,
name: "DoNotPerformDuplicateMoves",
setupGame: func(builder *faulttest.GameBuilder) {
// Expected move has already been made.
builder.Seq().AttackCorrect()
},
},
{
name: "RespondToAllClaimsAtDisagreeingLevel",
agreeWithOutputRoot: true,
name: "RespondToAllClaimsAtDisagreeingLevel",
setupGame: func(builder *faulttest.GameBuilder) {
honestClaim := builder.Seq().AttackCorrect()
honestClaim.AttackCorrect().ExpectDefend()
......@@ -71,8 +53,7 @@ func TestCalculateNextActions(t *testing.T) {
},
},
{
name: "StepAtMaxDepth",
agreeWithOutputRoot: true,
name: "StepAtMaxDepth",
setupGame: func(builder *faulttest.GameBuilder) {
lastHonestClaim := builder.Seq().
AttackCorrect().
......@@ -83,8 +64,7 @@ func TestCalculateNextActions(t *testing.T) {
},
},
{
name: "PoisonedPreState",
agreeWithOutputRoot: true,
name: "PoisonedPreState",
setupGame: func(builder *faulttest.GameBuilder) {
// A claim hash that has no pre-image
maliciousStateHash := common.Hash{0x01, 0xaa}
......@@ -106,7 +86,7 @@ func TestCalculateNextActions(t *testing.T) {
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
builder := claimBuilder.GameBuilder(test.agreeWithOutputRoot, test.rootClaimCorrect)
builder := claimBuilder.GameBuilder(test.rootClaimCorrect)
test.setupGame(builder)
game := builder.Game
for i, claim := range game.Claims() {
......
......@@ -160,7 +160,7 @@ func TestAttemptStep(t *testing.T) {
for _, tableTest := range tests {
tableTest := tableTest
t.Run(tableTest.name, func(t *testing.T) {
builder := claimBuilder.GameBuilder(tableTest.agreeWithOutputRoot, !tableTest.agreeWithOutputRoot)
builder := claimBuilder.GameBuilder(!tableTest.agreeWithOutputRoot)
tableTest.setupGame(builder)
alphabetSolver := newClaimSolver(maxDepth, trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()))
game := builder.Game
......
......@@ -8,17 +8,15 @@ import (
)
type GameBuilder struct {
builder *ClaimBuilder
Game types.Game
ExpectedActions []types.Action
agreeWithOutputRoot bool
builder *ClaimBuilder
Game types.Game
ExpectedActions []types.Action
}
func (c *ClaimBuilder) GameBuilder(agreeWithOutputRoot bool, rootCorrect bool) *GameBuilder {
func (c *ClaimBuilder) GameBuilder(rootCorrect bool) *GameBuilder {
return &GameBuilder{
builder: c,
agreeWithOutputRoot: agreeWithOutputRoot,
Game: types.NewGameState(agreeWithOutputRoot, []types.Claim{c.CreateRootClaim(rootCorrect)}, uint64(c.maxDepth)),
builder: c,
Game: types.NewGameState([]types.Claim{c.CreateRootClaim(rootCorrect)}, uint64(c.maxDepth)),
}
}
......@@ -45,7 +43,7 @@ func (g *GameBuilder) SeqFrom(claim types.Claim) *GameBuilderSeq {
func (s *GameBuilderSeq) addClaimToGame(claim *types.Claim) {
claim.ContractIndex = len(s.gameBuilder.Game.Claims())
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 {
......
......@@ -18,7 +18,7 @@ func TestAccessor_UsesSelector(t *testing.T) {
provider1 := test.NewAlphabetWithProofProvider(t, int(depth), nil)
provider2 := alphabet.NewTraceProvider("qrstuv", depth)
claim := types.Claim{}
game := types.NewGameState(true, []types.Claim{claim}, depth)
game := types.NewGameState([]types.Claim{claim}, depth)
pos1 := types.NewPositionFromGIndex(big.NewInt(4))
pos2 := types.NewPositionFromGIndex(big.NewInt(6))
......
......@@ -24,7 +24,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", true, tempDir, config.TraceTypeCannon)
cfg := config.NewConfig(common.Address{0xbb}, "http://localhost:8888", tempDir, config.TraceTypeCannon)
cfg.CannonAbsolutePreState = "pre.json"
cfg.CannonBin = "./bin/cannon"
cfg.CannonServer = "./bin/op-program"
......
......@@ -314,7 +314,7 @@ func setupAlphabetSplitSelector(t *testing.T) (*alphabet.AlphabetTraceProvider,
selector := NewSplitProviderSelector(top, topDepth, bottomCreator)
claimBuilder := test.NewAlphabetClaimBuilder(t, topDepth+bottomDepth)
gameBuilder := claimBuilder.GameBuilder(true, true)
gameBuilder := claimBuilder.GameBuilder(true)
return top, selector, gameBuilder
}
......
......@@ -9,9 +9,6 @@ import (
)
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 = errors.New("claim not found in game state")
)
......@@ -33,7 +30,7 @@ type Game interface {
IsDuplicate(claim Claim) bool
// AgreeWithClaimLevel returns if the game state agrees with the provided claim level.
AgreeWithClaimLevel(claim Claim) bool
AgreeWithClaimLevel(claim Claim, agreeWithRootClaim bool) bool
MaxDepth() uint64
}
......@@ -51,7 +48,6 @@ func computeClaimID(claim Claim) claimID {
// gameState is a struct that represents the state of a dispute game.
// The game state implements the [Game] interface.
type gameState struct {
agreeWithProposedOutput bool
// claims is the list of claims in the same order as the contract
claims []Claim
claimIDs map[claimID]bool
......@@ -60,28 +56,27 @@ type gameState struct {
// NewGameState returns a new game state.
// 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)
for _, claim := range claims {
claimIDs[computeClaimID(claim)] = true
}
return &gameState{
agreeWithProposedOutput: agreeWithProposedOutput,
claims: claims,
claimIDs: claimIDs,
depth: depth,
claims: claims,
claimIDs: claimIDs,
depth: depth,
}
}
// 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
// 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 g.agreeWithProposedOutput {
return isOddLevel
} else {
if agreeWithRootClaim {
return !isOddLevel
} else {
return isOddLevel
}
}
......
......@@ -51,7 +51,7 @@ func createTestClaims() (Claim, Claim, Claim, Claim) {
func TestIsDuplicate(t *testing.T) {
root, top, middle, bottom := createTestClaims()
g := NewGameState(false, []Claim{root, top}, testMaxDepth)
g := NewGameState([]Claim{root, top}, testMaxDepth)
// Root + Top should be duplicates
require.True(t, g.IsDuplicate(root))
......@@ -66,7 +66,7 @@ func TestGame_Claims(t *testing.T) {
// Setup the game state.
root, top, middle, bottom := createTestClaims()
expected := []Claim{root, top, middle, bottom}
g := NewGameState(false, expected, testMaxDepth)
g := NewGameState(expected, testMaxDepth)
// Validate claim pairs.
actual := g.Claims()
......@@ -111,7 +111,7 @@ func TestGame_DefendsParent(t *testing.T) {
},
{
name: "RootDoesntDefend",
game: NewGameState(false, []Claim{
game: NewGameState([]Claim{
{
ClaimData: ClaimData{
Position: NewPositionFromGIndex(big.NewInt(0)),
......@@ -145,5 +145,5 @@ func buildGameWithClaim(claimGIndex *big.Int, parentGIndex *big.Int) *gameState
ContractIndex: 1,
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 {
}
}
func WithAgreeProposedOutput(agree bool) Option {
return func(c *config.Config) {
c.AgreeWithProposedOutput = agree
}
}
func WithAlphabet(alphabet string) Option {
return func(c *config.Config) {
c.TraceTypes = append(c.TraceTypes, config.TraceTypeAlphabet)
......@@ -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 {
// 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.ReceiptQueryInterval = 1 * time.Second
if cfg.MaxConcurrency > 4 {
......
......@@ -16,10 +16,7 @@ func (g *AlphabetGameHelper) StartChallenger(ctx context.Context, l1Endpoint str
opts := []challenger.Option{
challenger.WithFactoryAddress(g.factoryAddr),
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.WithAgreeProposedOutput(false),
}
opts = append(opts, options...)
c := challenger.NewChallenger(g.t, ctx, l1Endpoint, name, opts...)
......
......@@ -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
// 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) {
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
err := wait.For(ctx, time.Second, func() (bool, error) {
actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: ctx})
err := wait.For(timedCtx, time.Second, func() (bool, error) {
actual, err := g.game.ClaimDataLen(&bind.CallOpts{Context: timedCtx})
if err != nil {
return false, err
}
g.t.Log("Waiting for claim count", "current", actual, "expected", count, "game", g.addr)
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 {
......
......@@ -94,13 +94,10 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
gameDuration := game.GameDuration(ctx)
game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Defender",
challenger.WithAgreeProposedOutput(false),
challenger.WithPrivKey(sys.Cfg.Secrets.Mallory),
)
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.WithPrivKey(sys.Cfg.Secrets.Alice),
)
......@@ -137,7 +134,6 @@ func TestChallengerCompleteExhaustiveDisputeGame(t *testing.T) {
// Start honest challenger
game.StartChallenger(ctx, sys.NodeEndpoint("l1"), "Challenger",
challenger.WithAgreeProposedOutput(!isRootCorrect),
challenger.WithAlphabet(disputegame.CorrectAlphabet),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
// Ensures the challenger responds to all claims before test timeout
......
......@@ -38,8 +38,6 @@ func TestCannonDisputeGame(t *testing.T) {
game.LogGameData(ctx)
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),
)
......@@ -78,8 +76,6 @@ func TestCannonDefendStep(t *testing.T) {
l1Endpoint := sys.NodeEndpoint("l1")
l2Endpoint := sys.NodeEndpoint("sequencer")
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),
)
......@@ -214,8 +210,6 @@ func TestCannonPoisonedPostState(t *testing.T) {
// Start the honest challenger
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),
)
......@@ -272,8 +266,6 @@ func TestCannonChallengeWithCorrectRoot(t *testing.T) {
game.LogGameData(ctx)
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),
)
......
......@@ -27,7 +27,6 @@ func TestMultipleCannonGames(t *testing.T) {
challenger := gameFactory.StartChallenger(ctx, sys.NodeEndpoint("l1"), "TowerDefense",
challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("sequencer")),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
challenger.WithAgreeProposedOutput(true),
)
game1 := gameFactory.StartCannonGame(ctx, common.Hash{0x01, 0xaa})
......@@ -88,7 +87,6 @@ func TestMultipleGameTypes(t *testing.T) {
challenger.WithCannon(t, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("sequencer")),
challenger.WithAlphabet(disputegame.CorrectAlphabet),
challenger.WithPrivKey(sys.Cfg.Secrets.Alice),
challenger.WithAgreeProposedOutput(true),
)
game1 := gameFactory.StartCannonGame(ctx, common.Hash{0x01, 0xaa})
......
......@@ -27,8 +27,6 @@ func TestOutputCannonGame(t *testing.T) {
game.LogGameData(ctx)
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),
)
......
......@@ -57,8 +57,6 @@ func setupDisputeGameForInvalidOutputRoot(t *testing.T, outputRoot common.Hash)
// Start the honest challenger
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),
)
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