Skip to content

Commit fd66af5

Browse files
authored
Merge pull request #17914 from holiman/block_analysis
core/vm, eth: add standard json tracing into filesystem dumps
2 parents 09d588e + 0983d02 commit fd66af5

File tree

5 files changed

+175
-27
lines changed

5 files changed

+175
-27
lines changed

cmd/evm/runner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func runCmd(ctx *cli.Context) error {
8989
genesisConfig *core.Genesis
9090
)
9191
if ctx.GlobalBool(MachineFlag.Name) {
92-
tracer = NewJSONLogger(logconfig, os.Stdout)
92+
tracer = vm.NewJSONLogger(logconfig, os.Stdout)
9393
} else if ctx.GlobalBool(DebugFlag.Name) {
9494
debugLogger = vm.NewStructLogger(logconfig)
9595
tracer = debugLogger

cmd/evm/staterunner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func stateTestCmd(ctx *cli.Context) error {
6868
)
6969
switch {
7070
case ctx.GlobalBool(MachineFlag.Name):
71-
tracer = NewJSONLogger(config, os.Stderr)
71+
tracer = vm.NewJSONLogger(config, os.Stderr)
7272

7373
case ctx.GlobalBool(DebugFlag.Name):
7474
debugger = vm.NewStructLogger(config)

cmd/evm/json_logger.go renamed to core/vm/logger_json.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
1616

17-
package main
17+
package vm
1818

1919
import (
2020
"encoding/json"
@@ -24,17 +24,16 @@ import (
2424

2525
"github.com/ethereum/go-ethereum/common"
2626
"github.com/ethereum/go-ethereum/common/math"
27-
"github.com/ethereum/go-ethereum/core/vm"
2827
)
2928

3029
type JSONLogger struct {
3130
encoder *json.Encoder
32-
cfg *vm.LogConfig
31+
cfg *LogConfig
3332
}
3433

3534
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
3635
// into the provided stream.
37-
func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger {
36+
func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger {
3837
return &JSONLogger{json.NewEncoder(writer), cfg}
3938
}
4039

@@ -43,8 +42,8 @@ func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create
4342
}
4443

4544
// CaptureState outputs state information on the logger.
46-
func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
47-
log := vm.StructLog{
45+
func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
46+
log := StructLog{
4847
Pc: pc,
4948
Op: op,
5049
Gas: gas,
@@ -65,7 +64,7 @@ func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos
6564
}
6665

6766
// CaptureFault outputs state information on the logger.
68-
func (l *JSONLogger) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
67+
func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
6968
return nil
7069
}
7170

eth/api_tracer.go

Lines changed: 155 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
package eth
1818

1919
import (
20+
"bufio"
2021
"bytes"
2122
"context"
2223
"errors"
2324
"fmt"
2425
"io/ioutil"
26+
"os"
2527
"runtime"
2628
"sync"
2729
"time"
@@ -60,6 +62,13 @@ type TraceConfig struct {
6062
Reexec *uint64
6163
}
6264

65+
// StdTraceConfig holds extra parameters to standard-json trace functions.
66+
type StdTraceConfig struct {
67+
*vm.LogConfig
68+
Reexec *uint64
69+
TxHash common.Hash
70+
}
71+
6372
// txTraceResult is the result of a single transaction trace.
6473
type txTraceResult struct {
6574
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
366375
func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
367376
block := api.eth.blockchain.GetBlockByHash(hash)
368377
if block == nil {
369-
return nil, fmt.Errorf("block #%x not found", hash)
378+
return nil, fmt.Errorf("block %#x not found", hash)
370379
}
371380
return api.traceBlock(ctx, block, config)
372381
}
@@ -391,13 +400,41 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string,
391400
return api.TraceBlock(ctx, blob, config)
392401
}
393402

394-
// TraceBadBlock returns the structured logs created during the execution of a block
395-
// within the blockchain 'badblocks' cache
396-
func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, index int, config *TraceConfig) ([]*txTraceResult, error) {
397-
if blocks := api.eth.blockchain.BadBlocks(); index < len(blocks) {
398-
return api.traceBlock(ctx, blocks[index], config)
403+
// TraceBadBlockByHash returns the structured logs created during the execution of
404+
// EVM against a block pulled from the pool of bad ones and returns them as a JSON
405+
// object.
406+
func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
407+
blocks := api.eth.blockchain.BadBlocks()
408+
for _, block := range blocks {
409+
if block.Hash() == hash {
410+
return api.traceBlock(ctx, block, config)
411+
}
412+
}
413+
return nil, fmt.Errorf("bad block %#x not found", hash)
414+
}
415+
416+
// StandardTraceBlockToFile dumps the structured logs created during the
417+
// execution of EVM to the local file system and returns a list of files
418+
// to the caller.
419+
func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
420+
block := api.eth.blockchain.GetBlockByHash(hash)
421+
if block == nil {
422+
return nil, fmt.Errorf("block %#x not found", hash)
399423
}
400-
return nil, fmt.Errorf("index out of range")
424+
return api.standardTraceBlockToFile(ctx, block, config)
425+
}
426+
427+
// StandardTraceBadBlockToFile dumps the structured logs created during the
428+
// execution of EVM against a block pulled from the pool of bad ones to the
429+
// local file system and returns a list of files to the caller.
430+
func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
431+
blocks := api.eth.blockchain.BadBlocks()
432+
for _, block := range blocks {
433+
if block.Hash() == hash {
434+
return api.standardTraceBlockToFile(ctx, block, config)
435+
}
436+
}
437+
return nil, fmt.Errorf("bad block %#x not found", hash)
401438
}
402439

403440
// 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,
410447
}
411448
parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
412449
if parent == nil {
413-
return nil, fmt.Errorf("parent %x not found", block.ParentHash())
450+
return nil, fmt.Errorf("parent %#x not found", block.ParentHash())
414451
}
415452
reexec := defaultTraceReexec
416453
if config != nil && config.Reexec != nil {
@@ -481,6 +518,106 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
481518
return results, nil
482519
}
483520

