diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index b27ce55710..bd66c3bcfd 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -94,6 +94,7 @@ if one is set. Otherwise it prints the genesis from the datadir.`, utils.MetricsInfluxDBBucketFlag, utils.MetricsInfluxDBOrganizationFlag, utils.TxLookupLimitFlag, + utils.VMTraceFlag, }, utils.DatabasePathFlags), Description: ` The import command imports blocks from an RLP-encoded form. The form can be one file diff --git a/cmd/geth/main.go b/cmd/geth/main.go index dcdf0045ce..904944be9a 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -130,6 +130,7 @@ var ( utils.DeveloperPeriodFlag, utils.DeveloperGasLimitFlag, utils.VMEnableDebugFlag, + utils.VMTraceFlag, utils.NetworkIdFlag, utils.EthStatsURLFlag, utils.NoCompactionFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 692970477d..e9cbe7f189 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -529,6 +529,11 @@ var ( Usage: "Record information useful for VM and contract debugging", Category: flags.VMCategory, } + VMTraceFlag = &cli.BoolFlag{ + Name: "vmtrace", + Usage: "Record internal VM operations (costly)", + Category: flags.VMCategory, + } // API options. RPCGlobalGasCapFlag = &cli.Uint64Flag{ @@ -2164,6 +2169,9 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh if err != nil { Fatalf("Can't create BlockChain: %v", err) } + if ctx.IsSet(VMTraceFlag.Name) { + chain.SetLogger(tracers.NewPrinter()) + } return chain, chainDb } diff --git a/core/blockchain.go b/core/blockchain.go index ec8b789c58..7acee54144 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -153,6 +153,12 @@ var defaultCacheConfig = &CacheConfig{ SnapshotWait: true, } +type BlockchainLogger interface { + vm.EVMLogger + CaptureBlockStart(*types.Block) + CaptureBlockEnd() +} + // BlockChain represents the canonical chain given a database with a genesis // block. The Blockchain manages chain imports, reverts, chain reorganisations. // @@ -226,6 +232,7 @@ type BlockChain struct { processor Processor // Block transaction processor interface forker *ForkChoice vmConfig vm.Config + logger BlockchainLogger } // NewBlockChain returns a fully initialised block chain using information @@ -443,6 +450,12 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis return bc, nil } +// TODO: need to move this to NewBlockchain to capture genesis block +func (bc *BlockChain) SetLogger(l BlockchainLogger) { + bc.logger = l + bc.vmConfig.Tracer = l +} + // empty returns an indicator whether the blockchain is empty. // Note, it's a special case that we connect a non-empty ancient // database with an empty node, so that we can plugin the ancient diff --git a/core/state_processor.go b/core/state_processor.go index 4837628e66..e72cea359b 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -67,6 +67,10 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg allLogs []*types.Log gp = new(GasPool).AddGas(block.GasLimit()) ) + if p.bc.logger != nil { + p.bc.logger.CaptureBlockStart(block) + defer p.bc.logger.CaptureBlockEnd() + } // Mutate the block and state according to any hard-fork specs if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { misc.ApplyDAOHardFork(statedb) diff --git a/eth/tracers/printer.go b/eth/tracers/printer.go new file mode 100644 index 0000000000..18608d6484 --- /dev/null +++ b/eth/tracers/printer.go @@ -0,0 +1,64 @@ +package tracers + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" +) + +type Printer struct{} + +func NewPrinter() *Printer { + return &Printer{} +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (p *Printer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + fmt.Printf("CaptureStart: from=%v, to=%v, create=%v, input=%v, gas=%v, value=%v\n", from, to, create, input, gas, value) +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (p *Printer) CaptureEnd(output []byte, gasUsed uint64, err error) { + fmt.Printf("CaptureEnd: output=%v, gasUsed=%v, err=%v\n", output, gasUsed, err) +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (p *Printer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + //fmt.Printf("CaptureState: pc=%v, op=%v, gas=%v, cost=%v, scope=%v, rData=%v, depth=%v, err=%v\n", pc, op, gas, cost, scope, rData, depth, err) +} + +// CaptureFault implements the EVMLogger interface to trace an execution fault. +func (p *Printer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { + fmt.Printf("CaptureFault: pc=%v, op=%v, gas=%v, cost=%v, depth=%v, err=%v\n", pc, op, gas, cost, depth, err) +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (p *Printer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + fmt.Printf("CaptureEnter: typ=%v, from=%v, to=%v, input=%v, gas=%v, value=%v\n", typ, from, to, input, gas, value) +} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (p *Printer) CaptureExit(output []byte, gasUsed uint64, err error) { + fmt.Printf("CaptureExit: output=%v, gasUsed=%v, err=%v\n", output, gasUsed, err) +} + +func (p *Printer) CaptureTxStart(gasLimit uint64) { + fmt.Printf("CaptureTxStart: gasLimit=%v\n", gasLimit) + +} + +func (p *Printer) CaptureTxEnd(restGas uint64) { + fmt.Printf("CaptureTxEnd: restGas=%v\n", restGas) +} + +func (p *Printer) CaptureBlockStart(b *types.Block) { + fmt.Printf("CaptureBlockStart: b=%v\n", b.NumberU64()) +} + +func (p *Printer) CaptureBlockEnd() { + fmt.Printf("CaptureBlockEnd\n") +}