Commit 07a461c3 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #6894 from ethereum-optimism/aj/cannon-multigame

op-challenger: Initial support for playing multiple games
parents c768aa07 341b179f
...@@ -50,17 +50,17 @@ type CannonTraceProvider struct { ...@@ -50,17 +50,17 @@ type CannonTraceProvider struct {
lastProof *proofData lastProof *proofData
} }
func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller) (*CannonTraceProvider, error) { func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller, gameAddr common.Address) (*CannonTraceProvider, error) {
l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2) l2Client, err := ethclient.DialContext(ctx, cfg.CannonL2)
if err != nil { if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err) return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
} }
defer l2Client.Close() // Not needed after fetching the inputs defer l2Client.Close() // Not needed after fetching the inputs
gameCaller, err := bindings.NewFaultDisputeGameCaller(cfg.GameAddress, l1Client) gameCaller, err := bindings.NewFaultDisputeGameCaller(gameAddr, l1Client)
if err != nil { if err != nil {
return nil, fmt.Errorf("create caller for game %v: %w", cfg.GameAddress, err) return nil, fmt.Errorf("create caller for game %v: %w", gameAddr, err)
} }
localInputs, err := fetchLocalInputs(ctx, cfg.GameAddress, gameCaller, l2Client) localInputs, err := fetchLocalInputs(ctx, gameAddr, gameCaller, l2Client)
if err != nil { if err != nil {
return nil, fmt.Errorf("fetch local game inputs: %w", err) return nil, fmt.Errorf("fetch local game inputs: %w", err)
} }
......
...@@ -31,11 +31,6 @@ type FaultDisputeGame struct { ...@@ -31,11 +31,6 @@ type FaultDisputeGame struct {
Proxy common.Address Proxy common.Address
} }
// GameLoader is a minimal interface for fetching on chain dispute games.
type GameLoader interface {
FetchAllGamesAtBlock(ctx context.Context) ([]FaultDisputeGame, error)
}
type gameLoader struct { type gameLoader struct {
caller MinimalDisputeGameFactoryCaller caller MinimalDisputeGameFactoryCaller
} }
......
package fault
import (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/fault/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
)
type Actor interface {
Act(ctx context.Context) error
}
type GameInfo interface {
GetGameStatus(context.Context) (types.GameStatus, error)
LogGameInfo(ctx context.Context)
}
type GamePlayer struct {
agent Actor
agreeWithProposedOutput bool
caller GameInfo
logger log.Logger
}
func NewGamePlayer(
ctx context.Context,
logger log.Logger,
cfg *config.Config,
addr common.Address,
txMgr txmgr.TxManager,
client *ethclient.Client,
) (*GamePlayer, error) {
logger = logger.New("game", addr)
contract, err := bindings.NewFaultDisputeGameCaller(addr, client)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault dispute game contract: %w", err)
}
loader := NewLoader(contract)
gameDepth, err := loader.FetchGameDepth(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
}
var provider types.TraceProvider
var updater types.OracleUpdater
switch cfg.TraceType {
case config.TraceTypeCannon:
provider, err = cannon.NewTraceProvider(ctx, logger, cfg, client, addr)
if err != nil {
return nil, fmt.Errorf("create cannon trace provider: %w", err)
}
updater, err = cannon.NewOracleUpdater(ctx, logger, txMgr, addr, client)
if err != nil {
return nil, fmt.Errorf("failed to create the cannon updater: %w", err)
}
case config.TraceTypeAlphabet:
provider = alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth)
updater = alphabet.NewOracleUpdater(logger)
default:
return nil, fmt.Errorf("unsupported trace type: %v", cfg.TraceType)
}
if err := ValidateAbsolutePrestate(ctx, provider, loader); err != nil {
return nil, fmt.Errorf("failed to validate absolute prestate: %w", err)
}
responder, err := NewFaultResponder(logger, txMgr, addr)
if err != nil {
return nil, fmt.Errorf("failed to create the responder: %w", err)
}
caller, err := NewFaultCallerFromBindings(addr, client, logger)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault contract: %w", err)
}
return &GamePlayer{
agent: NewAgent(loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, logger),
agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
caller: caller,
logger: logger,
}, nil
}
func (g *GamePlayer) ProgressGame(ctx context.Context) bool {
g.logger.Trace("Checking if actions are required")
if err := g.agent.Act(ctx); err != nil {
g.logger.Error("Error when acting on game", "err", err)
}
if status, err := g.caller.GetGameStatus(ctx); err != nil {
g.logger.Warn("Unable to retrieve game status", "err", err)
} else if status != 0 {
var expectedStatus types.GameStatus
if g.agreeWithProposedOutput {
expectedStatus = types.GameStatusChallengerWon
} else {
expectedStatus = types.GameStatusDefenderWon
}
if expectedStatus == status {
g.logger.Info("Game won", "status", status)
} else {
g.logger.Error("Game lost", "status", status)
}
return true
} else {
g.caller.LogGameInfo(ctx)
}
return false
}
package fault
import (
"context"
"errors"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
func TestProgressGameAndLogState(t *testing.T) {
_, game, actor, gameInfo := setupProgressGameTest(t, true)
done := game.ProgressGame(context.Background())
require.False(t, done, "should not be done")
require.Equal(t, 1, actor.callCount, "should perform next actions")
require.Equal(t, 1, gameInfo.logCount, "should log latest game state")
}
func TestProgressGame_LogErrorFromAct(t *testing.T) {
handler, game, actor, gameInfo := setupProgressGameTest(t, true)
actor.err = errors.New("boom")
done := game.ProgressGame(context.Background())
require.False(t, done, "should not be done")
require.Equal(t, 1, actor.callCount, "should perform next actions")
require.Equal(t, 1, gameInfo.logCount, "should log latest game state")
errLog := handler.FindLog(log.LvlError, "Error when acting on game")
require.NotNil(t, errLog, "should log error")
require.Equal(t, actor.err, errLog.GetContextValue("err"))
}
func TestProgressGame_LogErrorWhenGameLost(t *testing.T) {
tests := []struct {
name string
status types.GameStatus
agreeWithOutput bool
logLevel log.Lvl
logMsg string
}{
{
name: "GameLostAsDefender",
status: types.GameStatusChallengerWon,
agreeWithOutput: false,
logLevel: log.LvlError,
logMsg: "Game lost",
},
{
name: "GameLostAsChallenger",
status: types.GameStatusDefenderWon,
agreeWithOutput: true,
logLevel: log.LvlError,
logMsg: "Game lost",
},
{
name: "GameWonAsDefender",
status: types.GameStatusDefenderWon,
agreeWithOutput: false,
logLevel: log.LvlInfo,
logMsg: "Game won",
},
{
name: "GameWonAsChallenger",
status: types.GameStatusChallengerWon,
agreeWithOutput: true,
logLevel: log.LvlInfo,
logMsg: "Game won",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
handler, game, _, gameInfo := setupProgressGameTest(t, test.agreeWithOutput)
gameInfo.status = test.status
done := game.ProgressGame(context.Background())
require.True(t, done, "should be done")
require.Equal(t, 0, gameInfo.logCount, "should not log latest game state")
errLog := handler.FindLog(test.logLevel, test.logMsg)
require.NotNil(t, errLog, "should log game result")
require.Equal(t, test.status, errLog.GetContextValue("status"))
})
}
}
func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.CapturingHandler, *GamePlayer, *stubActor, *stubGameInfo) {
logger := testlog.Logger(t, log.LvlDebug)
handler := &testlog.CapturingHandler{
Delegate: logger.GetHandler(),
}
logger.SetHandler(handler)
actor := &stubActor{}
gameInfo := &stubGameInfo{}
game := &GamePlayer{
agent: actor,
agreeWithProposedOutput: agreeWithProposedRoot,
caller: gameInfo,
logger: logger,
}
return handler, game, actor, gameInfo
}
type stubActor struct {
callCount int
err error
}
func (a *stubActor) Act(ctx context.Context) error {
a.callCount++
return a.err
}
type stubGameInfo struct {
status types.GameStatus
err error
logCount int
}
func (s *stubGameInfo) GetGameStatus(ctx context.Context) (types.GameStatus, error) {
return s.status, s.err
}
func (s *stubGameInfo) LogGameInfo(ctx context.Context) {
s.logCount++
}
...@@ -2,63 +2,95 @@ package fault ...@@ -2,63 +2,95 @@ package fault
import ( import (
"context" "context"
"fmt"
"math/big"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
) )
type GameInfo interface { type gamePlayer interface {
GetGameStatus(context.Context) (types.GameStatus, error) ProgressGame(ctx context.Context) bool
LogGameInfo(ctx context.Context)
} }
type Actor interface { type playerCreator func(address common.Address) (gamePlayer, error)
Act(ctx context.Context) error type blockNumberFetcher func(ctx context.Context) (uint64, error)
// gameSource loads information about the games available to play
type gameSource interface {
FetchAllGamesAtBlock(ctx context.Context, blockNumber *big.Int) ([]FaultDisputeGame, error)
} }
func MonitorGame(ctx context.Context, logger log.Logger, agreeWithProposedOutput bool, actor Actor, caller GameInfo) error { type gameMonitor struct {
logger.Info("Monitoring fault dispute game", "agreeWithOutput", agreeWithProposedOutput) logger log.Logger
source gameSource
createPlayer playerCreator
fetchBlockNumber blockNumberFetcher
allowedGame common.Address
players map[common.Address]gamePlayer
}
for { func newGameMonitor(logger log.Logger, fetchBlockNumber blockNumberFetcher, allowedGame common.Address, source gameSource, createGame playerCreator) *gameMonitor {
done := progressGame(ctx, logger, agreeWithProposedOutput, actor, caller) return &gameMonitor{
if done { logger: logger,
return nil source: source,
createPlayer: createGame,
fetchBlockNumber: fetchBlockNumber,
allowedGame: allowedGame,
players: make(map[common.Address]gamePlayer),
}
}
func (m *gameMonitor) progressGames(ctx context.Context) error {
blockNum, err := m.fetchBlockNumber(ctx)
if err != nil {
return fmt.Errorf("failed to load current block number: %w", err)
}
games, err := m.source.FetchAllGamesAtBlock(ctx, new(big.Int).SetUint64(blockNum))
if err != nil {
return fmt.Errorf("failed to load games: %w", err)
}
for _, game := range games {
if m.allowedGame != (common.Address{}) && m.allowedGame != game.Proxy {
m.logger.Debug("Skipping game not on allow list", "game", game.Proxy)
continue
} }
select { player, err := m.fetchOrCreateGamePlayer(game)
case <-time.After(300 * time.Millisecond): if err != nil {
// Continue m.logger.Error("Error while progressing game", "game", game.Proxy, "err", err)
case <-ctx.Done(): continue
return ctx.Err()
} }
player.ProgressGame(ctx)
} }
return nil
} }
// progressGame checks the current state of the game, and attempts to progress it by performing moves, steps or resolving func (m *gameMonitor) fetchOrCreateGamePlayer(gameData FaultDisputeGame) (gamePlayer, error) {
// Returns true if the game is complete or false if it needs to be monitored further if player, ok := m.players[gameData.Proxy]; ok {
func progressGame(ctx context.Context, logger log.Logger, agreeWithProposedOutput bool, actor Actor, caller GameInfo) bool { return player, nil
logger.Trace("Checking if actions are required") }
if err := actor.Act(ctx); err != nil { player, err := m.createPlayer(gameData.Proxy)
logger.Error("Error when acting on game", "err", err) if err != nil {
return nil, fmt.Errorf("failed to create game player %v: %w", gameData.Proxy, err)
} }
if status, err := caller.GetGameStatus(ctx); err != nil { m.players[gameData.Proxy] = player
logger.Warn("Unable to retrieve game status", "err", err) return player, nil
} else if status != 0 { }
var expectedStatus types.GameStatus
if agreeWithProposedOutput { func (m *gameMonitor) MonitorGames(ctx context.Context) error {
expectedStatus = types.GameStatusChallengerWon m.logger.Info("Monitoring fault dispute games")
} else {
expectedStatus = types.GameStatusDefenderWon for {
err := m.progressGames(ctx)
if err != nil {
m.logger.Error("Failed to progress games", "err", err)
} }
if expectedStatus == status { select {
logger.Info("Game won", "status", status) case <-time.After(300 * time.Millisecond):
} else { // Continue
logger.Error("Game lost", "status", status) case <-ctx.Done():
return ctx.Err()
} }
return true
} else {
caller.LogGameInfo(ctx)
} }
return false
} }
...@@ -2,130 +2,123 @@ package fault ...@@ -2,130 +2,123 @@ package fault
import ( import (
"context" "context"
"errors" "math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
) )
func TestMonitorExitsWhenContextDone(t *testing.T) { func TestMonitorExitsWhenContextDone(t *testing.T) {
logger := testlog.Logger(t, log.LvlDebug) monitor, _, _ := setupMonitorTest(t, common.Address{})
actor := &stubActor{}
gameInfo := &stubGameInfo{}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
cancel() cancel()
err := MonitorGame(ctx, logger, true, actor, gameInfo) err := monitor.MonitorGames(ctx)
require.ErrorIs(t, err, context.Canceled) require.ErrorIs(t, err, context.Canceled)
} }
func TestProgressGameAndLogState(t *testing.T) { func TestMonitorCreateAndProgressGameAgents(t *testing.T) {
logger, _, actor, gameInfo := setupProgressGameTest(t) monitor, source, games := setupMonitorTest(t, common.Address{})
done := progressGame(context.Background(), logger, true, actor, gameInfo)
require.False(t, done, "should not be done")
require.Equal(t, 1, actor.callCount, "should perform next actions")
require.Equal(t, 1, gameInfo.logCount, "should log latest game state")
}
func TestProgressGame_LogErrorFromAct(t *testing.T) { addr1 := common.Address{0xaa}
logger, handler, actor, gameInfo := setupProgressGameTest(t) addr2 := common.Address{0xbb}
actor.err = errors.New("Boom") source.games = []FaultDisputeGame{
done := progressGame(context.Background(), logger, true, actor, gameInfo)
require.False(t, done, "should not be done")
require.Equal(t, 1, actor.callCount, "should perform next actions")
require.Equal(t, 1, gameInfo.logCount, "should log latest game state")
errLog := handler.FindLog(log.LvlError, "Error when acting on game")
require.NotNil(t, errLog, "should log error")
require.Equal(t, actor.err, errLog.GetContextValue("err"))
}
func TestProgressGame_LogErrorWhenGameLost(t *testing.T) {
tests := []struct {
name string
status types.GameStatus
agreeWithOutput bool
logLevel log.Lvl
logMsg string
}{
{ {
name: "GameLostAsDefender", Proxy: addr1,
status: types.GameStatusChallengerWon, Timestamp: 9999,
agreeWithOutput: false,
logLevel: log.LvlError,
logMsg: "Game lost",
}, },
{ {
name: "GameLostAsChallenger", Proxy: addr2,
status: types.GameStatusDefenderWon, Timestamp: 9999,
agreeWithOutput: true,
logLevel: log.LvlError,
logMsg: "Game lost",
}, },
}
err := monitor.progressGames(context.Background())
require.NoError(t, err)
require.Len(t, games.created, 2, "should create game agents")
require.Contains(t, games.created, addr1)
require.Contains(t, games.created, addr2)
require.Equal(t, 1, games.created[addr1].progressCount)
require.Equal(t, 1, games.created[addr2].progressCount)
// The stub will fail the test if a game is created with the same address multiple times
require.NoError(t, monitor.progressGames(context.Background()), "should only create games once")
require.Equal(t, 2, games.created[addr1].progressCount)
require.Equal(t, 2, games.created[addr2].progressCount)
}
func TestMonitorOnlyCreateSpecifiedGame(t *testing.T) {
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
monitor, source, games := setupMonitorTest(t, addr2)
source.games = []FaultDisputeGame{
{ {
name: "GameWonAsDefender", Proxy: addr1,
status: types.GameStatusDefenderWon, Timestamp: 9999,
agreeWithOutput: false,
logLevel: log.LvlInfo,
logMsg: "Game won",
}, },
{ {
name: "GameWonAsChallenger", Proxy: addr2,
status: types.GameStatusChallengerWon, Timestamp: 9999,
agreeWithOutput: true,
logLevel: log.LvlInfo,
logMsg: "Game won",
}, },
} }
for _, test := range tests {
test := test err := monitor.progressGames(context.Background())
t.Run(test.name, func(t *testing.T) { require.NoError(t, err)
logger, handler, actor, gameInfo := setupProgressGameTest(t)
gameInfo.status = test.status require.Len(t, games.created, 1, "should only create allowed game")
require.Contains(t, games.created, addr2)
done := progressGame(context.Background(), logger, test.agreeWithOutput, actor, gameInfo) require.NotContains(t, games.created, addr1)
require.True(t, done, "should be done") require.Equal(t, 1, games.created[addr2].progressCount)
require.Equal(t, 0, gameInfo.logCount, "should not log latest game state")
errLog := handler.FindLog(test.logLevel, test.logMsg)
require.NotNil(t, errLog, "should log game result")
require.Equal(t, test.status, errLog.GetContextValue("status"))
})
}
} }
func setupProgressGameTest(t *testing.T) (log.Logger, *testlog.CapturingHandler, *stubActor, *stubGameInfo) { func setupMonitorTest(t *testing.T, allowedGame common.Address) (*gameMonitor, *stubGameSource, *createdGames) {
logger := testlog.Logger(t, log.LvlDebug) logger := testlog.Logger(t, log.LvlDebug)
handler := &testlog.CapturingHandler{ source := &stubGameSource{}
Delegate: logger.GetHandler(), games := &createdGames{
t: t,
created: make(map[common.Address]*stubGame),
} }
logger.SetHandler(handler) fetchBlockNum := func(ctx context.Context) (uint64, error) {
actor := &stubActor{} return 1234, nil
gameInfo := &stubGameInfo{} }
return logger, handler, actor, gameInfo monitor := newGameMonitor(logger, fetchBlockNum, allowedGame, source, games.CreateGame)
return monitor, source, games
}
type stubGameSource struct {
games []FaultDisputeGame
} }
type stubActor struct { func (s *stubGameSource) FetchAllGamesAtBlock(ctx context.Context, blockNumber *big.Int) ([]FaultDisputeGame, error) {
callCount int return s.games, nil
err error
} }
func (a *stubActor) Act(ctx context.Context) error { type stubGame struct {
a.callCount++ addr common.Address
return a.err progressCount int
done bool
} }
type stubGameInfo struct { func (g *stubGame) ProgressGame(ctx context.Context) bool {
status types.GameStatus g.progressCount++
err error return g.done
logCount int
} }
func (s *stubGameInfo) GetGameStatus(ctx context.Context) (types.GameStatus, error) { type createdGames struct {
return s.status, s.err t *testing.T
created map[common.Address]*stubGame
} }
func (s *stubGameInfo) LogGameInfo(ctx context.Context) { func (c *createdGames) CreateGame(addr common.Address) (gamePlayer, error) {
s.logCount++ if _, exists := c.created[addr]; exists {
c.t.Fatalf("game %v already exists", addr)
}
game := &stubGame{addr: addr}
c.created[addr] = game
return game, nil
} }
...@@ -7,14 +7,12 @@ import ( ...@@ -7,14 +7,12 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault/alphabet"
"github.com/ethereum-optimism/optimism/op-challenger/fault/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum-optimism/optimism/op-service/txmgr/metrics" "github.com/ethereum-optimism/optimism/op-service/txmgr/metrics"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -26,10 +24,8 @@ type Service interface { ...@@ -26,10 +24,8 @@ type Service interface {
} }
type service struct { type service struct {
agent *Agent logger log.Logger
agreeWithProposedOutput bool monitor *gameMonitor
caller *FaultCaller
logger log.Logger
} }
// NewService creates a new Service. // NewService creates a new Service.
...@@ -44,63 +40,18 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*se ...@@ -44,63 +40,18 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*se
return nil, fmt.Errorf("failed to dial L1: %w", err) return nil, fmt.Errorf("failed to dial L1: %w", err)
} }
contract, err := bindings.NewFaultDisputeGameCaller(cfg.GameAddress, client) factory, err := bindings.NewDisputeGameFactory(cfg.GameFactoryAddress, client)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to bind the fault dispute game contract: %w", err) return nil, fmt.Errorf("failed to bind the fault dispute game factory contract: %w", err)
}
loader := NewLoader(contract)
gameDepth, err := loader.FetchGameDepth(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch the game depth: %w", err)
}
gameDepth = uint64(gameDepth)
var trace types.TraceProvider
var updater types.OracleUpdater
switch cfg.TraceType {
case config.TraceTypeCannon:
trace, err = cannon.NewTraceProvider(ctx, logger, cfg, client)
if err != nil {
return nil, fmt.Errorf("create cannon trace provider: %w", err)
}
updater, err = cannon.NewOracleUpdater(ctx, logger, txMgr, cfg.GameAddress, client)
if err != nil {
return nil, fmt.Errorf("failed to create the cannon updater: %w", err)
}
case config.TraceTypeAlphabet:
trace = alphabet.NewTraceProvider(cfg.AlphabetTrace, gameDepth)
updater = alphabet.NewOracleUpdater(logger)
default:
return nil, fmt.Errorf("unsupported trace type: %v", cfg.TraceType)
}
return newTypedService(ctx, logger, cfg, loader, gameDepth, client, trace, updater, txMgr)
}
// newTypedService creates a new Service from a provided trace provider.
func newTypedService(ctx context.Context, logger log.Logger, cfg *config.Config, loader Loader, gameDepth uint64, client *ethclient.Client, provider types.TraceProvider, updater types.OracleUpdater, txMgr txmgr.TxManager) (*service, error) {
if err := ValidateAbsolutePrestate(ctx, provider, loader); err != nil {
return nil, fmt.Errorf("failed to validate absolute prestate: %w", err)
}
gameLogger := logger.New("game", cfg.GameAddress)
responder, err := NewFaultResponder(gameLogger, txMgr, cfg.GameAddress)
if err != nil {
return nil, fmt.Errorf("failed to create the responder: %w", err)
}
caller, err := NewFaultCallerFromBindings(cfg.GameAddress, client, gameLogger)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault contract: %w", err)
} }
loader := NewGameLoader(factory)
monitor := newGameMonitor(logger, client.BlockNumber, cfg.GameAddress, loader, func(addr common.Address) (gamePlayer, error) {
return NewGamePlayer(ctx, logger, cfg, addr, txMgr, client)
})
return &service{ return &service{
agent: NewAgent(loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, gameLogger), monitor: monitor,
agreeWithProposedOutput: cfg.AgreeWithProposedOutput, logger: logger,
caller: caller,
logger: gameLogger,
}, nil }, nil
} }
...@@ -123,5 +74,5 @@ func ValidateAbsolutePrestate(ctx context.Context, trace types.TraceProvider, lo ...@@ -123,5 +74,5 @@ func ValidateAbsolutePrestate(ctx context.Context, trace types.TraceProvider, lo
// MonitorGame monitors the fault dispute game and attempts to progress it. // MonitorGame monitors the fault dispute game and attempts to progress it.
func (s *service) MonitorGame(ctx context.Context) error { func (s *service) MonitorGame(ctx context.Context) error {
return MonitorGame(ctx, s.logger, s.agreeWithProposedOutput, s.agent, s.caller) return s.monitor.MonitorGames(ctx)
} }
...@@ -38,7 +38,7 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol ...@@ -38,7 +38,7 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol
} }
opts = append(opts, options...) opts = append(opts, options...)
cfg := challenger.NewChallengerConfig(g.t, l1Endpoint, opts...) cfg := challenger.NewChallengerConfig(g.t, l1Endpoint, opts...)
provider, err := cannon.NewTraceProvider(ctx, testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace"), cfg, l1Client) provider, err := cannon.NewTraceProvider(ctx, testlog.Logger(g.t, log.LvlInfo).New("role", "CorrectTrace"), cfg, l1Client, g.addr)
g.require.NoError(err, "create cannon trace provider") g.require.NoError(err, "create cannon trace provider")
return &HonestHelper{ return &HonestHelper{
......
...@@ -12,8 +12,7 @@ import ( ...@@ -12,8 +12,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestCannonMultipleGames(t *testing.T) { func TestMultipleAlphabetGames(t *testing.T) {
t.Skip("Challenger doesn't yet support multiple games")
InitParallel(t) InitParallel(t)
ctx := context.Background() ctx := context.Background()
...@@ -41,6 +40,13 @@ func TestCannonMultipleGames(t *testing.T) { ...@@ -41,6 +40,13 @@ func TestCannonMultipleGames(t *testing.T) {
game2.WaitForClaimCount(ctx, 4) game2.WaitForClaimCount(ctx, 4)
game1.Defend(ctx, 1, common.Hash{0xaa}) game1.Defend(ctx, 1, common.Hash{0xaa})
game1.WaitForClaimCount(ctx, 4) game1.WaitForClaimCount(ctx, 4)
gameDuration := game1.GameDuration(ctx)
sys.TimeTravelClock.AdvanceTime(gameDuration)
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
game1.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game2.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
} }
func TestMultipleCannonGames(t *testing.T) { func TestMultipleCannonGames(t *testing.T) {
......
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