multicall.go 2.64 KB
Newer Older
1
package batching
2 3 4 5 6 7

import (
	"context"
	"fmt"
	"io"

8
	"github.com/ethereum/go-ethereum/common"
9 10 11 12
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/rpc"
)

13 14
var DefaultBatchSize = 100

15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
type EthRpc interface {
	CallContext(ctx context.Context, out interface{}, method string, args ...interface{}) error
	BatchCallContext(ctx context.Context, b []rpc.BatchElem) error
}

type MultiCaller struct {
	rpc       EthRpc
	batchSize int
}

func NewMultiCaller(rpc EthRpc, batchSize int) *MultiCaller {
	return &MultiCaller{
		rpc:       rpc,
		batchSize: batchSize,
	}
}

32 33
func (m *MultiCaller) SingleCall(ctx context.Context, block Block, call *ContractCall) (*CallResult, error) {
	results, err := m.Call(ctx, block, call)
34 35 36 37 38 39
	if err != nil {
		return nil, err
	}
	return results[0], nil
}

40
func (m *MultiCaller) Call(ctx context.Context, block Block, calls ...*ContractCall) ([]*CallResult, error) {
41 42 43 44 45 46 47 48
	keys := make([]interface{}, len(calls))
	for i := 0; i < len(calls); i++ {
		args, err := calls[i].ToCallArgs()
		if err != nil {
			return nil, err
		}
		keys[i] = args
	}
49
	fetcher := NewIterativeBatchCall[interface{}, *hexutil.Bytes](
50 51 52 53 54
		keys,
		func(args interface{}) (*hexutil.Bytes, rpc.BatchElem) {
			out := new(hexutil.Bytes)
			return out, rpc.BatchElem{
				Method: "eth_call",
55
				Args:   []interface{}{args, block.value},
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
				Result: &out,
			}
		},
		m.rpc.BatchCallContext,
		m.rpc.CallContext,
		m.batchSize)
	for {
		if err := fetcher.Fetch(ctx); err == io.EOF {
			break
		} else if err != nil {
			return nil, fmt.Errorf("failed to fetch claims: %w", err)
		}
	}
	results, err := fetcher.Result()
	if err != nil {
		return nil, fmt.Errorf("failed to get batch call results: %w", err)
	}

	callResults := make([]*CallResult, len(results))
	for i, result := range results {
		call := calls[i]
		out, err := call.Unpack(*result)
		if err != nil {
			return nil, fmt.Errorf("failed to unpack result: %w", err)
		}
81
		callResults[i] = out
82 83 84
	}
	return callResults, nil
}
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111

// Block represents the block ref value in RPC calls.
// It can be either a label (e.g. latest), a block number or block hash.
type Block struct {
	value any
}

func (b Block) ArgValue() any {
	return b.value
}

var (
	BlockPending   = Block{"pending"}
	BlockLatest    = Block{"latest"}
	BlockSafe      = Block{"safe"}
	BlockFinalized = Block{"finalized"}
)

// BlockByNumber references a canonical block by number.
func BlockByNumber(blockNum uint64) Block {
	return Block{rpc.BlockNumber(blockNum)}
}

// BlockByHash references a block by hash. Canonical or non-canonical blocks may be referenced.
func BlockByHash(hash common.Hash) Block {
	return Block{rpc.BlockNumberOrHashWithHash(hash, false)}
}