Commit efdf5a1f authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Verify large preimages in all oracles (#9841)

* op-challenger: Use a separate oracle registry and retrieve list on each verification cycle

* op-challenger: Register oracles for all active games.
parent 3eeb170c
......@@ -25,22 +25,27 @@ import (
type CloseFunc func()
type Registry interface {
RegisterGameType(gameType uint32, creator scheduler.PlayerCreator, oracle keccakTypes.LargePreimageOracle)
RegisterGameType(gameType uint32, creator scheduler.PlayerCreator)
RegisterBondContract(gameType uint32, creator claims.BondContractCreator)
}
type OracleRegistry interface {
RegisterOracle(oracle keccakTypes.LargePreimageOracle)
}
type RollupClient interface {
outputs.OutputRollupClient
SyncStatusProvider
}
func RegisterGameTypes(
registry Registry,
ctx context.Context,
cl faultTypes.ClockReader,
logger log.Logger,
m metrics.Metricer,
cfg *config.Config,
registry Registry,
oracles OracleRegistry,
rollupClient RollupClient,
txSender types.TxSender,
gameFactory *contracts.DisputeGameFactoryContract,
......@@ -62,17 +67,17 @@ func RegisterGameTypes(
syncValidator := newSyncStatusValidator(rollupClient)
if cfg.TraceTypeEnabled(config.TraceTypeCannon) {
if err := registerCannon(faultTypes.CannonGameType, registry, ctx, cl, logger, m, cfg, syncValidator, rollupClient, txSender, gameFactory, caller, l2Client, l1HeaderSource, selective, claimants); err != nil {
if err := registerCannon(faultTypes.CannonGameType, registry, oracles, ctx, cl, logger, m, cfg, syncValidator, rollupClient, txSender, gameFactory, caller, l2Client, l1HeaderSource, selective, claimants); err != nil {
return nil, fmt.Errorf("failed to register cannon game type: %w", err)
}
}
if cfg.TraceTypeEnabled(config.TraceTypePermissioned) {
if err := registerCannon(faultTypes.PermissionedGameType, registry, ctx, cl, logger, m, cfg, syncValidator, rollupClient, txSender, gameFactory, caller, l2Client, l1HeaderSource, selective, claimants); err != nil {
if err := registerCannon(faultTypes.PermissionedGameType, registry, oracles, ctx, cl, logger, m, cfg, syncValidator, rollupClient, txSender, gameFactory, caller, l2Client, l1HeaderSource, selective, claimants); err != nil {
return nil, fmt.Errorf("failed to register permissioned cannon game type: %w", err)
}
}
if cfg.TraceTypeEnabled(config.TraceTypeAlphabet) {
if err := registerAlphabet(registry, ctx, cl, logger, m, syncValidator, rollupClient, txSender, gameFactory, caller, l1HeaderSource, selective, claimants); err != nil {
if err := registerAlphabet(registry, oracles, ctx, cl, logger, m, syncValidator, rollupClient, txSender, gameFactory, caller, l1HeaderSource, selective, claimants); err != nil {
return nil, fmt.Errorf("failed to register alphabet game type: %w", err)
}
}
......@@ -81,6 +86,7 @@ func RegisterGameTypes(
func registerAlphabet(
registry Registry,
oracles OracleRegistry,
ctx context.Context,
cl faultTypes.ClockReader,
logger log.Logger,
......@@ -99,6 +105,11 @@ func registerAlphabet(
if err != nil {
return nil, err
}
oracle, err := contract.GetOracle(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load oracle for game %v: %w", game.Proxy, err)
}
oracles.RegisterOracle(oracle)
prestateBlock, poststateBlock, err := contract.GetBlockRange(ctx)
if err != nil {
return nil, err
......@@ -123,11 +134,11 @@ func registerAlphabet(
genesisValidator := NewPrestateValidator("output root", contract.GetGenesisOutputRoot, prestateProvider)
return NewGamePlayer(ctx, cl, logger, m, dir, game.Proxy, txSender, contract, syncValidator, []Validator{prestateValidator, genesisValidator}, creator, l1HeaderSource, selective, claimants)
}
oracle, err := createOracle(ctx, gameFactory, caller, faultTypes.AlphabetGameType)
err := registerOracle(ctx, oracles, gameFactory, caller, faultTypes.AlphabetGameType)
if err != nil {
return err
}
registry.RegisterGameType(faultTypes.AlphabetGameType, playerCreator, oracle)
registry.RegisterGameType(faultTypes.AlphabetGameType, playerCreator)
contractCreator := func(game types.GameMetadata) (claims.BondContract, error) {
return contracts.NewFaultDisputeGameContract(game.Proxy, caller)
......@@ -136,25 +147,27 @@ func registerAlphabet(
return nil
}
func createOracle(ctx context.Context, gameFactory *contracts.DisputeGameFactoryContract, caller *batching.MultiCaller, gameType uint32) (*contracts.PreimageOracleContract, error) {
func registerOracle(ctx context.Context, oracles OracleRegistry, gameFactory *contracts.DisputeGameFactoryContract, caller *batching.MultiCaller, gameType uint32) error {
implAddr, err := gameFactory.GetGameImpl(ctx, gameType)
if err != nil {
return nil, fmt.Errorf("failed to load implementation for game type %v: %w", gameType, err)
return fmt.Errorf("failed to load implementation for game type %v: %w", gameType, err)
}
contract, err := contracts.NewFaultDisputeGameContract(implAddr, caller)
if err != nil {
return nil, err
return err
}
oracle, err := contract.GetOracle(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load oracle address: %w", err)
return fmt.Errorf("failed to load oracle address: %w", err)
}
return oracle, nil
oracles.RegisterOracle(oracle)
return nil
}
func registerCannon(
gameType uint32,
registry Registry,
oracles OracleRegistry,
ctx context.Context,
cl faultTypes.ClockReader,
logger log.Logger,
......@@ -176,6 +189,11 @@ func registerCannon(
if err != nil {
return nil, err
}
oracle, err := contract.GetOracle(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load oracle for game %v: %w", game.Proxy, err)
}
oracles.RegisterOracle(oracle)
prestateBlock, poststateBlock, err := contract.GetBlockRange(ctx)
if err != nil {
return nil, err
......@@ -200,11 +218,11 @@ func registerCannon(
genesisValidator := NewPrestateValidator("output root", contract.GetGenesisOutputRoot, prestateProvider)
return NewGamePlayer(ctx, cl, logger, m, dir, game.Proxy, txSender, contract, syncValidator, []Validator{prestateValidator, genesisValidator}, creator, l1HeaderSource, selective, claimants)
}
oracle, err := createOracle(ctx, gameFactory, caller, gameType)
err := registerOracle(ctx, oracles, gameFactory, caller, gameType)
if err != nil {
return err
}
registry.RegisterGameType(gameType, playerCreator, oracle)
registry.RegisterGameType(gameType, playerCreator)
contractCreator := func(game types.GameMetadata) (claims.BondContract, error) {
return contracts.NewFaultDisputeGameContract(game.Proxy, caller)
......
......@@ -17,11 +17,15 @@ type Challenger interface {
Challenge(ctx context.Context, blockHash common.Hash, oracle Oracle, preimages []keccakTypes.LargePreimageMetaData) error
}
type OracleSource interface {
Oracles() []keccakTypes.LargePreimageOracle
}
type LargePreimageScheduler struct {
log log.Logger
cl faultTypes.ClockReader
ch chan common.Hash
oracles []keccakTypes.LargePreimageOracle
oracles OracleSource
challenger Challenger
cancel func()
wg sync.WaitGroup
......@@ -30,13 +34,13 @@ type LargePreimageScheduler struct {
func NewLargePreimageScheduler(
logger log.Logger,
cl faultTypes.ClockReader,
oracles []keccakTypes.LargePreimageOracle,
oracleSource OracleSource,
challenger Challenger) *LargePreimageScheduler {
return &LargePreimageScheduler{
log: logger,
cl: cl,
ch: make(chan common.Hash, 1),
oracles: oracles,
oracles: oracleSource,
challenger: challenger,
}
}
......@@ -79,7 +83,7 @@ func (s *LargePreimageScheduler) Schedule(blockHash common.Hash, _ uint64) error
func (s *LargePreimageScheduler) verifyPreimages(ctx context.Context, blockHash common.Hash) error {
var err error
for _, oracle := range s.oracles {
for _, oracle := range s.oracles.Oracles() {
err = errors.Join(err, s.verifyOraclePreimages(ctx, oracle, blockHash))
}
return err
......
......@@ -50,7 +50,7 @@ func TestScheduleNextCheck(t *testing.T) {
}
cl := clock.NewDeterministicClock(time.Unix(int64(currentTimestamp), 0))
challenger := &stubChallenger{}
scheduler := NewLargePreimageScheduler(logger, cl, []keccakTypes.LargePreimageOracle{oracle}, challenger)
scheduler := NewLargePreimageScheduler(logger, cl, OracleSourceArray{oracle}, challenger)
scheduler.Start(ctx)
defer scheduler.Close()
err := scheduler.Schedule(common.Hash{0xaa}, 3)
......@@ -133,3 +133,9 @@ func (s *stubChallenger) Checked() []keccakTypes.LargePreimageMetaData {
copy(v, s.checked)
return v
}
type OracleSourceArray []keccakTypes.LargePreimageOracle
func (o OracleSourceArray) Oracles() []keccakTypes.LargePreimageOracle {
return o
}
package registry
import (
"sync"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum/go-ethereum/common"
"golang.org/x/exp/maps"
)
type OracleRegistry struct {
l sync.Mutex
oracles map[common.Address]keccakTypes.LargePreimageOracle
}
func NewOracleRegistry() *OracleRegistry {
return &OracleRegistry{
oracles: make(map[common.Address]keccakTypes.LargePreimageOracle),
}
}
func (r *OracleRegistry) RegisterOracle(oracle keccakTypes.LargePreimageOracle) {
r.l.Lock()
defer r.l.Unlock()
r.oracles[oracle.Addr()] = oracle
}
func (r *OracleRegistry) Oracles() []keccakTypes.LargePreimageOracle {
r.l.Lock()
defer r.l.Unlock()
return maps.Values(r.oracles)
}
package registry
import (
"context"
"math/big"
"testing"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestDeduplicateOracles(t *testing.T) {
registry := NewOracleRegistry()
oracleA := stubPreimageOracle{0xaa}
oracleB := stubPreimageOracle{0xbb}
registry.RegisterOracle(oracleA)
registry.RegisterOracle(oracleB)
registry.RegisterOracle(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) ChallengePeriod(_ context.Context) (uint64, error) {
panic("not supported")
}
func (s stubPreimageOracle) GetProposalTreeRoot(_ context.Context, _ rpcblock.Block, _ keccakTypes.LargePreimageIdent) (common.Hash, error) {
panic("not supported")
}
func (s stubPreimageOracle) ChallengeTx(_ keccakTypes.LargePreimageIdent, _ keccakTypes.Challenge) (txmgr.TxCandidate, error) {
panic("not supported")
}
func (s stubPreimageOracle) GetInputDataBlocks(_ context.Context, _ rpcblock.Block, _ keccakTypes.LargePreimageIdent) ([]uint64, error) {
panic("not supported")
}
func (s stubPreimageOracle) DecodeInputData(_ []byte) (*big.Int, keccakTypes.InputData, error) {
panic("not supported")
}
func (s stubPreimageOracle) Addr() common.Address {
return common.Address(s)
}
func (s stubPreimageOracle) GetActivePreimages(_ context.Context, _ common.Hash) ([]keccakTypes.LargePreimageMetaData, error) {
return nil, nil
}
......@@ -5,11 +5,8 @@ import (
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"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")
......@@ -17,29 +14,22 @@ var ErrUnsupportedGameType = errors.New("unsupported game type")
type GameTypeRegistry struct {
types map[uint32]scheduler.PlayerCreator
bondCreators map[uint32]claims.BondContractCreator
oracles map[common.Address]keccakTypes.LargePreimageOracle
}
func NewGameTypeRegistry() *GameTypeRegistry {
return &GameTypeRegistry{
types: make(map[uint32]scheduler.PlayerCreator),
bondCreators: make(map[uint32]claims.BondContractCreator),
oracles: make(map[common.Address]keccakTypes.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 uint32, creator scheduler.PlayerCreator, oracle keccakTypes.LargePreimageOracle) {
func (r *GameTypeRegistry) RegisterGameType(gameType uint32, creator scheduler.PlayerCreator) {
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
}
}
func (r *GameTypeRegistry) RegisterBondContract(gameType uint32, creator claims.BondContractCreator) {
......@@ -65,7 +55,3 @@ func (r *GameTypeRegistry) CreateBondContract(game types.GameMetadata) (claims.B
}
return creator(game)
}
func (r *GameTypeRegistry) Oracles() []keccakTypes.LargePreimageOracle {
return maps.Values(r.oracles)
}
......@@ -6,11 +6,9 @@ import (
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims"
keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types"
"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-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
......@@ -29,7 +27,7 @@ func TestKnownGameType(t *testing.T) {
creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return expectedPlayer, nil
}
registry.RegisterGameType(0, creator, nil)
registry.RegisterGameType(0, creator)
player, err := registry.CreatePlayer(types.GameMetadata{GameType: 0}, "")
require.NoError(t, err)
require.Same(t, expectedPlayer, player)
......@@ -40,28 +38,12 @@ func TestPanicsOnDuplicateGameType(t *testing.T) {
creator := func(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
return nil, nil
}
registry.RegisterGameType(0, creator, nil)
registry.RegisterGameType(0, creator)
require.Panics(t, func() {
registry.RegisterGameType(0, creator, nil)
registry.RegisterGameType(0, creator)
})
}
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)
}
func TestBondContracts(t *testing.T) {
t.Run("UnknownGameType", func(t *testing.T) {
registry := NewGameTypeRegistry()
......@@ -91,36 +73,6 @@ func TestBondContracts(t *testing.T) {
})
}
type stubPreimageOracle common.Address
func (s stubPreimageOracle) ChallengePeriod(_ context.Context) (uint64, error) {
panic("not supported")
}
func (s stubPreimageOracle) GetProposalTreeRoot(_ context.Context, _ rpcblock.Block, _ keccakTypes.LargePreimageIdent) (common.Hash, error) {
panic("not supported")
}
func (s stubPreimageOracle) ChallengeTx(_ keccakTypes.LargePreimageIdent, _ keccakTypes.Challenge) (txmgr.TxCandidate, error) {
panic("not supported")
}
func (s stubPreimageOracle) GetInputDataBlocks(_ context.Context, _ rpcblock.Block, _ keccakTypes.LargePreimageIdent) ([]uint64, error) {
panic("not supported")
}
func (s stubPreimageOracle) DecodeInputData(_ []byte) (*big.Int, keccakTypes.InputData, error) {
panic("not supported")
}
func (s stubPreimageOracle) Addr() common.Address {
return common.Address(s)
}
func (s stubPreimageOracle) GetActivePreimages(_ context.Context, _ common.Hash) ([]keccakTypes.LargePreimageMetaData, error) {
return nil, nil
}
type stubBondContract struct{}
func (s *stubBondContract) GetCredit(ctx context.Context, receipient common.Address) (*big.Int, types.GameStatus, error) {
......
......@@ -53,6 +53,7 @@ type Service struct {
factoryContract *contracts.DisputeGameFactoryContract
registry *registry.GameTypeRegistry
oracles *registry.OracleRegistry
rollupClient *sources.RollupClient
l1Client *ethclient.Client
......@@ -108,7 +109,7 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
if err := s.registerGameTypes(ctx, cfg); err != nil {
return fmt.Errorf("failed to register game types: %w", err)
}
if err := s.initBondClaims(cfg); err != nil {
if err := s.initBondClaims(); err != nil {
return fmt.Errorf("failed to init bond claiming: %w", err)
}
if err := s.initScheduler(cfg); err != nil {
......@@ -204,7 +205,7 @@ func (s *Service) initFactoryContract(cfg *config.Config) error {
return nil
}
func (s *Service) initBondClaims(cfg *config.Config) error {
func (s *Service) initBondClaims() error {
claimer := claims.NewBondClaimer(s.logger, s.metrics, s.registry.CreateBondContract, s.txSender, s.claimants...)
s.claimer = claims.NewBondClaimScheduler(s.logger, s.metrics, claimer)
return nil
......@@ -224,13 +225,15 @@ func (s *Service) initRollupClient(ctx context.Context, cfg *config.Config) erro
func (s *Service) registerGameTypes(ctx context.Context, cfg *config.Config) error {
gameTypeRegistry := registry.NewGameTypeRegistry()
oracles := registry.NewOracleRegistry()
caller := batching.NewMultiCaller(s.l1Client.Client(), batching.DefaultBatchSize)
closer, err := fault.RegisterGameTypes(gameTypeRegistry, ctx, s.cl, s.logger, s.metrics, cfg, s.rollupClient, s.txSender, s.factoryContract, caller, s.l1Client, cfg.SelectiveClaimResolution, s.claimants)
closer, err := fault.RegisterGameTypes(ctx, s.cl, s.logger, s.metrics, cfg, gameTypeRegistry, oracles, s.rollupClient, s.txSender, s.factoryContract, caller, s.l1Client, cfg.SelectiveClaimResolution, s.claimants)
if err != nil {
return err
}
s.faultGamesCloser = closer
s.registry = gameTypeRegistry
s.oracles = oracles
return nil
}
......@@ -244,7 +247,7 @@ func (s *Service) initLargePreimages() error {
fetcher := fetcher.NewPreimageFetcher(s.logger, s.l1Client)
verifier := keccak.NewPreimageVerifier(s.logger, fetcher)
challenger := keccak.NewPreimageChallenger(s.logger, s.metrics, verifier, s.txSender)
s.preimages = keccak.NewLargePreimageScheduler(s.logger, s.cl, s.registry.Oracles(), challenger)
s.preimages = keccak.NewLargePreimageScheduler(s.logger, s.cl, s.oracles, challenger)
return 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