|
|
@@ -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
|