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 (
"fmt"
"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/metrics"
"github.com/ethereum-optimism/optimism/op-service/testlog"
......@@ -30,7 +31,7 @@ func TestScheduleNewGames(t *testing.T) {
players = append(players, j.player)
}
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)
}
}
......@@ -233,34 +234,18 @@ func setupCoordinatorTest(t *testing.T, bufferSize int) (*coordinator, <-chan jo
resultQueue := make(chan job, bufferSize)
games := &createdGames{
t: t,
created: make(map[common.Address]*stubGame),
created: make(map[common.Address]*test.StubGamePlayer),
}
disk := &stubDiskManager{gameDirExists: make(map[common.Address]bool)}
c := newCoordinator(logger, metrics.NoopMetrics, workQueue, resultQueue, games.CreateGame, 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 {
t *testing.T
createCompleted 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) {
......@@ -275,10 +260,10 @@ func (c *createdGames) CreateGame(fdg types.GameMetadata, dir string) (GamePlaye
if addr == c.createCompleted {
status = types.GameStatusDefenderWon
}
game := &stubGame{
addr: addr,
status: status,
dir: dir,
game := &test.StubGamePlayer{
Addr: addr,
StatusValue: status,
Dir: dir,
}
c.created[addr] = game
return game, nil
......
......@@ -4,6 +4,7 @@ import (
"context"
"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/metrics"
"github.com/ethereum-optimism/optimism/op-service/testlog"
......@@ -16,7 +17,7 @@ func TestSchedulerProcessesGames(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo)
ctx := context.Background()
createPlayer := func(g types.GameMetadata, dir string) (GamePlayer, error) {
return &stubPlayer{}, nil
return &test.StubGamePlayer{}, nil
}
removeExceptCalls := make(chan []common.Address)
disk := &trackingDiskManager{removeExceptCalls: removeExceptCalls}
......@@ -44,7 +45,7 @@ func TestSchedulerProcessesGames(t *testing.T) {
func TestReturnBusyWhenScheduleQueueFull(t *testing.T) {
logger := testlog.Logger(t, log.LvlInfo)
createPlayer := func(game types.GameMetadata, dir string) (GamePlayer, error) {
return &stubPlayer{}, nil
return &test.StubGamePlayer{}, nil
}
removeExceptCalls := make(chan []common.Address)
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 (
"testing"
"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-e2e/e2eutils/wait"
......@@ -24,7 +25,7 @@ func TestWorkerShouldProcessJobsUntilContextDone(t *testing.T) {
go progressGames(ctx, in, out, &wg, ms.ThreadActive, ms.ThreadIdle)
in <- job{
player: &stubPlayer{status: types.GameStatusInProgress},
player: &test.StubGamePlayer{StatusValue: types.GameStatusInProgress},
}
waitErr := wait.For(context.Background(), 100*time.Millisecond, func() (bool, error) {
return ms.activeCalls >= 1, nil
......@@ -34,7 +35,7 @@ func TestWorkerShouldProcessJobsUntilContextDone(t *testing.T) {
require.Equal(t, ms.idleCalls, 1)
in <- job{
player: &stubPlayer{status: types.GameStatusDefenderWon},
player: &test.StubGamePlayer{StatusValue: types.GameStatusDefenderWon},
}
waitErr = wait.For(context.Background(), 100*time.Millisecond, func() (bool, error) {
return ms.activeCalls >= 2, nil
......@@ -67,18 +68,6 @@ func (m *metricSink) ThreadIdle() {
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 {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
......
......@@ -9,8 +9,8 @@ import (
"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/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/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-challenger/version"
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
}
loader := loader.NewGameLoader(factoryContract)
registry := registry2.NewGameTypeRegistry()
fault.RegisterGameTypes(registry, ctx, logger, m, cfg, txMgr, l1Client)
disk := newDiskManager(cfg.Datadir)
s.sched = scheduler.NewScheduler(
logger,
m,
disk,
cfg.MaxConcurrency,
func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return fault.NewGamePlayer(ctx, logger, m, cfg, dir, game.Proxy, txMgr, l1Client)
})
registry.CreatePlayer)
pollClient, err := opClient.NewRPCWithClient(ctx, logger, cfg.L1EthRpc, opClient.NewBaseRPCClient(l1Client.Client()), cfg.PollInterval)
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