eth, internal: extend the TraceCall API (#22245)
Adds an an optional parameter `overrides *map[common.Address]account` to the `eth_call` API in order for the caller to can customize the state.
This commit is contained in:
parent
cc33398cef
commit
dd9c3225cf
@ -161,6 +161,16 @@ type TraceConfig struct {
|
|||||||
Reexec *uint64
|
Reexec *uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TraceCallConfig is the config for traceCall API. It holds one more
|
||||||
|
// field to override the state for tracing.
|
||||||
|
type TraceCallConfig struct {
|
||||||
|
*vm.LogConfig
|
||||||
|
Tracer *string
|
||||||
|
Timeout *string
|
||||||
|
Reexec *uint64
|
||||||
|
StateOverrides *ethapi.StateOverride
|
||||||
|
}
|
||||||
|
|
||||||
// StdTraceConfig holds extra parameters to standard-json trace functions.
|
// StdTraceConfig holds extra parameters to standard-json trace functions.
|
||||||
type StdTraceConfig struct {
|
type StdTraceConfig struct {
|
||||||
vm.LogConfig
|
vm.LogConfig
|
||||||
@ -720,7 +730,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
|
|||||||
// created during the execution of EVM if the given transaction was added on
|
// created during the execution of EVM if the given transaction was added on
|
||||||
// top of the provided block and returns them as a JSON object.
|
// top of the provided block and returns them as a JSON object.
|
||||||
// You can provide -2 as a block number to trace on top of the pending block.
|
// You can provide -2 as a block number to trace on top of the pending block.
|
||||||
func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (interface{}, error) {
|
func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
|
||||||
// Try to retrieve the specified block
|
// Try to retrieve the specified block
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
@ -730,6 +740,8 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHa
|
|||||||
block, err = api.blockByHash(ctx, hash)
|
block, err = api.blockByHash(ctx, hash)
|
||||||
} else if number, ok := blockNrOrHash.Number(); ok {
|
} else if number, ok := blockNrOrHash.Number(); ok {
|
||||||
block, err = api.blockByNumber(ctx, number)
|
block, err = api.blockByNumber(ctx, number)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("invalid arguments; neither block nor hash specified")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -743,11 +755,26 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// Apply the customized state rules if required.
|
||||||
|
if config != nil {
|
||||||
|
if err := config.StateOverrides.Apply(statedb); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
// Execute the trace
|
// Execute the trace
|
||||||
msg := args.ToMessage(api.backend.RPCGasCap())
|
msg := args.ToMessage(api.backend.RPCGasCap())
|
||||||
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
|
|
||||||
return api.traceTx(ctx, msg, new(txTraceContext), vmctx, statedb, config)
|
var traceConfig *TraceConfig
|
||||||
|
if config != nil {
|
||||||
|
traceConfig = &TraceConfig{
|
||||||
|
LogConfig: config.LogConfig,
|
||||||
|
Tracer: config.Tracer,
|
||||||
|
Timeout: config.Timeout,
|
||||||
|
Reexec: config.Reexec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return api.traceTx(ctx, msg, new(txTraceContext), vmctx, statedb, traceConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// traceTx configures a new tracer according to the provided configuration, and
|
// traceTx configures a new tracer according to the provided configuration, and
|
||||||
@ -797,7 +824,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *txTrac
|
|||||||
|
|
||||||
result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
|
result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("tracing failed: %v", err)
|
return nil, fmt.Errorf("tracing failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Depending on the tracer type, format and return the output.
|
// Depending on the tracer type, format and return the output.
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
@ -198,7 +199,7 @@ func TestTraceCall(t *testing.T) {
|
|||||||
var testSuite = []struct {
|
var testSuite = []struct {
|
||||||
blockNumber rpc.BlockNumber
|
blockNumber rpc.BlockNumber
|
||||||
call ethapi.CallArgs
|
call ethapi.CallArgs
|
||||||
config *TraceConfig
|
config *TraceCallConfig
|
||||||
expectErr error
|
expectErr error
|
||||||
expect interface{}
|
expect interface{}
|
||||||
}{
|
}{
|
||||||
@ -305,6 +306,147 @@ func TestTraceCall(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOverridenTraceCall(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Initialize test accounts
|
||||||
|
accounts := newAccounts(3)
|
||||||
|
genesis := &core.Genesis{Alloc: core.GenesisAlloc{
|
||||||
|
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
|
||||||
|
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
|
||||||
|
accounts[2].addr: {Balance: big.NewInt(params.Ether)},
|
||||||
|
}}
|
||||||
|
genBlocks := 10
|
||||||
|
signer := types.HomesteadSigner{}
|
||||||
|
api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) {
|
||||||
|
// Transfer from account[0] to account[1]
|
||||||
|
// value: 1000 wei
|
||||||
|
// fee: 0 wei
|
||||||
|
tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key)
|
||||||
|
b.AddTx(tx)
|
||||||
|
}))
|
||||||
|
randomAccounts, tracer := newAccounts(3), "callTracer"
|
||||||
|
|
||||||
|
var testSuite = []struct {
|
||||||
|
blockNumber rpc.BlockNumber
|
||||||
|
call ethapi.CallArgs
|
||||||
|
config *TraceCallConfig
|
||||||
|
expectErr error
|
||||||
|
expect *callTrace
|
||||||
|
}{
|
||||||
|
// Succcessful call with state overriding
|
||||||
|
{
|
||||||
|
blockNumber: rpc.PendingBlockNumber,
|
||||||
|
call: ethapi.CallArgs{
|
||||||
|
From: &randomAccounts[0].addr,
|
||||||
|
To: &randomAccounts[1].addr,
|
||||||
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||||
|
},
|
||||||
|
config: &TraceCallConfig{
|
||||||
|
Tracer: &tracer,
|
||||||
|
StateOverrides: ðapi.StateOverride{
|
||||||
|
randomAccounts[0].addr: ethapi.OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectErr: nil,
|
||||||
|
expect: &callTrace{
|
||||||
|
Type: "CALL",
|
||||||
|
From: randomAccounts[0].addr,
|
||||||
|
To: randomAccounts[1].addr,
|
||||||
|
Gas: newRPCUint64(24979000),
|
||||||
|
GasUsed: newRPCUint64(0),
|
||||||
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Invalid call without state overriding
|
||||||
|
{
|
||||||
|
blockNumber: rpc.PendingBlockNumber,
|
||||||
|
call: ethapi.CallArgs{
|
||||||
|
From: &randomAccounts[0].addr,
|
||||||
|
To: &randomAccounts[1].addr,
|
||||||
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||||
|
},
|
||||||
|
config: &TraceCallConfig{
|
||||||
|
Tracer: &tracer,
|
||||||
|
},
|
||||||
|
expectErr: core.ErrInsufficientFundsForTransfer,
|
||||||
|
expect: nil,
|
||||||
|
},
|
||||||
|
// Sucessful simple contract call
|
||||||
|
//
|
||||||
|
// // SPDX-License-Identifier: GPL-3.0
|
||||||
|
//
|
||||||
|
// pragma solidity >=0.7.0 <0.8.0;
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @title Storage
|
||||||
|
// * @dev Store & retrieve value in a variable
|
||||||
|
// */
|
||||||
|
// contract Storage {
|
||||||
|
// uint256 public number;
|
||||||
|
// constructor() {
|
||||||
|
// number = block.number;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
{
|
||||||
|
blockNumber: rpc.PendingBlockNumber,
|
||||||
|
call: ethapi.CallArgs{
|
||||||
|
From: &randomAccounts[0].addr,
|
||||||
|
To: &randomAccounts[2].addr,
|
||||||
|
Data: newRPCBytes(common.Hex2Bytes("8381f58a")), // call number()
|
||||||
|
},
|
||||||
|
config: &TraceCallConfig{
|
||||||
|
Tracer: &tracer,
|
||||||
|
StateOverrides: ðapi.StateOverride{
|
||||||
|
randomAccounts[2].addr: ethapi.OverrideAccount{
|
||||||
|
Code: newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000548156fea2646970667358221220eab35ffa6ab2adfe380772a48b8ba78e82a1b820a18fcb6f59aa4efb20a5f60064736f6c63430007040033")),
|
||||||
|
StateDiff: newStates([]common.Hash{{}}, []common.Hash{common.BigToHash(big.NewInt(123))}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectErr: nil,
|
||||||
|
expect: &callTrace{
|
||||||
|
Type: "CALL",
|
||||||
|
From: randomAccounts[0].addr,
|
||||||
|
To: randomAccounts[2].addr,
|
||||||
|
Input: hexutil.Bytes(common.Hex2Bytes("8381f58a")),
|
||||||
|
Output: hexutil.Bytes(common.BigToHash(big.NewInt(123)).Bytes()),
|
||||||
|
Gas: newRPCUint64(24978936),
|
||||||
|
GasUsed: newRPCUint64(2283),
|
||||||
|
Value: (*hexutil.Big)(big.NewInt(0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testspec := range testSuite {
|
||||||
|
result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config)
|
||||||
|
if testspec.expectErr != nil {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expect error %v, get nothing", testspec.expectErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !errors.Is(err, testspec.expectErr) {
|
||||||
|
t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expect no error, get %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret := new(callTrace)
|
||||||
|
if err := json.Unmarshal(result.(json.RawMessage), ret); err != nil {
|
||||||
|
t.Fatalf("failed to unmarshal trace result: %v", err)
|
||||||
|
}
|
||||||
|
if !jsonEqual(ret, testspec.expect) {
|
||||||
|
// uncomment this for easier debugging
|
||||||
|
//have, _ := json.MarshalIndent(ret, "", " ")
|
||||||
|
//want, _ := json.MarshalIndent(testspec.expect, "", " ")
|
||||||
|
//t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
|
||||||
|
t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, testspec.expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTraceTransaction(t *testing.T) {
|
func TestTraceTransaction(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@ -469,3 +611,29 @@ func newAccounts(n int) (accounts Accounts) {
|
|||||||
sort.Sort(accounts)
|
sort.Sort(accounts)
|
||||||
return accounts
|
return accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRPCBalance(balance *big.Int) **hexutil.Big {
|
||||||
|
rpcBalance := (*hexutil.Big)(balance)
|
||||||
|
return &rpcBalance
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRPCUint64(number uint64) *hexutil.Uint64 {
|
||||||
|
rpcUint64 := hexutil.Uint64(number)
|
||||||
|
return &rpcUint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRPCBytes(bytes []byte) *hexutil.Bytes {
|
||||||
|
rpcBytes := hexutil.Bytes(bytes)
|
||||||
|
return &rpcBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStates(keys []common.Hash, vals []common.Hash) *map[common.Hash]common.Hash {
|
||||||
|
if len(keys) != len(vals) {
|
||||||
|
panic("invalid input")
|
||||||
|
}
|
||||||
|
m := make(map[common.Hash]common.Hash)
|
||||||
|
for i := 0; i < len(keys); i++ {
|
||||||
|
m[keys[i]] = vals[i]
|
||||||
|
}
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/consensus/clique"
|
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
@ -802,13 +803,13 @@ func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message {
|
|||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// account indicates the overriding fields of account during the execution of
|
// OverrideAccount indicates the overriding fields of account during the execution
|
||||||
// a message call.
|
// of a message call.
|
||||||
// Note, state and stateDiff can't be specified at the same time. If state is
|
// Note, state and stateDiff can't be specified at the same time. If state is
|
||||||
// set, message execution will only use the data in the given state. Otherwise
|
// set, message execution will only use the data in the given state. Otherwise
|
||||||
// if statDiff is set, all diff will be applied first and then execute the call
|
// if statDiff is set, all diff will be applied first and then execute the call
|
||||||
// message.
|
// message.
|
||||||
type account struct {
|
type OverrideAccount struct {
|
||||||
Nonce *hexutil.Uint64 `json:"nonce"`
|
Nonce *hexutil.Uint64 `json:"nonce"`
|
||||||
Code *hexutil.Bytes `json:"code"`
|
Code *hexutil.Bytes `json:"code"`
|
||||||
Balance **hexutil.Big `json:"balance"`
|
Balance **hexutil.Big `json:"balance"`
|
||||||
@ -816,15 +817,15 @@ type account struct {
|
|||||||
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
|
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
|
// StateOverride is the collection of overriden accounts.
|
||||||
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
|
type StateOverride map[common.Address]OverrideAccount
|
||||||
|
|
||||||
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
// Apply overrides the fields of specified accounts into the given state.
|
||||||
if state == nil || err != nil {
|
func (diff *StateOverride) Apply(state *state.StateDB) error {
|
||||||
return nil, err
|
if diff == nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
// Override the fields of specified contracts before execution.
|
for addr, account := range *diff {
|
||||||
for addr, account := range overrides {
|
|
||||||
// Override account nonce.
|
// Override account nonce.
|
||||||
if account.Nonce != nil {
|
if account.Nonce != nil {
|
||||||
state.SetNonce(addr, uint64(*account.Nonce))
|
state.SetNonce(addr, uint64(*account.Nonce))
|
||||||
@ -838,7 +839,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
|
|||||||
state.SetBalance(addr, (*big.Int)(*account.Balance))
|
state.SetBalance(addr, (*big.Int)(*account.Balance))
|
||||||
}
|
}
|
||||||
if account.State != nil && account.StateDiff != nil {
|
if account.State != nil && account.StateDiff != nil {
|
||||||
return nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
|
return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
|
||||||
}
|
}
|
||||||
// Replace entire state if caller requires.
|
// Replace entire state if caller requires.
|
||||||
if account.State != nil {
|
if account.State != nil {
|
||||||
@ -851,6 +852,19 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
|
||||||
|
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
|
||||||
|
|
||||||
|
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||||||
|
if state == nil || err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := overrides.Apply(state); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// Setup context so it may be cancelled the call has completed
|
// Setup context so it may be cancelled the call has completed
|
||||||
// or, in case of unmetered gas, setup a context with a timeout.
|
// or, in case of unmetered gas, setup a context with a timeout.
|
||||||
var cancel context.CancelFunc
|
var cancel context.CancelFunc
|
||||||
@ -929,12 +943,8 @@ func (e *revertError) ErrorData() interface{} {
|
|||||||
//
|
//
|
||||||
// Note, this function doesn't make and changes in the state/blockchain and is
|
// Note, this function doesn't make and changes in the state/blockchain and is
|
||||||
// useful to execute and retrieve values.
|
// useful to execute and retrieve values.
|
||||||
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]account) (hexutil.Bytes, error) {
|
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
|
||||||
var accounts map[common.Address]account
|
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
|
||||||
if overrides != nil {
|
|
||||||
accounts = *overrides
|
|
||||||
}
|
|
||||||
result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user