Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

eth/tracers: add support for block overrides in debug_traceCall #24871

Merged
merged 4 commits into from Jun 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/evm.go
Expand Up @@ -84,6 +84,11 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash
var cache []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 len(cache) == 0 {
cache = append(cache, ref.ParentHash)
Expand Down
15 changes: 12 additions & 3 deletions eth/tracers/api.go
Expand Up @@ -180,6 +180,7 @@ type TraceCallConfig struct {
Timeout *string
Reexec *uint64
StateOverrides *ethapi.StateOverride
BlockOverrides *ethapi.BlockOverrides
}

// StdTraceConfig holds extra parameters to standard-json trace functions.
Expand Down Expand Up @@ -807,7 +808,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
// 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.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) {
// Try to retrieve the specified block
var (
Expand All @@ -817,6 +817,14 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
if hash, ok := blockNrOrHash.Hash(); ok {
block, err = api.blockByHash(ctx, hash)
} 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)
} else {
return nil, errors.New("invalid arguments; neither block nor hash specified")
Expand All @@ -833,18 +841,19 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
if err != nil {
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 err := config.StateOverrides.Apply(statedb); err != nil {
return nil, err
}
config.BlockOverrides.Apply(&vmctx)
}
// Execute the trace
msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee())
if err != nil {
return nil, err
}
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)

var traceConfig *TraceConfig
if config != nil {
Expand Down
107 changes: 68 additions & 39 deletions eth/tracers/api_test.go
Expand Up @@ -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)
b.AddTx(tx)
}))

var testSuite = []struct {
blockNumber rpc.BlockNumber
call ethapi.TransactionArgs
config *TraceCallConfig
expectErr error
expect interface{}
expect string
}{
// Standard JSON trace upon the genesis, plain transfer.
{
Expand All @@ -214,12 +213,7 @@ func TestTraceCall(t *testing.T) {
},
config: nil,
expectErr: nil,
expect: &logger.ExecutionResult{
Gas: params.TxGas,
Failed: false,
ReturnValue: "",
StructLogs: []logger.StructLogRes{},
},
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
},
// Standard JSON trace upon the head, plain transfer.
{
Expand All @@ -231,12 +225,7 @@ func TestTraceCall(t *testing.T) {
},
config: nil,
expectErr: nil,
expect: &logger.ExecutionResult{
Gas: params.TxGas,
Failed: false,
ReturnValue: "",
StructLogs: []logger.StructLogRes{},
},
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
},
// Standard JSON trace upon the non-existent block, error expects
{
Expand All @@ -248,7 +237,7 @@ func TestTraceCall(t *testing.T) {
},
config: nil,
expectErr: fmt.Errorf("block #%d not found", genBlocks+1),
expect: nil,
//expect: nil,
},
// Standard JSON trace upon the latest block
{
Expand All @@ -260,14 +249,9 @@ func TestTraceCall(t *testing.T) {
},
config: nil,
expectErr: nil,
expect: &logger.ExecutionResult{
Gas: params.TxGas,
Failed: false,
ReturnValue: "",
StructLogs: []logger.StructLogRes{},
},
expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`,
},
// Standard JSON trace upon the pending block
// Tracing on 'pending' should fail:
{
blockNumber: rpc.PendingBlockNumber,
call: ethapi.TransactionArgs{
Expand All @@ -276,36 +260,48 @@ func TestTraceCall(t *testing.T) {
Value: (*hexutil.Big)(big.NewInt(1000)),
},
config: nil,
expectErr: nil,
expect: &logger.ExecutionResult{
Gas: params.TxGas,
Failed: false,
ReturnValue: "",
StructLogs: []logger.StructLogRes{},
expectErr: errors.New("tracing on top of pending is not supported"),
},
{
blockNumber: rpc.LatestBlockNumber,
call: ethapi.TransactionArgs{
From: &accounts[0].addr,
Input: &hexutil.Bytes{0x43}, // blocknumber
},
config: &TraceCallConfig{
BlockOverrides: &ethapi.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)
if testspec.expectErr != 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
}
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 {
if err != nil {
t.Errorf("Expect no error, get %v", err)
t.Errorf("test %d: expect no error, got %v", i, err)
continue
}
var have *logger.ExecutionResult
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)
}
var want *logger.ExecutionResult
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, testspec.expect) {
t.Errorf("Result mismatch, want %v, get %v", testspec.expect, have)
if !reflect.DeepEqual(have, want) {
t.Errorf("test %d: result mismatch, want %v, got %v", i, testspec.expect, string(result.(json.RawMessage)))
}
}
}
Expand Down Expand Up @@ -446,7 +442,7 @@ func TestTracingWithOverrides(t *testing.T) {
type res struct {
Gas int
Failed bool
returnValue string
ReturnValue string
}
var testSuite = []struct {
blockNumber rpc.BlockNumber
Expand All @@ -457,7 +453,7 @@ func TestTracingWithOverrides(t *testing.T) {
}{
// Call which can only succeed if state is state overridden
{
blockNumber: rpc.PendingBlockNumber,
blockNumber: rpc.LatestBlockNumber,
call: ethapi.TransactionArgs{
From: &randomAccounts[0].addr,
To: &randomAccounts[1].addr,
Expand All @@ -472,7 +468,7 @@ func TestTracingWithOverrides(t *testing.T) {
},
// Invalid call without state overriding
{
blockNumber: rpc.PendingBlockNumber,
blockNumber: rpc.LatestBlockNumber,
call: ethapi.TransactionArgs{
From: &randomAccounts[0].addr,
To: &randomAccounts[1].addr,
Expand All @@ -498,7 +494,7 @@ func TestTracingWithOverrides(t *testing.T) {
// }
// }
{
blockNumber: rpc.PendingBlockNumber,
blockNumber: rpc.LatestBlockNumber,
call: ethapi.TransactionArgs{
From: &randomAccounts[0].addr,
To: &randomAccounts[2].addr,
Expand All @@ -515,6 +511,39 @@ func TestTracingWithOverrides(t *testing.T) {
},
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: &ethapi.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: &ethapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
},
want: `{"gas":72666,"failed":false,"returnValue":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}`,
},
}
for i, tc := range testSuite {
result, err := api.TraceCall(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, tc.config)
Expand Down
35 changes: 35 additions & 0 deletions internal/ethapi/api.go
Expand Up @@ -888,6 +888,41 @@ func (diff *StateOverride) Apply(state *state.StateDB) error {
return nil
}

// BlockOverrides is a set of header fields to override.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why you defined this in internal/ethapi instead of eth/tracers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why you defined this in internal/ethapi instead of eth/tracers?

I didn't really think about it, I guess it wound up there because that's where StateOverride is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I guess we can keep it there in case we want to add BlockOverride to eth_call as well

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) {
holiman marked this conversation as resolved.
Show resolved Hide resolved
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) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())

Expand Down