Skip to content

Commit d5394ed

Browse files
rjl493456442atif-konasl
authored andcommitted
eth, internal: extend the TraceCall API (ethereum#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.
1 parent acdffc0 commit d5394ed

File tree

3 files changed

+226
-21
lines changed

3 files changed

+226
-21
lines changed

eth/tracers/api.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,16 @@ type TraceConfig struct {
161161
Reexec *uint64
162162
}
163163

164+
// TraceCallConfig is the config for traceCall API. It holds one more
165+
// field to override the state for tracing.
166+
type TraceCallConfig struct {
167+
*vm.LogConfig
168+
Tracer *string
169+
Timeout *string
170+
Reexec *uint64
171+
StateOverrides *ethapi.StateOverride
172+
}
173+
164174
// StdTraceConfig holds extra parameters to standard-json trace functions.
165175
type StdTraceConfig struct {
166176
vm.LogConfig
@@ -720,7 +730,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
720730
// created during the execution of EVM if the given transaction was added on
721731
// top of the provided block and returns them as a JSON object.
722732
// You can provide -2 as a block number to trace on top of the pending block.
723-
func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceConfig) (interface{}, error) {
733+
func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
724734
// Try to retrieve the specified block
725735
var (
726736
err error
@@ -730,6 +740,8 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHa
730740
block, err = api.blockByHash(ctx, hash)
731741
} else if number, ok := blockNrOrHash.Number(); ok {
732742
block, err = api.blockByNumber(ctx, number)
743+
} else {
744+
return nil, errors.New("invalid arguments; neither block nor hash specified")
733745
}
734746
if err != nil {
735747
return nil, err
@@ -743,11 +755,26 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHa
743755
if err != nil {
744756
return nil, err
745757
}
758+
// Apply the customized state rules if required.
759+
if config != nil {
760+
if err := config.StateOverrides.Apply(statedb); err != nil {
761+
return nil, err
762+
}
763+
}
746764
// Execute the trace
747765
msg := args.ToMessage(api.backend.RPCGasCap())
748766
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
749767

750-
return api.traceTx(ctx, msg, new(txTraceContext), vmctx, statedb, config)
768+
var traceConfig *TraceConfig
769+
if config != nil {
770+
traceConfig = &TraceConfig{
771+
LogConfig: config.LogConfig,
772+
Tracer: config.Tracer,
773+
Timeout: config.Timeout,
774+
Reexec: config.Reexec,
775+
}
776+
}
777+
return api.traceTx(ctx, msg, new(txTraceContext), vmctx, statedb, traceConfig)
751778
}
752779

753780
// 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
797824

798825
result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
799826
if err != nil {
800-
return nil, fmt.Errorf("tracing failed: %v", err)
827+
return nil, fmt.Errorf("tracing failed: %w", err)
801828
}
802829

803830
// Depending on the tracer type, format and return the output.

eth/tracers/api_test.go

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"bytes"
2121
"context"
2222
"crypto/ecdsa"
23+
"encoding/json"
2324
"errors"
2425
"fmt"
2526
"math/big"
@@ -198,7 +199,7 @@ func TestTraceCall(t *testing.T) {
198199
var testSuite = []struct {
199200
blockNumber rpc.BlockNumber
200201
call ethapi.CallArgs
201-
config *TraceConfig
202+
config *TraceCallConfig
202203
expectErr error
203204
expect interface{}
204205
}{
@@ -305,6 +306,147 @@ func TestTraceCall(t *testing.T) {
305306
}
306307
}
307308

309+
func TestOverridenTraceCall(t *testing.T) {
310+
t.Parallel()
311+
312+
// Initialize test accounts
313+
accounts := newAccounts(3)
314+
genesis := &core.Genesis{Alloc: core.GenesisAlloc{
315+
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
316+
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
317+
accounts[2].addr: {Balance: big.NewInt(params.Ether)},
318+
}}
319+
genBlocks := 10
320+
signer := types.HomesteadSigner{}
321+
api := NewAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) {
322+
// Transfer from account[0] to account[1]
323+
// value: 1000 wei
324+
// fee: 0 wei
325+
tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key)
326+
b.AddTx(tx)
327+
}))
328+
randomAccounts, tracer := newAccounts(3), "callTracer"
329+
330+
var testSuite = []struct {
331+
blockNumber rpc.BlockNumber
332+
call ethapi.CallArgs
333+
config *TraceCallConfig
334+
expectErr error
335+
expect *callTrace
336+
}{
337+
// Succcessful call with state overriding
338+
{
339+
blockNumber: rpc.PendingBlockNumber,
340+
call: ethapi.CallArgs{
341+
From: &randomAccounts[0].addr,
342+
To: &randomAccounts[1].addr,
343+
Value: (*hexutil.Big)(big.NewInt(1000)),
344+
},
345+
config: &TraceCallConfig{
346+
Tracer: &tracer,
347+
StateOverrides: &ethapi.StateOverride{
348+
randomAccounts[0].addr: ethapi.OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))},
349+
},
350+
},
351+
expectErr: nil,
352+
expect: &callTrace{
353+
Type: "CALL",
354+
From: randomAccounts[0].addr,
355+
To: randomAccounts[1].addr,
356+
Gas: newRPCUint64(24979000),
357+
GasUsed: newRPCUint64(0),
358+
Value: (*hexutil.Big)(big.NewInt(1000)),
359+
},
360+
},
361+
// Invalid call without state overriding
362+
{
363+
blockNumber: rpc.PendingBlockNumber,
364+
call: ethapi.CallArgs{
365+
From: &randomAccounts[0].addr,
366+
To: &randomAccounts[1].addr,
367+
Value: (*hexutil.Big)(big.NewInt(1000)),
368+
},
369+
config: &TraceCallConfig{
370+
Tracer: &tracer,
371+
},
372+
expectErr: core.ErrInsufficientFundsForTransfer,
373+
expect: nil,
374+
},
375+
// Sucessful simple contract call
376+
//
377+
// // SPDX-License-Identifier: GPL-3.0
378+
//
379+
// pragma solidity >=0.7.0 <0.8.0;
380+
//
381+
// /**
382+
// * @title Storage
383+
// * @dev Store & retrieve value in a variable
384+
// */
385+
// contract Storage {
386+
// uint256 public number;
387+
// constructor() {
388+
// number = block.number;
389+
// }
390+
// }
391+
{
392+
blockNumber: rpc.PendingBlockNumber,
393+
call: ethapi.CallArgs{
394+
From: &randomAccounts[0].addr,
395+
To: &randomAccounts[2].addr,
396+
Data: newRPCBytes(common.Hex2Bytes("8381f58a")), // call number()
397+
},
398+
config: &TraceCallConfig{
399+
Tracer: &tracer,
400+
StateOverrides: &ethapi.StateOverride{
401+
randomAccounts[2].addr: ethapi.OverrideAccount{
402+
Code: newRPCBytes(common.Hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000548156fea2646970667358221220eab35ffa6ab2adfe380772a48b8ba78e82a1b820a18fcb6f59aa4efb20a5f60064736f6c63430007040033")),
403+
StateDiff: newStates([]common.Hash{{}}, []common.Hash{common.BigToHash(big.NewInt(123))}),
404+
},
405+
},
406+
},
407+
expectErr: nil,
408+
expect: &callTrace{
409+
Type: "CALL",
410+
From: randomAccounts[0].addr,
411+
To: randomAccounts[2].addr,
412+
Input: hexutil.Bytes(common.Hex2Bytes("8381f58a")),
413+
Output: hexutil.Bytes(common.BigToHash(big.NewInt(123)).Bytes()),
414+
Gas: newRPCUint64(24978936),
415+
GasUsed: newRPCUint64(2283),
416+
Value: (*hexutil.Big)(big.NewInt(0)),
417+
},
418+
},
419+
}
420+
for _, testspec := range testSuite {
421+
result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config)
422+
if testspec.expectErr != nil {
423+
if err == nil {
424+
t.Errorf("Expect error %v, get nothing", testspec.expectErr)
425+
continue
426+
}
427+
if !errors.Is(err, testspec.expectErr) {
428+
t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err)
429+
}
430+
} else {
431+
if err != nil {
432+
t.Errorf("Expect no error, get %v", err)
433+
continue
434+
}
435+
ret := new(callTrace)
436+
if err := json.Unmarshal(result.(json.RawMessage), ret); err != nil {
437+
t.Fatalf("failed to unmarshal trace result: %v", err)
438+
}
439+
if !jsonEqual(ret, testspec.expect) {
440+
// uncomment this for easier debugging
441+
//have, _ := json.MarshalIndent(ret, "", " ")
442+
//want, _ := json.MarshalIndent(testspec.expect, "", " ")
443+
//t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
444+
t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, testspec.expect)
445+
}
446+
}
447+
}
448+
}
449+
308450
func TestTraceTransaction(t *testing.T) {
309451
t.Parallel()
310452

@@ -469,3 +611,29 @@ func newAccounts(n int) (accounts Accounts) {
469611
sort.Sort(accounts)
470612
return accounts
471613
}
614+
615+
func newRPCBalance(balance *big.Int) **hexutil.Big {
616+
rpcBalance := (*hexutil.Big)(balance)
617+
return &rpcBalance
618+
}
619+
620+
func newRPCUint64(number uint64) *hexutil.Uint64 {
621+
rpcUint64 := hexutil.Uint64(number)
622+
return &rpcUint64
623+
}
624+
625+
func newRPCBytes(bytes []byte) *hexutil.Bytes {
626+
rpcBytes := hexutil.Bytes(bytes)
627+
return &rpcBytes
628+
}
629+
630+
func newStates(keys []common.Hash, vals []common.Hash) *map[common.Hash]common.Hash {
631+
if len(keys) != len(vals) {
632+
panic("invalid input")
633+
}
634+
m := make(map[common.Hash]common.Hash)
635+
for i := 0; i < len(keys); i++ {
636+
m[keys[i]] = vals[i]
637+
}
638+
return &m
639+
}

internal/ethapi/api.go

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/ethereum/go-ethereum/consensus/clique"
3737
"github.com/ethereum/go-ethereum/consensus/ethash"
3838
"github.com/ethereum/go-ethereum/core"
39+
"github.com/ethereum/go-ethereum/core/state"
3940
"github.com/ethereum/go-ethereum/core/types"
4041
"github.com/ethereum/go-ethereum/core/vm"
4142
"github.com/ethereum/go-ethereum/crypto"
@@ -802,29 +803,29 @@ func (args *CallArgs) ToMessage(globalGasCap uint64) types.Message {
802803
return msg
803804
}
804805

805-
// account indicates the overriding fields of account during the execution of
806-
// a message call.
806+
// OverrideAccount indicates the overriding fields of account during the execution
807+
// of a message call.
807808
// Note, state and stateDiff can't be specified at the same time. If state is
808809
// set, message execution will only use the data in the given state. Otherwise
809810
// if statDiff is set, all diff will be applied first and then execute the call
810811
// message.
811-
type account struct {
812+
type OverrideAccount struct {
812813
Nonce *hexutil.Uint64 `json:"nonce"`
813814
Code *hexutil.Bytes `json:"code"`
814815
Balance **hexutil.Big `json:"balance"`
815816
State *map[common.Hash]common.Hash `json:"state"`
816817
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
817818
}
818819

819-
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) {
820-
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
820+
// StateOverride is the collection of overriden accounts.
821+
type StateOverride map[common.Address]OverrideAccount
821822

822-
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
823-
if state == nil || err != nil {
824-
return nil, err
823+
// Apply overrides the fields of specified accounts into the given state.
824+
func (diff *StateOverride) Apply(state *state.StateDB) error {
825+
if diff == nil {
826+
return nil
825827
}
826-
// Override the fields of specified contracts before execution.
827-
for addr, account := range overrides {
828+
for addr, account := range *diff {
828829
// Override account nonce.
829830
if account.Nonce != nil {
830831
state.SetNonce(addr, uint64(*account.Nonce))
@@ -838,7 +839,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
838839
state.SetBalance(addr, (*big.Int)(*account.Balance))
839840
}
840841
if account.State != nil && account.StateDiff != nil {
841-
return nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
842+
return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
842843
}
843844
// Replace entire state if caller requires.
844845
if account.State != nil {
@@ -851,6 +852,19 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
851852
}
852853
}
853854
}
855+
return nil
856+
}
857+
858+
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) {
859+
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
860+
861+
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
862+
if state == nil || err != nil {
863+
return nil, err
864+
}
865+
if err := overrides.Apply(state); err != nil {
866+
return nil, err
867+
}
854868
// Setup context so it may be cancelled the call has completed
855869
// or, in case of unmetered gas, setup a context with a timeout.
856870
var cancel context.CancelFunc
@@ -929,12 +943,8 @@ func (e *revertError) ErrorData() interface{} {
929943
//
930944
// Note, this function doesn't make and changes in the state/blockchain and is
931945
// useful to execute and retrieve values.
932-
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]account) (hexutil.Bytes, error) {
933-
var accounts map[common.Address]account
934-
if overrides != nil {
935-
accounts = *overrides
936-
}
937-
result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
946+
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
947+
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
938948
if err != nil {
939949
return nil, err
940950
}

0 commit comments

Comments
 (0)