|
@@ -20,7 +20,6 @@ import (
|
|
|
"bytes"
|
|
"bytes"
|
|
|
"compress/gzip"
|
|
"compress/gzip"
|
|
|
"context"
|
|
"context"
|
|
|
- "errors"
|
|
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"io"
|
|
"io"
|
|
|
"io/ioutil"
|
|
"io/ioutil"
|
|
@@ -41,6 +40,7 @@ import (
|
|
|
"github.com/ethereum/go-ethereum/params"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
|
|
|
+ "github.com/ethereum/go-ethereum/trie"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
const defaultTraceTimeout = 5 * time.Second
|
|
const defaultTraceTimeout = 5 * time.Second
|
|
@@ -526,59 +526,67 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common.
|
|
|
if tx == nil {
|
|
if tx == nil {
|
|
|
return nil, fmt.Errorf("transaction %x not found", txHash)
|
|
return nil, fmt.Errorf("transaction %x not found", txHash)
|
|
|
}
|
|
}
|
|
|
|
|
+ msg, context, statedb, err := api.computeTxEnv(blockHash, int(txIndex))
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Run the transaction with tracing enabled.
|
|
|
|
|
+ vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{Debug: true, Tracer: tracer})
|
|
|
|
|
+ ret, gas, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()))
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, fmt.Errorf("tracing failed: %v", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ switch tracer := tracer.(type) {
|
|
|
|
|
+ case *vm.StructLogger:
|
|
|
|
|
+ return ðapi.ExecutionResult{
|
|
|
|
|
+ Gas: gas,
|
|
|
|
|
+ ReturnValue: fmt.Sprintf("%x", ret),
|
|
|
|
|
+ StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
|
|
|
|
|
+ }, nil
|
|
|
|
|
+ case *ethapi.JavascriptTracer:
|
|
|
|
|
+ return tracer.GetResult()
|
|
|
|
|
+ default:
|
|
|
|
|
+ panic(fmt.Sprintf("bad tracer type %T", tracer))
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// computeTxEnv returns the execution environment of a certain transaction.
|
|
|
|
|
+func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int) (core.Message, vm.Context, *state.StateDB, error) {
|
|
|
|
|
+ // Create the parent state.
|
|
|
block := api.eth.BlockChain().GetBlockByHash(blockHash)
|
|
block := api.eth.BlockChain().GetBlockByHash(blockHash)
|
|
|
if block == nil {
|
|
if block == nil {
|
|
|
- return nil, fmt.Errorf("block %x not found", blockHash)
|
|
|
|
|
|
|
+ return nil, vm.Context{}, nil, fmt.Errorf("block %x not found", blockHash)
|
|
|
}
|
|
}
|
|
|
- // Create the state database to mutate and eventually trace
|
|
|
|
|
parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1)
|
|
parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1)
|
|
|
if parent == nil {
|
|
if parent == nil {
|
|
|
- return nil, fmt.Errorf("block parent %x not found", block.ParentHash())
|
|
|
|
|
|
|
+ return nil, vm.Context{}, nil, fmt.Errorf("block parent %x not found", block.ParentHash())
|
|
|
}
|
|
}
|
|
|
- stateDb, err := api.eth.BlockChain().StateAt(parent.Root())
|
|
|
|
|
|
|
+ statedb, err := api.eth.BlockChain().StateAt(parent.Root())
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, err
|
|
|
|
|
|
|
+ return nil, vm.Context{}, nil, err
|
|
|
}
|
|
}
|
|
|
|
|
+ txs := block.Transactions()
|
|
|
|
|
|
|
|
|
|
+ // Recompute transactions up to the target index.
|
|
|
signer := types.MakeSigner(api.config, block.Number())
|
|
signer := types.MakeSigner(api.config, block.Number())
|
|
|
- // Mutate the state and trace the selected transaction
|
|
|
|
|
- for idx, tx := range block.Transactions() {
|
|
|
|
|
|
|
+ for idx, tx := range txs {
|
|
|
// Assemble the transaction call message
|
|
// Assemble the transaction call message
|
|
|
- msg, err := tx.AsMessage(signer)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("sender retrieval failed: %v", err)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ msg, _ := tx.AsMessage(signer)
|
|
|
context := core.NewEVMContext(msg, block.Header(), api.eth.BlockChain(), nil)
|
|
context := core.NewEVMContext(msg, block.Header(), api.eth.BlockChain(), nil)
|
|
|
-
|
|
|
|
|
- // Mutate the state if we haven't reached the tracing transaction yet
|
|
|
|
|
- if uint64(idx) < txIndex {
|
|
|
|
|
- vmenv := vm.NewEVM(context, stateDb, api.config, vm.Config{})
|
|
|
|
|
- _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()))
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return nil, fmt.Errorf("mutation failed: %v", err)
|
|
|
|
|
- }
|
|
|
|
|
- stateDb.DeleteSuicides()
|
|
|
|
|
- continue
|
|
|
|
|
|
|
+ if idx == txIndex {
|
|
|
|
|
+ return msg, context, statedb, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- vmenv := vm.NewEVM(context, stateDb, api.config, vm.Config{Debug: true, Tracer: tracer})
|
|
|
|
|
- ret, gas, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()))
|
|
|
|
|
|
|
+ vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{})
|
|
|
|
|
+ gp := new(core.GasPool).AddGas(tx.Gas())
|
|
|
|
|
+ _, _, err := core.ApplyMessage(vmenv, msg, gp)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, fmt.Errorf("tracing failed: %v", err)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- switch tracer := tracer.(type) {
|
|
|
|
|
- case *vm.StructLogger:
|
|
|
|
|
- return ðapi.ExecutionResult{
|
|
|
|
|
- Gas: gas,
|
|
|
|
|
- ReturnValue: fmt.Sprintf("%x", ret),
|
|
|
|
|
- StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
|
|
|
|
|
- }, nil
|
|
|
|
|
- case *ethapi.JavascriptTracer:
|
|
|
|
|
- return tracer.GetResult()
|
|
|
|
|
|
|
+ return nil, vm.Context{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err)
|
|
|
}
|
|
}
|
|
|
|
|
+ statedb.DeleteSuicides()
|
|
|
}
|
|
}
|
|
|
- return nil, errors.New("database inconsistency")
|
|
|
|
|
|
|
+ return nil, vm.Context{}, nil, fmt.Errorf("tx index %d out of range for block %x", txIndex, blockHash)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Preimage is a debug API function that returns the preimage for a sha3 hash, if known.
|
|
// Preimage is a debug API function that returns the preimage for a sha3 hash, if known.
|
|
@@ -592,3 +600,48 @@ func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hex
|
|
|
func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]core.BadBlockArgs, error) {
|
|
func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]core.BadBlockArgs, error) {
|
|
|
return api.eth.BlockChain().BadBlocks()
|
|
return api.eth.BlockChain().BadBlocks()
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// StorageRangeResult is the result of a debug_storageRangeAt API call.
|
|
|
|
|
+type StorageRangeResult struct {
|
|
|
|
|
+ Storage storageMap `json:"storage"`
|
|
|
|
|
+ NextKey *common.Hash `json:"nextKey"` // nil if Storage includes the last key in the trie.
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type storageMap map[common.Hash]storageEntry
|
|
|
|
|
+
|
|
|
|
|
+type storageEntry struct {
|
|
|
|
|
+ Key *common.Hash `json:"key"`
|
|
|
|
|
+ Value common.Hash `json:"value"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// StorageRangeAt returns the storage at the given block height and transaction index.
|
|
|
|
|
+func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) {
|
|
|
|
|
+ _, _, statedb, err := api.computeTxEnv(blockHash, txIndex)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return StorageRangeResult{}, err
|
|
|
|
|
+ }
|
|
|
|
|
+ st := statedb.StorageTrie(contractAddress)
|
|
|
|
|
+ if st == nil {
|
|
|
|
|
+ return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress)
|
|
|
|
|
+ }
|
|
|
|
|
+ return storageRangeAt(st, keyStart, maxResult), nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func storageRangeAt(st *trie.SecureTrie, start []byte, maxResult int) StorageRangeResult {
|
|
|
|
|
+ it := trie.NewIterator(st.NodeIterator(start))
|
|
|
|
|
+ result := StorageRangeResult{Storage: storageMap{}}
|
|
|
|
|
+ for i := 0; i < maxResult && it.Next(); i++ {
|
|
|
|
|
+ e := storageEntry{Value: common.BytesToHash(it.Value)}
|
|
|
|
|
+ if preimage := st.GetKey(it.Key); preimage != nil {
|
|
|
|
|
+ preimage := common.BytesToHash(preimage)
|
|
|
|
|
+ e.Key = &preimage
|
|
|
|
|
+ }
|
|
|
|
|
+ result.Storage[common.BytesToHash(it.Key)] = e
|
|
|
|
|
+ }
|
|
|
|
|
+ // Add the 'next key' so clients can continue downloading.
|
|
|
|
|
+ if it.Next() {
|
|
|
|
|
+ next := common.BytesToHash(it.Key)
|
|
|
|
|
+ result.NextKey = &next
|
|
|
|
|
+ }
|
|
|
|
|
+ return result
|
|
|
|
|
+}
|