mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 06:06:44 +00:00
core/vm, rpc/api, xeth: added eth_debugTransaction RPC call
This commit is contained in:
parent
e7f6798b59
commit
9813cd01f6
7 changed files with 192 additions and 1 deletions
|
|
@ -28,6 +28,9 @@ import (
|
||||||
// Global Debug flag indicating Debug VM (full logging)
|
// Global Debug flag indicating Debug VM (full logging)
|
||||||
var Debug bool
|
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 is the VM type accepted by **NewVm**
|
||||||
type Type byte
|
type Type byte
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 emits a log event to the environment for each opcode encountered. This is not to be confused with the
|
||||||
// LOG* opcode.
|
// LOG* opcode.
|
||||||
func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *stack, contract *Contract, err error) {
|
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()))
|
mem := make([]byte, len(memory.Data()))
|
||||||
copy(mem, memory.Data())
|
copy(mem, memory.Data())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ var (
|
||||||
"eth_transact": (*ethApi).SendTransaction,
|
"eth_transact": (*ethApi).SendTransaction,
|
||||||
"eth_estimateGas": (*ethApi).EstimateGas,
|
"eth_estimateGas": (*ethApi).EstimateGas,
|
||||||
"eth_call": (*ethApi).Call,
|
"eth_call": (*ethApi).Call,
|
||||||
|
"eth_debugTransaction": (*ethApi).DebugTransaction,
|
||||||
"eth_flush": (*ethApi).Flush,
|
"eth_flush": (*ethApi).Flush,
|
||||||
"eth_getBlockByHash": (*ethApi).GetBlockByHash,
|
"eth_getBlockByHash": (*ethApi).GetBlockByHash,
|
||||||
"eth_getBlockByNumber": (*ethApi).GetBlockByNumber,
|
"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) {
|
func (self *ethApi) Flush(req *shared.Request) (interface{}, error) {
|
||||||
return nil, shared.NewNotImplementedError(req.Method)
|
return nil, shared.NewNotImplementedError(req.Method)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1102,3 +1102,50 @@ func (args *ResendArgs) UnmarshalJSON(b []byte) (err error) {
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,11 @@ web3._extend({
|
||||||
call: 'eth_submitTransaction',
|
call: 'eth_submitTransaction',
|
||||||
params: 1,
|
params: 1,
|
||||||
inputFormatter: [web3._extend.formatters.inputTransactionFormatter]
|
inputFormatter: [web3._extend.formatters.inputTransactionFormatter]
|
||||||
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'debugTransaction',
|
||||||
|
call: 'eth_debugTransaction',
|
||||||
|
params: 3
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
properties:
|
properties:
|
||||||
|
|
|
||||||
|
|
@ -462,6 +462,23 @@ func NewReceiptRes(rec *types.Receipt) *ReceiptRes {
|
||||||
return v
|
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) {
|
func numString(raw interface{}) (*big.Int, error) {
|
||||||
var number *big.Int
|
var number *big.Int
|
||||||
// Parse as integer
|
// Parse as integer
|
||||||
|
|
|
||||||
55
xeth/xeth.go
55
xeth/xeth.go
|
|
@ -822,6 +822,61 @@ func (self *XEth) PushTx(encodedTx string) (string, error) {
|
||||||
return tx.Hash().Hex(), nil
|
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) {
|
func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, string, error) {
|
||||||
statedb := self.State().State().Copy()
|
statedb := self.State().State().Copy()
|
||||||
var from *state.StateObject
|
var from *state.StateObject
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue