diff --git a/core/vm/common.go b/core/vm/common.go index 2d1aa93326..b9058e8f86 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -28,6 +28,9 @@ import ( // Global Debug flag indicating Debug VM (full logging) var Debug bool +// Global Debug flag indicating to generate structured logs of the evm execution (full logging) +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 4b03e55f04..a54f57633d 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/rpc/api/eth.go b/rpc/api/eth.go index db7a643d82..fcc97e7983 100644 --- a/rpc/api/eth.go +++ b/rpc/api/eth.go @@ -78,6 +78,7 @@ var ( "eth_transact": (*ethApi).SendTransaction, "eth_estimateGas": (*ethApi).EstimateGas, "eth_call": (*ethApi).Call, + "eth_debugTransaction": (*ethApi).DebugTransaction, "eth_flush": (*ethApi).Flush, "eth_getBlockByHash": (*ethApi).GetBlockByHash, "eth_getBlockByNumber": (*ethApi).GetBlockByNumber, @@ -406,6 +407,69 @@ func (self *ethApi) Call(req *shared.Request) (interface{}, error) { } } +func (self *ethApi) DebugTransaction(req *shared.Request) (interface{}, error) { + + args := new(DebugTransactionArgs) + if err := self.codec.Decode(req.Params, &args); err != nil { + return nil, shared.NewDecodeParamError(err.Error()) + } + + vmtrace, ret, gas, err := self.xeth.DebugTransaction(args.TxHash) + + if err != nil { + return nil, err + } + + res := TransactionExecutionRes{ + Gas: gas, + ReturnValue: fmt.Sprintf("%x", ret), + StructLogs: make([]StructLogRes, len(vmtrace)), + } + + for index, trace := range vmtrace { + + stackLength := len(trace.Stack) + + // Return full stack by default + if args.StackDepth != -1 && args.StackDepth < stackLength { + stackLength = args.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 + memorySize := args.MemorySize + + // Return full memory by default + if memorySize == -1 { + memorySize = len(trace.Memory) + } + + for i := 0; i+16 <= len(trace.Memory) && addr < memorySize; i += 16 { + res.StructLogs[index].Memory[fmt.Sprintf("%04d", addr*16)] = fmt.Sprintf("%x", trace.Memory[i:i+16]) + addr++ + } + + for storageIndex, storageValue := range trace.Storage { + res.StructLogs[index].Storage[fmt.Sprintf("%x", storageIndex)] = fmt.Sprintf("%x", storageValue) + } + } + return res, nil +} + func (self *ethApi) Flush(req *shared.Request) (interface{}, error) { return nil, shared.NewNotImplementedError(req.Method) } diff --git a/rpc/api/eth_args.go b/rpc/api/eth_args.go index ed3d761f17..3e9fc876d6 100644 --- a/rpc/api/eth_args.go +++ b/rpc/api/eth_args.go @@ -1102,3 +1102,50 @@ func (args *ResendArgs) UnmarshalJSON(b []byte) (err error) { return nil } + +type DebugTransactionArgs struct { + TxHash string + StackDepth int + MemorySize int +} + +func (args *DebugTransactionArgs) UnmarshalJSON(b []byte) (err error) { + var obj []interface{} + + if err := json.Unmarshal(b, &obj); err != nil { + return shared.NewDecodeParamError(err.Error()) + } + + if len(obj) < 1 { + return shared.NewInsufficientParamsError(len(obj), 1) + } + + argstr, ok := obj[0].(string) + if !ok { + return shared.NewInvalidTypeError("txHash", "not a string") + } + args.TxHash = argstr + + if len(obj) > 1 && obj[1] != nil { + if stackDepthVal, ok := obj[1].(float64); ok { + args.StackDepth = int(stackDepthVal) + } else { + return shared.NewInvalidTypeError("stackDepth", "invalid number") + } + } else { + args.StackDepth = -1 + } + + if len(obj) > 2 && obj[2] != nil { + + if memorySizeVal, ok := obj[2].(float64); ok { + args.MemorySize = int(memorySizeVal) + } else { + return shared.NewInvalidTypeError("memorySize", "invalid number") + } + } else { + args.MemorySize = -1 + } + + return nil +} diff --git a/rpc/api/eth_js.go b/rpc/api/eth_js.go index dfc104ad82..4e3f8cdd10 100644 --- a/rpc/api/eth_js.go +++ b/rpc/api/eth_js.go @@ -53,6 +53,11 @@ web3._extend({ call: 'eth_submitTransaction', params: 1, inputFormatter: [web3._extend.formatters.inputTransactionFormatter] + }), + new web3._extend.Method({ + name: 'debugTransaction', + call: 'eth_debugTransaction', + params: 3 }) ], properties: diff --git a/rpc/api/parsing.go b/rpc/api/parsing.go index 7667616ff4..8ea58ea78f 100644 --- a/rpc/api/parsing.go +++ b/rpc/api/parsing.go @@ -462,6 +462,23 @@ func NewReceiptRes(rec *types.Receipt) *ReceiptRes { return v } +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` +} + +type TransactionExecutionRes struct { + Gas *big.Int `json:Gas` + ReturnValue string `json:ReturnValue` + StructLogs []StructLogRes `json:StructLogs` +} + func numString(raw interface{}) (*big.Int, error) { var number *big.Int // Parse as integer diff --git a/xeth/xeth.go b/xeth/xeth.go index 5a5399a3e7..944af554b3 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -822,6 +822,61 @@ func (self *XEth) PushTx(encodedTx string) (string, error) { return tx.Hash().Hex(), nil } +func (self *XEth) DebugTransaction(hash string) ([]vm.StructLog, []byte, *big.Int, error) { + + // Make sure the transaction exists and has been included into a block + tx, _, number, _ := core.GetTransaction(self.EthereumService().ChainDb(), common.HexToHash(hash)) + + if tx == nil { + return nil, nil, nil, fmt.Errorf("Transaction not found or not yet mined") + } + + // Retrieve the block prior to the block which containts the transaction + block := self.EthereumService().BlockChain().GetBlockByNumber(number - 1) + + if block == nil { + return nil, nil, nil, fmt.Errorf("Unable to retrieve prior block") + } + + statedb, err := state.New(block.Root(), self.EthereumService().ChainDb()) + + if err != nil { + return nil, nil, nil, fmt.Errorf("Unable to create transient state database") + } + + 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, self.EthereumService().BlockChain(), msg, block.Header()) + + gp := new(core.GasPool).AddGas(block.GasLimit()) + + // vmDebugValue := vm.Debug + vm.GenerateStructLogs = true + ret, gas, err := core.ApplyMessage(vmenv, msg, gp) + vm.GenerateStructLogs = false + + if err != nil { + return nil, nil, nil, fmt.Errorf("Error executing transaction %v", err) + } + + return vmenv.StructLogs(), ret, gas, nil +} + func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, string, error) { statedb := self.State().State().Copy() var from *state.StateObject