From 2e66ceae80ec775374d8c716ffca5cb4827aaeb3 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 6 Dec 2016 00:07:51 +0100 Subject: [PATCH 1/2] core/state: expose GetTrie --- core/state/dump.go | 2 +- core/state/state_object.go | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/state/dump.go b/core/state/dump.go index 8294d61b9f..edd58a8639 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -61,7 +61,7 @@ func (self *StateDB) RawDump() Dump { Code: common.Bytes2Hex(obj.Code(self.db)), Storage: make(map[string]string), } - storageIt := obj.getTrie(self.db).Iterator() + storageIt := obj.GetTrie(self.db).Iterator() for storageIt.Next() { account.Storage[common.Bytes2Hex(self.trie.GetKey(storageIt.Key))] = common.Bytes2Hex(storageIt.Value) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 87aa8ccd65..9dd9259faa 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -152,7 +152,9 @@ func (c *StateObject) touch() { c.touched = true } -func (c *StateObject) getTrie(db trie.Database) *trie.SecureTrie { +// GetTrie returns the current instantiated trie or creates a new one +// using the data's root to initialise the trie. +func (c *StateObject) GetTrie(db trie.Database) *trie.SecureTrie { if c.trie == nil { var err error c.trie, err = trie.NewSecure(c.data.Root, db, 0) @@ -171,7 +173,7 @@ func (self *StateObject) GetState(db trie.Database, key common.Hash) common.Hash return value } // Load from DB in case it is missing. - if enc := self.getTrie(db).Get(key[:]); len(enc) > 0 { + if enc := self.GetTrie(db).Get(key[:]); len(enc) > 0 { _, content, _, err := rlp.Split(enc) if err != nil { self.setError(err) @@ -206,7 +208,7 @@ func (self *StateObject) setState(key, value common.Hash) { // updateTrie writes cached storage modifications into the object's storage trie. func (self *StateObject) updateTrie(db trie.Database) { - tr := self.getTrie(db) + tr := self.GetTrie(db) for key, value := range self.dirtyStorage { delete(self.dirtyStorage, key) if (value == common.Hash{}) { @@ -388,7 +390,7 @@ func (self *StateObject) ForEachStorage(cb func(key, value common.Hash) bool) { cb(h, value) } - it := self.getTrie(self.db.db).Iterator() + it := self.GetTrie(self.db.db).Iterator() for it.Next() { // ignore cached values key := common.BytesToHash(self.trie.GetKey(it.Key)) From d653e63852d46f1c26ff895ea2e437709ff6b2ff Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Tue, 6 Dec 2016 00:09:12 +0100 Subject: [PATCH 2/2] eth, internal/web3ext: implement storageAt StorageAt is currently limited and requires needless iterations due to the limitation of the trie iterator. At present we can't start iterating from any given key, thus we need to loop over any key that's not inclusive in the range of start and end. --- eth/api.go | 84 +++++++++++++++++++++++++++++++++++++ internal/web3ext/web3ext.go | 5 +++ 2 files changed, 89 insertions(+) diff --git a/eth/api.go b/eth/api.go index a86ed95cfc..bce25ef7ed 100644 --- a/eth/api.go +++ b/eth/api.go @@ -547,3 +547,87 @@ func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common. } return nil, errors.New("database inconsistency") } + +type StorageRangeAtResult struct { + Storage map[string]string `json:"storage"` + Complete bool `json:"complete"` +} + +// StorageRangeAt returns the storage at the given block height and +// transaction index (exclusive). It may be limited using the +// storageAddress, storageAddressEnd (inclusive) and maxResult parameters. +// +// StorageRangeAt is currently limited and requires needless iterators +// due to the limitation of the trie iterator. At present we can't start +// iterating from any given key, thus we need to loop over any key that's +// not inclusive in the range of start and end. +// +// BUG: Because the state objects make use of the secure storage, iterating +// trie keys is out of order and will be returned the exact same way. +func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, storageAddressStart, storageAddressEnd common.Hash, maxResult int) (interface{}, error) { + block := api.eth.BlockChain().GetBlockByHash(blockHash) + if block == nil { + return 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()) + } + stateDb, err := api.eth.BlockChain().StateAt(parent.Root()) + if err != nil { + return nil, err + } + + result := StorageRangeAtResult{Storage: make(map[string]string)} + + signer := types.MakeSigner(api.config, block.Number()) + // Mutate the state and trace the selected transaction +done: + for idx, tx := range block.Transactions() { + // Assemble the transaction call message + msg, err := tx.AsMessage(signer) + if err != nil { + return nil, fmt.Errorf("sender retrieval failed: %v", err) + } + context := core.NewEVMContext(msg, block.Header(), api.eth.BlockChain()) + + // Mutate the state if we haven't reached the tracing transaction yet + if idx < txIndex { + vmenv := vm.NewEnvironment(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 + } + + stateObject := stateDb.GetStateObject(contractAddress) + // We need to check if the object exists again. It might have been deleted in between the + // transactions. + if stateObject != nil { + trie := stateObject.GetTrie(api.eth.ChainDb()) + it := trie.Iterator() + + for it.Next() && len(result.Storage) < maxResult { + var ( + value common.Hash + key = trie.GetKey(it.Key) + ) + + if bytes.Compare(storageAddressStart[:], key[:]) > 0 { + continue + } + if bytes.Compare(storageAddressEnd[:], key[:]) < 0 { + break done + } + + rlp.DecodeBytes(it.Value, &value) + result.Storage[common.ToHex(key)] = value.Hex() + } + result.Complete = !it.Next() + } + } + return result, nil +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index ce50d3634c..e6f5aea9a4 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -385,6 +385,11 @@ web3._extend({ call: 'debug_traceTransaction', params: 2, inputFormatter: [null, null] + }), + new web3._extend.Method({ + name: 'storageRangeAt', + call: 'debug_storageRangeAt', + params: 6, }) ], properties: []