|
|
|
@ -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)
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
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 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 |
|
|
|
|
return nil, fmt.Errorf("Error executing transaction %v", err) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
res := TransactionExecutionResult{ |
|
|
|
|
return &ExecutionResult{ |
|
|
|
|
Gas: gas, |
|
|
|
|
ReturnValue: fmt.Sprintf("%x", ret), |
|
|
|
|
StructLogs: make([]structLogRes, len(structLogs)), |
|
|
|
|
StructLogs: formatLogs(vmenv.StructLogs()), |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for index, trace := range structLogs { |
|
|
|
|
|
|
|
|
|
stackLength := len(trace.Stack) |
|
|
|
|
|
|
|
|
|
// Return full stack by default
|
|
|
|
|
if stackDepth != -1 && stackDepth < stackLength { |
|
|
|
|
stackLength = stackDepth |
|
|
|
|
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.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), |
|
|
|
|
// 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) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for i := 0; i < stackLength; i++ { |
|
|
|
|
res.StructLogs[index].Stack[i] = fmt.Sprintf("%x", common.LeftPadBytes(trace.Stack[i].Bytes(), 32)) |
|
|
|
|
} else { |
|
|
|
|
from = stateDb.GetOrNewStateObject(args.From) |
|
|
|
|
} |
|
|
|
|
from.SetBalance(common.MaxBig) |
|
|
|
|
|
|
|
|
|
addr := 0 |
|
|
|
|
memorySizeLocal := memorySize |
|
|
|
|
|
|
|
|
|
// Return full memory by default
|
|
|
|
|
if memorySize == -1 { |
|
|
|
|
memorySizeLocal = len(trace.Memory) |
|
|
|
|
// 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), |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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++ |
|
|
|
|
if msg.gas.Cmp(common.Big0) == 0 { |
|
|
|
|
msg.gas = big.NewInt(50000000) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
storageLength := len(trace.Stack) |
|
|
|
|
if storageSize != -1 && storageSize < storageLength { |
|
|
|
|
storageLength = storageSize |
|
|
|
|
if msg.gasPrice.Cmp(common.Big0) == 0 { |
|
|
|
|
msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
// Execute the call and return
|
|
|
|
|
vmenv := core.NewEnv(stateDb, s.bc, msg, block.Header(), nil) |
|
|
|
|
gp := new(core.GasPool).AddGas(common.MaxBig) |
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|