add : Add state sync transaction to debug_traceBlock

This commit is contained in:
Shivam Sharma 2022-10-12 20:06:00 +05:30 committed by Jerry
parent 439d588fc9
commit b8c3b0043c
4 changed files with 178 additions and 46 deletions

View file

@ -30,22 +30,22 @@ func (c ChainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
} }
// callmsg implements core.Message to allow passing it as a transaction simulator. // callmsg implements core.Message to allow passing it as a transaction simulator.
type callmsg struct { type Callmsg struct {
ethereum.CallMsg ethereum.CallMsg
} }
func (m callmsg) From() common.Address { return m.CallMsg.From } func (m Callmsg) From() common.Address { return m.CallMsg.From }
func (m callmsg) Nonce() uint64 { return 0 } func (m Callmsg) Nonce() uint64 { return 0 }
func (m callmsg) CheckNonce() bool { return false } func (m Callmsg) CheckNonce() bool { return false }
func (m callmsg) To() *common.Address { return m.CallMsg.To } func (m Callmsg) To() *common.Address { return m.CallMsg.To }
func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice } func (m Callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callmsg) Gas() uint64 { return m.CallMsg.Gas } func (m Callmsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callmsg) Value() *big.Int { return m.CallMsg.Value } func (m Callmsg) Value() *big.Int { return m.CallMsg.Value }
func (m callmsg) Data() []byte { return m.CallMsg.Data } func (m Callmsg) Data() []byte { return m.CallMsg.Data }
// get system message // get system message
func GetSystemMessage(toAddress common.Address, data []byte) callmsg { func GetSystemMessage(toAddress common.Address, data []byte) Callmsg {
return callmsg{ return Callmsg{
ethereum.CallMsg{ ethereum.CallMsg{
From: systemAddress, From: systemAddress,
Gas: math.MaxUint64 / 2, Gas: math.MaxUint64 / 2,
@ -59,7 +59,7 @@ func GetSystemMessage(toAddress common.Address, data []byte) callmsg {
// apply message // apply message
func ApplyMessage( func ApplyMessage(
msg callmsg, msg Callmsg,
state *state.StateDB, state *state.StateDB,
header *types.Header, header *types.Header,
chainConfig *params.ChainConfig, chainConfig *params.ChainConfig,
@ -90,4 +90,32 @@ func ApplyMessage(
gasUsed := initialGas - gasLeft gasUsed := initialGas - gasLeft
return gasUsed, nil return gasUsed, nil
}
func ApplyBorMessage(vmenv vm.EVM, msg Callmsg) (*core.ExecutionResult, error) {
initialGas := msg.Gas()
// Apply the transaction to the current state (included in the env)
ret, gasLeft, err := vmenv.Call(
vm.AccountRef(msg.From()),
*msg.To(),
msg.Data(),
msg.Gas(),
msg.Value(),
)
// Update the state with pending changes
if err != nil {
vmenv.StateDB.Finalise(true)
}
gasUsed := initialGas - gasLeft
return &core.ExecutionResult{
UsedGas: gasUsed,
Err: err,
ReturnData: ret,
}, nil
} }

View file

@ -74,6 +74,8 @@ type StateDB interface {
AddPreimage(common.Hash, []byte) AddPreimage(common.Hash, []byte)
ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error
Finalise(bool)
} }
// CallContext provides a basic interface for the EVM calling conventions. The EVM // CallContext provides a basic interface for the EVM calling conventions. The EVM

View file

@ -28,9 +28,11 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/bor/statefull"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
@ -80,6 +82,9 @@ type Backend interface {
// so this method should be called with the parent. // so this method should be called with the parent.
StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error)
StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error)
// Bor related APIs
GetBorBlockTransactionWithBlockHash(ctx context.Context, txHash common.Hash, blockHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
} }
// API is the collection of tracing APIs exposed over the private debugging endpoint. // API is the collection of tracing APIs exposed over the private debugging endpoint.
@ -164,6 +169,25 @@ func (api *API) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber
return api.blockByHash(ctx, hash) return api.blockByHash(ctx, hash)
} }
// returns block transactions along with state-sync transaction if present
func (api *API) getAllBlockTransactions(ctx context.Context, block *types.Block) (types.Transactions, bool) {
txs := block.Transactions()
stateSyncPresent := false
borReceipt := rawdb.ReadBorReceipt(api.backend.ChainDb(), block.Hash(), block.NumberU64())
if borReceipt != nil {
txHash := types.GetDerivedBorTxHash(types.BorReceiptKey(block.Number().Uint64(), block.Hash()))
if txHash != (common.Hash{}) {
borTx, _, _, _, _ := api.backend.GetBorBlockTransactionWithBlockHash(ctx, txHash, block.Hash())
txs = append(txs, borTx)
stateSyncPresent = true
}
}
return txs, stateSyncPresent
}
// TraceConfig holds extra parameters to trace functions. // TraceConfig holds extra parameters to trace functions.
type TraceConfig struct { type TraceConfig struct {
*logger.Config *logger.Config
@ -274,19 +298,30 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number()) signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number())
blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(localctx), nil) blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(localctx), nil)
// Trace all the transactions contained within // Trace all the transactions contained within
for i, tx := range task.block.Transactions() { txs, stateSyncPresent := api.getAllBlockTransactions(ctx, task.block)
for i, tx := range txs {
msg, _ := tx.AsMessage(signer, task.block.BaseFee()) msg, _ := tx.AsMessage(signer, task.block.BaseFee())
txctx := &Context{ txctx := &Context{
BlockHash: task.block.Hash(), BlockHash: task.block.Hash(),
TxIndex: i, TxIndex: i,
TxHash: tx.Hash(), TxHash: tx.Hash(),
} }
res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config)
var res interface{}
var err error
if stateSyncPresent && i == len(txs)-1 {
res, err = api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config, true)
} else {
res, err = api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config, false)
}
if err != nil { if err != nil {
task.results[i] = &txTraceResult{Error: err.Error()} task.results[i] = &txTraceResult{Error: err.Error()}
log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err)
break break
} }
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
task.statedb.Finalise(api.backend.ChainConfig().IsEIP158(task.block.Number())) task.statedb.Finalise(api.backend.ChainConfig().IsEIP158(task.block.Number()))
task.results[i] = &txTraceResult{Result: res} task.results[i] = &txTraceResult{Result: res}
@ -492,6 +527,23 @@ func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash,
return api.standardTraceBlockToFile(ctx, block, config) return api.standardTraceBlockToFile(ctx, block, config)
} }
func prepareCallMessage(msg core.Message) statefull.Callmsg {
return statefull.Callmsg{
ethereum.CallMsg{
From: msg.From(),
To: msg.To(),
Gas: msg.Gas(),
GasPrice: msg.GasPrice(),
GasFeeCap: msg.GasFeeCap(),
GasTipCap: msg.GasTipCap(),
Value: msg.Value(),
Data: msg.Data(),
AccessList: msg.AccessList(),
}}
}
// IntermediateRoots executes a block (bad- or canon- or side-), and returns a list // IntermediateRoots executes a block (bad- or canon- or side-), and returns a list
// of intermediate roots: the stateroot after each transaction. // of intermediate roots: the stateroot after each transaction.
func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config *TraceConfig) ([]common.Hash, error) { func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config *TraceConfig) ([]common.Hash, error) {
@ -525,23 +577,42 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
deleteEmptyObjects = chainConfig.IsEIP158(block.Number()) deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
) )
for i, tx := range block.Transactions() { txs, stateSyncPresent := api.getAllBlockTransactions(ctx, block)
for i, tx := range txs {
var ( var (
msg, _ = tx.AsMessage(signer, block.BaseFee()) msg, _ = tx.AsMessage(signer, block.BaseFee())
txContext = core.NewEVMTxContext(msg) txContext = core.NewEVMTxContext(msg)
vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{}) vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{})
) )
statedb.Prepare(tx.Hash(), i) statedb.Prepare(tx.Hash(), i)
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { if stateSyncPresent && i == len(txs)-1 {
log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err) callmsg := prepareCallMessage(msg)
// We intentionally don't return the error here: if we do, then the RPC server will not
// return the roots. Most likely, the caller already knows that a certain transaction fails to if _, err := statefull.ApplyMessage(ctx, callmsg, statedb, block.Header(), api.backend.ChainConfig(), api.chainContext(ctx)); err != nil {
// be included, but still want the intermediate roots that led to that point. log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err)
// It may happen the tx_N causes an erroneous state, which in turn causes tx_N+M to not be // We intentionally don't return the error here: if we do, then the RPC server will not
// executable. // return the roots. Most likely, the caller already knows that a certain transaction fails to
// N.B: This should never happen while tracing canon blocks, only when tracing bad blocks. // be included, but still want the intermediate roots that led to that point.
return roots, nil // It may happen the tx_N causes an erroneous state, which in turn causes tx_N+M to not be
// executable.
// N.B: This should never happen while tracing canon blocks, only when tracing bad blocks.
return roots, nil
}
} else {
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err)
// We intentionally don't return the error here: if we do, then the RPC server will not
// return the roots. Most likely, the caller already knows that a certain transaction fails to
// be included, but still want the intermediate roots that led to that point.
// It may happen the tx_N causes an erroneous state, which in turn causes tx_N+M to not be
// executable.
// N.B: This should never happen while tracing canon blocks, only when tracing bad blocks.
return roots, nil
}
} }
// calling IntermediateRoot will internally call Finalize on the state // calling IntermediateRoot will internally call Finalize on the state
// so any modifications are written to the trie // so any modifications are written to the trie
roots = append(roots, statedb.IntermediateRoot(deleteEmptyObjects)) roots = append(roots, statedb.IntermediateRoot(deleteEmptyObjects))
@ -581,9 +652,9 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
} }
// Execute all the transaction contained within the block concurrently // Execute all the transaction contained within the block concurrently
var ( var (
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
txs = block.Transactions() txs, stateSyncPresent = api.getAllBlockTransactions(ctx, block)
results = make([]*txTraceResult, len(txs)) results = make([]*txTraceResult, len(txs))
pend = new(sync.WaitGroup) pend = new(sync.WaitGroup)
jobs = make(chan *txTraceTask, len(txs)) jobs = make(chan *txTraceTask, len(txs))
@ -606,7 +677,13 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
TxIndex: task.index, TxIndex: task.index,
TxHash: txs[task.index].Hash(), TxHash: txs[task.index].Hash(),
} }
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) var res interface{}
var err error
if stateSyncPresent && task.index == len(txs)-1 {
res, err = api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, true)
} else {
res, err = api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, false)
}
if err != nil { if err != nil {
results[task.index] = &txTraceResult{Error: err.Error()} results[task.index] = &txTraceResult{Error: err.Error()}
continue continue
@ -650,7 +727,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) { func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) {
// If we're tracing a single transaction, make sure it's present // If we're tracing a single transaction, make sure it's present
if config != nil && config.TxHash != (common.Hash{}) { if config != nil && config.TxHash != (common.Hash{}) {
if !containsTx(block, config.TxHash) { if !api.containsTx(ctx, block, config.TxHash) {
return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash) return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash)
} }
} }
@ -705,7 +782,8 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
canon = false canon = false
} }
} }
for i, tx := range block.Transactions() { txs, stateSyncPresent := api.getAllBlockTransactions(ctx, block)
for i, tx := range txs {
// Prepare the trasaction for un-traced execution // Prepare the trasaction for un-traced execution
var ( var (
msg, _ = tx.AsMessage(signer, block.BaseFee()) msg, _ = tx.AsMessage(signer, block.BaseFee())
@ -739,10 +817,19 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
// Execute the transaction and flush any traces to disk // Execute the transaction and flush any traces to disk
vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf)
statedb.Prepare(tx.Hash(), i) statedb.Prepare(tx.Hash(), i)
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) if stateSyncPresent && i == len(txs)-1 {
if writer != nil { callmsg := prepareCallMessage(msg)
writer.Flush() _, err = statefull.ApplyBorMessage(*vmenv, callmsg)
if writer != nil {
writer.Flush()
}
} else {
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
if writer != nil {
writer.Flush()
}
} }
if dump != nil { if dump != nil {
dump.Close() dump.Close()
log.Info("Wrote standard trace", "file", dump.Name()) log.Info("Wrote standard trace", "file", dump.Name())
@ -764,8 +851,9 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
// containsTx reports whether the transaction with a certain hash // containsTx reports whether the transaction with a certain hash
// is contained within the specified block. // is contained within the specified block.
func containsTx(block *types.Block, hash common.Hash) bool { func (api *API) containsTx(ctx context.Context, block *types.Block, hash common.Hash) bool {
for _, tx := range block.Transactions() { txs, _ := api.getAllBlockTransactions(ctx, block)
for _, tx := range txs {
if tx.Hash() == hash { if tx.Hash() == hash {
return true return true
} }
@ -775,7 +863,7 @@ func containsTx(block *types.Block, hash common.Hash) bool {
// TraceTransaction returns the structured logs created during the execution of EVM // TraceTransaction returns the structured logs created during the execution of EVM
// and returns them as a JSON object. // and returns them as a JSON object.
func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig, borTx bool) (interface{}, error) {
tx, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) tx, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash)
if tx == nil { if tx == nil {
// For BorTransaction, there will be no trace available // For BorTransaction, there will be no trace available
@ -811,14 +899,14 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
TxIndex: int(index), TxIndex: int(index),
TxHash: hash, TxHash: hash,
} }
return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) return api.traceTx(ctx, msg, txctx, vmctx, statedb, config, borTx)
} }
// 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. // 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, borTx bool) (interface{}, error) {
// Try to retrieve the specified block // Try to retrieve the specified block
var ( var (
err error err error
@ -865,13 +953,13 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
Reexec: config.Reexec, Reexec: config.Reexec,
} }
} }
return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig, borTx)
} }
// traceTx configures a new tracer according to the provided configuration, and // traceTx configures a new tracer according to the provided configuration, and
// executes the given message in the provided environment. The return value will // executes the given message in the provided environment. The return value will
// be tracer dependent. // be tracer dependent.
func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig, borTx bool) (interface{}, error) {
// Assemble the structured logger or the JavaScript tracer // Assemble the structured logger or the JavaScript tracer
var ( var (
tracer vm.EVMLogger tracer vm.EVMLogger
@ -911,9 +999,18 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
// Call Prepare to clear out the statedb access list // Call Prepare to clear out the statedb access list
statedb.Prepare(txctx.TxHash, txctx.TxIndex) statedb.Prepare(txctx.TxHash, txctx.TxIndex)
result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) var result *core.ExecutionResult
if err != nil { if borTx {
return nil, fmt.Errorf("tracing failed: %w", err) callmsg := prepareCallMessage(message)
if result, err = statefull.ApplyBorMessage(*vmenv, callmsg); err != nil {
return nil, fmt.Errorf("tracing failed: %w", err)
}
} else {
result, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
if err != nil {
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.

View file

@ -176,6 +176,11 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash())
} }
func (b *testBackend) GetBorBlockTransactionWithBlockHash(ctx context.Context, txHash common.Hash, blockHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) {
tx, blockHash, blockNumber, index := rawdb.ReadBorTransactionWithBlockHash(b.ChainDb(), txHash, blockHash)
return tx, blockHash, blockNumber, index, nil
}
func TestTraceCall(t *testing.T) { func TestTraceCall(t *testing.T) {
t.Parallel() t.Parallel()
@ -285,7 +290,7 @@ func TestTraceCall(t *testing.T) {
}, },
} }
for _, testspec := range testSuite { for _, 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, false)
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("Expect error %v, get nothing", testspec.expectErr)
@ -325,7 +330,7 @@ func TestTraceTransaction(t *testing.T) {
b.AddTx(tx) b.AddTx(tx)
target = tx.Hash() target = tx.Hash()
})) }))
result, err := api.TraceTransaction(context.Background(), target, nil) result, err := api.TraceTransaction(context.Background(), target, nil, false)
if err != nil { if err != nil {
t.Errorf("Failed to trace transaction %v", err) t.Errorf("Failed to trace transaction %v", err)
} }
@ -508,7 +513,7 @@ func TestTracingWithOverrides(t *testing.T) {
}, },
} }
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, false)
if tc.expectErr != nil { if tc.expectErr != nil {
if err == nil { if err == nil {
t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr)