Commit 8c688bc1 authored by Adrian Sutton's avatar Adrian Sutton

Move batching and multicall logic to a new package in op-service

Add BoundContract to simplify creating method calls.
parent c1b643af
...@@ -8,17 +8,16 @@ import ( ...@@ -8,17 +8,16 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
type FaultDisputeGameContract struct { type FaultDisputeGameContract struct {
multiCaller *MultiCaller multiCaller *batching.MultiCaller
addr common.Address contract *batching.BoundContract
abi *abi.ABI
} }
func NewFaultDisputeGameContract(addr common.Address, caller *MultiCaller) (*FaultDisputeGameContract, error) { func NewFaultDisputeGameContract(addr common.Address, caller *batching.MultiCaller) (*FaultDisputeGameContract, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi() fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load fault dispute game ABI: %w", err) return nil, fmt.Errorf("failed to load fault dispute game ABI: %w", err)
...@@ -26,13 +25,12 @@ func NewFaultDisputeGameContract(addr common.Address, caller *MultiCaller) (*Fau ...@@ -26,13 +25,12 @@ func NewFaultDisputeGameContract(addr common.Address, caller *MultiCaller) (*Fau
return &FaultDisputeGameContract{ return &FaultDisputeGameContract{
multiCaller: caller, multiCaller: caller,
abi: fdgAbi, contract: batching.NewBoundContract(fdgAbi, addr),
addr: addr,
}, nil }, nil
} }
func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64, error) { func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, NewContractCall(f.abi, f.addr, "GAME_DURATION")) result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("GAME_DURATION"))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to fetch game duration: %w", err) return 0, fmt.Errorf("failed to fetch game duration: %w", err)
} }
...@@ -40,7 +38,7 @@ func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64, ...@@ -40,7 +38,7 @@ func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64,
} }
func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64, error) { func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, NewContractCall(f.abi, f.addr, "MAX_GAME_DEPTH")) result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("MAX_GAME_DEPTH"))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to fetch max game depth: %w", err) return 0, fmt.Errorf("failed to fetch max game depth: %w", err)
} }
...@@ -48,7 +46,7 @@ func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64, ...@@ -48,7 +46,7 @@ func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64,
} }
func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) { func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) (common.Hash, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, NewContractCall(f.abi, f.addr, "ABSOLUTE_PRESTATE")) result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("ABSOLUTE_PRESTATE"))
if err != nil { if err != nil {
return common.Hash{}, fmt.Errorf("failed to fetch absolute prestate hash: %w", err) return common.Hash{}, fmt.Errorf("failed to fetch absolute prestate hash: %w", err)
} }
...@@ -56,7 +54,7 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context) ...@@ -56,7 +54,7 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context)
} }
func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) { func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.GameStatus, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, NewContractCall(f.abi, f.addr, "status")) result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("status"))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to fetch status: %w", err) return 0, fmt.Errorf("failed to fetch status: %w", err)
} }
...@@ -64,7 +62,7 @@ func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.Gam ...@@ -64,7 +62,7 @@ func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.Gam
} }
func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, error) { func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, NewContractCall(f.abi, f.addr, "claimDataLen")) result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("claimDataLen"))
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to fetch claim count: %w", err) return 0, fmt.Errorf("failed to fetch claim count: %w", err)
} }
...@@ -72,7 +70,7 @@ func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, e ...@@ -72,7 +70,7 @@ func (f *FaultDisputeGameContract) GetClaimCount(ctx context.Context) (uint64, e
} }
func (f *FaultDisputeGameContract) GetClaim(ctx context.Context, idx uint64) (types.Claim, error) { func (f *FaultDisputeGameContract) GetClaim(ctx context.Context, idx uint64) (types.Claim, error) {
result, err := f.multiCaller.SingleCallLatest(ctx, NewContractCall(f.abi, f.addr, "claimData", new(big.Int).SetUint64(idx))) result, err := f.multiCaller.SingleCallLatest(ctx, f.contract.Call("claimData", new(big.Int).SetUint64(idx)))
if err != nil { if err != nil {
return types.Claim{}, fmt.Errorf("failed to fetch claim %v: %w", idx, err) return types.Claim{}, fmt.Errorf("failed to fetch claim %v: %w", idx, err)
} }
...@@ -85,9 +83,9 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl ...@@ -85,9 +83,9 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl
return nil, fmt.Errorf("failed to load claim count: %w", err) return nil, fmt.Errorf("failed to load claim count: %w", err)
} }
calls := make([]*ContractCall, count) calls := make([]*batching.ContractCall, count)
for i := uint64(0); i < count; i++ { for i := uint64(0); i < count; i++ {
calls[i] = NewContractCall(f.abi, f.addr, "claimData", new(big.Int).SetUint64(i)) calls[i] = f.contract.Call("claimData", new(big.Int).SetUint64(i))
} }
results, err := f.multiCaller.CallLatest(ctx, calls...) results, err := f.multiCaller.CallLatest(ctx, calls...)
...@@ -102,7 +100,7 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl ...@@ -102,7 +100,7 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl
return claims, nil return claims, nil
} }
func (f *FaultDisputeGameContract) decodeClaim(result *CallResult, contractIndex int) types.Claim { func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim {
parentIndex := result.GetUint32(0) parentIndex := result.GetUint32(0)
countered := result.GetBool(1) countered := result.GetBool(1)
claim := result.GetHash(2) claim := result.GetHash(2)
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings" "github.com/ethereum-optimism/optimism/op-bindings/bindings"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -68,7 +69,7 @@ func setup(t *testing.T) (*abiBasedRpc, *FaultDisputeGameContract) { ...@@ -68,7 +69,7 @@ func setup(t *testing.T) (*abiBasedRpc, *FaultDisputeGameContract) {
expectedArgs: make(map[string][]interface{}), expectedArgs: make(map[string][]interface{}),
outputs: make(map[string][]interface{}), outputs: make(map[string][]interface{}),
} }
caller := NewMultiCaller(stubRpc, 1) caller := batching.NewMultiCaller(stubRpc, 1)
game, err := NewFaultDisputeGameContract(address, caller) game, err := NewFaultDisputeGameContract(address, caller)
require.NoError(t, err) require.NoError(t, err)
return stubRpc, game return stubRpc, game
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-challenger/metrics"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
...@@ -46,7 +47,7 @@ func NewGamePlayer( ...@@ -46,7 +47,7 @@ func NewGamePlayer(
creator resourceCreator, creator resourceCreator,
) (*GamePlayer, error) { ) (*GamePlayer, error) {
logger = logger.New("game", addr) logger = logger.New("game", addr)
loader, err := contracts.NewFaultDisputeGameContract(addr, contracts.NewMultiCaller(client.Client(), 100)) loader, err := contracts.NewFaultDisputeGameContract(addr, batching.NewMultiCaller(client.Client(), 100))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create fault dispute game contract wrapper: %w", err) return nil, fmt.Errorf("failed to create fault dispute game contract wrapper: %w", err)
} }
......
package sources package batching
import ( import (
"context" "context"
......
package sources package batching
import ( import (
"context" "context"
......
package batching
import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type BoundContract struct {
abi *abi.ABI
addr common.Address
}
func NewBoundContract(abi *abi.ABI, addr common.Address) *BoundContract {
return &BoundContract{
abi: abi,
addr: addr,
}
}
func (b *BoundContract) Call(method string, args ...interface{}) *ContractCall {
return NewContractCall(b.abi, b.addr, method, args...)
}
type ContractCall struct {
Abi *abi.ABI
Addr common.Address
Method string
Args []interface{}
}
func NewContractCall(abi *abi.ABI, addr common.Address, method string, args ...interface{}) *ContractCall {
return &ContractCall{
Abi: abi,
Addr: addr,
Method: method,
Args: args,
}
}
func (c *ContractCall) ToCallArgs() (interface{}, error) {
data, err := c.Abi.Pack(c.Method, c.Args...)
if err != nil {
return nil, fmt.Errorf("failed to pack arguments: %w", err)
}
msg := ethereum.CallMsg{
To: &c.Addr,
Data: data,
}
return toCallArg(msg), nil
}
func (c *ContractCall) Unpack(hex hexutil.Bytes) ([]interface{}, error) {
out, err := c.Abi.Unpack(c.Method, hex)
if err != nil {
return nil, fmt.Errorf("failed to unpack data: %w", err)
}
return out, nil
}
type CallResult struct {
out []interface{}
}
func (c *CallResult) GetUint8(i int) uint8 {
return *abi.ConvertType(c.out[i], new(uint8)).(*uint8)
}
func (c *CallResult) GetUint32(i int) uint32 {
return *abi.ConvertType(c.out[i], new(uint32)).(*uint32)
}
func (c *CallResult) GetBool(i int) bool {
return *abi.ConvertType(c.out[i], new(bool)).(*bool)
}
func (c *CallResult) GetHash(i int) common.Hash {
return *abi.ConvertType(c.out[i], new([32]byte)).(*[32]byte)
}
func (c *CallResult) GetBigInt(i int) *big.Int {
return *abi.ConvertType(c.out[i], new(*big.Int)).(**big.Int)
}
package contracts package batching
import ( import (
"context" "context"
"fmt" "fmt"
"io" "io"
"math/big"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
// Note: All of this stuff would wind up moving to somewhere in op-service so it can be easily reused.
type EthRpc interface { type EthRpc interface {
CallContext(ctx context.Context, out interface{}, method string, args ...interface{}) error CallContext(ctx context.Context, out interface{}, method string, args ...interface{}) error
BatchCallContext(ctx context.Context, b []rpc.BatchElem) error BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
} }
type ContractCall struct {
Abi *abi.ABI
Addr common.Address
Method string
Args []interface{}
}
func NewContractCall(abi *abi.ABI, addr common.Address, method string, args ...interface{}) *ContractCall {
return &ContractCall{
Abi: abi,
Addr: addr,
Method: method,
Args: args,
}
}
func (c *ContractCall) ToCallArgs() (interface{}, error) {
data, err := c.Abi.Pack(c.Method, c.Args...)
if err != nil {
return nil, fmt.Errorf("failed to pack arguments: %w", err)
}
msg := ethereum.CallMsg{
To: &c.Addr,
Data: data,
}
return toCallArg(msg), nil
}
func (c *ContractCall) Unpack(hex hexutil.Bytes) ([]interface{}, error) {
out, err := c.Abi.Unpack(c.Method, hex)
if err != nil {
return nil, fmt.Errorf("failed to unpack data: %w", err)
}
return out, nil
}
type CallResult struct {
out []interface{}
}
func (c *CallResult) GetUint8(i int) uint8 {
return *abi.ConvertType(c.out[i], new(uint8)).(*uint8)
}
func (c *CallResult) GetUint32(i int) uint32 {
return *abi.ConvertType(c.out[i], new(uint32)).(*uint32)
}
func (c *CallResult) GetBool(i int) bool {
return *abi.ConvertType(c.out[i], new(bool)).(*bool)
}
func (c *CallResult) GetHash(i int) common.Hash {
return *abi.ConvertType(c.out[i], new([32]byte)).(*[32]byte)
}
func (c *CallResult) GetBigInt(i int) *big.Int {
return *abi.ConvertType(c.out[i], new(*big.Int)).(**big.Int)
}
type MultiCaller struct { type MultiCaller struct {
rpc EthRpc rpc EthRpc
batchSize int batchSize int
...@@ -110,7 +44,7 @@ func (m *MultiCaller) CallLatest(ctx context.Context, calls ...*ContractCall) ([ ...@@ -110,7 +44,7 @@ func (m *MultiCaller) CallLatest(ctx context.Context, calls ...*ContractCall) ([
} }
keys[i] = args keys[i] = args
} }
fetcher := sources.NewIterativeBatchCall[interface{}, *hexutil.Bytes]( fetcher := NewIterativeBatchCall[interface{}, *hexutil.Bytes](
keys, keys,
func(args interface{}) (*hexutil.Bytes, rpc.BatchElem) { func(args interface{}) (*hexutil.Bytes, rpc.BatchElem) {
out := new(hexutil.Bytes) out := new(hexutil.Bytes)
......
package batching
import (
"context"
"github.com/ethereum/go-ethereum/rpc"
)
type BatchCallContextFn func(ctx context.Context, b []rpc.BatchElem) error
type CallContextFn func(ctx context.Context, result any, method string, args ...any) error
...@@ -4,16 +4,17 @@ import ( ...@@ -4,16 +4,17 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
) )
type DebugClient struct { type DebugClient struct {
callContext CallContextFn callContext batching.CallContextFn
} }
func NewDebugClient(callContext CallContextFn) *DebugClient { func NewDebugClient(callContext batching.CallContextFn) *DebugClient {
return &DebugClient{callContext} return &DebugClient{callContext}
} }
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"io" "io"
"sync" "sync"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
...@@ -369,7 +370,7 @@ type receiptsFetchingJob struct { ...@@ -369,7 +370,7 @@ type receiptsFetchingJob struct {
receiptHash common.Hash receiptHash common.Hash
txHashes []common.Hash txHashes []common.Hash
fetcher *IterativeBatchCall[common.Hash, *types.Receipt] fetcher *batching.IterativeBatchCall[common.Hash, *types.Receipt]
result types.Receipts result types.Receipts
} }
...@@ -398,7 +399,7 @@ type ReceiptsRequester interface { ...@@ -398,7 +399,7 @@ type ReceiptsRequester interface {
func (job *receiptsFetchingJob) runFetcher(ctx context.Context) error { func (job *receiptsFetchingJob) runFetcher(ctx context.Context) error {
if job.fetcher == nil { if job.fetcher == nil {
// start new work // start new work
job.fetcher = NewIterativeBatchCall[common.Hash, *types.Receipt]( job.fetcher = batching.NewIterativeBatchCall[common.Hash, *types.Receipt](
job.txHashes, job.txHashes,
makeReceiptRequest, makeReceiptRequest,
job.client.BatchCallContext, job.client.BatchCallContext,
......
package sources package sources
import ( import (
"context"
"fmt" "fmt"
"math/big" "math/big"
"strings" "strings"
...@@ -18,10 +17,6 @@ import ( ...@@ -18,10 +17,6 @@ import (
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
) )
type BatchCallContextFn func(ctx context.Context, b []rpc.BatchElem) error
type CallContextFn func(ctx context.Context, result any, method string, args ...any) error
// Note: these types are used, instead of the geth types, to enable: // Note: these types are used, instead of the geth types, to enable:
// - batched calls of many block requests (standard bindings do extra uncle-header fetches, cannot be batched nicely) // - batched calls of many block requests (standard bindings do extra uncle-header fetches, cannot be batched nicely)
// - ignore uncle data (does not even exist anymore post-Merge) // - ignore uncle data (does not even exist anymore post-Merge)
......
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