diff --git a/core/vm/common.go b/core/vm/common.go index 2d1aa9332..395ed0471 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -28,6 +28,8 @@ import ( // Global Debug flag indicating Debug VM (full logging) var Debug bool +var GenerateStructLogs bool = false + // Type is the VM type accepted by **NewVm** type Type byte diff --git a/core/vm/vm.go b/core/vm/vm.go index 8e07aaa89..0c6bbcd42 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -367,7 +367,7 @@ func (self *Vm) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Co // log emits a log event to the environment for each opcode encountered. This is not to be confused with the // LOG* opcode. func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *stack, contract *Contract, err error) { - if Debug { + if Debug || GenerateStructLogs { mem := make([]byte, len(memory.Data())) copy(mem, memory.Data()) diff --git a/eth/api.go b/eth/api.go index 0f55c60a9..bc7d17534 100644 --- a/eth/api.go +++ b/eth/api.go @@ -1501,6 +1501,145 @@ func (api *PrivateDebugAPI) SetHead(number uint64) { api.eth.BlockChain().SetHead(number) } +// StructLogRes stores a structured log emitted by the evm while replaying a +// transaction in debug mode +type structLogRes struct { + Pc uint64 `json:"pc"` + Op string `json:"op"` + Gas *big.Int `json:"gas"` + GasCost *big.Int `json:"gasCost"` + Error error `json:"error"` + Stack []string `json:"stack"` + Memory map[string]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"` +} + +func (s *PrivateDebugAPI) doReplayTransaction(txHash common.Hash) ([]vm.StructLog, []byte, *big.Int, 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") + } + + block := s.eth.BlockChain().GetBlockByNumber(blockIndex - 1) + if block == nil { + return nil, nil, 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 + } + + txFrom, err := tx.From() + + if err != nil { + return nil, nil, nil, fmt.Errorf("Unable to create transaction sender") + } + from := stateDb.GetOrNewStateObject(txFrom) + msg := callmsg{ + from: from, + to: tx.To(), + gas: tx.Gas(), + gasPrice: tx.GasPrice(), + value: tx.Value(), + data: tx.Data(), + } + + vmenv := core.NewEnv(stateDb, s.eth.BlockChain(), msg, block.Header()) + gp := new(core.GasPool).AddGas(block.GasLimit()) + vm.GenerateStructLogs = true + defer func() { vm.GenerateStructLogs = false }() + + 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 + } + + 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) + } + + 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++ + } + + storageLength := len(trace.Stack) + if storageSize != -1 && storageSize < storageLength { + storageLength = storageSize + } + + 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 +} + // PublicNetAPI offers network related RPC methods type PublicNetAPI struct { net *p2p.Server diff --git a/rpc/javascript.go b/rpc/javascript.go index c145163e5..4c0ac5354 100644 --- a/rpc/javascript.go +++ b/rpc/javascript.go @@ -407,6 +407,11 @@ web3._extend({ call: 'debug_writeMemProfile', params: 1 }), + new web3._extend.Method({ + name: 'replayTransaction', + call: 'debug_replayTransaction', + params: 4 + }) ], properties: [