eth/tracers: add support for block overrides in debug_traceCall (#24871)
This PR adds support for block overrides when doing debug_traceCall. - Previously, debug_traceCall against pending erroneously used a common.Hash{} stateroot when looking up the state, meaning that a totally empty state was used -- so it always failed, - With this change, we reject executing debug_traceCall against pending. - And we add ability to override all evm-visible header fields.
This commit is contained in:
parent
f9806dc872
commit
d8a2305565
@ -84,6 +84,11 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash
|
|||||||
var cache []common.Hash
|
var cache []common.Hash
|
||||||
|
|
||||||
return func(n uint64) common.Hash {
|
return func(n uint64) common.Hash {
|
||||||
|
if ref.Number.Uint64() <= n {
|
||||||
|
// This situation can happen if we're doing tracing and using
|
||||||
|
// block overrides.
|
||||||
|
return common.Hash{}
|
||||||
|
}
|
||||||
// If there's no hash cache yet, make one
|
// If there's no hash cache yet, make one
|
||||||
if len(cache) == 0 {
|
if len(cache) == 0 {
|
||||||
cache = append(cache, ref.ParentHash)
|
cache = append(cache, ref.ParentHash)
|
||||||
|
@ -179,6 +179,7 @@ type TraceCallConfig struct {
|
|||||||
Timeout *string
|
Timeout *string
|
||||||
Reexec *uint64
|
Reexec *uint64
|
||||||
StateOverrides *ethapi.StateOverride
|
StateOverrides *ethapi.StateOverride
|
||||||
|
BlockOverrides *ethapi.BlockOverrides
|
||||||
}
|
}
|
||||||
|
|
||||||
// StdTraceConfig holds extra parameters to standard-json trace functions.
|
// StdTraceConfig holds extra parameters to standard-json trace functions.
|
||||||
@ -806,7 +807,6 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
|
|||||||
// TraceCall lets you trace a given eth_call. It collects the structured logs
|
// TraceCall lets you trace a given eth_call. It collects the structured logs
|
||||||
// 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.
|
|
||||||
func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
|
func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
|
||||||
// Try to retrieve the specified block
|
// Try to retrieve the specified block
|
||||||
var (
|
var (
|
||||||
@ -816,6 +816,14 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
|||||||
if hash, ok := blockNrOrHash.Hash(); ok {
|
if hash, ok := blockNrOrHash.Hash(); ok {
|
||||||
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 {
|
||||||
|
if number == rpc.PendingBlockNumber {
|
||||||
|
// We don't have access to the miner here. For tracing 'future' transactions,
|
||||||
|
// it can be done with block- and state-overrides instead, which offers
|
||||||
|
// more flexibility and stability than trying to trace on 'pending', since
|
||||||
|
// the contents of 'pending' is unstable and probably not a true representation
|
||||||
|
// of what the next actual block is likely to contain.
|
||||||
|
return nil, errors.New("tracing on top of pending is not supported")
|
||||||
|
}
|
||||||
block, err = api.blockByNumber(ctx, number)
|
block, err = api.blockByNumber(ctx, number)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("invalid arguments; neither block nor hash specified")
|
return nil, errors.New("invalid arguments; neither block nor hash specified")
|
||||||
@ -832,18 +840,19 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Apply the customized state rules if required.
|
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
|
// Apply the customization rules if required.
|
||||||
if config != nil {
|
if config != nil {
|
||||||
if err := config.StateOverrides.Apply(statedb); err != nil {
|
if err := config.StateOverrides.Apply(statedb); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
config.BlockOverrides.Apply(&vmctx)
|
||||||
}
|
}
|
||||||
// Execute the trace
|
// Execute the trace
|
||||||
msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee())
|
msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
|
||||||
|
|
||||||
var traceConfig *TraceConfig
|
var traceConfig *TraceConfig
|
||||||
if config != nil {
|
if config != nil {
|
||||||
|
@ -196,13 +196,12 @@ func TestTraceCall(t *testing.T) {
|
|||||||
tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key)
|
tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key)
|
||||||
b.AddTx(tx)
|
b.AddTx(tx)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
var testSuite = []struct {
|
var testSuite = []struct {
|
||||||
blockNumber rpc.BlockNumber
|
blockNumber rpc.BlockNumber
|
||||||
call ethapi.TransactionArgs
|
call ethapi.TransactionArgs
|
||||||
config *TraceCallConfig
|
config *TraceCallConfig
|
||||||
expectErr error
|
expectErr error
|
||||||
expect interface{}
|
expect string
|
||||||
}{
|
}{
|
||||||
// Standard JSON trace upon the genesis, plain transfer.
|
// Standard JSON trace upon the genesis, plain transfer.
|
||||||
{
|
{
|
||||||
@ -214,12 +213,7 @@ func TestTraceCall(t *testing.T) {
|
|||||||
},
|
},
|
||||||
config: nil,
|
config: nil,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
expect: &logger.ExecutionResult{
|
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
|
||||||
Gas: params.TxGas,
|
|
||||||
Failed: false,
|
|
||||||
ReturnValue: "",
|
|
||||||
StructLogs: []logger.StructLogRes{},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// Standard JSON trace upon the head, plain transfer.
|
// Standard JSON trace upon the head, plain transfer.
|
||||||
{
|
{
|
||||||
@ -231,12 +225,7 @@ func TestTraceCall(t *testing.T) {
|
|||||||
},
|
},
|
||||||
config: nil,
|
config: nil,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
expect: &logger.ExecutionResult{
|
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
|
||||||
Gas: params.TxGas,
|
|
||||||
Failed: false,
|
|
||||||
ReturnValue: "",
|
|
||||||
StructLogs: []logger.StructLogRes{},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// Standard JSON trace upon the non-existent block, error expects
|
// Standard JSON trace upon the non-existent block, error expects
|
||||||
{
|
{
|
||||||
@ -248,7 +237,7 @@ func TestTraceCall(t *testing.T) {
|
|||||||
},
|
},
|
||||||
config: nil,
|
config: nil,
|
||||||
expectErr: fmt.Errorf("block #%d not found", genBlocks+1),
|
expectErr: fmt.Errorf("block #%d not found", genBlocks+1),
|
||||||
expect: nil,
|
//expect: nil,
|
||||||
},
|
},
|
||||||
// Standard JSON trace upon the latest block
|
// Standard JSON trace upon the latest block
|
||||||
{
|
{
|
||||||
@ -260,14 +249,9 @@ func TestTraceCall(t *testing.T) {
|
|||||||
},
|
},
|
||||||
config: nil,
|
config: nil,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
expect: &logger.ExecutionResult{
|
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
|
||||||
Gas: params.TxGas,
|
|
||||||
Failed: false,
|
|
||||||
ReturnValue: "",
|
|
||||||
StructLogs: []logger.StructLogRes{},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// Standard JSON trace upon the pending block
|
// Tracing on 'pending' should fail:
|
||||||
{
|
{
|
||||||
blockNumber: rpc.PendingBlockNumber,
|
blockNumber: rpc.PendingBlockNumber,
|
||||||
call: ethapi.TransactionArgs{
|
call: ethapi.TransactionArgs{
|
||||||
@ -276,36 +260,48 @@ func TestTraceCall(t *testing.T) {
|
|||||||
Value: (*hexutil.Big)(big.NewInt(1000)),
|
Value: (*hexutil.Big)(big.NewInt(1000)),
|
||||||
},
|
},
|
||||||
config: nil,
|
config: nil,
|
||||||
expectErr: nil,
|
expectErr: errors.New("tracing on top of pending is not supported"),
|
||||||
expect: &logger.ExecutionResult{
|
},
|
||||||
Gas: params.TxGas,
|
{
|
||||||
Failed: false,
|
blockNumber: rpc.LatestBlockNumber,
|
||||||
ReturnValue: "",
|
call: ethapi.TransactionArgs{
|
||||||
StructLogs: []logger.StructLogRes{},
|
From: &accounts[0].addr,
|
||||||
|
Input: &hexutil.Bytes{0x43}, // blocknumber
|
||||||
},
|
},
|
||||||
|
config: &TraceCallConfig{
|
||||||
|
BlockOverrides: ðapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
|
||||||
|
},
|
||||||
|
expectErr: nil,
|
||||||
|
expect: ` {"gas":53018,"failed":false,"returnValue":"","structLogs":[
|
||||||
|
{"pc":0,"op":"NUMBER","gas":24946984,"gasCost":2,"depth":1,"stack":[]},
|
||||||
|
{"pc":1,"op":"STOP","gas":24946982,"gasCost":0,"depth":1,"stack":["0x1337"]}]}`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, testspec := range testSuite {
|
for i, testspec := range testSuite {
|
||||||
result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config)
|
result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config)
|
||||||
if testspec.expectErr != nil {
|
if testspec.expectErr != nil {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expect error %v, get nothing", testspec.expectErr)
|
t.Errorf("test %d: expect error %v, got nothing", i, testspec.expectErr)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(err, testspec.expectErr) {
|
if !reflect.DeepEqual(err, testspec.expectErr) {
|
||||||
t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err)
|
t.Errorf("test %d: error mismatch, want %v, git %v", i, testspec.expectErr, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expect no error, get %v", err)
|
t.Errorf("test %d: expect no error, got %v", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var have *logger.ExecutionResult
|
var have *logger.ExecutionResult
|
||||||
if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil {
|
if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil {
|
||||||
t.Errorf("failed to unmarshal result %v", err)
|
t.Errorf("test %d: failed to unmarshal result %v", i, err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(have, testspec.expect) {
|
var want *logger.ExecutionResult
|
||||||
t.Errorf("Result mismatch, want %v, get %v", testspec.expect, have)
|
if err := json.Unmarshal([]byte(testspec.expect), &want); err != nil {
|
||||||
|
t.Errorf("test %d: failed to unmarshal result %v", i, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(have, want) {
|
||||||
|
t.Errorf("test %d: result mismatch, want %v, got %v", i, testspec.expect, string(result.(json.RawMessage)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -446,7 +442,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||||||
type res struct {
|
type res struct {
|
||||||
Gas int
|
Gas int
|
||||||
Failed bool
|
Failed bool
|
||||||
returnValue string
|
ReturnValue string
|
||||||
}
|
}
|
||||||
var testSuite = []struct {
|
var testSuite = []struct {
|
||||||
blockNumber rpc.BlockNumber
|
blockNumber rpc.BlockNumber
|
||||||
@ -457,7 +453,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
// Call which can only succeed if state is state overridden
|
// Call which can only succeed if state is state overridden
|
||||||
{
|
{
|
||||||
blockNumber: rpc.PendingBlockNumber,
|
blockNumber: rpc.LatestBlockNumber,
|
||||||
call: ethapi.TransactionArgs{
|
call: ethapi.TransactionArgs{
|
||||||
From: &randomAccounts[0].addr,
|
From: &randomAccounts[0].addr,
|
||||||
To: &randomAccounts[1].addr,
|
To: &randomAccounts[1].addr,
|
||||||
@ -472,7 +468,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||||||
},
|
},
|
||||||
// Invalid call without state overriding
|
// Invalid call without state overriding
|
||||||
{
|
{
|
||||||
blockNumber: rpc.PendingBlockNumber,
|
blockNumber: rpc.LatestBlockNumber,
|
||||||
call: ethapi.TransactionArgs{
|
call: ethapi.TransactionArgs{
|
||||||
From: &randomAccounts[0].addr,
|
From: &randomAccounts[0].addr,
|
||||||
To: &randomAccounts[1].addr,
|
To: &randomAccounts[1].addr,
|
||||||
@ -498,7 +494,7 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
{
|
{
|
||||||
blockNumber: rpc.PendingBlockNumber,
|
blockNumber: rpc.LatestBlockNumber,
|
||||||
call: ethapi.TransactionArgs{
|
call: ethapi.TransactionArgs{
|
||||||
From: &randomAccounts[0].addr,
|
From: &randomAccounts[0].addr,
|
||||||
To: &randomAccounts[2].addr,
|
To: &randomAccounts[2].addr,
|
||||||
@ -515,6 +511,39 @@ func TestTracingWithOverrides(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: `{"gas":23347,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000007b"}`,
|
want: `{"gas":23347,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000007b"}`,
|
||||||
},
|
},
|
||||||
|
{ // Override blocknumber
|
||||||
|
blockNumber: rpc.LatestBlockNumber,
|
||||||
|
call: ethapi.TransactionArgs{
|
||||||
|
From: &accounts[0].addr,
|
||||||
|
// BLOCKNUMBER PUSH1 MSTORE
|
||||||
|
Input: newRPCBytes(common.Hex2Bytes("4360005260206000f3")),
|
||||||
|
//&hexutil.Bytes{0x43}, // blocknumber
|
||||||
|
},
|
||||||
|
config: &TraceCallConfig{
|
||||||
|
BlockOverrides: ðapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
|
||||||
|
},
|
||||||
|
want: `{"gas":59537,"failed":false,"returnValue":"0000000000000000000000000000000000000000000000000000000000001337"}`,
|
||||||
|
},
|
||||||
|
{ // Override blocknumber, and query a blockhash
|
||||||
|
blockNumber: rpc.LatestBlockNumber,
|
||||||
|
call: ethapi.TransactionArgs{
|
||||||
|
From: &accounts[0].addr,
|
||||||
|
Input: &hexutil.Bytes{
|
||||||
|
0x60, 0x00, 0x40, // BLOCKHASH(0)
|
||||||
|
0x60, 0x00, 0x52, // STORE memory offset 0
|
||||||
|
0x61, 0x13, 0x36, 0x40, // BLOCKHASH(0x1336)
|
||||||
|
0x60, 0x20, 0x52, // STORE memory offset 32
|
||||||
|
0x61, 0x13, 0x37, 0x40, // BLOCKHASH(0x1337)
|
||||||
|
0x60, 0x40, 0x52, // STORE memory offset 64
|
||||||
|
0x60, 0x60, 0x60, 0x00, 0xf3, // RETURN (0-96)
|
||||||
|
|
||||||
|
}, // blocknumber
|
||||||
|
},
|
||||||
|
config: &TraceCallConfig{
|
||||||
|
BlockOverrides: ðapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
|
||||||
|
},
|
||||||
|
want: `{"gas":72666,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for i, tc := range testSuite {
|
for i, tc := range testSuite {
|
||||||
result, err := api.TraceCall(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, tc.config)
|
result, err := api.TraceCall(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, tc.config)
|
||||||
|
@ -888,6 +888,41 @@ func (diff *StateOverride) Apply(state *state.StateDB) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockOverrides is a set of header fields to override.
|
||||||
|
type BlockOverrides struct {
|
||||||
|
Number *hexutil.Big
|
||||||
|
Difficulty *hexutil.Big
|
||||||
|
Time *hexutil.Big
|
||||||
|
GasLimit *hexutil.Uint64
|
||||||
|
Coinbase *common.Address
|
||||||
|
Random *common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply overrides the given header fields into the given block context.
|
||||||
|
func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
|
||||||
|
if diff == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if diff.Number != nil {
|
||||||
|
blockCtx.BlockNumber = diff.Number.ToInt()
|
||||||
|
}
|
||||||
|
if diff.Difficulty != nil {
|
||||||
|
blockCtx.Difficulty = diff.Difficulty.ToInt()
|
||||||
|
}
|
||||||
|
if diff.Time != nil {
|
||||||
|
blockCtx.Time = diff.Time.ToInt()
|
||||||
|
}
|
||||||
|
if diff.GasLimit != nil {
|
||||||
|
blockCtx.GasLimit = uint64(*diff.GasLimit)
|
||||||
|
}
|
||||||
|
if diff.Coinbase != nil {
|
||||||
|
blockCtx.Coinbase = *diff.Coinbase
|
||||||
|
}
|
||||||
|
if diff.Random != nil {
|
||||||
|
blockCtx.Random = diff.Random
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
|
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, 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())
|
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user