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 (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/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"
)
type FaultDisputeGameContract struct {
multiCaller *MultiCaller
addr common.Address
abi *abi.ABI
multiCaller *batching.MultiCaller
contract *batching.BoundContract
}
func NewFaultDisputeGameContract(addr common.Address, caller *MultiCaller) (*FaultDisputeGameContract, error) {
func NewFaultDisputeGameContract(addr common.Address, caller *batching.MultiCaller) (*FaultDisputeGameContract, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil {
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
return &FaultDisputeGameContract{
multiCaller: caller,
abi: fdgAbi,
addr: addr,
contract: batching.NewBoundContract(fdgAbi, addr),
}, nil
}
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 {
return 0, fmt.Errorf("failed to fetch game duration: %w", err)
}
......@@ -40,7 +38,7 @@ func (f *FaultDisputeGameContract) GetGameDuration(ctx context.Context) (uint64,
}
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 {
return 0, fmt.Errorf("failed to fetch max game depth: %w", err)
}
......@@ -48,7 +46,7 @@ func (f *FaultDisputeGameContract) GetMaxGameDepth(ctx context.Context) (uint64,
}
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 {
return common.Hash{}, fmt.Errorf("failed to fetch absolute prestate hash: %w", err)
}
......@@ -56,7 +54,7 @@ func (f *FaultDisputeGameContract) GetAbsolutePrestateHash(ctx context.Context)
}
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 {
return 0, fmt.Errorf("failed to fetch status: %w", err)
}
......@@ -64,7 +62,7 @@ func (f *FaultDisputeGameContract) GetStatus(ctx context.Context) (gameTypes.Gam
}
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 {
return 0, fmt.Errorf("failed to fetch claim count: %w", err)
}
......@@ -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) {
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 {
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
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++ {
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...)
......@@ -102,7 +100,7 @@ func (f *FaultDisputeGameContract) GetAllClaims(ctx context.Context) ([]types.Cl
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)
countered := result.GetBool(1)
claim := result.GetHash(2)
......
......@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
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-service/sources/batching"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
......@@ -68,7 +69,7 @@ func setup(t *testing.T) (*abiBasedRpc, *FaultDisputeGameContract) {
expectedArgs: make(map[string][]interface{}),
outputs: make(map[string][]interface{}),
}
caller := NewMultiCaller(stubRpc, 1)
caller := batching.NewMultiCaller(stubRpc, 1)
game, err := NewFaultDisputeGameContract(address, caller)
require.NoError(t, err)
return stubRpc, game
......
......@@ -11,6 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/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-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
......@@ -46,7 +47,7 @@ func NewGamePlayer(
creator resourceCreator,
) (*GamePlayer, error) {
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 {
return nil, fmt.Errorf("failed to create fault dispute game contract wrapper: %w", err)
}
......
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 (
"context"
"fmt"
"io"
"math/big"
"github.com/ethereum-optimism/optimism/op-service/sources"
"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/rpc"
)
// Note: All of this stuff would wind up moving to somewhere in op-service so it can be easily reused.
type EthRpc interface {
CallContext(ctx context.Context, out interface{}, method string, args ...interface{}) 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 {
rpc EthRpc
batchSize int
......@@ -110,7 +44,7 @@ func (m *MultiCaller) CallLatest(ctx context.Context, calls ...*ContractCall) ([
}
keys[i] = args
}
fetcher := sources.NewIterativeBatchCall[interface{}, *hexutil.Bytes](
fetcher := NewIterativeBatchCall[interface{}, *hexutil.Bytes](
keys,
func(args interface{}) (*hexutil.Bytes, rpc.BatchElem) {
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 (
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
)
type DebugClient struct {
callContext CallContextFn
callContext batching.CallContextFn
}
func NewDebugClient(callContext CallContextFn) *DebugClient {
func NewDebugClient(callContext batching.CallContextFn) *DebugClient {
return &DebugClient{callContext}
}
......
......@@ -6,6 +6,7 @@ import (
"io"
"sync"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
......@@ -369,7 +370,7 @@ type receiptsFetchingJob struct {
receiptHash common.Hash
txHashes []common.Hash
fetcher *IterativeBatchCall[common.Hash, *types.Receipt]
fetcher *batching.IterativeBatchCall[common.Hash, *types.Receipt]
result types.Receipts
}
......@@ -398,7 +399,7 @@ type ReceiptsRequester interface {
func (job *receiptsFetchingJob) runFetcher(ctx context.Context) error {
if job.fetcher == nil {
// start new work
job.fetcher = NewIterativeBatchCall[common.Hash, *types.Receipt](
job.fetcher = batching.NewIterativeBatchCall[common.Hash, *types.Receipt](
job.txHashes,
makeReceiptRequest,
job.client.BatchCallContext,
......
package sources
import (
"context"
"fmt"
"math/big"
"strings"
......@@ -18,10 +17,6 @@ import (
"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:
// - 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)
......
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