From 3601320ccd0b3db59d1f720c8a2a2383f5c8435f Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Wed, 3 Feb 2016 23:47:58 +0100 Subject: [PATCH] eth, rpc: implemented block debugging rpc calls Implemented the following block debugging RPC calls * Block(RLP) * BlockByFile(fileName) * BlockByNumber(number) * BlockByHash(hash) --- eth/api.go | 280 ++++++++++++++++++++++++++++++++-------------- rpc/javascript.go | 27 ++++- 2 files changed, 214 insertions(+), 93 deletions(-) diff --git a/eth/api.go b/eth/api.go index beae3aabb..fbaa280f4 100644 --- a/eth/api.go +++ b/eth/api.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "io" + "io/ioutil" "math/big" "os" "sync" @@ -1506,35 +1507,113 @@ func NewPrivateDebugAPI(eth *Ethereum) *PrivateDebugAPI { return &PrivateDebugAPI{eth: eth} } -// ProcessBlock reprocesses an already owned block. -func (api *PrivateDebugAPI) ProcessBlock(number uint64) (bool, error) { +// BlockTraceResults is the returned value when replaying a block to check for +// consensus results and full VM trace logs for all included transactions. +type BlockTraceResult struct { + Validated bool `json: "validated"` + StructLogs []structLogRes `json:"structLogs"` + Error error `json:"error"` +} + +// TraceBlock processes the given block's RLP but does not import the block in to +// the chain. +func (api *PrivateDebugAPI) TraceBlock(blockRlp []byte, config vm.Config) BlockTraceResult { + var block types.Block + err := rlp.Decode(bytes.NewReader(blockRlp), &block) + if err != nil { + return BlockTraceResult{Error: fmt.Errorf("could not decode block: %v", err)} + } + + validated, logs, err := api.traceBlock(&block, config) + return BlockTraceResult{ + Validated: validated, + StructLogs: formatLogs(logs), + Error: err, + } +} + +// TraceBlockFromFile loads the block's RLP from the given file name and attempts to +// process it but does not import the block in to the chain. +func (api *PrivateDebugAPI) TraceBlockFromFile(file string, config vm.Config) BlockTraceResult { + blockRlp, err := ioutil.ReadFile(file) + if err != nil { + return BlockTraceResult{Error: fmt.Errorf("could not read file: %v", err)} + } + return api.TraceBlock(blockRlp, config) +} + +// TraceProcessBlock processes the block by canonical block number. +func (api *PrivateDebugAPI) TraceBlockByNumber(number uint64, config vm.Config) BlockTraceResult { // Fetch the block that we aim to reprocess block := api.eth.BlockChain().GetBlockByNumber(number) if block == nil { - return false, fmt.Errorf("block #%d not found", number) + return BlockTraceResult{Error: fmt.Errorf("block #%d not found", number)} + } + + validated, logs, err := api.traceBlock(block, config) + return BlockTraceResult{ + Validated: validated, + StructLogs: formatLogs(logs), + Error: err, + } +} + +// TraceBlockByHash processes the block by hash. +func (api *PrivateDebugAPI) TraceBlockByHash(hash common.Hash, config vm.Config) BlockTraceResult { + // Fetch the block that we aim to reprocess + block := api.eth.BlockChain().GetBlock(hash) + if block == nil { + return BlockTraceResult{Error: fmt.Errorf("block #%x not found", hash)} } + validated, logs, err := api.traceBlock(block, config) + return BlockTraceResult{ + Validated: validated, + StructLogs: formatLogs(logs), + Error: err, + } +} + +// TraceCollector collects EVM structered logs. +// +// TraceCollector implements vm.Collector +type TraceCollector struct { + traces []vm.StructLog +} + +// AddStructLog adds a structered log. +func (t *TraceCollector) AddStructLog(slog vm.StructLog) { + t.traces = append(t.traces, slog) +} + +// traceBlock processes the given block but does not save the state. +func (api *PrivateDebugAPI) traceBlock(block *types.Block, config vm.Config) (bool, []vm.StructLog, error) { // Validate and reprocess the block var ( blockchain = api.eth.BlockChain() validator = blockchain.Validator() processor = blockchain.Processor() + collector = &TraceCollector{} ) + config.Debug = true // make sure debug is set. + config.Logger.Collector = collector + if err := core.ValidateHeader(blockchain.AuxValidator(), block.Header(), blockchain.GetHeader(block.ParentHash()), true, false); err != nil { - return false, err + return false, collector.traces, err } statedb, err := state.New(blockchain.GetBlock(block.ParentHash()).Root(), api.eth.ChainDb()) if err != nil { - return false, err + return false, collector.traces, err } - receipts, _, usedGas, err := processor.Process(block, statedb, nil) + + receipts, _, usedGas, err := processor.Process(block, statedb, &config) if err != nil { - return false, err + return false, collector.traces, err } if err := validator.ValidateState(block, blockchain.GetBlock(block.ParentHash()), statedb, receipts, usedGas); err != nil { - return false, err + return false, collector.traces, err } - return true, nil + return true, collector.traces, nil } // SetHead rewinds the head of the blockchain to a previous block. @@ -1542,7 +1621,16 @@ func (api *PrivateDebugAPI) SetHead(number uint64) { api.eth.BlockChain().SetHead(number) } -// StructLogRes stores a structured log emitted by the EVM while replaying a +// ExecutionResult groups all structured logs emitted by the EVM +// while replaying a transaction in debug mode as well as the amount of +// gas used and the return value +type ExecutionResult struct { + Gas *big.Int `json:"gas"` + ReturnValue string `json:"returnValue"` + StructLogs []structLogRes `json:"structLogs"` +} + +// structLogRes stores a structured log emitted by the EVM while replaying a // transaction in debug mode type structLogRes struct { Pc uint64 `json:"pc"` @@ -1551,42 +1639,73 @@ type structLogRes struct { GasCost *big.Int `json:"gasCost"` Error error `json:"error"` Stack []string `json:"stack"` - Memory map[string]string `json:"memory"` + Memory []string `json:"memory"` Storage map[string]string `json:"storage"` } -// TransactionExecutionRes groups all structured logs emitted by the EVM -// while replaying a transaction in debug mode as well as the amount of -// gas used and the return value -type TransactionExecutionResult struct { - Gas *big.Int `json:"gas"` - ReturnValue string `json:"returnValue"` - StructLogs []structLogRes `json:"structLogs"` +// VmLoggerOptions are the options used for debugging transactions and capturing +// specific data. +type VmLoggerOptions struct { + DisableMemory bool // disable memory capture + DisableStack bool // disable stack capture + DisableStorage bool // disable storage capture + FullStorage bool // show full storage (slow) } -func (s *PrivateDebugAPI) doReplayTransaction(txHash common.Hash) ([]vm.StructLog, []byte, *big.Int, error) { +// formatLogs formats EVM returned structured logs for json output +func formatLogs(structLogs []vm.StructLog) []structLogRes { + formattedStructLogs := make([]structLogRes, len(structLogs)) + for index, trace := range structLogs { + formattedStructLogs[index] = structLogRes{ + Pc: trace.Pc, + Op: trace.Op.String(), + Gas: trace.Gas, + GasCost: trace.GasCost, + Error: trace.Err, + Stack: make([]string, len(trace.Stack)), + Storage: make(map[string]string), + } + + for i, stackValue := range trace.Stack { + formattedStructLogs[index].Stack[i] = fmt.Sprintf("%x", common.LeftPadBytes(stackValue.Bytes(), 32)) + } + + for i := 0; i+32 <= len(trace.Memory); i += 32 { + formattedStructLogs[index].Memory = append(formattedStructLogs[index].Memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) + } + + for i, storageValue := range trace.Storage { + formattedStructLogs[index].Storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) + } + } + return formattedStructLogs +} + +// TraceTransaction returns the structured logs created during the execution of EVM +// and returns them as a JSON object. +func (s *PrivateDebugAPI) TraceTransaction(txHash common.Hash, logger vm.LogConfig) (*ExecutionResult, error) { // Retrieve the tx from the chain tx, _, blockIndex, _ := core.GetTransaction(s.eth.ChainDb(), txHash) if tx == nil { - return nil, nil, nil, fmt.Errorf("Transaction not found") + return nil, fmt.Errorf("Transaction not found") } block := s.eth.BlockChain().GetBlockByNumber(blockIndex - 1) if block == nil { - return nil, nil, nil, fmt.Errorf("Unable to retrieve prior block") + return nil, fmt.Errorf("Unable to retrieve prior block") } // Create the state database stateDb, err := state.New(block.Root(), s.eth.ChainDb()) if err != nil { - return nil, nil, nil, err + return nil, err } txFrom, err := tx.FromFrontier() if err != nil { - return nil, nil, nil, fmt.Errorf("Unable to create transaction sender") + return nil, fmt.Errorf("Unable to create transaction sender") } from := stateDb.GetOrNewStateObject(txFrom) msg := callmsg{ @@ -1598,85 +1717,72 @@ func (s *PrivateDebugAPI) doReplayTransaction(txHash common.Hash) ([]vm.StructLo data: tx.Data(), } - vmenv := core.NewEnv(stateDb, s.eth.BlockChain(), msg, block.Header(), nil) + vmenv := core.NewEnv(stateDb, s.eth.BlockChain(), msg, block.Header(), &vm.Config{ + Debug: true, + Logger: logger, + }) gp := new(core.GasPool).AddGas(block.GasLimit()) ret, gas, err := core.ApplyMessage(vmenv, msg, gp) if err != nil { - return nil, nil, nil, fmt.Errorf("Error executing transaction %v", err) + return nil, fmt.Errorf("Error executing transaction %v", err) } - return vmenv.StructLogs(), ret, gas, nil + return &ExecutionResult{ + Gas: gas, + ReturnValue: fmt.Sprintf("%x", ret), + StructLogs: formatLogs(vmenv.StructLogs()), + }, nil } -// Executes a transaction and returns the structured logs of the EVM -// gathered during the execution -func (s *PrivateDebugAPI) ReplayTransaction(txHash common.Hash, stackDepth int, memorySize int, storageSize int) (*TransactionExecutionResult, error) { - - structLogs, ret, gas, err := s.doReplayTransaction(txHash) - - if err != nil { +func (s *PublicBlockChainAPI) TraceCall(args CallArgs, blockNr rpc.BlockNumber) (*ExecutionResult, error) { + // Fetch the state associated with the block number + stateDb, block, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if stateDb == nil || err != nil { return nil, err } + stateDb = stateDb.Copy() - res := TransactionExecutionResult{ - Gas: gas, - ReturnValue: fmt.Sprintf("%x", ret), - StructLogs: make([]structLogRes, len(structLogs)), - } - - for index, trace := range structLogs { - - stackLength := len(trace.Stack) - - // Return full stack by default - if stackDepth != -1 && stackDepth < stackLength { - stackLength = stackDepth - } - - res.StructLogs[index] = structLogRes{ - Pc: trace.Pc, - Op: trace.Op.String(), - Gas: trace.Gas, - GasCost: trace.GasCost, - Error: trace.Err, - Stack: make([]string, stackLength), - Memory: make(map[string]string), - Storage: make(map[string]string), - } - - for i := 0; i < stackLength; i++ { - res.StructLogs[index].Stack[i] = fmt.Sprintf("%x", common.LeftPadBytes(trace.Stack[i].Bytes(), 32)) - } - - addr := 0 - memorySizeLocal := memorySize - - // Return full memory by default - if memorySize == -1 { - memorySizeLocal = len(trace.Memory) + // Retrieve the account state object to interact with + var from *state.StateObject + if args.From == (common.Address{}) { + accounts, err := s.am.Accounts() + if err != nil || len(accounts) == 0 { + from = stateDb.GetOrNewStateObject(common.Address{}) + } else { + from = stateDb.GetOrNewStateObject(accounts[0].Address) } + } else { + from = stateDb.GetOrNewStateObject(args.From) + } + from.SetBalance(common.MaxBig) - for i := 0; i+16 <= len(trace.Memory) && addr < memorySizeLocal; i += 16 { - res.StructLogs[index].Memory[fmt.Sprintf("%04d", addr*16)] = fmt.Sprintf("%x", trace.Memory[i:i+16]) - addr++ - } + // Assemble the CALL invocation + msg := callmsg{ + from: from, + to: args.To, + gas: args.Gas.BigInt(), + gasPrice: args.GasPrice.BigInt(), + value: args.Value.BigInt(), + data: common.FromHex(args.Data), + } + if msg.gas.Cmp(common.Big0) == 0 { + msg.gas = big.NewInt(50000000) + } + if msg.gasPrice.Cmp(common.Big0) == 0 { + msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) + } - storageLength := len(trace.Stack) - if storageSize != -1 && storageSize < storageLength { - storageLength = storageSize - } + // Execute the call and return + vmenv := core.NewEnv(stateDb, s.bc, msg, block.Header(), nil) + gp := new(core.GasPool).AddGas(common.MaxBig) - i := 0 - for storageIndex, storageValue := range trace.Storage { - if i >= storageLength { - break - } - res.StructLogs[index].Storage[fmt.Sprintf("%x", storageIndex)] = fmt.Sprintf("%x", storageValue) - i++ - } - } - return &res, nil + ret, gas, err := core.ApplyMessage(vmenv, msg, gp) + return &ExecutionResult{ + Gas: gas, + ReturnValue: fmt.Sprintf("%x", ret), + StructLogs: formatLogs(vmenv.StructLogs()), + }, nil } // PublicNetAPI offers network related RPC methods diff --git a/rpc/javascript.go b/rpc/javascript.go index 72290a2a6..c4fa80c0b 100644 --- a/rpc/javascript.go +++ b/rpc/javascript.go @@ -291,9 +291,24 @@ web3._extend({ params: 1 }), new web3._extend.Method({ - name: 'processBlock', - call: 'debug_processBlock', - params: 1 + name: 'traceBlock', + call: 'debug_traceBlock', + params: 2 + }), + new web3._extend.Method({ + name: 'traceBlockByFile', + call: 'debug_traceBlockByFile', + params: 2 + }), + new web3._extend.Method({ + name: 'traceBlockByNumber', + call: 'debug_traceBlockByNumber', + params: 2 + }), + new web3._extend.Method({ + name: 'traceBlockByHash', + call: 'debug_traceBlockByHash', + params: 2 }), new web3._extend.Method({ name: 'seedHash', @@ -382,9 +397,9 @@ web3._extend({ params: 1 }), new web3._extend.Method({ - name: 'replayTransaction', - call: 'debug_replayTransaction', - params: 4 + name: 'traceTransaction', + call: 'debug_traceTransaction', + params: 2 }) ], properties: []