Merge pull request #2156 from ppratscher/add_replay_tx
core/vm, rpc/api: added debug_replayTransaction RPC call
This commit is contained in:
commit
a8fd0de0d3
@ -28,6 +28,8 @@ import (
|
|||||||
// Global Debug flag indicating Debug VM (full logging)
|
// Global Debug flag indicating Debug VM (full logging)
|
||||||
var Debug bool
|
var Debug bool
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
139
eth/api.go
139
eth/api.go
@ -1501,6 +1501,145 @@ 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
|
||||||
|
// 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
|
// PublicNetAPI offers network related RPC methods
|
||||||
type PublicNetAPI struct {
|
type PublicNetAPI struct {
|
||||||
net *p2p.Server
|
net *p2p.Server
|
||||||
|
@ -407,6 +407,11 @@ web3._extend({
|
|||||||
call: 'debug_writeMemProfile',
|
call: 'debug_writeMemProfile',
|
||||||
params: 1
|
params: 1
|
||||||
}),
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'replayTransaction',
|
||||||
|
call: 'debug_replayTransaction',
|
||||||
|
params: 4
|
||||||
|
})
|
||||||
],
|
],
|
||||||
properties:
|
properties:
|
||||||
[
|
[
|
||||||
|
Loading…
Reference in New Issue
Block a user