Skip to content

Commit

Permalink
core/vm, rpc/api: renamed to debug.replayTransaction, migrated to new…
Browse files Browse the repository at this point in the history
… RPC, integrated feedback

Integrated code review suggestions

Integrated last review comments
  • Loading branch information
Peter Pratscher committed Feb 2, 2016
1 parent 528dcc3 commit 15780ea
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 1 deletion.
2 changes: 2 additions & 0 deletions core/vm/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion core/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down
139 changes: 139 additions & 0 deletions eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,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
Expand Down
5 changes: 5 additions & 0 deletions rpc/javascript.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,11 @@ web3._extend({
call: 'debug_writeMemProfile',
params: 1
}),
new web3._extend.Method({
name: 'replayTransaction',
call: 'debug_replayTransaction',
params: 4
})
],
properties:
[
Expand Down

0 comments on commit 15780ea

Please sign in to comment.