|
|
@@ -66,7 +66,7 @@ type TraceConfig struct {
|
|
|
type StdTraceConfig struct {
|
|
|
*vm.LogConfig
|
|
|
Reexec *uint64
|
|
|
- TxHash *common.Hash
|
|
|
+ TxHash common.Hash
|
|
|
}
|
|
|
|
|
|
// txTraceResult is the result of a single transaction trace.
|
|
|
@@ -375,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)
|
|
|
}
|
|
|
@@ -400,37 +400,41 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string,
|
|
|
return api.TraceBlock(ctx, blob, config)
|
|
|
}
|
|
|
|
|
|
-// TraceBadBlockByHash returns the structured logs created during the execution of a block
|
|
|
-func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, blockHash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
|
|
|
+// 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() == blockHash {
|
|
|
+ if block.Hash() == hash {
|
|
|
return api.traceBlock(ctx, block, config)
|
|
|
}
|
|
|
}
|
|
|
- return nil, fmt.Errorf("hash not found among bad blocks")
|
|
|
+ return nil, fmt.Errorf("bad block %#x not found", hash)
|
|
|
}
|
|
|
|
|
|
-// StandardTraceBadBlockToFile dumps the standard-json logs to files on the local filesystem,
|
|
|
-// and returns a list of files to the caller.
|
|
|
-func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, blockHash common.Hash, stdConfig *StdTraceConfig) ([]string, error) {
|
|
|
- blocks := api.eth.blockchain.BadBlocks()
|
|
|
- for _, block := range blocks {
|
|
|
- if block.Hash() == blockHash {
|
|
|
- return api.standardTraceBlockToFile(ctx, block, stdConfig)
|
|
|
- }
|
|
|
+// 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("hash not found among bad blocks")
|
|
|
+ return api.standardTraceBlockToFile(ctx, block, config)
|
|
|
}
|
|
|
|
|
|
-// StandardTraceBlockToFile dumps the standard-json logs to files on the local filesystem,
|
|
|
-// and returns a list of files to the caller.
|
|
|
-func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, blockHash common.Hash, stdConfig *StdTraceConfig) ([]string, error) {
|
|
|
- block := api.eth.blockchain.GetBlockByHash(blockHash)
|
|
|
- if block == nil {
|
|
|
- return nil, fmt.Errorf("block #%x not found", blockHash)
|
|
|
+// 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 api.standardTraceBlockToFile(ctx, block, stdConfig)
|
|
|
+ return nil, fmt.Errorf("bad block %#x not found", hash)
|
|
|
}
|
|
|
|
|
|
// traceBlock configures a new tracer according to the provided configuration, and
|
|
|
@@ -443,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 {
|
|
|
@@ -514,90 +518,104 @@ 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, stdConfig *StdTraceConfig) ([]string, error) {
|
|
|
+// 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())
|
|
|
+ return nil, fmt.Errorf("parent %#x not found", block.ParentHash())
|
|
|
}
|
|
|
- var (
|
|
|
- signer = types.MakeSigner(api.config, block.Number())
|
|
|
- done = false
|
|
|
- blockPrefix = fmt.Sprintf("block_0x%x", block.Hash().Bytes()[:4])
|
|
|
- usedLogConfig = &vm.LogConfig{Debug: true}
|
|
|
- files []string
|
|
|
- reExec_val = defaultTraceReexec
|
|
|
- txHash *common.Hash
|
|
|
- )
|
|
|
- if stdConfig != nil {
|
|
|
- if stdConfig.Reexec != nil {
|
|
|
- reExec_val = *stdConfig.Reexec
|
|
|
- }
|
|
|
- if stdConfig.LogConfig != nil {
|
|
|
- usedLogConfig.DisableMemory = stdConfig.LogConfig.DisableMemory
|
|
|
- usedLogConfig.DisableStack = stdConfig.LogConfig.DisableStack
|
|
|
- usedLogConfig.DisableStorage = stdConfig.LogConfig.DisableStorage
|
|
|
- usedLogConfig.Limit = stdConfig.LogConfig.Limit
|
|
|
- }
|
|
|
- txHash = stdConfig.TxHash
|
|
|
+ reexec := defaultTraceReexec
|
|
|
+ if config != nil && config.Reexec != nil {
|
|
|
+ reexec = *config.Reexec
|
|
|
}
|
|
|
- statedb, err := api.computeStateDB(parent, reExec_val)
|
|
|
+ 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 (
|
|
|
- outfile *os.File
|
|
|
- err error
|
|
|
+ msg, _ = tx.AsMessage(signer)
|
|
|
+ vmctx = core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
|
|
|
+
|
|
|
+ vmConf vm.Config
|
|
|
+ dump *os.File
|
|
|
+ err error
|
|
|
)
|
|
|
- msg, _ := tx.AsMessage(signer)
|
|
|
- vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
|
|
|
- vmConf := vm.Config{}
|
|
|
- if txHash == nil || bytes.Equal(txHash.Bytes(), tx.Hash().Bytes()) {
|
|
|
- prefix := fmt.Sprintf("%v-%d-0x%x-", blockPrefix, i, tx.Hash().Bytes()[:4])
|
|
|
- // Open a file to dump trace into
|
|
|
- outfile, err = ioutil.TempFile(os.TempDir(), prefix)
|
|
|
+ // 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
|
|
|
}
|
|
|
- files = append(files, outfile.Name())
|
|
|
+ dumps = append(dumps, dump.Name())
|
|
|
+
|
|
|
+ // Swap out the noop logger to the standard tracer
|
|
|
vmConf = vm.Config{
|
|
|
Debug: true,
|
|
|
- Tracer: vm.NewJSONLogger(usedLogConfig, bufio.NewWriter(outfile)),
|
|
|
+ Tracer: vm.NewJSONLogger(&logConfig, bufio.NewWriter(dump)),
|
|
|
EnablePreimageRecording: true,
|
|
|
}
|
|
|
- if txHash != nil { // Only one tx to trace
|
|
|
- done = 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 outfile != nil {
|
|
|
- outfile.Close()
|
|
|
- log.Info("Wrote trace", "file", outfile.Name())
|
|
|
+ if dump != nil {
|
|
|
+ dump.Close()
|
|
|
+ log.Info("Wrote standard trace", "file", dump.Name())
|
|
|
}
|
|
|
if err != nil {
|
|
|
- return files, err
|
|
|
+ return dumps, err
|
|
|
}
|
|
|
// Finalize the state so any modifications are written to the trie
|
|
|
statedb.Finalise(true)
|
|
|
|
|
|
- if done {
|
|
|
+ // If we've traced the transaction we were looking for, abort
|
|
|
+ if tx.Hash() == txHash {
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
- if txHash != nil && !done {
|
|
|
- return nil, fmt.Errorf("transaction hash not found in block")
|
|
|
- }
|
|
|
- return files, nil
|
|
|
+ return dumps, nil
|
|
|
}
|
|
|
|
|
|
// computeStateDB retrieves the state database associated with a certain block.
|
|
|
@@ -675,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 {
|
|
|
@@ -755,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 {
|
|
|
@@ -778,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)
|
|
|
}
|