Commit 302d3b0e authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Register the oracle used by each game type (#9034)

This is prep work for being able to monitor the preimage oracle for large preimages that need to be validated and potentially challenged.
parent 20ca6496
...@@ -118,15 +118,19 @@ func (f *FaultDisputeGameContract) addLocalDataTx(claimIdx uint64, data *types.P ...@@ -118,15 +118,19 @@ func (f *FaultDisputeGameContract) addLocalDataTx(claimIdx uint64, data *types.P
} }
func (f *FaultDisputeGameContract) addGlobalDataTx(ctx context.Context, data *types.PreimageOracleData) (txmgr.TxCandidate, error) { func (f *FaultDisputeGameContract) addGlobalDataTx(ctx context.Context, data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
vm, err := f.vm(ctx) oracle, err := f.GetOracle(ctx)
if err != nil { if err != nil {
return txmgr.TxCandidate{}, err return txmgr.TxCandidate{}, err
} }
oracle, err := vm.Oracle(ctx) return oracle.AddGlobalDataTx(data)
}
func (f *FaultDisputeGameContract) GetOracle(ctx context.Context) (*PreimageOracleContract, error) {
vm, err := f.vm(ctx)
if err != nil { if err != nil {
return txmgr.TxCandidate{}, err return nil, err
} }
return oracle.AddGlobalDataTx(data) return vm.Oracle(ctx)
} }
func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64, error) { func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64, error) {
......
...@@ -105,6 +105,16 @@ func TestSimpleGetters(t *testing.T) { ...@@ -105,6 +105,16 @@ func TestSimpleGetters(t *testing.T) {
} }
} }
func TestGetOracleAddr(t *testing.T) {
stubRpc, game := setupFaultDisputeGameTest(t)
stubRpc.SetResponse(fdgAddr, methodVM, batching.BlockLatest, nil, []interface{}{vmAddr})
stubRpc.SetResponse(vmAddr, methodOracle, batching.BlockLatest, nil, []interface{}{oracleAddr})
actual, err := game.GetOracle(context.Background())
require.NoError(t, err)
require.Equal(t, oracleAddr, actual.Addr())
}
func TestGetClaim(t *testing.T) { func TestGetClaim(t *testing.T) {
stubRpc, game := setupFaultDisputeGameTest(t) stubRpc, game := setupFaultDisputeGameTest(t)
idx := big.NewInt(2) idx := big.NewInt(2)
......
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
const ( const (
methodGameCount = "gameCount" methodGameCount = "gameCount"
methodGameAtIndex = "gameAtIndex" methodGameAtIndex = "gameAtIndex"
methodGameImpls = "gameImpls"
) )
type DisputeGameFactoryContract struct { type DisputeGameFactoryContract struct {
...@@ -48,6 +49,14 @@ func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, bl ...@@ -48,6 +49,14 @@ func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, bl
return f.decodeGame(result), nil return f.decodeGame(result), nil
} }
func (f *DisputeGameFactoryContract) GetGameImpl(ctx context.Context, gameType uint8) (common.Address, error) {
result, err := f.multiCaller.SingleCall(ctx, batching.BlockLatest, f.contract.Call(methodGameImpls, gameType))
if err != nil {
return common.Address{}, fmt.Errorf("failed to load game impl for type %v: %w", gameType, err)
}
return result.GetAddress(0), nil
}
func (f *DisputeGameFactoryContract) decodeGame(result *batching.CallResult) types.GameMetadata { func (f *DisputeGameFactoryContract) decodeGame(result *batching.CallResult) types.GameMetadata {
gameType := result.GetUint8(0) gameType := result.GetUint8(0)
timestamp := result.GetUint64(1) timestamp := result.GetUint64(1)
......
...@@ -78,6 +78,21 @@ func TestLoadGame(t *testing.T) { ...@@ -78,6 +78,21 @@ func TestLoadGame(t *testing.T) {
} }
} }
func TestGetGameImpl(t *testing.T) {
stubRpc, factory := setupDisputeGameFactoryTest(t)
gameType := uint8(3)
gameImplAddr := common.Address{0xaa}
stubRpc.SetResponse(
factoryAddr,
"gameImpls",
batching.BlockLatest,
[]interface{}{gameType},
[]interface{}{gameImplAddr})
actual, err := factory.GetGameImpl(context.Background(), gameType)
require.NoError(t, err)
require.Equal(t, gameImplAddr, actual)
}
func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockHash common.Hash, game types.GameMetadata) { func expectGetGame(stubRpc *batchingTest.AbiBasedRpc, idx int, blockHash common.Hash, game types.GameMetadata) {
stubRpc.SetResponse( stubRpc.SetResponse(
factoryAddr, factoryAddr,
......
...@@ -17,6 +17,7 @@ const ( ...@@ -17,6 +17,7 @@ const (
// PreimageOracleContract is a binding that works with contracts implementing the IPreimageOracle interface // PreimageOracleContract is a binding that works with contracts implementing the IPreimageOracle interface
type PreimageOracleContract struct { type PreimageOracleContract struct {
addr common.Address
multiCaller *batching.MultiCaller multiCaller *batching.MultiCaller
contract *batching.BoundContract contract *batching.BoundContract
} }
...@@ -28,12 +29,17 @@ func NewPreimageOracleContract(addr common.Address, caller *batching.MultiCaller ...@@ -28,12 +29,17 @@ func NewPreimageOracleContract(addr common.Address, caller *batching.MultiCaller
} }
return &PreimageOracleContract{ return &PreimageOracleContract{
addr: addr,
multiCaller: caller, multiCaller: caller,
contract: batching.NewBoundContract(mipsAbi, addr), contract: batching.NewBoundContract(mipsAbi, addr),
}, nil }, nil
} }
func (c PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) { func (c *PreimageOracleContract) Addr() common.Address {
return c.addr
}
func (c *PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
call := c.contract.Call(methodLoadKeccak256PreimagePart, new(big.Int).SetUint64(uint64(data.OracleOffset)), data.GetPreimageWithoutSize()) call := c.contract.Call(methodLoadKeccak256PreimagePart, new(big.Int).SetUint64(uint64(data.OracleOffset)), data.GetPreimageWithoutSize())
return call.ToTxCandidate() return call.ToTxCandidate()
} }
...@@ -26,7 +26,7 @@ var ( ...@@ -26,7 +26,7 @@ var (
type CloseFunc func() type CloseFunc func()
type Registry interface { type Registry interface {
RegisterGameType(gameType uint8, creator scheduler.PlayerCreator) RegisterGameType(gameType uint8, creator scheduler.PlayerCreator, oracle types.LargePreimageOracle)
} }
func RegisterGameTypes( func RegisterGameTypes(
...@@ -37,6 +37,7 @@ func RegisterGameTypes( ...@@ -37,6 +37,7 @@ func RegisterGameTypes(
cfg *config.Config, cfg *config.Config,
rollupClient outputs.OutputRollupClient, rollupClient outputs.OutputRollupClient,
txMgr txmgr.TxManager, txMgr txmgr.TxManager,
gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller, caller *batching.MultiCaller,
) (CloseFunc, error) { ) (CloseFunc, error) {
var closer CloseFunc var closer CloseFunc
...@@ -50,10 +51,14 @@ func RegisterGameTypes( ...@@ -50,10 +51,14 @@ func RegisterGameTypes(
closer = l2Client.Close closer = l2Client.Close
} }
if cfg.TraceTypeEnabled(config.TraceTypeCannon) { if cfg.TraceTypeEnabled(config.TraceTypeCannon) {
registerCannon(registry, ctx, logger, m, cfg, rollupClient, txMgr, caller, l2Client) if err := registerCannon(registry, ctx, logger, m, cfg, rollupClient, txMgr, gameFactory, caller, l2Client); err != nil {
return nil, fmt.Errorf("failed to register cannon game type: %w", err)
}
} }
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) { if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
registerAlphabet(registry, ctx, logger, m, rollupClient, txMgr, caller) if err := registerAlphabet(registry, ctx, logger, m, rollupClient, txMgr, gameFactory, caller); err != nil {
return nil, fmt.Errorf("failed to register alphabet game type: %w", err)
}
} }
return closer, nil return closer, nil
} }
...@@ -65,8 +70,9 @@ func registerAlphabet( ...@@ -65,8 +70,9 @@ func registerAlphabet(
m metrics.Metricer, m metrics.Metricer,
rollupClient outputs.OutputRollupClient, rollupClient outputs.OutputRollupClient,
txMgr txmgr.TxManager, txMgr txmgr.TxManager,
gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller, caller *batching.MultiCaller,
) { ) error {
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
contract, err := contracts.NewFaultDisputeGameContract(game.Proxy, caller) contract, err := contracts.NewFaultDisputeGameContract(game.Proxy, caller)
if err != nil { if err != nil {
...@@ -92,7 +98,28 @@ func registerAlphabet( ...@@ -92,7 +98,28 @@ func registerAlphabet(
genesisValidator := NewPrestateValidator(contract.GetGenesisOutputRoot, prestateProvider) genesisValidator := NewPrestateValidator(contract.GetGenesisOutputRoot, prestateProvider)
return NewGamePlayer(ctx, logger, m, dir, game.Proxy, txMgr, contract, []Validator{prestateValidator, genesisValidator}, creator) return NewGamePlayer(ctx, logger, m, dir, game.Proxy, txMgr, contract, []Validator{prestateValidator, genesisValidator}, creator)
} }
registry.RegisterGameType(alphabetGameType, playerCreator) oracle, err := createOracle(ctx, gameFactory, caller)
if err != nil {
return err
}
registry.RegisterGameType(alphabetGameType, playerCreator, oracle)
return nil
}
func createOracle(ctx context.Context, gameFactory *contracts.DisputeGameFactoryContract, caller *batching.MultiCaller) (*contracts.PreimageOracleContract, error) {
implAddr, err := gameFactory.GetGameImpl(ctx, alphabetGameType)
if err != nil {
return nil, fmt.Errorf("failed to load alphabet game implementation: %w", err)
}
contract, err := contracts.NewFaultDisputeGameContract(implAddr, caller)
if err != nil {
return nil, err
}
oracle, err := contract.GetOracle(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load oracle address: %w", err)
}
return oracle, nil
} }
func registerCannon( func registerCannon(
...@@ -103,9 +130,10 @@ func registerCannon( ...@@ -103,9 +130,10 @@ func registerCannon(
cfg *config.Config, cfg *config.Config,
rollupClient outputs.OutputRollupClient, rollupClient outputs.OutputRollupClient,
txMgr txmgr.TxManager, txMgr txmgr.TxManager,
gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller, caller *batching.MultiCaller,
l2Client cannon.L2HeaderSource, l2Client cannon.L2HeaderSource,
) { ) error {
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
contract, err := contracts.NewFaultDisputeGameContract(game.Proxy, caller) contract, err := contracts.NewFaultDisputeGameContract(game.Proxy, caller)
if err != nil { if err != nil {
...@@ -131,5 +159,10 @@ func registerCannon( ...@@ -131,5 +159,10 @@ func registerCannon(
genesisValidator := NewPrestateValidator(contract.GetGenesisOutputRoot, prestateProvider) genesisValidator := NewPrestateValidator(contract.GetGenesisOutputRoot, prestateProvider)
return NewGamePlayer(ctx, logger, m, dir, game.Proxy, txMgr, contract, []Validator{prestateValidator, genesisValidator}, creator) return NewGamePlayer(ctx, logger, m, dir, game.Proxy, txMgr, contract, []Validator{prestateValidator, genesisValidator}, creator)
} }
registry.RegisterGameType(cannonGameType, playerCreator) oracle, err := createOracle(ctx, gameFactory, caller)
if err != nil {
return err
}
registry.RegisterGameType(cannonGameType, playerCreator, oracle)
return nil
} }
...@@ -6,6 +6,8 @@ import ( ...@@ -6,6 +6,8 @@ import (
"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/game/types"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/exp/maps"
) )
var ( var (
...@@ -13,22 +15,29 @@ var ( ...@@ -13,22 +15,29 @@ var (
) )
type GameTypeRegistry struct { type GameTypeRegistry struct {
types map[uint8]scheduler.PlayerCreator types map[uint8]scheduler.PlayerCreator
oracles map[common.Address]types.LargePreimageOracle
} }
func NewGameTypeRegistry() *GameTypeRegistry { func NewGameTypeRegistry() *GameTypeRegistry {
return &GameTypeRegistry{ return &GameTypeRegistry{
types: make(map[uint8]scheduler.PlayerCreator), types: make(map[uint8]scheduler.PlayerCreator),
oracles: make(map[common.Address]types.LargePreimageOracle),
} }
} }
// RegisterGameType registers a scheduler.PlayerCreator to use for a specific game type. // RegisterGameType registers a scheduler.PlayerCreator to use for a specific game type.
// Panics if the same game type is registered multiple times, since this indicates a significant programmer error. // Panics if the same game type is registered multiple times, since this indicates a significant programmer error.
func (r *GameTypeRegistry) RegisterGameType(gameType uint8, creator scheduler.PlayerCreator) { func (r *GameTypeRegistry) RegisterGameType(gameType uint8, creator scheduler.PlayerCreator, oracle types.LargePreimageOracle) {
if _, ok := r.types[gameType]; ok { if _, ok := r.types[gameType]; ok {
panic(fmt.Errorf("duplicate creator registered for game type: %v", gameType)) panic(fmt.Errorf("duplicate creator registered for game type: %v", gameType))
} }
r.types[gameType] = creator r.types[gameType] = creator
if oracle != nil {
// It's ok to have two game types use the same oracle contract.
// We add them to a map deliberately to deduplicate them.
r.oracles[oracle.Addr()] = oracle
}
} }
// CreatePlayer creates a new game player for the given game, using the specified directory for persisting data. // CreatePlayer creates a new game player for the given game, using the specified directory for persisting data.
...@@ -39,3 +48,7 @@ func (r *GameTypeRegistry) CreatePlayer(game types.GameMetadata, dir string) (sc ...@@ -39,3 +48,7 @@ func (r *GameTypeRegistry) CreatePlayer(game types.GameMetadata, dir string) (sc
} }
return creator(game, dir) return creator(game, dir)
} }
func (r *GameTypeRegistry) Oracles() []types.LargePreimageOracle {
return maps.Values(r.oracles)
}
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"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/scheduler/test" "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/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -22,7 +23,7 @@ func TestKnownGameType(t *testing.T) { ...@@ -22,7 +23,7 @@ func TestKnownGameType(t *testing.T) {
creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return expectedPlayer, nil return expectedPlayer, nil
} }
registry.RegisterGameType(0, creator) registry.RegisterGameType(0, creator, nil)
player, err := registry.CreatePlayer(types.GameMetadata{GameType: 0}, "") player, err := registry.CreatePlayer(types.GameMetadata{GameType: 0}, "")
require.NoError(t, err) require.NoError(t, err)
require.Same(t, expectedPlayer, player) require.Same(t, expectedPlayer, player)
...@@ -33,8 +34,30 @@ func TestPanicsOnDuplicateGameType(t *testing.T) { ...@@ -33,8 +34,30 @@ func TestPanicsOnDuplicateGameType(t *testing.T) {
creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) { creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return nil, nil return nil, nil
} }
registry.RegisterGameType(0, creator) registry.RegisterGameType(0, creator, nil)
require.Panics(t, func() { require.Panics(t, func() {
registry.RegisterGameType(0, creator) registry.RegisterGameType(0, creator, nil)
}) })
} }
func TestDeduplicateOracles(t *testing.T) {
registry := NewGameTypeRegistry()
creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return nil, nil
}
oracleA := stubPreimageOracle{0xaa}
oracleB := stubPreimageOracle{0xbb}
registry.RegisterGameType(0, creator, oracleA)
registry.RegisterGameType(1, creator, oracleB)
registry.RegisterGameType(2, creator, oracleB)
oracles := registry.Oracles()
require.Len(t, oracles, 2)
require.Contains(t, oracles, oracleA)
require.Contains(t, oracles, oracleB)
}
type stubPreimageOracle common.Address
func (s stubPreimageOracle) Addr() common.Address {
return common.Address(s)
}
...@@ -41,7 +41,9 @@ type Service struct { ...@@ -41,7 +41,9 @@ type Service struct {
loader *loader.GameLoader loader *loader.GameLoader
rollupClient *sources.RollupClient factoryContract *contracts.DisputeGameFactoryContract
registry *registry.GameTypeRegistry
rollupClient *sources.RollupClient
l1Client *ethclient.Client l1Client *ethclient.Client
pollClient client.RPC pollClient client.RPC
...@@ -88,10 +90,16 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error ...@@ -88,10 +90,16 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
if err := s.initMetricsServer(&cfg.MetricsConfig); err != nil { if err := s.initMetricsServer(&cfg.MetricsConfig); err != nil {
return fmt.Errorf("failed to init metrics server: %w", err) return fmt.Errorf("failed to init metrics server: %w", err)
} }
if err := s.initGameLoader(cfg); err != nil { if err := s.initFactoryContract(cfg); err != nil {
return fmt.Errorf("failed to create factory contract bindings: %w", err)
}
if err := s.initGameLoader(); err != nil {
return fmt.Errorf("failed to init game loader: %w", err) return fmt.Errorf("failed to init game loader: %w", err)
} }
if err := s.initScheduler(ctx, cfg); err != nil { if err := s.registerGameTypes(ctx, cfg); err != nil {
return fmt.Errorf("failed to register game types: %w", err)
}
if err := s.initScheduler(cfg); err != nil {
return fmt.Errorf("failed to init scheduler: %w", err) return fmt.Errorf("failed to init scheduler: %w", err)
} }
...@@ -165,13 +173,18 @@ func (s *Service) initMetricsServer(cfg *opmetrics.CLIConfig) error { ...@@ -165,13 +173,18 @@ func (s *Service) initMetricsServer(cfg *opmetrics.CLIConfig) error {
return nil return nil
} }
func (s *Service) initGameLoader(cfg *config.Config) error { func (s *Service) initFactoryContract(cfg *config.Config) error {
factoryContract, err := contracts.NewDisputeGameFactoryContract(cfg.GameFactoryAddress, factoryContract, err := contracts.NewDisputeGameFactoryContract(cfg.GameFactoryAddress,
batching.NewMultiCaller(s.l1Client.Client(), batching.DefaultBatchSize)) batching.NewMultiCaller(s.l1Client.Client(), batching.DefaultBatchSize))
if err != nil { if err != nil {
return fmt.Errorf("failed to bind the fault dispute game factory contract: %w", err) return fmt.Errorf("failed to bind the fault dispute game factory contract: %w", err)
} }
s.loader = loader.NewGameLoader(factoryContract) s.factoryContract = factoryContract
return nil
}
func (s *Service) initGameLoader() error {
s.loader = loader.NewGameLoader(s.factoryContract)
return nil return nil
} }
...@@ -187,17 +200,21 @@ func (s *Service) initRollupClient(ctx context.Context, cfg *config.Config) erro ...@@ -187,17 +200,21 @@ func (s *Service) initRollupClient(ctx context.Context, cfg *config.Config) erro
return nil return nil
} }
func (s *Service) initScheduler(ctx context.Context, cfg *config.Config) error { func (s *Service) registerGameTypes(ctx context.Context, cfg *config.Config) error {
gameTypeRegistry := registry.NewGameTypeRegistry() gameTypeRegistry := registry.NewGameTypeRegistry()
caller := batching.NewMultiCaller(s.l1Client.Client(), batching.DefaultBatchSize) caller := batching.NewMultiCaller(s.l1Client.Client(), batching.DefaultBatchSize)
closer, err := fault.RegisterGameTypes(gameTypeRegistry, ctx, s.logger, s.metrics, cfg, s.rollupClient, s.txMgr, caller) closer, err := fault.RegisterGameTypes(gameTypeRegistry, ctx, s.logger, s.metrics, cfg, s.rollupClient, s.txMgr, s.factoryContract, caller)
if err != nil { if err != nil {
return err return err
} }
s.faultGamesCloser = closer s.faultGamesCloser = closer
s.registry = gameTypeRegistry
return nil
}
func (s *Service) initScheduler(cfg *config.Config) error {
disk := newDiskManager(cfg.Datadir) disk := newDiskManager(cfg.Datadir)
s.sched = scheduler.NewScheduler(s.logger, s.metrics, disk, cfg.MaxConcurrency, gameTypeRegistry.CreatePlayer) s.sched = scheduler.NewScheduler(s.logger, s.metrics, disk, cfg.MaxConcurrency, s.registry.CreatePlayer)
return nil return nil
} }
......
...@@ -41,3 +41,7 @@ type GameMetadata struct { ...@@ -41,3 +41,7 @@ type GameMetadata struct {
Timestamp uint64 Timestamp uint64
Proxy common.Address Proxy common.Address
} }
type LargePreimageOracle interface {
Addr() common.Address
}
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