Commit cbedb215 authored by Adrian Sutton's avatar Adrian Sutton

op-challenger: Support tracking multiple alphabet games

parent f09a411e
......@@ -50,17 +50,17 @@ type CannonTraceProvider struct {
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)
if err != nil {
return nil, fmt.Errorf("dial l2 client %v: %w", cfg.CannonL2, err)
}
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 {
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 {
return nil, fmt.Errorf("fetch local game inputs: %w", err)
}
......
......@@ -31,11 +31,6 @@ type FaultDisputeGame struct {
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 {
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 Game struct {
agent Actor
agreeWithProposedOutput bool
caller GameInfo
logger log.Logger
}
func NewGame(
ctx context.Context,
logger log.Logger,
cfg *config.Config,
addr common.Address,
txMgr txmgr.TxManager,
client *ethclient.Client,
) (*Game, error) {
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)
}
gameLogger := logger.New("game", addr)
responder, err := NewFaultResponder(gameLogger, txMgr, addr)
if err != nil {
return nil, fmt.Errorf("failed to create the responder: %w", err)
}
caller, err := NewFaultCallerFromBindings(addr, client, gameLogger)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault contract: %w", err)
}
return &Game{
agent: NewAgent(loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, gameLogger),
agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
caller: caller,
logger: gameLogger,
}, nil
}
func (g *Game) 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, *Game, *stubActor, *stubGameInfo) {
logger := testlog.Logger(t, log.LvlDebug)
handler := &testlog.CapturingHandler{
Delegate: logger.GetHandler(),
}
logger.SetHandler(handler)
actor := &stubActor{}
gameInfo := &stubGameInfo{}
game := &Game{
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,29 +2,82 @@ package fault
import (
"context"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
)
type GameInfo interface {
GetGameStatus(context.Context) (types.GameStatus, error)
LogGameInfo(ctx context.Context)
type gameAgent interface {
ProgressGame(ctx context.Context) bool
}
type gameCreator func(address common.Address) (gameAgent, 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)
}
type gameMonitor struct {
logger log.Logger
source gameSource
createGame gameCreator
fetchBlockNumber blockNumberFetcher
games map[common.Address]gameAgent
}
type Actor interface {
Act(ctx context.Context) error
func newGameMonitor(logger log.Logger, fetchBlockNumber blockNumberFetcher, source gameSource, createGame gameCreator) *gameMonitor {
return &gameMonitor{
logger: logger,
source: source,
createGame: createGame,
fetchBlockNumber: fetchBlockNumber,
games: make(map[common.Address]gameAgent),
}
}
func MonitorGame(ctx context.Context, logger log.Logger, agreeWithProposedOutput bool, actor Actor, caller GameInfo) error {
logger.Info("Monitoring fault dispute game", "agreeWithOutput", agreeWithProposedOutput)
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 err := m.progressGame(ctx, game); err != nil {
m.logger.Error("Error while progressing game", "game", game.Proxy, "err", err)
}
}
return nil
}
func (m *gameMonitor) progressGame(ctx context.Context, gameData FaultDisputeGame) error {
game, ok := m.games[gameData.Proxy]
if !ok {
newGame, err := m.createGame(gameData.Proxy)
if err != nil {
return fmt.Errorf("failed to progress game %v: %w", gameData.Proxy, err)
}
m.games[gameData.Proxy] = newGame
game = newGame
}
game.ProgressGame(ctx)
return nil
}
func (m *gameMonitor) MonitorGames(ctx context.Context) error {
m.logger.Info("Monitoring fault dispute games")
for {
done := progressGame(ctx, logger, agreeWithProposedOutput, actor, caller)
if done {
return nil
err := m.progressGames(ctx)
if err != nil {
m.logger.Error("Failed to progress games", "err", err)
}
select {
case <-time.After(300 * time.Millisecond):
......@@ -34,31 +87,3 @@ func MonitorGame(ctx context.Context, logger log.Logger, agreeWithProposedOutput
}
}
}
// progressGame checks the current state of the game, and attempts to progress it by performing moves, steps or resolving
// Returns true if the game is complete or false if it needs to be monitored further
func progressGame(ctx context.Context, logger log.Logger, agreeWithProposedOutput bool, actor Actor, caller GameInfo) bool {
logger.Trace("Checking if actions are required")
if err := actor.Act(ctx); err != nil {
logger.Error("Error when acting on game", "err", err)
}
if status, err := caller.GetGameStatus(ctx); err != nil {
logger.Warn("Unable to retrieve game status", "err", err)
} else if status != 0 {
var expectedStatus types.GameStatus
if agreeWithProposedOutput {
expectedStatus = types.GameStatusChallengerWon
} else {
expectedStatus = types.GameStatusDefenderWon
}
if expectedStatus == status {
logger.Info("Game won", "status", status)
} else {
logger.Error("Game lost", "status", status)
}
return true
} else {
caller.LogGameInfo(ctx)
}
return false
}
......@@ -2,130 +2,98 @@ package fault
import (
"context"
"errors"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-node/testlog"
)
func TestMonitorExitsWhenContextDone(t *testing.T) {
logger := testlog.Logger(t, log.LvlDebug)
actor := &stubActor{}
gameInfo := &stubGameInfo{}
monitor, _, _ := setupMonitorTest(t)
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := MonitorGame(ctx, logger, true, actor, gameInfo)
err := monitor.MonitorGames(ctx)
require.ErrorIs(t, err, context.Canceled)
}
func TestProgressGameAndLogState(t *testing.T) {
logger, _, actor, gameInfo := setupProgressGameTest(t)
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) {
logger, handler, actor, gameInfo := setupProgressGameTest(t)
actor.err = errors.New("Boom")
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 TestMonitorCreateAndProgressGameAgents(t *testing.T) {
monitor, source, games := setupMonitorTest(t)
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",
},
addr1 := common.Address{0xaa}
addr2 := common.Address{0xbb}
source.games = []FaultDisputeGame{
{
name: "GameWonAsDefender",
status: types.GameStatusDefenderWon,
agreeWithOutput: false,
logLevel: log.LvlInfo,
logMsg: "Game won",
Proxy: addr1,
Timestamp: big.NewInt(9999),
},
{
name: "GameWonAsChallenger",
status: types.GameStatusChallengerWon,
agreeWithOutput: true,
logLevel: log.LvlInfo,
logMsg: "Game won",
Proxy: addr2,
Timestamp: big.NewInt(9999),
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
logger, handler, actor, gameInfo := setupProgressGameTest(t)
gameInfo.status = test.status
done := progressGame(context.Background(), logger, test.agreeWithOutput, actor, gameInfo)
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"))
})
}
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 setupProgressGameTest(t *testing.T) (log.Logger, *testlog.CapturingHandler, *stubActor, *stubGameInfo) {
func setupMonitorTest(t *testing.T) (*gameMonitor, *stubGameSource, *createdGames) {
logger := testlog.Logger(t, log.LvlDebug)
handler := &testlog.CapturingHandler{
Delegate: logger.GetHandler(),
source := &stubGameSource{}
games := &createdGames{
t: t,
created: make(map[common.Address]*stubGame),
}
logger.SetHandler(handler)
actor := &stubActor{}
gameInfo := &stubGameInfo{}
return logger, handler, actor, gameInfo
fetchBlockNum := func(ctx context.Context) (uint64, error) {
return 1234, nil
}
monitor := newGameMonitor(logger, fetchBlockNum, source, games.CreateGame)
return monitor, source, games
}
type stubGameSource struct {
games []FaultDisputeGame
}
type stubActor struct {
callCount int
err error
func (s *stubGameSource) FetchAllGamesAtBlock(ctx context.Context, blockNumber *big.Int) ([]FaultDisputeGame, error) {
return s.games, nil
}
func (a *stubActor) Act(ctx context.Context) error {
a.callCount++
return a.err
type stubGame struct {
addr common.Address
progressCount int
done bool
}
type stubGameInfo struct {
status types.GameStatus
err error
logCount int
func (g *stubGame) ProgressGame(ctx context.Context) bool {
g.progressCount++
return g.done
}
func (s *stubGameInfo) GetGameStatus(ctx context.Context) (types.GameStatus, error) {
return s.status, s.err
type createdGames struct {
t *testing.T
created map[common.Address]*stubGame
}
func (s *stubGameInfo) LogGameInfo(ctx context.Context) {
s.logCount++
func (c *createdGames) CreateGame(addr common.Address) (gameAgent, error) {
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 (
"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/client"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"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/ethclient"
"github.com/ethereum/go-ethereum/log"
)
......@@ -26,10 +24,8 @@ type Service interface {
}
type service struct {
agent *Agent
agreeWithProposedOutput bool
caller *FaultCaller
logger log.Logger
logger log.Logger
monitor *gameMonitor
}
// NewService creates a new Service.
......@@ -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)
}
contract, err := bindings.NewFaultDisputeGameCaller(cfg.GameAddress, client)
factory, err := bindings.NewDisputeGameFactory(cfg.GameFactoryAddress, 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)
}
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)
return nil, fmt.Errorf("failed to bind the fault dispute game factory contract: %w", err)
}
loader := NewGameLoader(factory)
monitor := newGameMonitor(logger, client.BlockNumber, loader, func(addr common.Address) (gameAgent, error) {
return NewGame(ctx, logger, cfg, addr, txMgr, client)
})
return &service{
agent: NewAgent(loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, gameLogger),
agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
caller: caller,
logger: gameLogger,
monitor: monitor,
logger: logger,
}, nil
}
......@@ -123,5 +74,5 @@ func ValidateAbsolutePrestate(ctx context.Context, trace types.TraceProvider, lo
// MonitorGame monitors the fault dispute game and attempts to progress it.
func (s *service) MonitorGame(ctx context.Context) error {
return MonitorGame(ctx, s.logger, s.agreeWithProposedOutput, s.agent, s.caller)
return s.monitor.MonitorGames(ctx) // TODO MonitorGame(ctx, s.logger, s.agreeWithProposedOutput, s.agent, s.caller)
}
......@@ -38,7 +38,7 @@ func (g *CannonGameHelper) CreateHonestActor(ctx context.Context, rollupCfg *rol
}
opts = append(opts, options...)
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")
return &HonestHelper{
......
......@@ -13,7 +13,6 @@ import (
)
func TestCannonMultipleGames(t *testing.T) {
t.Skip("Challenger doesn't yet support multiple games")
InitParallel(t)
ctx := context.Background()
......
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