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

op-challenger: Register games that support bond claims through the game type registry. (#9294)

Preserves the ability to use different contract types for different game types.
parent 9a062d54
......@@ -6,20 +6,12 @@ import (
"fmt"
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
var _ BondClaimer = (*claimer)(nil)
type BondClaimer interface {
ClaimBonds(ctx context.Context, games []types.GameMetadata) error
}
type BondClaimMetrics interface {
RecordBondClaimed(amount uint64)
}
......@@ -29,45 +21,47 @@ type BondContract interface {
ClaimCredit(receipient common.Address) (txmgr.TxCandidate, error)
}
type claimer struct {
type BondContractCreator func(game types.GameMetadata) (BondContract, error)
type Claimer struct {
logger log.Logger
metrics BondClaimMetrics
caller *batching.MultiCaller
contractCreator BondContractCreator
txSender types.TxSender
}
func NewBondClaimer(l log.Logger, m BondClaimMetrics, c *batching.MultiCaller, txSender types.TxSender) *claimer {
return &claimer{
var _ BondClaimer = (*Claimer)(nil)
func NewBondClaimer(l log.Logger, m BondClaimMetrics, contractCreator BondContractCreator, txSender types.TxSender) *Claimer {
return &Claimer{
logger: l,
metrics: m,
caller: c,
contractCreator: contractCreator,
txSender: txSender,
}
}
func (c *claimer) ClaimBonds(ctx context.Context, games []types.GameMetadata) (err error) {
func (c *Claimer) ClaimBonds(ctx context.Context, games []types.GameMetadata) (err error) {
for _, game := range games {
err = errors.Join(err, c.claimBond(ctx, game.Proxy))
err = errors.Join(err, c.claimBond(ctx, game))
}
return err
}
func (c *claimer) claimBond(ctx context.Context, gameAddr common.Address) error {
c.logger.Debug("attempting to claim bonds for", "game", gameAddr)
func (c *Claimer) claimBond(ctx context.Context, game types.GameMetadata) error {
c.logger.Debug("Attempting to claim bonds for", "game", game.Proxy)
contract, err := contracts.NewFaultDisputeGameContract(gameAddr, c.caller)
contract, err := c.contractCreator(game)
if err != nil {
return fmt.Errorf("failed to create contract: %w", err)
return fmt.Errorf("failed to create bond contract bindings: %w", err)
}
credit, err := contract.GetCredit(ctx, c.txSender.From())
if err != nil {
return fmt.Errorf("failed to get credit: %w", err)
}
if credit.Cmp(big.NewInt(0)) == 0 {
c.logger.Debug("no credit to claim", "game", gameAddr)
c.logger.Debug("No credit to claim", "game", game.Proxy)
return nil
}
......
......@@ -6,10 +6,7 @@ import (
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
......@@ -19,15 +16,14 @@ import (
)
var (
methodCredit = "credit"
mockTxMgrSendError = errors.New("mock tx mgr send error")
)
func TestClaimer_ClaimBonds(t *testing.T) {
t.Run("MultipleBondClaimsSucceed", func(t *testing.T) {
gameAddr := common.HexToAddress("0x1234")
c, m, rpc, txSender := newTestClaimer(t, gameAddr)
rpc.SetResponse(gameAddr, methodCredit, batching.BlockLatest, []interface{}{txSender.From()}, []interface{}{big.NewInt(1)})
c, m, contract, txSender := newTestClaimer(t, gameAddr)
contract.credit = 1
err := c.ClaimBonds(context.Background(), []types.GameMetadata{{Proxy: gameAddr}, {Proxy: gameAddr}, {Proxy: gameAddr}})
require.NoError(t, err)
require.Equal(t, 3, txSender.sends)
......@@ -36,8 +32,8 @@ func TestClaimer_ClaimBonds(t *testing.T) {
t.Run("BondClaimSucceeds", func(t *testing.T) {
gameAddr := common.HexToAddress("0x1234")
c, m, rpc, txSender := newTestClaimer(t, gameAddr)
rpc.SetResponse(gameAddr, methodCredit, batching.BlockLatest, []interface{}{txSender.From()}, []interface{}{big.NewInt(1)})
c, m, contract, txSender := newTestClaimer(t, gameAddr)
contract.credit = 1
err := c.ClaimBonds(context.Background(), []types.GameMetadata{{Proxy: gameAddr}})
require.NoError(t, err)
require.Equal(t, 1, txSender.sends)
......@@ -46,9 +42,9 @@ func TestClaimer_ClaimBonds(t *testing.T) {
t.Run("BondClaimFails", func(t *testing.T) {
gameAddr := common.HexToAddress("0x1234")
c, m, rpc, txSender := newTestClaimer(t, gameAddr)
c, m, contract, txSender := newTestClaimer(t, gameAddr)
txSender.sendFails = true
rpc.SetResponse(gameAddr, methodCredit, batching.BlockLatest, []interface{}{txSender.From()}, []interface{}{big.NewInt(1)})
contract.credit = 1
err := c.ClaimBonds(context.Background(), []types.GameMetadata{{Proxy: gameAddr}})
require.ErrorIs(t, err, mockTxMgrSendError)
require.Equal(t, 1, txSender.sends)
......@@ -57,8 +53,8 @@ func TestClaimer_ClaimBonds(t *testing.T) {
t.Run("ZeroCreditReturnsNil", func(t *testing.T) {
gameAddr := common.HexToAddress("0x1234")
c, m, rpc, txSender := newTestClaimer(t, gameAddr)
rpc.SetResponse(gameAddr, methodCredit, batching.BlockLatest, []interface{}{txSender.From()}, []interface{}{big.NewInt(0)})
c, m, contract, txSender := newTestClaimer(t, gameAddr)
contract.credit = 0
err := c.ClaimBonds(context.Background(), []types.GameMetadata{{Proxy: gameAddr}})
require.NoError(t, err)
require.Equal(t, 0, txSender.sends)
......@@ -67,8 +63,8 @@ func TestClaimer_ClaimBonds(t *testing.T) {
t.Run("MultipleBondClaimFails", func(t *testing.T) {
gameAddr := common.HexToAddress("0x1234")
c, m, rpc, txSender := newTestClaimer(t, gameAddr)
rpc.SetResponse(gameAddr, methodCredit, batching.BlockLatest, []interface{}{txSender.From()}, []interface{}{big.NewInt(1)})
c, m, contract, txSender := newTestClaimer(t, gameAddr)
contract.credit = 1
txSender.sendFails = true
err := c.ClaimBonds(context.Background(), []types.GameMetadata{{Proxy: gameAddr}, {Proxy: gameAddr}, {Proxy: gameAddr}})
require.ErrorIs(t, err, mockTxMgrSendError)
......@@ -77,16 +73,16 @@ func TestClaimer_ClaimBonds(t *testing.T) {
})
}
func newTestClaimer(t *testing.T, gameAddr common.Address) (*claimer, *mockClaimMetrics, *batchingTest.AbiBasedRpc, *mockTxSender) {
func newTestClaimer(t *testing.T, gameAddr common.Address) (*Claimer, *mockClaimMetrics, *stubBondContract, *mockTxSender) {
logger := testlog.Logger(t, log.LvlDebug)
m := &mockClaimMetrics{}
txSender := &mockTxSender{}
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
require.NoError(t, err)
stubRpc := batchingTest.NewAbiBasedRpc(t, gameAddr, fdgAbi)
caller := batching.NewMultiCaller(stubRpc, 100)
c := NewBondClaimer(logger, m, caller, txSender)
return c, m, stubRpc, txSender
bondContract := &stubBondContract{}
contractCreator := func(game types.GameMetadata) (BondContract, error) {
return bondContract, nil
}
c := NewBondClaimer(logger, m, contractCreator, txSender)
return c, m, bondContract, txSender
}
type mockClaimMetrics struct {
......@@ -117,3 +113,15 @@ func (s *mockTxSender) SendAndWait(_ string, _ ...txmgr.TxCandidate) ([]*ethtype
}
return []*ethtypes.Receipt{{Status: ethtypes.ReceiptStatusSuccessful}}, nil
}
type stubBondContract struct {
credit int64
}
func (s *stubBondContract) GetCredit(_ context.Context, _ common.Address) (*big.Int, error) {
return big.NewInt(s.credit), nil
}
func (s *stubBondContract) ClaimCredit(_ common.Address) (txmgr.TxCandidate, error) {
return txmgr.TxCandidate{}, nil
}
......@@ -8,6 +8,10 @@ import (
"github.com/ethereum/go-ethereum/log"
)
type BondClaimer interface {
ClaimBonds(ctx context.Context, games []types.GameMetadata) error
}
type BondClaimScheduler struct {
log log.Logger
metrics BondClaimSchedulerMetrics
......
......@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/claims"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs"
......@@ -28,6 +29,7 @@ type CloseFunc func()
type Registry interface {
RegisterGameType(gameType uint32, creator scheduler.PlayerCreator, oracle keccakTypes.LargePreimageOracle)
RegisterBondContract(gameType uint32, creator claims.BondContractCreator)
}
func RegisterGameTypes(
......@@ -106,6 +108,11 @@ func registerAlphabet(
return err
}
registry.RegisterGameType(alphabetGameType, playerCreator, oracle)
contractCreator := func(game types.GameMetadata) (claims.BondContract, error) {
return contracts.NewFaultDisputeGameContract(game.Proxy, caller)
}
registry.RegisterBondContract(alphabetGameType, contractCreator)
return nil
}
......@@ -168,5 +175,10 @@ func registerCannon(
return err
}
registry.RegisterGameType(cannonGameType, playerCreator, oracle)
contractCreator := func(game types.GameMetadata) (claims.BondContract, error) {
return contracts.NewFaultDisputeGameContract(game.Proxy, caller)
}
registry.RegisterBondContract(cannonGameType, contractCreator)
return nil
}
......@@ -4,6 +4,7 @@ import (
"errors"
"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"
......@@ -15,12 +16,14 @@ 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),
}
}
......@@ -39,6 +42,13 @@ func (r *GameTypeRegistry) RegisterGameType(gameType uint32, creator scheduler.P
}
}
func (r *GameTypeRegistry) RegisterBondContract(gameType uint32, creator claims.BondContractCreator) {
if _, ok := r.bondCreators[gameType]; ok {
panic(fmt.Errorf("duplicate bond contract registered for game type: %v", gameType))
}
r.bondCreators[gameType] = creator
}
// CreatePlayer creates a new game player for the given game, using the specified directory for persisting data.
func (r *GameTypeRegistry) CreatePlayer(game types.GameMetadata, dir string) (scheduler.GamePlayer, error) {
creator, ok := r.types[game.GameType]
......@@ -48,6 +58,14 @@ func (r *GameTypeRegistry) CreatePlayer(game types.GameMetadata, dir string) (sc
return creator(game, dir)
}
func (r *GameTypeRegistry) CreateBondContract(game types.GameMetadata) (claims.BondContract, error) {
creator, ok := r.bondCreators[game.GameType]
if !ok {
return nil, fmt.Errorf("%w: %v", ErrUnsupportedGameType, game.GameType)
}
return creator(game)
}
func (r *GameTypeRegistry) Oracles() []keccakTypes.LargePreimageOracle {
return maps.Values(r.oracles)
}
......@@ -5,6 +5,7 @@ import (
"math/big"
"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"
......@@ -61,6 +62,35 @@ func TestDeduplicateOracles(t *testing.T) {
require.Contains(t, oracles, oracleB)
}
func TestBondContracts(t *testing.T) {
t.Run("UnknownGameType", func(t *testing.T) {
registry := NewGameTypeRegistry()
contract, err := registry.CreateBondContract(types.GameMetadata{GameType: 0})
require.ErrorIs(t, err, ErrUnsupportedGameType)
require.Nil(t, contract)
})
t.Run("KnownGameType", func(t *testing.T) {
registry := NewGameTypeRegistry()
expected := &stubBondContract{}
registry.RegisterBondContract(0, func(game types.GameMetadata) (claims.BondContract, error) {
return expected, nil
})
creator, err := registry.CreateBondContract(types.GameMetadata{GameType: 0})
require.NoError(t, err)
require.Same(t, expected, creator)
})
t.Run("PanicsOnDuplicate", func(t *testing.T) {
registry := NewGameTypeRegistry()
creator := func(game types.GameMetadata) (claims.BondContract, error) {
return nil, nil
}
registry.RegisterBondContract(0, creator)
require.Panics(t, func() {
registry.RegisterBondContract(0, creator)
})
})
}
type stubPreimageOracle common.Address
func (s stubPreimageOracle) ChallengeTx(_ keccakTypes.LargePreimageIdent, _ keccakTypes.Challenge) (txmgr.TxCandidate, error) {
......@@ -82,3 +112,13 @@ func (s stubPreimageOracle) Addr() common.Address {
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, error) {
panic("not supported")
}
func (s *stubBondContract) ClaimCredit(receipient common.Address) (txmgr.TxCandidate, error) {
panic("not supported")
}
......@@ -108,12 +108,12 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
if err := s.initGameLoader(); err != nil {
return fmt.Errorf("failed to init game loader: %w", err)
}
if err := s.initBondClaims(cfg); err != nil {
return fmt.Errorf("failed to init bond claiming: %w", err)
}
if err := s.registerGameTypes(ctx, cfg); err != nil {
return fmt.Errorf("failed to register game types: %w", err)
}
if err := s.initBondClaims(); err != nil {
return fmt.Errorf("failed to init bond claiming: %w", err)
}
if err := s.initScheduler(cfg); err != nil {
return fmt.Errorf("failed to init scheduler: %w", err)
}
......@@ -207,9 +207,8 @@ func (s *Service) initGameLoader() error {
return nil
}
func (s *Service) initBondClaims(cfg *config.Config) error {
caller := batching.NewMultiCaller(s.l1Client.Client(), batching.DefaultBatchSize)
claimer := claims.NewBondClaimer(s.logger, s.metrics, caller, s.txSender)
func (s *Service) initBondClaims() error {
claimer := claims.NewBondClaimer(s.logger, s.metrics, s.registry.CreateBondContract, s.txSender)
s.claimer = claims.NewBondClaimScheduler(s.logger, s.metrics, claimer)
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