Skip to content
11 changes: 6 additions & 5 deletions core/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,12 @@ type Trie interface {
// PrefetchAccount attempts to resolve specific accounts from the database
// to accelerate subsequent trie operations.
PrefetchAccount([]common.Address) error

// GetStorage returns the value for key stored in the trie. The value bytes
// must not be modified by the caller. If a node was not found in the database,
// a trie.MissingNodeError is returned.
GetStorage(addr common.Address, key []byte) ([]byte, error)

// GetStorage returns the value for key stored in the trie along with the depth
// (number of nodes traversed) used to resolve it. The value bytes must not be
// modified by the caller. If a node was not found in the database, a
// trie.MissingNodeError is returned.
GetStorage(addr common.Address, key []byte) ([]byte, uint64, error)

// PrefetchStorage attempts to resolve specific storage slots from the database
// to accelerate subsequent trie operations.
Expand Down
2 changes: 1 addition & 1 deletion core/state/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
r.muSubTries.Unlock()
}
}
ret, err := tr.GetStorage(addr, key.Bytes())
ret, _, err := tr.GetStorage(addr, key.Bytes())
if err != nil {
return common.Hash{}, err
}
Expand Down
228 changes: 222 additions & 6 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ type stateObject struct {
originStorage Storage // Storage entries that have been accessed within the current block
dirtyStorage Storage // Storage entries that have been modified within the current transaction
pendingStorage Storage // Storage entries that have been modified within the current block
// originStorageDepth stores the lookup-path depth for accessed slots.
originStorageDepth map[common.Hash]uint64

// uncommittedStorage tracks a set of storage entries that have been modified
// but not yet committed since the "last commit operation", along with their
Expand Down Expand Up @@ -108,6 +110,7 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s
origin: origin,
data: *acct,
originStorage: make(Storage),
originStorageDepth: make(map[common.Hash]uint64),
dirtyStorage: make(Storage),
pendingStorage: make(Storage),
uncommittedStorage: make(Storage),
Expand Down Expand Up @@ -163,6 +166,26 @@ func (s *stateObject) GetState(key common.Hash) common.Hash {
return value
}

// GetStateWithDepth retrieves a value associated with the given storage key,
// along with the depth (number of nodes traversed) used to resolve it.
func (s *stateObject) GetStateWithDepth(key common.Hash) (common.Hash, uint64) {
value, depth := s.getStateWithDepth(key)
return value, depth
}

// GetStateWithMeter retrieves a value associated with the given storage key,
// and charges per-node via the meter during trie traversal.
func (s *stateObject) GetStateWithMeter(key common.Hash, meter func(uint64) error) (common.Hash, uint64, error) {
origin, depth, err := s.GetCommittedStateWithMeter(key, meter)
if err != nil {
return common.Hash{}, 0, err
}
if value, dirty := s.dirtyStorage[key]; dirty {
return value, depth, nil
}
return origin, depth, nil
}

// getState retrieves a value associated with the given storage key, along with
// its original value.
func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) {
Expand All @@ -174,18 +197,54 @@ func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) {
return origin, origin
}

// getStateWithDepth retrieves a value associated with the given storage key,
// along with the depth of the lookup path.
func (s *stateObject) getStateWithDepth(key common.Hash) (common.Hash, uint64) {
origin, depth := s.GetCommittedStateWithDepth(key)
value, dirty := s.dirtyStorage[key]
if dirty {
return value, depth
}
return origin, depth
}

func (s *stateObject) getCachedStateDepth(key common.Hash) (uint64, bool) {
s.storageMutex.Lock()
defer s.storageMutex.Unlock()
depth, ok := s.originStorageDepth[key]
return depth, ok
}

// GetCommittedState retrieves the value associated with the specific key
// without any mutations caused in the current execution.
func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
value, _ := s.GetCommittedStateWithDepth(key)
return value
}

