diff --git a/core/state/dump.go b/core/state/dump.go index 8294d61b9f2..edd58a86392 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 87aa8ccd652..9dd9259faa4 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)) diff --git a/eth/api.go b/eth/api.go index a86ed95cfcf..bce25ef7ed1 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 ce50d3634c3..e6f5aea9a44 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: []