diff options
Diffstat (limited to 'eth/api.go')
-rw-r--r-- | eth/api.go | 127 |
1 files changed, 90 insertions, 37 deletions
diff --git a/eth/api.go b/eth/api.go index b386c08b4..61f7bdd92 100644 --- a/eth/api.go +++ b/eth/api.go @@ -20,7 +20,6 @@ import ( "bytes" "compress/gzip" "context" - "errors" "fmt" "io" "io/ioutil" @@ -41,6 +40,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" ) const defaultTraceTimeout = 5 * time.Second @@ -526,59 +526,67 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common. if tx == nil { 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) 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) 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 { - 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()) - // Mutate the state and trace the selected transaction - for idx, tx := range block.Transactions() { + for idx, tx := range txs { // 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) - - // 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 { - 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. @@ -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) { 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 +} |