diff --git a/core/state/state_object.go b/core/state/state_object.go index b2378c69c..c787e11eb 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -169,6 +169,10 @@ func (c *stateObject) getTrie(db Database) Trie { return c.trie } +func (so *stateObject) storageRoot(db Database) common.Hash { + return so.getTrie(db).Hash() +} + // GetState returns a value in account storage. func (self *stateObject) GetState(db Database, key common.Hash) common.Hash { value, exists := self.cachedStorage[key] diff --git a/core/state/statedb.go b/core/state/statedb.go index 002fa6249..64f6ad5b8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -255,6 +255,15 @@ func (self *StateDB) HasSuicided(addr common.Address) bool { return false } +// GetStorageRoot returns the root of the storage associated with the given address. +func (self *StateDB) GetStorageRoot(addr common.Address) (common.Hash, error) { + so := self.getStateObject(addr) + if so == nil { + return common.Hash{}, fmt.Errorf("can't find state object") + } + return so.storageRoot(self.db), nil +} + /* * SETTERS */ diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index b2bd18e65..f9a5df629 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -117,6 +117,37 @@ func TestIntermediateLeaks(t *testing.T) { } } +func TestStorageRoot(t *testing.T) { + var ( + mem, _ = ethdb.NewMemDatabase() + db = NewDatabase(mem) + state, _ = New(common.Hash{}, db) + addr = common.Address{1} + key = common.Hash{1} + value = common.Hash{42} + + empty = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + ) + + so := state.GetOrNewStateObject(addr) + + emptyRoot := so.storageRoot(db) + if emptyRoot != empty { + t.Errorf("Invalid empty storage root, expected %x, got %x", empty, emptyRoot) + } + + // add a bit of state + so.SetState(db, key, value) + state.CommitTo(mem, false) + + root := so.storageRoot(db) + expected := common.HexToHash("63511abd258fa907afa30cb118b53744a4f49055bb3f531da512c6b866fc2ffb") + + if expected != root { + t.Errorf("Invalid storage root, expected %x, got %x", expected, root) + } +} + func TestSnapshotRandom(t *testing.T) { config := &quick.Config{MaxCount: 1000} err := quick.Check((*snapshotTest).run, config) diff --git a/eth/api.go b/eth/api.go index de9305180..b75f85023 100644 --- a/eth/api.go +++ b/eth/api.go @@ -66,6 +66,33 @@ func (api *PublicEthereumAPI) Coinbase() (common.Address, error) { return api.Etherbase() } +// StorageRoot returns the storage root of an account on the the given (optional) block height. +// If block number is not given the latest block is used. +func (s *PublicEthereumAPI) StorageRoot(addr common.Address, blockNr *rpc.BlockNumber) (common.Hash, error) { + var ( + pub, priv *state.StateDB + err error + ) + + if blockNr == nil || blockNr.Int64() == rpc.LatestBlockNumber.Int64() { + pub, priv, err = s.e.blockchain.State() + } else { + if ch := s.e.blockchain.GetHeaderByNumber(uint64(blockNr.Int64())); ch != nil { + pub, priv, err = s.e.blockchain.StateAt(ch.Root) + } else { + return common.Hash{}, fmt.Errorf("invalid block number") + } + } + + if err != nil { + return common.Hash{}, err + } + + if priv.Exist(addr) { + return priv.GetStorageRoot(addr) + } + return pub.GetStorageRoot(addr) +} // Hashrate returns the POW hashrate func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 { return hexutil.Uint64(api.e.Miner().HashRate()) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 7102419fa..b85e0ecbb 100755 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -402,6 +402,12 @@ web3._extend({ params: 2, inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter, web3._extend.utils.toHex] }), + new web3._extend.Method({ + name: 'storageRoot', + call: 'eth_storageRoot', + params: 2, + inputFormatter: [web3._extend.formatters.inputAddressFormatter, null] + }) ], properties: [ new web3._extend.Property({