|
|
@@ -954,6 +954,92 @@ func doCall(ctx context.Context, b Backend, args CallArgs, state *state.StateDB,
|
|
|
return result, nil
|
|
|
}
|
|
|
|
|
|
+// BatchCallConfig is the config object to be passed to eth_batchCall.
|
|
|
+type BatchCallConfig struct {
|
|
|
+ Block rpc.BlockNumberOrHash
|
|
|
+ StateOverrides *StateOverride
|
|
|
+ Calls []BatchCallArgs
|
|
|
+}
|
|
|
+
|
|
|
+// BatchCallArgs is the object specifying each call within eth_batchCall. It
|
|
|
+// extends TransactionArgs with the list of block metadata overrides.
|
|
|
+type BatchCallArgs struct {
|
|
|
+ CallArgs
|
|
|
+ BlockOverrides *BlockOverrides
|
|
|
+}
|
|
|
+
|
|
|
+// CallResult is the result of one call.
|
|
|
+type CallResult struct {
|
|
|
+ Return hexutil.Bytes
|
|
|
+ Error error
|
|
|
+}
|
|
|
+
|
|
|
+// BlockChainAPI provides an API to access Ethereum blockchain data.
|
|
|
+type BlockChainAPI struct {
|
|
|
+ b Backend
|
|
|
+}
|
|
|
+
|
|
|
+// NewBlockChainAPI creates a new Ethereum blockchain API.
|
|
|
+func NewBlockChainAPI(b Backend) *BlockChainAPI {
|
|
|
+ return &BlockChainAPI{b}
|
|
|
+}
|
|
|
+
|
|
|
+// BatchCall executes a series of transactions on the state of a given block as base.
|
|
|
+// The base state can be overridden once before transactions are executed.
|
|
|
+//
|
|
|
+// Additionally, each call can override block context fields such as number.
|
|
|
+//
|
|
|
+// Note, this function doesn't make any changes in the state/blockchain and is
|
|
|
+// useful to execute and retrieve values.
|
|
|
+func (s *BlockChainAPI) BatchCall(ctx context.Context, config BatchCallConfig) ([]CallResult, error) {
|
|
|
+ state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, config.Block)
|
|
|
+ if state == nil || err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ // State overrides are applied once before all calls
|
|
|
+ if err := config.StateOverrides.Apply(state); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Setup context so it may be cancelled before the calls completed
|
|
|
+ // or, in case of unmetered gas, setup a context with a timeout.
|
|
|
+ var (
|
|
|
+ cancel context.CancelFunc
|
|
|
+ //timeout = s.b.RPCEVMTimeout()
|
|
|
+ timeout = time.Duration(5000000000)
|
|
|
+ )
|
|
|
+ if timeout > 0 {
|
|
|
+ ctx, cancel = context.WithTimeout(ctx, timeout)
|
|
|
+ } else {
|
|
|
+ ctx, cancel = context.WithCancel(ctx)
|
|
|
+ }
|
|
|
+ // Make sure the context is cancelled when the call has completed
|
|
|
+ // this makes sure resources are cleaned up.
|
|
|
+ defer cancel()
|
|
|
+ var (
|
|
|
+ results []CallResult
|
|
|
+ // Each tx and all the series of txes shouldn't consume more gas than cap
|
|
|
+ globalGasCap = s.b.RPCGasCap()
|
|
|
+ gp = new(core.GasPool).AddGas(globalGasCap)
|
|
|
+ )
|
|
|
+ for _, call := range config.Calls {
|
|
|
+ blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil)
|
|
|
+ if call.BlockOverrides != nil {
|
|
|
+ call.BlockOverrides.Apply(&blockContext)
|
|
|
+ }
|
|
|
+ result, err := doCall(ctx, s.b, call.CallArgs, state, header, timeout, gp, &blockContext)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ // If the result contains a revert reason, try to unpack it.
|
|
|
+ if len(result.Revert()) > 0 {
|
|
|
+ result.Err = newRevertError(result)
|
|
|
+ }
|
|
|
+ results = append(results, CallResult{Return: result.Return(), Error: result.Err})
|
|
|
+ }
|
|
|
+ return results, nil
|
|
|
+}
|
|
|
+
|
|
|
func newRevertError(result *core.ExecutionResult) *revertError {
|
|
|
reason, errUnpack := abi.UnpackRevert(result.Revert())
|
|
|
err := errors.New("execution reverted")
|