Commit 33d0e509 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #7003 from ethereum-optimism/refcell/delete-caller

fix(op-challenger): Combine Fault Caller and Loader Components
parents aac273a9 5669b83f
...@@ -19,9 +19,13 @@ type Responder interface { ...@@ -19,9 +19,13 @@ type Responder interface {
Step(ctx context.Context, stepData types.StepCallData) error Step(ctx context.Context, stepData types.StepCallData) error
} }
type ClaimLoader interface {
FetchClaims(ctx context.Context) ([]types.Claim, error)
}
type Agent struct { type Agent struct {
solver *solver.Solver solver *solver.Solver
loader Loader loader ClaimLoader
responder Responder responder Responder
updater types.OracleUpdater updater types.OracleUpdater
maxDepth int maxDepth int
...@@ -29,7 +33,7 @@ type Agent struct { ...@@ -29,7 +33,7 @@ type Agent struct {
log log.Logger log log.Logger
} }
func NewAgent(loader Loader, maxDepth int, trace types.TraceProvider, responder Responder, updater types.OracleUpdater, agreeWithProposedOutput bool, log log.Logger) *Agent { func NewAgent(loader ClaimLoader, maxDepth int, trace types.TraceProvider, responder Responder, updater types.OracleUpdater, agreeWithProposedOutput bool, log log.Logger) *Agent {
return &Agent{ return &Agent{
solver: solver.NewSolver(maxDepth, trace), solver: solver.NewSolver(maxDepth, trace),
loader: loader, loader: loader,
......
package fault
import (
"context"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
)
type FaultDisputeGameCaller interface {
Status(opts *bind.CallOpts) (uint8, error)
ClaimDataLen(opts *bind.CallOpts) (*big.Int, error)
}
type FaultCaller struct {
contract FaultDisputeGameCaller
}
func NewFaultCaller(caller FaultDisputeGameCaller) *FaultCaller {
return &FaultCaller{
caller,
}
}
func NewFaultCallerFromBindings(fdgAddr common.Address, client bind.ContractCaller) (*FaultCaller, error) {
caller, err := bindings.NewFaultDisputeGameCaller(fdgAddr, client)
if err != nil {
return nil, err
}
return &FaultCaller{
caller,
}, nil
}
// GetGameStatus returns the current game status.
// 0: In Progress
// 1: Challenger Won
// 2: Defender Won
func (fc *FaultCaller) GetGameStatus(ctx context.Context) (types.GameStatus, error) {
status, err := fc.contract.Status(&bind.CallOpts{Context: ctx})
return types.GameStatus(status), err
}
// GetClaimCount returns the number of claims in the game.
func (fc *FaultCaller) GetClaimCount(ctx context.Context) (uint64, error) {
count, err := fc.contract.ClaimDataLen(&bind.CallOpts{Context: ctx})
if err != nil {
return 0, err
}
return count.Uint64(), nil
}
package fault
import (
"context"
"errors"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/stretchr/testify/require"
)
var (
errMock = errors.New("mock error")
)
type mockFaultDisputeGameCaller struct {
status uint8
errStatus bool
claimDataLen *big.Int
errClaimDataLen bool
}
func (m *mockFaultDisputeGameCaller) Status(opts *bind.CallOpts) (uint8, error) {
if m.errStatus {
return 0, errMock
}
return m.status, nil
}
func (m *mockFaultDisputeGameCaller) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) {
if m.errClaimDataLen {
return nil, errMock
}
return m.claimDataLen, nil
}
func TestFaultCaller_GetGameStatus(t *testing.T) {
tests := []struct {
name string
caller FaultDisputeGameCaller
expectedStatus types.GameStatus
expectedErr error
}{
{
name: "success",
caller: &mockFaultDisputeGameCaller{
status: 1,
},
expectedStatus: types.GameStatusChallengerWon,
expectedErr: nil,
},
{
name: "error",
caller: &mockFaultDisputeGameCaller{
errStatus: true,
},
expectedStatus: 0,
expectedErr: errMock,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fc := NewFaultCaller(test.caller)
status, err := fc.GetGameStatus(context.Background())
require.Equal(t, test.expectedStatus, status)
require.Equal(t, test.expectedErr, err)
})
}
}
func TestFaultCaller_GetClaimCount(t *testing.T) {
tests := []struct {
name string
caller FaultDisputeGameCaller
expectedClaimDataLen uint64
expectedErr error
}{
{
name: "success",
caller: &mockFaultDisputeGameCaller{
claimDataLen: big.NewInt(1),
},
expectedClaimDataLen: 1,
expectedErr: nil,
},
{
name: "error",
caller: &mockFaultDisputeGameCaller{
errClaimDataLen: true,
},
expectedClaimDataLen: 0,
expectedErr: errMock,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fc := NewFaultCaller(test.caller)
claimDataLen, err := fc.GetClaimCount(context.Background())
require.Equal(t, test.expectedClaimDataLen, claimDataLen)
require.Equal(t, test.expectedErr, err)
})
}
}
...@@ -6,10 +6,10 @@ import ( ...@@ -6,10 +6,10 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -144,6 +144,9 @@ func newMockMinimalDisputeGameFactoryCaller(count uint64, gameCountErr bool, ind ...@@ -144,6 +144,9 @@ func newMockMinimalDisputeGameFactoryCaller(count uint64, gameCountErr bool, ind
games: generateMockGames(count), games: generateMockGames(count),
} }
} }
func (m *mockMinimalDisputeGameFactoryCaller) Status(opts *bind.CallOpts) (uint8, error) {
return uint8(types.GameStatusInProgress), nil
}
func (m *mockMinimalDisputeGameFactoryCaller) GameCount(opts *bind.CallOpts) (*big.Int, error) { func (m *mockMinimalDisputeGameFactoryCaller) GameCount(opts *bind.CallOpts) (*big.Int, error) {
if m.gameCountErr { if m.gameCountErr {
......
...@@ -4,8 +4,11 @@ import ( ...@@ -4,8 +4,11 @@ import (
"context" "context"
"math/big" "math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
) )
// MinimalFaultDisputeGameCaller is a minimal interface around [bindings.FaultDisputeGameCaller]. // MinimalFaultDisputeGameCaller is a minimal interface around [bindings.FaultDisputeGameCaller].
...@@ -18,18 +21,12 @@ type MinimalFaultDisputeGameCaller interface { ...@@ -18,18 +21,12 @@ type MinimalFaultDisputeGameCaller interface {
Position *big.Int Position *big.Int
Clock *big.Int Clock *big.Int
}, error) }, error)
Status(opts *bind.CallOpts) (uint8, error)
ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error)
MAXGAMEDEPTH(opts *bind.CallOpts) (*big.Int, error) MAXGAMEDEPTH(opts *bind.CallOpts) (*big.Int, error)
ABSOLUTEPRESTATE(opts *bind.CallOpts) ([32]byte, error) ABSOLUTEPRESTATE(opts *bind.CallOpts) ([32]byte, error)
} }
// Loader is a minimal interface for loading onchain [Claim] data.
type Loader interface {
FetchClaims(ctx context.Context) ([]types.Claim, error)
FetchGameDepth(ctx context.Context) (uint64, error)
FetchAbsolutePrestateHash(ctx context.Context) ([]byte, error)
}
// loader pulls in fault dispute game claim data periodically and over subscriptions. // loader pulls in fault dispute game claim data periodically and over subscriptions.
type loader struct { type loader struct {
caller MinimalFaultDisputeGameCaller caller MinimalFaultDisputeGameCaller
...@@ -42,6 +39,30 @@ func NewLoader(caller MinimalFaultDisputeGameCaller) *loader { ...@@ -42,6 +39,30 @@ func NewLoader(caller MinimalFaultDisputeGameCaller) *loader {
} }
} }
// NewLoaderFromBindings creates a new [loader] from a [bindings.FaultDisputeGameCaller].
func NewLoaderFromBindings(fdgAddr common.Address, client bind.ContractCaller) (*loader, error) {
caller, err := bindings.NewFaultDisputeGameCaller(fdgAddr, client)
if err != nil {
return nil, err
}
return NewLoader(caller), nil
}
// GetGameStatus returns the current game status.
func (l *loader) GetGameStatus(ctx context.Context) (types.GameStatus, error) {
status, err := l.caller.Status(&bind.CallOpts{Context: ctx})
return types.GameStatus(status), err
}
// GetClaimCount returns the number of claims in the game.
func (l *loader) GetClaimCount(ctx context.Context) (uint64, error) {
count, err := l.caller.ClaimDataLen(&bind.CallOpts{Context: ctx})
if err != nil {
return 0, err
}
return count.Uint64(), nil
}
// FetchGameDepth fetches the game depth from the fault dispute game. // FetchGameDepth fetches the game depth from the fault dispute game.
func (l *loader) FetchGameDepth(ctx context.Context) (uint64, error) { func (l *loader) FetchGameDepth(ctx context.Context) (uint64, error) {
callOpts := bind.CallOpts{ callOpts := bind.CallOpts{
......
...@@ -18,98 +18,52 @@ var ( ...@@ -18,98 +18,52 @@ var (
mockClaimLenError = fmt.Errorf("claim len errored") mockClaimLenError = fmt.Errorf("claim len errored")
mockMaxGameDepthError = fmt.Errorf("max game depth errored") mockMaxGameDepthError = fmt.Errorf("max game depth errored")
mockPrestateError = fmt.Errorf("prestate errored") mockPrestateError = fmt.Errorf("prestate errored")
mockStatusError = fmt.Errorf("status errored")
) )
type mockCaller struct { // TestLoader_GetGameStatus tests fetching the game status.
claimDataError bool func TestLoader_GetGameStatus(t *testing.T) {
claimLenError bool tests := []struct {
maxGameDepthError bool name string
prestateError bool status uint8
maxGameDepth uint64 expectedError bool
currentIndex uint64
returnClaims []struct {
ParentIndex uint32
Countered bool
Claim [32]byte
Position *big.Int
Clock *big.Int
}
}
func newMockCaller() *mockCaller {
return &mockCaller{
returnClaims: []struct {
ParentIndex uint32
Countered bool
Claim [32]byte
Position *big.Int
Clock *big.Int
}{ }{
{ {
Claim: [32]byte{0x00}, name: "challenger won status",
Position: big.NewInt(0), status: uint8(types.GameStatusChallengerWon),
Countered: false,
Clock: big.NewInt(0),
}, },
{ {
Claim: [32]byte{0x01}, name: "defender won status",
Position: big.NewInt(0), status: uint8(types.GameStatusDefenderWon),
Countered: false,
Clock: big.NewInt(0),
}, },
{ {
Claim: [32]byte{0x02}, name: "in progress status",
Position: big.NewInt(0), status: uint8(types.GameStatusInProgress),
Countered: false,
Clock: big.NewInt(0),
}, },
{
name: "error bubbled up",
expectedError: true,
}, },
} }
}
func (m *mockCaller) ClaimData(opts *bind.CallOpts, arg0 *big.Int) (struct { for _, test := range tests {
ParentIndex uint32 t.Run(test.name, func(t *testing.T) {
Countered bool mockCaller := newMockCaller()
Claim [32]byte mockCaller.status = test.status
Position *big.Int mockCaller.statusError = test.expectedError
Clock *big.Int loader := NewLoader(mockCaller)
}, error) { status, err := loader.GetGameStatus(context.Background())
if m.claimDataError { if test.expectedError {
return struct { require.ErrorIs(t, err, mockStatusError)
ParentIndex uint32 } else {
Countered bool require.NoError(t, err)
Claim [32]byte require.Equal(t, types.GameStatus(test.status), status)
Position *big.Int
Clock *big.Int
}{}, mockClaimDataError
}
returnClaim := m.returnClaims[m.currentIndex]
m.currentIndex++
return returnClaim, nil
}
func (m *mockCaller) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) {
if m.claimLenError {
return big.NewInt(0), mockClaimLenError
}
return big.NewInt(int64(len(m.returnClaims))), nil
}
func (m *mockCaller) MAXGAMEDEPTH(opts *bind.CallOpts) (*big.Int, error) {
if m.maxGameDepthError {
return nil, mockMaxGameDepthError
} }
return big.NewInt(int64(m.maxGameDepth)), nil })
}
func (m *mockCaller) ABSOLUTEPRESTATE(opts *bind.CallOpts) ([32]byte, error) {
if m.prestateError {
return [32]byte{}, mockPrestateError
} }
return common.HexToHash("0xdEad"), nil
} }
// TestLoader_FetchGameDepth tests [loader.FetchGameDepth]. // TestLoader_FetchGameDepth tests fetching the game depth.
func TestLoader_FetchGameDepth(t *testing.T) { func TestLoader_FetchGameDepth(t *testing.T) {
t.Run("Succeeds", func(t *testing.T) { t.Run("Succeeds", func(t *testing.T) {
mockCaller := newMockCaller() mockCaller := newMockCaller()
...@@ -130,7 +84,7 @@ func TestLoader_FetchGameDepth(t *testing.T) { ...@@ -130,7 +84,7 @@ func TestLoader_FetchGameDepth(t *testing.T) {
}) })
} }
// TestLoader_FetchAbsolutePrestateHash tests the [loader.FetchAbsolutePrestateHash] function. // TestLoader_FetchAbsolutePrestateHash tests fetching the absolute prestate hash.
func TestLoader_FetchAbsolutePrestateHash(t *testing.T) { func TestLoader_FetchAbsolutePrestateHash(t *testing.T) {
t.Run("Succeeds", func(t *testing.T) { t.Run("Succeeds", func(t *testing.T) {
mockCaller := newMockCaller() mockCaller := newMockCaller()
...@@ -150,8 +104,9 @@ func TestLoader_FetchAbsolutePrestateHash(t *testing.T) { ...@@ -150,8 +104,9 @@ func TestLoader_FetchAbsolutePrestateHash(t *testing.T) {
}) })
} }
// TestLoader_FetchClaims_Succeeds tests [loader.FetchClaims]. // TestLoader_FetchClaims tests fetching claims.
func TestLoader_FetchClaims_Succeeds(t *testing.T) { func TestLoader_FetchClaims(t *testing.T) {
t.Run("Succeeds", func(t *testing.T) {
mockCaller := newMockCaller() mockCaller := newMockCaller()
expectedClaims := mockCaller.returnClaims expectedClaims := mockCaller.returnClaims
loader := NewLoader(mockCaller) loader := NewLoader(mockCaller)
...@@ -198,26 +153,121 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) { ...@@ -198,26 +153,121 @@ func TestLoader_FetchClaims_Succeeds(t *testing.T) {
ContractIndex: 2, ContractIndex: 2,
}, },
}, claims) }, claims)
} })
// TestLoader_FetchClaims_ClaimDataErrors tests [loader.FetchClaims] t.Run("Claim Data Errors", func(t *testing.T) {
// when the claim fetcher [ClaimData] function call errors.
func TestLoader_FetchClaims_ClaimDataErrors(t *testing.T) {
mockCaller := newMockCaller() mockCaller := newMockCaller()
mockCaller.claimDataError = true mockCaller.claimDataError = true
loader := NewLoader(mockCaller) loader := NewLoader(mockCaller)
claims, err := loader.FetchClaims(context.Background()) claims, err := loader.FetchClaims(context.Background())
require.ErrorIs(t, err, mockClaimDataError) require.ErrorIs(t, err, mockClaimDataError)
require.Empty(t, claims) require.Empty(t, claims)
} })
// TestLoader_FetchClaims_ClaimLenErrors tests [loader.FetchClaims] t.Run("Claim Len Errors", func(t *testing.T) {
// when the claim fetcher [ClaimDataLen] function call errors.
func TestLoader_FetchClaims_ClaimLenErrors(t *testing.T) {
mockCaller := newMockCaller() mockCaller := newMockCaller()
mockCaller.claimLenError = true mockCaller.claimLenError = true
loader := NewLoader(mockCaller) loader := NewLoader(mockCaller)
claims, err := loader.FetchClaims(context.Background()) claims, err := loader.FetchClaims(context.Background())
require.ErrorIs(t, err, mockClaimLenError) require.ErrorIs(t, err, mockClaimLenError)
require.Empty(t, claims) require.Empty(t, claims)
})
}
type mockCaller struct {
claimDataError bool
claimLenError bool
maxGameDepthError bool
prestateError bool
statusError bool
maxGameDepth uint64
currentIndex uint64
status uint8
returnClaims []struct {
ParentIndex uint32
Countered bool
Claim [32]byte
Position *big.Int
Clock *big.Int
}
}
func newMockCaller() *mockCaller {
return &mockCaller{
returnClaims: []struct {
ParentIndex uint32
Countered bool
Claim [32]byte
Position *big.Int
Clock *big.Int
}{
{
Claim: [32]byte{0x00},
Position: big.NewInt(0),
Countered: false,
Clock: big.NewInt(0),
},
{
Claim: [32]byte{0x01},
Position: big.NewInt(0),
Countered: false,
Clock: big.NewInt(0),
},
{
Claim: [32]byte{0x02},
Position: big.NewInt(0),
Countered: false,
Clock: big.NewInt(0),
},
},
}
}
func (m *mockCaller) ClaimData(opts *bind.CallOpts, arg0 *big.Int) (struct {
ParentIndex uint32
Countered bool
Claim [32]byte
Position *big.Int
Clock *big.Int
}, error) {
if m.claimDataError {
return struct {
ParentIndex uint32
Countered bool
Claim [32]byte
Position *big.Int
Clock *big.Int
}{}, mockClaimDataError
}
returnClaim := m.returnClaims[m.currentIndex]
m.currentIndex++
return returnClaim, nil
}
func (m *mockCaller) Status(opts *bind.CallOpts) (uint8, error) {
if m.statusError {
return 0, mockStatusError
}
return m.status, nil
}
func (m *mockCaller) ClaimDataLen(opts *bind.CallOpts) (*big.Int, error) {
if m.claimLenError {
return big.NewInt(0), mockClaimLenError
}
return big.NewInt(int64(len(m.returnClaims))), nil
}
func (m *mockCaller) MAXGAMEDEPTH(opts *bind.CallOpts) (*big.Int, error) {
if m.maxGameDepthError {
return nil, mockMaxGameDepthError
}
return big.NewInt(int64(m.maxGameDepth)), nil
}
func (m *mockCaller) ABSOLUTEPRESTATE(opts *bind.CallOpts) ([32]byte, error) {
if m.prestateError {
return [32]byte{}, mockPrestateError
}
return common.HexToHash("0xdEad"), nil
} }
...@@ -27,7 +27,7 @@ type GameInfo interface { ...@@ -27,7 +27,7 @@ type GameInfo interface {
type GamePlayer struct { type GamePlayer struct {
agent Actor agent Actor
agreeWithProposedOutput bool agreeWithProposedOutput bool
caller GameInfo loader GameInfo
logger log.Logger logger log.Logger
completed bool completed bool
...@@ -84,15 +84,10 @@ func NewGamePlayer( ...@@ -84,15 +84,10 @@ func NewGamePlayer(
return nil, fmt.Errorf("failed to create the responder: %w", err) return nil, fmt.Errorf("failed to create the responder: %w", err)
} }
caller, err := NewFaultCallerFromBindings(addr, client)
if err != nil {
return nil, fmt.Errorf("failed to bind the fault contract: %w", err)
}
return &GamePlayer{ return &GamePlayer{
agent: NewAgent(loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, logger), agent: NewAgent(loader, int(gameDepth), provider, responder, updater, cfg.AgreeWithProposedOutput, logger),
agreeWithProposedOutput: cfg.AgreeWithProposedOutput, agreeWithProposedOutput: cfg.AgreeWithProposedOutput,
caller: caller, loader: loader,
logger: logger, logger: logger,
}, nil }, nil
} }
...@@ -107,7 +102,7 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) bool { ...@@ -107,7 +102,7 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) bool {
if err := g.agent.Act(ctx); err != nil { if err := g.agent.Act(ctx); err != nil {
g.logger.Error("Error when acting on game", "err", err) g.logger.Error("Error when acting on game", "err", err)
} }
if status, err := g.caller.GetGameStatus(ctx); err != nil { if status, err := g.loader.GetGameStatus(ctx); err != nil {
g.logger.Warn("Unable to retrieve game status", "err", err) g.logger.Warn("Unable to retrieve game status", "err", err)
} else { } else {
g.logGameStatus(ctx, status) g.logGameStatus(ctx, status)
...@@ -119,7 +114,7 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) bool { ...@@ -119,7 +114,7 @@ func (g *GamePlayer) ProgressGame(ctx context.Context) bool {
func (g *GamePlayer) logGameStatus(ctx context.Context, status types.GameStatus) { func (g *GamePlayer) logGameStatus(ctx context.Context, status types.GameStatus) {
if status == types.GameStatusInProgress { if status == types.GameStatusInProgress {
claimCount, err := g.caller.GetClaimCount(ctx) claimCount, err := g.loader.GetClaimCount(ctx)
if err != nil { if err != nil {
g.logger.Error("Failed to get claim count for in progress game", "err", err) g.logger.Error("Failed to get claim count for in progress game", "err", err)
return return
......
...@@ -115,7 +115,7 @@ func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.C ...@@ -115,7 +115,7 @@ func setupProgressGameTest(t *testing.T, agreeWithProposedRoot bool) (*testlog.C
game := &GamePlayer{ game := &GamePlayer{
agent: gameState, agent: gameState,
agreeWithProposedOutput: agreeWithProposedRoot, agreeWithProposedOutput: agreeWithProposedRoot,
caller: gameState, loader: gameState,
logger: logger, logger: logger,
} }
return handler, game, gameState return handler, game, gameState
......
...@@ -19,13 +19,16 @@ import ( ...@@ -19,13 +19,16 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
// Service provides a clean interface for the challenger to interact // Service exposes top-level fault dispute game challenger functionality.
// with the fault package.
type Service interface { type Service interface {
// MonitorGame monitors the fault dispute game and attempts to progress it. // MonitorGame monitors the fault dispute game and attempts to progress it.
MonitorGame(context.Context) error MonitorGame(context.Context) error
} }
type Loader interface {
FetchAbsolutePrestateHash(ctx context.Context) ([]byte, error)
}
type service struct { type service struct {
logger log.Logger logger log.Logger
metrics metrics.Metricer metrics metrics.Metricer
......
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