Commit a440f03e authored by Adrian Sutton's avatar Adrian Sutton

op-challenger: Introduce game type registry so we only create games for supported game types.

# Conflicts:
#	op-challenger/game/scheduler/scheduler_test.go
#	op-challenger/game/service.go
parent 4cd7b947
package fault
import (
"context"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/log"
)
var (
cannonGameType = uint8(0)
alphabetGameType = uint8(255)
)
type Registry interface {
RegisterGameType(gameType uint8, creator scheduler.PlayerCreator)
}
func RegisterGameTypes(
registry Registry,
ctx context.Context,
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
txMgr txmgr.TxManager,
client bind.ContractCaller) {
creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, client)
}
switch cfg.TraceType {
case config.TraceTypeCannon:
registry.RegisterGameType(cannonGameType, creator)
case config.TraceTypeOutputCannon:
registry.RegisterGameType(cannonGameType, creator)
case config.TraceTypeAlphabet:
registry.RegisterGameType(alphabetGameType, creator)
}
}
package registry
import (
"errors"
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
)
var (
ErrUnsupportedGameType = errors.New("unsupported game type")
)
type GameTypeRegistry struct {
types map[uint8]scheduler.PlayerCreator
}
func NewGameTypeRegistry() *GameTypeRegistry {
return &GameTypeRegistry{
types: make(map[uint8]scheduler.PlayerCreator),
}
}
func (r *GameTypeRegistry) RegisterGameType(gameType uint8, creator scheduler.PlayerCreator) {
if _, ok := r.types[gameType]; ok {
panic(fmt.Errorf("duplicate creator registered for game type: %v", gameType))
}
r.types[gameType] = creator
}
func (r *GameTypeRegistry) CreatePlayer(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
creator, ok := r.types[game.GameType]
if !ok {
return nil, fmt.Errorf("%w: %v", ErrUnsupportedGameType, game.GameType)
}
return creator(game, dir)
}
package registry
import (
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/stretchr/testify/require"
)
func TestUnknownGameType(t *testing.T) {
registry := NewGameTypeRegistry()
player, err := registry.CreatePlayer(types.GameMetadata{GameType: 0}, "")
require.ErrorIs(t, err, ErrUnsupportedGameType)
require.Nil(t, player)
}
func TestKnownGameType(t *testing.T) {
registry := NewGameTypeRegistry()
expectedPlayer := &test.StubGamePlayer{}
creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return expectedPlayer, nil
}
registry.RegisterGameType(0, creator)
player, err := registry.CreatePlayer(types.GameMetadata{GameType: 0}, "")
require.NoError(t, err)
require.Same(t, expectedPlayer, player)
}
func TestPanicsOnDuplicateGameType(t *testing.T) {
registry := NewGameTypeRegistry()
creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return nil, nil
}
registry.RegisterGameType(0, creator)
require.Panics(t, func() {
registry.RegisterGameType(0, creator)
})
}
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
...@@ -30,7 +31,7 @@ func TestScheduleNewGames(t *testing.T) { ...@@ -30,7 +31,7 @@ func TestScheduleNewGames(t *testing.T) {
players = append(players, j.player) players = append(players, j.player)
} }
for addr, player := range games.created { for addr, player := range games.created {
require.Equal(t, disk.DirForGame(addr), player.dir, "should use allocated directory") require.Equal(t, disk.DirForGame(addr), player.Dir, "should use allocated directory")
require.Containsf(t, players, player, "should have created a job for player %v", addr) require.Containsf(t, players, player, "should have created a job for player %v", addr)
} }
} }
...@@ -233,34 +234,18 @@ func setupCoordinatorTest(t *testing.T, bufferSize int) (*coordinator, <-chan jo ...@@ -233,34 +234,18 @@ func setupCoordinatorTest(t *testing.T, bufferSize int) (*coordinator, <-chan jo
resultQueue := make(chan job, bufferSize) resultQueue := make(chan job, bufferSize)
games := &createdGames{ games := &createdGames{
t: t, t: t,
created: make(map[common.Address]*stubGame), created: make(map[common.Address]*test.StubGamePlayer),
} }
disk := &stubDiskManager{gameDirExists: make(map[common.Address]bool)} disk := &stubDiskManager{gameDirExists: make(map[common.Address]bool)}
c := newCoordinator(logger, metrics.NoopMetrics, workQueue, resultQueue, games.CreateGame, disk) c := newCoordinator(logger, metrics.NoopMetrics, workQueue, resultQueue, games.CreateGame, disk)
return c, workQueue, resultQueue, games, disk return c, workQueue, resultQueue, games, disk
} }
type stubGame struct {
addr common.Address
progressCount int
status types.GameStatus
dir string
}
func (g *stubGame) ProgressGame(_ context.Context) types.GameStatus {
g.progressCount++
return g.status
}
func (g *stubGame) Status() types.GameStatus {
return g.status
}
type createdGames struct { type createdGames struct {
t *testing.T t *testing.T
createCompleted common.Address createCompleted common.Address
creationFails common.Address creationFails common.Address
created map[common.Address]*stubGame created map[common.Address]*test.StubGamePlayer
} }
func (c *createdGames) CreateGame(fdg types.GameMetadata, dir string) (GamePlayer, error) { func (c *createdGames) CreateGame(fdg types.GameMetadata, dir string) (GamePlayer, error) {
...@@ -275,10 +260,10 @@ func (c *createdGames) CreateGame(fdg types.GameMetadata, dir string) (GamePlaye ...@@ -275,10 +260,10 @@ func (c *createdGames) CreateGame(fdg types.GameMetadata, dir string) (GamePlaye
if addr == c.createCompleted { if addr == c.createCompleted {
status = types.GameStatusDefenderWon status = types.GameStatusDefenderWon
} }
game := &stubGame{ game := &test.StubGamePlayer{
addr: addr, Addr: addr,
status: status, StatusValue: status,
dir: dir, Dir: dir,
} }
c.created[addr] = game c.created[addr] = game
return game, nil return game, nil
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
...@@ -16,7 +17,7 @@ func TestSchedulerProcessesGames(t *testing.T) { ...@@ -16,7 +17,7 @@ func TestSchedulerProcessesGames(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
ctx := context.Background() ctx := context.Background()
createPlayer := func(g types.GameMetadata, dir string) (GamePlayer, error) { createPlayer := func(g types.GameMetadata, dir string) (GamePlayer, error) {
return &stubPlayer{}, nil return &test.StubGamePlayer{}, nil
} }
removeExceptCalls := make(chan []common.Address) removeExceptCalls := make(chan []common.Address)
disk := &trackingDiskManager{removeExceptCalls: removeExceptCalls} disk := &trackingDiskManager{removeExceptCalls: removeExceptCalls}
...@@ -44,7 +45,7 @@ func TestSchedulerProcessesGames(t *testing.T) { ...@@ -44,7 +45,7 @@ func TestSchedulerProcessesGames(t *testing.T) {
func TestReturnBusyWhenScheduleQueueFull(t *testing.T) { func TestReturnBusyWhenScheduleQueueFull(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo) logger := testlog.Logger(t, log.LvlInfo)
createPlayer := func(game types.GameMetadata, dir string) (GamePlayer, error) { createPlayer := func(game types.GameMetadata, dir string) (GamePlayer, error) {
return &stubPlayer{}, nil return &test.StubGamePlayer{}, nil
} }
removeExceptCalls := make(chan []common.Address) removeExceptCalls := make(chan []common.Address)
disk := &trackingDiskManager{removeExceptCalls: removeExceptCalls} disk := &trackingDiskManager{removeExceptCalls: removeExceptCalls}
......
package test
import (
"context"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/common"
)
type StubGamePlayer struct {
Addr common.Address
ProgressCount int
StatusValue types.GameStatus
Dir string
}
func (g *StubGamePlayer) ProgressGame(_ context.Context) types.GameStatus {
g.ProgressCount++
return g.StatusValue
}
func (g *StubGamePlayer) Status() types.GameStatus {
return g.StatusValue
}
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler/test"
"github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
...@@ -24,7 +25,7 @@ func TestWorkerShouldProcessJobsUntilContextDone(t *testing.T) { ...@@ -24,7 +25,7 @@ func TestWorkerShouldProcessJobsUntilContextDone(t *testing.T) {
go progressGames(ctx, in, out, &wg, ms.ThreadActive, ms.ThreadIdle) go progressGames(ctx, in, out, &wg, ms.ThreadActive, ms.ThreadIdle)
in <- job{ in <- job{
player: &stubPlayer{status: types.GameStatusInProgress}, player: &test.StubGamePlayer{StatusValue: types.GameStatusInProgress},
} }
waitErr := wait.For(context.Background(), 100*time.Millisecond, func() (bool, error) { waitErr := wait.For(context.Background(), 100*time.Millisecond, func() (bool, error) {
return ms.activeCalls >= 1, nil return ms.activeCalls >= 1, nil
...@@ -34,7 +35,7 @@ func TestWorkerShouldProcessJobsUntilContextDone(t *testing.T) { ...@@ -34,7 +35,7 @@ func TestWorkerShouldProcessJobsUntilContextDone(t *testing.T) {
require.Equal(t, ms.idleCalls, 1) require.Equal(t, ms.idleCalls, 1)
in <- job{ in <- job{
player: &stubPlayer{status: types.GameStatusDefenderWon}, player: &test.StubGamePlayer{StatusValue: types.GameStatusDefenderWon},
} }
waitErr = wait.For(context.Background(), 100*time.Millisecond, func() (bool, error) { waitErr = wait.For(context.Background(), 100*time.Millisecond, func() (bool, error) {
return ms.activeCalls >= 2, nil return ms.activeCalls >= 2, nil
...@@ -67,18 +68,6 @@ func (m *metricSink) ThreadIdle() { ...@@ -67,18 +68,6 @@ func (m *metricSink) ThreadIdle() {
m.idleCalls++ m.idleCalls++
} }
type stubPlayer struct {
status types.GameStatus
}
func (s *stubPlayer) ProgressGame(ctx context.Context) types.GameStatus {
return s.status
}
func (s *stubPlayer) Status() types.GameStatus {
return s.status
}
func readWithTimeout[T any](t *testing.T, ch <-chan T) T { func readWithTimeout[T any](t *testing.T, ch <-chan T) T {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
......
...@@ -9,8 +9,8 @@ import ( ...@@ -9,8 +9,8 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault" "github.com/ethereum-optimism/optimism/op-challenger/game/fault"
"github.com/ethereum-optimism/optimism/op-challenger/game/loader" "github.com/ethereum-optimism/optimism/op-challenger/game/loader"
registry2 "github.com/ethereum-optimism/optimism/op-challenger/game/registry"
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler" "github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-challenger/version" "github.com/ethereum-optimism/optimism/op-challenger/version"
opClient "github.com/ethereum-optimism/optimism/op-service/client" opClient "github.com/ethereum-optimism/optimism/op-service/client"
...@@ -94,15 +94,16 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se ...@@ -94,15 +94,16 @@ func NewService(ctx context.Context, logger log.Logger, cfg *config.Config) (*Se
} }
loader := loader.NewGameLoader(factoryContract) loader := loader.NewGameLoader(factoryContract)
registry := registry2.NewGameTypeRegistry()
fault.RegisterGameTypes(registry, ctx, logger, m, cfg, txMgr, l1Client)
disk := newDiskManager(cfg.Datadir) disk := newDiskManager(cfg.Datadir)
s.sched = scheduler.NewScheduler( s.sched = scheduler.NewScheduler(
logger, logger,
m, m,
disk, disk,
cfg.MaxConcurrency, cfg.MaxConcurrency,
func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { registry.CreatePlayer)
return fault.NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, l1Client)
})
pollClient, err := opClient.NewRPCWithClient(ctx, logger, cfg.L1EthRpc, opClient.NewBaseRPCClient(l1Client.Client()), cfg.PollInterval) pollClient, err := opClient.NewRPCWithClient(ctx, logger, cfg.L1EthRpc, opClient.NewBaseRPCClient(l1Client.Client()), cfg.PollInterval)
if err != nil { if err != nil {
......
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