eth, rpc: implemented block debugging rpc calls

Implemented the following block debugging RPC calls

* Block(RLP)
* BlockByFile(fileName)
* BlockByNumber(number)
* BlockByHash(hash)
release/1.4
Jeffrey Wilcke 9 years ago committed by Jeffrey Wilcke
parent 14013372ae
commit 3601320ccd
  1. 272
      eth/api.go
  2. 27
      rpc/javascript.go

@ -22,6 +22,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"math/big" "math/big"
"os" "os"
"sync" "sync"
@ -1506,35 +1507,113 @@ func NewPrivateDebugAPI(eth *Ethereum) *PrivateDebugAPI {
return &PrivateDebugAPI{eth: eth} return &PrivateDebugAPI{eth: eth}
} }
// ProcessBlock reprocesses an already owned block. // BlockTraceResults is the returned value when replaying a block to check for
func (api *PrivateDebugAPI) ProcessBlock(number uint64) (bool, error) { // 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 // Fetch the block that we aim to reprocess
block := api.eth.BlockChain().GetBlockByNumber(number) block := api.eth.BlockChain().GetBlockByNumber(number)
if block == nil { 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 // Validate and reprocess the block
var ( var (
blockchain = api.eth.BlockChain() blockchain = api.eth.BlockChain()
validator = blockchain.Validator() validator = blockchain.Validator()
processor = blockchain.Processor() 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 { 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()) statedb, err := state.New(blockchain.GetBlock(block.ParentHash()).Root(), api.eth.ChainDb())
if err != nil { 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 { 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 { 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. // 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) 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 // transaction in debug mode
type structLogRes struct { type structLogRes struct {
Pc uint64 `json:"pc"` Pc uint64 `json:"pc"`
@ -1551,42 +1639,73 @@ type structLogRes struct {
GasCost *big.Int `json:"gasCost"` GasCost *big.Int `json:"gasCost"`
Error error `json:"error"` Error error `json:"error"`
Stack []string `json:"stack"` Stack []string `json:"stack"`
Memory map[string]string `json:"memory"` Memory []string `json:"memory"`
Storage map[string]string `json:"storage"` Storage map[string]string `json:"storage"`
} }
// TransactionExecutionRes groups all structured logs emitted by the EVM // VmLoggerOptions are the options used for debugging transactions and capturing
// while replaying a transaction in debug mode as well as the amount of // specific data.
// gas used and the return value type VmLoggerOptions struct {
type TransactionExecutionResult struct { DisableMemory bool // disable memory capture
Gas *big.Int `json:"gas"` DisableStack bool // disable stack capture
ReturnValue string `json:"returnValue"` DisableStorage bool // disable storage capture
StructLogs []structLogRes `json:"structLogs"` FullStorage bool // show full storage (slow)
}
// 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
} }
func (s *PrivateDebugAPI) doReplayTransaction(txHash common.Hash) ([]vm.StructLog, []byte, *big.Int, error) { // 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 // Retrieve the tx from the chain
tx, _, blockIndex, _ := core.GetTransaction(s.eth.ChainDb(), txHash) tx, _, blockIndex, _ := core.GetTransaction(s.eth.ChainDb(), txHash)
if tx == nil { 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) block := s.eth.BlockChain().GetBlockByNumber(blockIndex - 1)
if block == nil { 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 // Create the state database
stateDb, err := state.New(block.Root(), s.eth.ChainDb()) stateDb, err := state.New(block.Root(), s.eth.ChainDb())
if err != nil { if err != nil {
return nil, nil, nil, err return nil, err
} }
txFrom, err := tx.FromFrontier() txFrom, err := tx.FromFrontier()
if err != nil { 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) from := stateDb.GetOrNewStateObject(txFrom)
msg := callmsg{ msg := callmsg{
@ -1598,85 +1717,72 @@ func (s *PrivateDebugAPI) doReplayTransaction(txHash common.Hash) ([]vm.StructLo
data: tx.Data(), 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()) gp := new(core.GasPool).AddGas(block.GasLimit())
ret, gas, err := core.ApplyMessage(vmenv, msg, gp) ret, gas, err := core.ApplyMessage(vmenv, msg, gp)
if err != nil { 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
}
// 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 {
return nil, err
} }
res := TransactionExecutionResult{ return &ExecutionResult{
Gas: gas, Gas: gas,
ReturnValue: fmt.Sprintf("%x", ret), ReturnValue: fmt.Sprintf("%x", ret),
StructLogs: make([]structLogRes, len(structLogs)), StructLogs: formatLogs(vmenv.StructLogs()),
}, nil
} }
for index, trace := range structLogs { func (s *PublicBlockChainAPI) TraceCall(args CallArgs, blockNr rpc.BlockNumber) (*ExecutionResult, error) {
// Fetch the state associated with the block number
stackLength := len(trace.Stack) stateDb, block, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb)
if stateDb == nil || err != nil {
// Return full stack by default return nil, err
if stackDepth != -1 && stackDepth < stackLength {
stackLength = stackDepth
} }
stateDb = stateDb.Copy()
res.StructLogs[index] = structLogRes{ // Retrieve the account state object to interact with
Pc: trace.Pc, var from *state.StateObject
Op: trace.Op.String(), if args.From == (common.Address{}) {
Gas: trace.Gas, accounts, err := s.am.Accounts()
GasCost: trace.GasCost, if err != nil || len(accounts) == 0 {
Error: trace.Err, from = stateDb.GetOrNewStateObject(common.Address{})
Stack: make([]string, stackLength), } else {
Memory: make(map[string]string), from = stateDb.GetOrNewStateObject(accounts[0].Address)
Storage: make(map[string]string),
} }
} else {
for i := 0; i < stackLength; i++ { from = stateDb.GetOrNewStateObject(args.From)
res.StructLogs[index].Stack[i] = fmt.Sprintf("%x", common.LeftPadBytes(trace.Stack[i].Bytes(), 32))
} }
from.SetBalance(common.MaxBig)
addr := 0 // Assemble the CALL invocation
memorySizeLocal := memorySize msg := callmsg{
from: from,
// Return full memory by default to: args.To,
if memorySize == -1 { gas: args.Gas.BigInt(),
memorySizeLocal = len(trace.Memory) gasPrice: args.GasPrice.BigInt(),
value: args.Value.BigInt(),
data: common.FromHex(args.Data),
} }
if msg.gas.Cmp(common.Big0) == 0 {
for i := 0; i+16 <= len(trace.Memory) && addr < memorySizeLocal; i += 16 { msg.gas = big.NewInt(50000000)
res.StructLogs[index].Memory[fmt.Sprintf("%04d", addr*16)] = fmt.Sprintf("%x", trace.Memory[i:i+16])
addr++
} }
if msg.gasPrice.Cmp(common.Big0) == 0 {
storageLength := len(trace.Stack) msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon)
if storageSize != -1 && storageSize < storageLength {
storageLength = storageSize
} }
i := 0 // Execute the call and return
for storageIndex, storageValue := range trace.Storage { vmenv := core.NewEnv(stateDb, s.bc, msg, block.Header(), nil)
if i >= storageLength { gp := new(core.GasPool).AddGas(common.MaxBig)
break
} ret, gas, err := core.ApplyMessage(vmenv, msg, gp)
res.StructLogs[index].Storage[fmt.Sprintf("%x", storageIndex)] = fmt.Sprintf("%x", storageValue) return &ExecutionResult{
i++ Gas: gas,
} ReturnValue: fmt.Sprintf("%x", ret),
} StructLogs: formatLogs(vmenv.StructLogs()),
return &res, nil }, nil
} }
// PublicNetAPI offers network related RPC methods // PublicNetAPI offers network related RPC methods

@ -291,9 +291,24 @@ web3._extend({
params: 1 params: 1
}), }),
new web3._extend.Method({ new web3._extend.Method({
name: 'processBlock', name: 'traceBlock',
call: 'debug_processBlock', call: 'debug_traceBlock',
params: 1 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({ new web3._extend.Method({
name: 'seedHash', name: 'seedHash',
@ -382,9 +397,9 @@ web3._extend({
params: 1 params: 1
}), }),
new web3._extend.Method({ new web3._extend.Method({
name: 'replayTransaction', name: 'traceTransaction',
call: 'debug_replayTransaction', call: 'debug_traceTransaction',
params: 4 params: 2
}) })
], ],
properties: [] properties: []

Loading…
Cancel
Save