// GetCommittedStateWithDepth retrieves the value associated with the specific key
// without any mutations caused in the current execution, along with the depth
// of the lookup path.
func (s *stateObject) GetCommittedStateWithDepth(key common.Hash) (common.Hash, uint64) {
s.storageMutex.Lock()
defer s.storageMutex.Unlock()
// If we have a pending write or clean cached, return that
if value, pending := s.pendingStorage[key]; pending {
return value
return value, s.originStorageDepth[key]
}

if value, cached := s.originStorage[key]; cached {
return value
if depth, ok := s.originStorageDepth[key]; ok {
return value, depth
}
// Depth missing, resolve it from trie without altering the cached value.
depth, err := s.resolveStorageDepth(key)
if err != nil {
log.Error("Failed to resolve storage slot depth", "address", s.address, "err", err)
return value, 0
}
s.originStorageDepth[key] = depth
return value, depth
}

// If the object was destructed in *this* block (and potentially resurrected),
Expand All @@ -196,15 +255,16 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// 2) we don't have new values, and can deliver empty response back
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
s.originStorage[key] = common.Hash{} // track the empty slot as origin value
return common.Hash{}
s.originStorageDepth[key] = 0 // depth is undefined for cleared storage
return common.Hash{}, 0
}
s.db.StorageLoaded++

start := time.Now()
value, err := s.db.reader.Storage(s.address, key)
value, depth, err := s.resolveStorageWithDepth(key)
if err != nil {
s.db.setError(err)
return common.Hash{}
return common.Hash{}, 0
}
s.db.StorageReads += time.Since(start)

Expand All @@ -215,8 +275,110 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
}
}
s.originStorage[key] = value
s.originStorageDepth[key] = depth

return value
return value, depth
}

// GetCommittedStateWithMeter retrieves the value associated with the specific key
// without any mutations caused in the current execution, charging the meter
// during trie traversal.
func (s *stateObject) GetCommittedStateWithMeter(key common.Hash, meter func(uint64) error) (common.Hash, uint64, error) {
s.storageMutex.Lock()
defer s.storageMutex.Unlock()
// If we have a pending write or clean cached, return that
if value, pending := s.pendingStorage[key]; pending {
return value, s.originStorageDepth[key], nil
}

if value, cached := s.originStorage[key]; cached {
if depth, ok := s.originStorageDepth[key]; ok {
return value, depth, nil
}
// Depth missing, resolve it from trie without altering the cached value.
depth, err := s.resolveStorageDepthWithMeter(key, meter)
if err != nil {
log.Error("Failed to resolve storage slot depth", "address", s.address, "err", err)
return value, 0, err
}
s.originStorageDepth[key] = depth
return value, depth, nil
}

// If the object was destructed in *this* block (and potentially resurrected),
// the storage has been cleared out, and we should *not* consult the previous
// database about any storage values. The only possible alternatives are:
// 1) resurrect happened, and new slot values were set -- those should
// have been handles via pendingStorage above.
// 2) we don't have new values, and can deliver empty response back
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
s.originStorage[key] = common.Hash{} // track the empty slot as origin value
s.originStorageDepth[key] = 0 // depth is undefined for cleared storage
return common.Hash{}, 0, nil
}
s.db.StorageLoaded++

start := time.Now()
value, depth, err := s.resolveStorageWithMeter(key, meter)
if err != nil {
s.db.setError(err)
return common.Hash{}, 0, err
}
s.db.StorageReads += time.Since(start)

// Schedule the resolved storage slots for prefetching if it's enabled.
if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash {
if err = s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, nil, []common.Hash{key}, true); err != nil {
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
}
}
s.originStorage[key] = value
s.originStorageDepth[key] = depth

return value, depth, nil
}

func (s *stateObject) resolveStorageWithDepth(key common.Hash) (common.Hash, uint64, error) {
tr, err := s.getTrie()
if err != nil {
return common.Hash{}, 0, err
}
ret, depth, err := tr.GetStorage(s.address, key.Bytes())
if err != nil {
return common.Hash{}, 0, err
}
var value common.Hash
value.SetBytes(ret)
return value, depth, nil
}

