|
|
|
@ -17,11 +17,13 @@ |
|
|
|
|
package eth |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bufio" |
|
|
|
|
"bytes" |
|
|
|
|
"context" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"io/ioutil" |
|
|
|
|
"os" |
|
|
|
|
"runtime" |
|
|
|
|
"sync" |
|
|
|
|
"time" |
|
|
|
@ -60,6 +62,13 @@ type TraceConfig struct { |
|
|
|
|
Reexec *uint64 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// StdTraceConfig holds extra parameters to standard-json trace functions.
|
|
|
|
|
type StdTraceConfig struct { |
|
|
|
|
*vm.LogConfig |
|
|
|
|
Reexec *uint64 |
|
|
|
|
TxHash common.Hash |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// txTraceResult is the result of a single transaction trace.
|
|
|
|
|
type txTraceResult struct { |
|
|
|
|
Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
|
|
|
|
@ -366,7 +375,7 @@ func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.B |
|
|
|
|
func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { |
|
|
|
|
block := api.eth.blockchain.GetBlockByHash(hash) |
|
|
|
|
if block == nil { |
|
|
|
|
return nil, fmt.Errorf("block #%x not found", hash) |
|
|
|
|
return nil, fmt.Errorf("block %#x not found", hash) |
|
|
|
|
} |
|
|
|
|
return api.traceBlock(ctx, block, config) |
|
|
|
|
} |
|
|
|
@ -391,13 +400,41 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, |
|
|
|
|
return api.TraceBlock(ctx, blob, config) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TraceBadBlock returns the structured logs created during the execution of a block
|
|
|
|
|
// within the blockchain 'badblocks' cache
|
|
|
|
|
func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, index int, config *TraceConfig) ([]*txTraceResult, error) { |
|
|
|
|
if blocks := api.eth.blockchain.BadBlocks(); index < len(blocks) { |
|
|
|
|
return api.traceBlock(ctx, blocks[index], config) |
|
|
|
|
// TraceBadBlockByHash returns the structured logs created during the execution of
|
|
|
|
|
// EVM against a block pulled from the pool of bad ones and returns them as a JSON
|
|
|
|
|
// object.
|
|
|
|
|
func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { |
|
|
|
|
blocks := api.eth.blockchain.BadBlocks() |
|
|
|
|
for _, block := range blocks { |
|
|
|
|
if block.Hash() == hash { |
|
|
|
|
return api.traceBlock(ctx, block, config) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return nil, fmt.Errorf("bad block %#x not found", hash) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// StandardTraceBlockToFile dumps the structured logs created during the
|
|
|
|
|
// execution of EVM to the local file system and returns a list of files
|
|
|
|
|
// to the caller.
|
|
|
|
|
func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { |
|
|
|
|
block := api.eth.blockchain.GetBlockByHash(hash) |
|
|
|
|
if block == nil { |
|
|
|
|
return nil, fmt.Errorf("block %#x not found", hash) |
|
|
|
|
} |
|
|
|
|
return nil, fmt.Errorf("index out of range") |
|
|
|
|
return api.standardTraceBlockToFile(ctx, block, config) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// StandardTraceBadBlockToFile dumps the structured logs created during the
|
|
|
|
|
// execution of EVM against a block pulled from the pool of bad ones to the
|
|
|
|
|
// local file system and returns a list of files to the caller.
|
|
|
|
|
func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { |
|
|
|
|
blocks := api.eth.blockchain.BadBlocks() |
|
|
|
|
for _, block := range blocks { |
|
|
|
|
if block.Hash() == hash { |
|
|
|
|
return api.standardTraceBlockToFile(ctx, block, config) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return nil, fmt.Errorf("bad block %#x not found", hash) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// traceBlock configures a new tracer according to the provided configuration, and
|
|
|
|
@ -410,7 +447,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, |
|
|
|
|
} |
|
|
|
|
parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) |
|
|
|
|
if parent == nil { |
|
|
|
|
return nil, fmt.Errorf("parent %x not found", block.ParentHash()) |
|
|
|
|
return nil, fmt.Errorf("parent %#x not found", block.ParentHash()) |
|
|
|
|
} |
|
|
|
|
reexec := defaultTraceReexec |
|
|
|
|
if config != nil && config.Reexec != nil { |
|
|
|
@ -481,6 +518,106 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, |
|
|
|
|
return results, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// standardTraceBlockToFile configures a new tracer which uses standard JSON output,
|
|
|
|
|
// and traces either a full block or an individual transaction. The return value will
|
|
|
|
|
// be one filename per transaction traced.
|
|
|
|
|
func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) { |
|
|
|
|
// If we're tracing a single transaction, make sure it's present
|
|
|
|
|
if config != nil && config.TxHash != (common.Hash{}) { |
|
|
|
|
var exists bool |
|
|
|
|
for _, tx := range block.Transactions() { |
|
|
|
|
if exists = (tx.Hash() == config.TxHash); exists { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if !exists { |
|
|
|
|
return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Create the parent state database
|
|
|
|
|
if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) |
|
|
|
|
if parent == nil { |
|
|
|
|
return nil, fmt.Errorf("parent %#x not found", block.ParentHash()) |
|
|
|
|
} |
|
|
|
|
reexec := defaultTraceReexec |
|
|
|
|
if config != nil && config.Reexec != nil { |
|
|
|
|
reexec = *config.Reexec |
|
|
|
|
} |
|
|
|
|
statedb, err := api.computeStateDB(parent, reexec) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
// Retrieve the tracing configurations, or use default values
|
|
|
|
|
var ( |
|
|
|
|
logConfig vm.LogConfig |
|
|
|
|
txHash common.Hash |
|
|
|
|
) |
|
|
|
|
if config != nil { |
|
|
|
|
if config.LogConfig != nil { |
|
|
|
|
logConfig = *config.LogConfig |
|
|
|
|
} |
|
|
|
|
txHash = config.TxHash |
|
|
|
|
} |
|
|
|
|
logConfig.Debug = true |
|
|
|
|
|
|
|
|
|
// Execute transaction, either tracing all or just the requested one
|
|
|
|
|
var ( |
|
|
|
|
signer = types.MakeSigner(api.config, block.Number()) |
|
|
|
|
dumps []string |
|
|
|
|
) |
|
|
|
|
for i, tx := range block.Transactions() { |
|
|
|
|
// Prepare the trasaction for un-traced execution
|
|
|
|
|
var ( |
|
|
|
|
msg, _ = tx.AsMessage(signer) |
|
|
|
|
vmctx = core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) |
|
|
|
|
|
|
|
|
|
vmConf vm.Config |
|
|
|
|
dump *os.File |
|
|
|
|
err error |
|
|
|
|
) |
|
|
|
|
// If the transaction needs tracing, swap out the configs
|
|
|
|
|
if tx.Hash() == txHash || txHash == (common.Hash{}) { |
|
|
|
|
// Generate a unique temporary file to dump it into
|
|
|
|
|
prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4]) |
|
|
|
|
|
|
|
|
|
dump, err = ioutil.TempFile(os.TempDir(), prefix) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
dumps = append(dumps, dump.Name()) |
|
|
|
|
|
|
|
|
|
// Swap out the noop logger to the standard tracer
|
|
|
|
|
vmConf = vm.Config{ |
|
|
|
|
Debug: true, |
|
|
|
|
Tracer: vm.NewJSONLogger(&logConfig, bufio.NewWriter(dump)), |
|
|
|
|
EnablePreimageRecording: true, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Execute the transaction and flush any traces to disk
|
|
|
|
|
vmenv := vm.NewEVM(vmctx, statedb, api.config, vmConf) |
|
|
|
|
_, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) |
|
|
|
|
|
|
|
|
|
if dump != nil { |
|
|
|
|
dump.Close() |
|
|
|
|
log.Info("Wrote standard trace", "file", dump.Name()) |
|
|
|
|
} |
|
|
|
|
if err != nil { |
|
|
|
|
return dumps, err |
|
|
|
|
} |
|
|
|
|
// Finalize the state so any modifications are written to the trie
|
|
|
|
|
statedb.Finalise(true) |
|
|
|
|
|
|
|
|
|
// If we've traced the transaction we were looking for, abort
|
|
|
|
|
if tx.Hash() == txHash { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return dumps, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// computeStateDB retrieves the state database associated with a certain block.
|
|
|
|
|
// If no state is locally available for the given block, a number of blocks are
|
|
|
|
|
// attempted to be reexecuted to generate the desired state.
|
|
|
|
@ -506,7 +643,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* |
|
|
|
|
if err != nil { |
|
|
|
|
switch err.(type) { |
|
|
|
|
case *trie.MissingNodeError: |
|
|
|
|
return nil, errors.New("required historical state unavailable") |
|
|
|
|
return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec) |
|
|
|
|
default: |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
@ -520,7 +657,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* |
|
|
|
|
for block.NumberU64() < origin { |
|
|
|
|
// Print progress logs if long enough time elapsed
|
|
|
|
|
if time.Since(logged) > 8*time.Second { |
|
|
|
|
log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "elapsed", time.Since(start)) |
|
|
|
|
log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start)) |
|
|
|
|
logged = time.Now() |
|
|
|
|
} |
|
|
|
|
// Retrieve the next block to regenerate and process it
|
|
|
|
@ -529,15 +666,15 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* |
|
|
|
|
} |
|
|
|
|
_, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{}) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err) |
|
|
|
|
} |
|
|
|
|
// Finalize the state so any modifications are written to the trie
|
|
|
|
|
root, err := statedb.Commit(true) |
|
|
|
|
root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number())) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
if err := statedb.Reset(root); err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err) |
|
|
|
|
} |
|
|
|
|
database.TrieDB().Reference(root, common.Hash{}) |
|
|
|
|
if proot != (common.Hash{}) { |
|
|
|
@ -556,7 +693,7 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha |
|
|
|
|
// Retrieve the transaction and assemble its EVM context
|
|
|
|
|
tx, blockHash, _, index := rawdb.ReadTransaction(api.eth.ChainDb(), hash) |
|
|
|
|
if tx == nil { |
|
|
|
|
return nil, fmt.Errorf("transaction %x not found", hash) |
|
|
|
|
return nil, fmt.Errorf("transaction %#x not found", hash) |
|
|
|
|
} |
|
|
|
|
reexec := defaultTraceReexec |
|
|
|
|
if config != nil && config.Reexec != nil { |
|
|
|
@ -636,11 +773,11 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree |
|
|
|
|
// Create the parent state database
|
|
|
|
|
block := api.eth.blockchain.GetBlockByHash(blockHash) |
|
|
|
|
if block == nil { |
|
|
|
|
return nil, vm.Context{}, nil, fmt.Errorf("block %x not found", blockHash) |
|
|
|
|
return nil, vm.Context{}, nil, fmt.Errorf("block %#x not found", blockHash) |
|
|
|
|
} |
|
|
|
|
parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) |
|
|
|
|
if parent == nil { |
|
|
|
|
return nil, vm.Context{}, nil, fmt.Errorf("parent %x not found", block.ParentHash()) |
|
|
|
|
return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) |
|
|
|
|
} |
|
|
|
|
statedb, err := api.computeStateDB(parent, reexec) |
|
|
|
|
if err != nil { |
|
|
|
@ -659,10 +796,10 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree |
|
|
|
|
// Not yet the searched for transaction, execute on top of the current state
|
|
|
|
|
vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{}) |
|
|
|
|
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { |
|
|
|
|
return nil, vm.Context{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err) |
|
|
|
|
return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) |
|
|
|
|
} |
|
|
|
|
// Ensure any modifications are committed to the state
|
|
|
|
|
statedb.Finalise(true) |
|
|
|
|
} |
|
|
|
|
return nil, vm.Context{}, nil, fmt.Errorf("tx index %d out of range for block %x", txIndex, blockHash) |
|
|
|
|
return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, blockHash) |
|
|
|
|
} |
|
|
|
|