Skip to content

Commit

Permalink
op-challenger: Register the oracle used by each game type (ethereum-o…
Browse files Browse the repository at this point in the history
…ptimism#9034)

This is prep work for being able to monitor the preimage oracle for large preimages that need to be validated and potentially challenged.
  • Loading branch information
ajsutton authored Jan 17, 2024
1 parent 20ca649 commit 302d3b0
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 26 deletions.
12 changes: 8 additions & 4 deletions op-challenger/game/fault/contracts/faultdisputegame.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
10 changes: 10 additions & 0 deletions op-challenger/game/fault/contracts/faultdisputegame_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions op-challenger/game/fault/contracts/gamefactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
const (
methodGameCount = "gameCount"
methodGameAtIndex = "gameAtIndex"
methodGameImpls = "gameImpls"
)

type DisputeGameFactoryContract struct {
Expand Down Expand Up @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions op-challenger/game/fault/contracts/gamefactory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion op-challenger/game/fault/contracts/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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()
}
47 changes: 40 additions & 7 deletions op-challenger/game/fault/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -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
}
Expand All @@ -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 {
Expand All @@ -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(
Expand All @@ -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 {
Expand All @@ -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
}
19 changes: 16 additions & 3 deletions op-challenger/game/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,38 @@ 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 (
ErrUnsupportedGameType = errors.New("unsupported game type")
)

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.
Expand All @@ -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)
}
29 changes: 26 additions & 3 deletions op-challenger/game/registry/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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)
Expand All @@ -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)
}
33 changes: 25 additions & 8 deletions op-challenger/game/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
}

Expand All @@ -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
}

Expand Down
4 changes: 4 additions & 0 deletions op-challenger/game/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ type GameMetadata struct {
Timestamp uint64
Proxy common.Address
}

type LargePreimageOracle interface {
Addr() common.Address
}

0 comments on commit 302d3b0

Please sign in to comment.