521+
// standardTraceBlockToFile configures a new tracer which uses standard JSON output,
522+
// and traces either a full block or an individual transaction. The return value will
523+
// be one filename per transaction traced.
524+
func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) {
525+
// If we're tracing a single transaction, make sure it's present
526+
if config != nil && config.TxHash != (common.Hash{}) {
527+
var exists bool
528+
for _, tx := range block.Transactions() {
529+
if exists = (tx.Hash() == config.TxHash); exists {
530+
break
531+
}
532+
}
533+
if !exists {
534+
return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash)
535+
}
536+
}
537+
// Create the parent state database
538+
if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil {
539+
return nil, err
540+
}
541+
parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
542+
if parent == nil {
543+
return nil, fmt.Errorf("parent %#x not found", block.ParentHash())
544+
}
545+
reexec := defaultTraceReexec
546+
if config != nil && config.Reexec != nil {
547+
reexec = *config.Reexec
548+
}
549+
statedb, err := api.computeStateDB(parent, reexec)
550+
if err != nil {
551+
return nil, err
552+
}
553+
// Retrieve the tracing configurations, or use default values
554+
var (
555+
logConfig vm.LogConfig
556+
txHash common.Hash
557+
)
558+
if config != nil {
559+
if config.LogConfig != nil {
560+
logConfig = *config.LogConfig
561+
}
562+
txHash = config.TxHash
563+
}
564+
logConfig.Debug = true
565+
566+
// Execute transaction, either tracing all or just the requested one
567+
var (
568+
signer = types.MakeSigner(api.config, block.Number())
569+
dumps []string
570+
)
571+
for i, tx := range block.Transactions() {
572+
// Prepare the trasaction for un-traced execution
573+
var (
574+
msg, _ = tx.AsMessage(signer)
575+
vmctx = core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
576+
577+
vmConf vm.Config
578+
dump *os.File
579+
err error
580+
)
581+
// If the transaction needs tracing, swap out the configs
582+
if tx.Hash() == txHash || txHash == (common.Hash{}) {
583+
// Generate a unique temporary file to dump it into
584+
prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4])
585+
586+
dump, err = ioutil.TempFile(os.TempDir(), prefix)
587+
if err != nil {
588+
return nil, err
589+
}
590+
dumps = append(dumps, dump.Name())
591+
592+
// Swap out the noop logger to the standard tracer
593+
vmConf = vm.Config{
594+
Debug: true,
595+
Tracer: vm.NewJSONLogger(&logConfig, bufio.NewWriter(dump)),
596+
EnablePreimageRecording: true,
597+
}
598+
}
599+
// Execute the transaction and flush any traces to disk
600+
vmenv := vm.NewEVM(vmctx, statedb, api.config, vmConf)
601+
_, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
602+
603+
if dump != nil {
604+
dump.Close()
605+
log.Info("Wrote standard trace", "file", dump.Name())
606+
}
607+
if err != nil {
608+
return dumps, err
609+
}
610+
// Finalize the state so any modifications are written to the trie
611+
statedb.Finalise(true)
612+
613+
// If we've traced the transaction we were looking for, abort
614+
if tx.Hash() == txHash {
615+
break
616+
}
617+
}
618+
return dumps, nil
619+
}
620+
484621
// computeStateDB retrieves the state database associated with a certain block.
485622
// If no state is locally available for the given block, a number of blocks are
486623
// attempted to be reexecuted to generate the desired state.
@@ -506,7 +643,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
506643
if err != nil {
507644
switch err.(type) {
508645
case *trie.MissingNodeError:
509-
return nil, errors.New("required historical state unavailable")
646+
return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec)
510647
default:
511648
return nil, err
512649
}
@@ -520,7 +657,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
520657
for block.NumberU64() < origin {
521658
// Print progress logs if long enough time elapsed
522659
if time.Since(logged) > 8*time.Second {
523-
log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "elapsed", time.Since(start))
660+
log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start))
524661
logged = time.Now()
525662
}
526663
// Retrieve the next block to regenerate and process it
@@ -529,15 +666,15 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
529666
}
530667
_, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
531668
if err != nil {
532-
return nil, err
669+
return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err)
533670
}
534671
// Finalize the state so any modifications are written to the trie
535-
root, err := statedb.Commit(true)
672+
root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
536673
if err != nil {
537674
return nil, err
538675
}
539676
if err := statedb.Reset(root); err != nil {
540-
return nil, err
677+
return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err)
541678
}
542679
database.TrieDB().Reference(root, common.Hash{})
543680
if proot != (common.Hash{}) {
@@ -556,7 +693,7 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Ha
556693
// Retrieve the transaction and assemble its EVM context
557694
tx, blockHash, _, index := rawdb.ReadTransaction(api.eth.ChainDb(), hash)
558695
if tx == nil {
559-
return nil, fmt.Errorf("transaction %x not found", hash)
696+
return nil, fmt.Errorf("transaction %#x not found", hash)
560697
}
561698
reexec := defaultTraceReexec
562699
if config != nil && config.Reexec != nil {
@@ -636,11 +773,11 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree
636773
// Create the parent state database
637774
block := api.eth.blockchain.GetBlockByHash(blockHash)
638775
if block == nil {
639-
return nil, vm.Context{}, nil, fmt.Errorf("block %x not found", blockHash)
776+
return nil, vm.Context{}, nil, fmt.Errorf("block %#x not found", blockHash)
640777
}
641778
parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
642779
if parent == nil {
643-
return nil, vm.Context{}, nil, fmt.Errorf("parent %x not found", block.ParentHash())
780+
return nil, vm.Context{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash())
644781
}
645782
statedb, err := api.computeStateDB(parent, reexec)
646783
if err != nil {
@@ -659,10 +796,10 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree
659796
// Not yet the searched for transaction, execute on top of the current state
660797
vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{})
661798
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
662-
return nil, vm.Context{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err)
799+
return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
663800
}
664801
// Ensure any modifications are committed to the state
665802
statedb.Finalise(true)
666803
}
667-
return nil, vm.Context{}, nil, fmt.Errorf("tx index %d out of range for block %x", txIndex, blockHash)
804+
return nil, vm.Context{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, blockHash)
668805
}

internal/web3ext/web3ext.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,18 @@ web3._extend({
384384
params: 1,
385385
inputFormatter: [null]
386386
}),
387+
new web3._extend.Method({
388+
name: 'standardTraceBadBlockToFile',
389+
call: 'debug_standardTraceBadBlockToFile',
390+
params: 2,
391+
inputFormatter: [null, null]
392+
}),
393+
new web3._extend.Method({
394+
name: 'standardTraceBlockToFile',
395+
call: 'debug_standardTraceBlockToFile',
396+
params: 2,
397+
inputFormatter: [null, null]
398+
}),
387399
new web3._extend.Method({
388400
name: 'traceBlockByNumber',
389401
call: 'debug_traceBlockByNumber',

0 commit comments

Comments
 (0)