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
}
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 {
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 {
return txmgr.TxCandidate{}, err
return nil, err
}
return oracle.AddGlobalDataTx(data)
return vm.Oracle(ctx)
}
func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64, error) {
......
......@@ -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) {
stubRpc, game := setupFaultDisputeGameTest(t)
idx := big.NewInt(2)
......
......@@ -14,6 +14,7 @@ import (
const (
methodGameCount = "gameCount"
methodGameAtIndex = "gameAtIndex"
methodGameImpls = "gameImpls"
)
type DisputeGameFactoryContract struct {
......@@ -48,6 +49,14 @@ func (f *DisputeGameFactoryContract) GetGame(ctx context.Context, idx uint64, bl
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 {
gameType := result.GetUint8(0)
timestamp := result.GetUint64(1)
......
......@@ -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) {
stubRpc.SetResponse(
factoryAddr,
......
......@@ -17,6 +17,7 @@ const (
// PreimageOracleContract is a binding that works with contracts implementing the IPreimageOracle interface
type PreimageOracleContract struct {
addr common.Address
multiCaller *batching.MultiCaller
contract *batching.BoundContract
}
......@@ -28,12 +29,17 @@ func NewPreimageOracleContract(addr common.Address, caller *batching.MultiCaller
}
return &PreimageOracleContract{
addr: addr,
multiCaller: caller,
contract: batching.NewBoundContract(mipsAbi, addr),
}, 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())
return call.ToTxCandidate()
}
......@@ -26,7 +26,7 @@ var (
type CloseFunc func()
type Registry interface {
RegisterGameType(gameType uint8, creator scheduler.PlayerCreator)
RegisterGameType(gameType uint8, creator scheduler.PlayerCreator, oracle types.LargePreimageOracle)
}
func RegisterGameTypes(
......@@ -37,6 +37,7 @@ func RegisterGameTypes(
cfg *config.Config,
rollupClient outputs.OutputRollupClient,
txMgr txmgr.TxManager,
gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller,
) (CloseFunc, error) {
var closer CloseFunc
......@@ -50,10 +51,14 @@ func RegisterGameTypes(
closer = l2Client.Close
}
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) {
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
}
......@@ -65,8 +70,9 @@ func registerAlphabet(
m metrics.Metricer,
rollupClient outputs.OutputRollupClient,
txMgr txmgr.TxManager,
gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller,
) {
) error {
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
contract, err := contracts.NewFaultDisputeGameContract(game.Proxy, caller)
if err != nil {
......@@ -92,7 +98,28 @@ func registerAlphabet(
genesisValidator := NewPrestateValidator(contract.GetGenesisOutputRoot, prestateProvider)
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(
......@@ -103,9 +130,10 @@ func registerCannon(
cfg *config.Config,
rollupClient outputs.OutputRollupClient,
txMgr txmgr.TxManager,
gameFactory *contracts.DisputeGameFactoryContract,
caller *batching.MultiCaller,
l2Client cannon.L2HeaderSource,
) {
) error {
playerCreator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
contract, err := contracts.NewFaultDisputeGameContract(game.Proxy, caller)
if err != nil {
......@@ -131,5 +159,10 @@ func registerCannon(
genesisValidator := NewPrestateValidator(contract.GetGenesisOutputRoot, prestateProvider)
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 (
"github.com/ethereum-optimism/optimism/op-challenger/game/scheduler"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/exp/maps"
)
var (
......@@ -13,22 +15,29 @@ var (
)
type GameTypeRegistry struct {
types map[uint8]scheduler.PlayerCreator
types map[uint8]scheduler.PlayerCreator
oracles map[common.Address]types.LargePreimageOracle
}
func NewGameTypeRegistry() *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.
// 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 {
panic(fmt.Errorf("duplicate creator registered for game type: %v", gameType))
}
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.
......@@ -39,3 +48,7 @@ func (r *GameTypeRegistry) CreatePlayer(game types.GameMetadata, dir string) (sc
}
return creator(game, dir)
}
func (r *GameTypeRegistry) Oracles() []types.LargePreimageOracle {
return maps.Values(r.oracles)
}
......@@ -6,6 +6,7 @@ import (
"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/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
......@@ -22,7 +23,7 @@ func TestKnownGameType(t *testing.T) {
creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return expectedPlayer, nil
}
registry.RegisterGameType(0, creator)
registry.RegisterGameType(0, creator, nil)
player, err := registry.CreatePlayer(types.GameMetadata{GameType: 0}, "")
require.NoError(t, err)
require.Same(t, expectedPlayer, player)
......@@ -33,8 +34,30 @@ func TestPanicsOnDuplicateGameType(t *testing.T) {
creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return nil, nil
}
registry.RegisterGameType(0, creator)
registry.RegisterGameType(0, creator, nil)
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 {
loader *loader.GameLoader
rollupClient *sources.RollupClient
factoryContract *contracts.DisputeGameFactoryContract
registry *registry.GameTypeRegistry
rollupClient *sources.RollupClient
l1Client *ethclient.Client
pollClient client.RPC
......@@ -88,10 +90,16 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
if err := s.initMetricsServer(&cfg.MetricsConfig); err != nil {
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)
}
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)
}
......@@ -165,13 +173,18 @@ func (s *Service) initMetricsServer(cfg *opmetrics.CLIConfig) error {
return nil
}
func (s *Service) initGameLoader(cfg *config.Config) error {
func (s *Service) initFactoryContract(cfg *config.Config) error {
factoryContract, err := contracts.NewDisputeGameFactoryContract(cfg.GameFactoryAddress,
batching.NewMultiCaller(s.l1Client.Client(), batching.DefaultBatchSize))
if err != nil {
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
}
......@@ -187,17 +200,21 @@ func (s *Service) initRollupClient(ctx context.Context, cfg *config.Config) erro
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()
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 {
return err
}
s.faultGamesCloser = closer
s.registry = gameTypeRegistry
return nil
}
func (s *Service) initScheduler(cfg *config.Config) error {
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
}
......
......@@ -41,3 +41,7 @@ type GameMetadata struct {
Timestamp uint64
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