diff --git a/core/state/state_object.go b/core/state/state_object.go index edb073173..ddfc13ed2 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -146,6 +146,10 @@ func (c *StateObject) getTrie(db trie.Database) *trie.SecureTrie { return c.trie } +func (so *StateObject) storageRoot(db trie.Database) common.Hash { + return so.getTrie(db).Hash() +} + // GetState returns a value in account storage. func (self *StateObject) GetState(db trie.Database, key common.Hash) common.Hash { value, exists := self.cachedStorage[key] diff --git a/core/state/statedb.go b/core/state/statedb.go index ae106e03b..a857d63dc 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -286,6 +286,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 5d041c740..5055f9ae0 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -115,6 +115,36 @@ func TestIntermediateLeaks(t *testing.T) { } } +func TestStorageRoot(t *testing.T) { + var ( + db, _ = ethdb.NewMemDatabase() + 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 storate root, expected %x, got %x", empty, emptyRoot) + } + + // add a bit of state + so.SetState(db, key, value) + state.Commit() + + 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 054375a87..1dd9e9319 100644 --- a/eth/api.go +++ b/eth/api.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" "golang.org/x/net/context" ) @@ -59,6 +60,34 @@ func (s *PublicEthereumAPI) Coinbase() (common.Address, error) { return s.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) +} + // PrivateAdminAPI is the collection of Etheruem full node-related APIs // exposed over the private admin endpoint. type PrivateAdminAPI struct {