func (s *stateObject) resolveStorageWithMeter(key common.Hash, meter func(uint64) error) (common.Hash, uint64, error) {
tr, err := s.getTrie()
if err != nil {
return common.Hash{}, 0, err
}
if metered, ok := tr.(interface {
GetStorageWithMeter(common.Address, []byte, func(uint64) error) ([]byte, uint64, error)
}); ok {
ret, depth, err := metered.GetStorageWithMeter(s.address, key.Bytes(), meter)
if err != nil {
return common.Hash{}, 0, err
}
var value common.Hash
value.SetBytes(ret)
return value, depth, nil
}
return s.resolveStorageWithDepth(key)
}

func (s *stateObject) resolveStorageDepthWithMeter(key common.Hash, meter func(uint64) error) (uint64, error) {
_, depth, err := s.resolveStorageWithMeter(key, meter)
return depth, err
}

func (s *stateObject) resolveStorageDepth(key common.Hash) (uint64, error) {
_, depth, err := s.resolveStorageWithDepth(key)
return depth, err
}

// SetState updates a value in account storage.
Expand All @@ -234,6 +396,48 @@ func (s *stateObject) SetState(key, value common.Hash) common.Hash {
return prev
}

// SetStateWithDepth updates a value in account storage and returns the previous
// value along with the lookup depth used to resolve the slot.
func (s *stateObject) SetStateWithDepth(key, value common.Hash) (common.Hash, uint64) {
origin, depth := s.GetCommittedStateWithDepth(key)
prev := origin
if dirtyValue, dirty := s.dirtyStorage[key]; dirty {
prev = dirtyValue
}
// If the new value is the same as old, don't set. Otherwise, track only the
// dirty changes, supporting reverting all of it back to no change.
if prev == value {
return prev, depth
}
// New value is different, update and journal the change
s.db.journal.storageChange(s.address, key, prev, origin)
s.setState(key, value, origin)
return prev, depth
}

// SetStateWithMeter updates a value in account storage and returns the previous
// value along with the lookup depth used to resolve the slot, charging the meter
// during trie traversal.
func (s *stateObject) SetStateWithMeter(key, value common.Hash, meter func(uint64) error) (common.Hash, uint64, error) {
origin, depth, err := s.GetCommittedStateWithMeter(key, meter)
if err != nil {
return common.Hash{}, 0, err
}
prev := origin
if dirtyValue, dirty := s.dirtyStorage[key]; dirty {
prev = dirtyValue
}
// If the new value is the same as old, don't set. Otherwise, track only the
// dirty changes, supporting reverting all of it back to no change.
if prev == value {
return prev, depth, nil
}
// New value is different, update and journal the change
s.db.journal.storageChange(s.address, key, prev, origin)
s.setState(key, value, origin)
return prev, depth, nil
}

// setState updates a value in account dirty storage. The dirtiness will be
// removed if the value being set equals to the original value.
func (s *stateObject) setState(key common.Hash, value common.Hash, origin common.Hash) {
Expand Down Expand Up @@ -500,6 +704,7 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
data: s.data,
code: s.code,
originStorage: s.originStorage.Copy(),
originStorageDepth: copyDepthMap(s.originStorageDepth),
pendingStorage: s.pendingStorage.Copy(),
dirtyStorage: s.dirtyStorage.Copy(),
uncommittedStorage: s.uncommittedStorage.Copy(),
Expand All @@ -525,6 +730,17 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
return obj
}

func copyDepthMap(src map[common.Hash]uint64) map[common.Hash]uint64 {
if len(src) == 0 {
return make(map[common.Hash]uint64)
}
dst := make(map[common.Hash]uint64, len(src))
for k, v := range src {
dst[k] = v
}
return dst
}

//
// Attribute accessors
//
Expand Down
Loading
Loading