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:
gary rong
2021-04-21 15:21:22 +08:00
committed by GitHub
parent cc33398cef
commit dd9c3225cf
3 changed files with 226 additions and 21 deletions

View File

@ -161,6 +161,16 @@ type TraceConfig struct {
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.
type StdTraceConfig struct {
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
// 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.
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
var (
err error
@ -730,6 +740,8 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHa
block, err = api.blockByHash(ctx, hash)
} else if number, ok := blockNrOrHash.Number(); ok {
block, err = api.blockByNumber(ctx, number)
} else {
return nil, errors.New("invalid arguments; neither block nor hash specified")
}
if err != nil {
return nil, err
@ -743,11 +755,26 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.CallArgs, blockNrOrHa
if err != nil {
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
msg := args.ToMessage(api.backend.RPCGasCap())
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
@ -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()))
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.

View File

@ -20,6 +20,7 @@ import (
"bytes"
"context"
"crypto/ecdsa"
"encoding/json"
"errors"
"fmt"
"math/big"
@ -198,7 +199,7 @@ func TestTraceCall(t *testing.T) {
var testSuite = []struct {
blockNumber rpc.BlockNumber
call ethapi.CallArgs
config *TraceConfig
config *TraceCallConfig
expectErr error
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: &ethapi.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: &ethapi.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) {
t.Parallel()
@ -469,3 +611,29 @@ func newAccounts(n int) (accounts Accounts) {
sort.Sort(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
}