core, trie: extend state reader interface with StorageExists

pull/30218/head
Gary Rong 2 months ago
parent a4fdf71de0
commit f54ee58920
  1. 7
      core/rawdb/accessors_snapshot.go
  2. 5
      core/state/database.go
  3. 61
      core/state/reader.go
  4. 31
      core/state/snapshot/difflayer.go
  5. 31
      core/state/snapshot/disklayer.go
  6. 48
      core/state/snapshot/disklayer_test.go
  7. 4
      core/state/snapshot/snapshot.go
  8. 10
      trie/secure_trie.go
  9. 33
      trie/secure_trie_test.go
  10. 11
      trie/verkle.go

@ -98,6 +98,13 @@ func ReadStorageSnapshot(db ethdb.KeyValueReader, accountHash, storageHash commo
return data return data
} }
// HasStorageSnapshot returns a flag indicating whether the requested storage
// slot exists.
func HasStorageSnapshot(db ethdb.KeyValueReader, accountHash, storageHash common.Hash) bool {
exists, _ := db.Has(storageSnapshotKey(accountHash, storageHash))
return exists
}
// WriteStorageSnapshot stores the snapshot entry of a storage trie leaf. // WriteStorageSnapshot stores the snapshot entry of a storage trie leaf.
func WriteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash common.Hash, entry []byte) { func WriteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash common.Hash, entry []byte) {
if err := db.Put(storageSnapshotKey(accountHash, storageHash), entry); err != nil { if err := db.Put(storageSnapshotKey(accountHash, storageHash), entry); err != nil {

@ -95,6 +95,11 @@ type Trie interface {
// a trie.MissingNodeError is returned. // a trie.MissingNodeError is returned.
GetStorage(addr common.Address, key []byte) ([]byte, error) GetStorage(addr common.Address, key []byte) ([]byte, error)
// StorageExists returns a flag indicating whether the requested storage slot
// is existent in the trie or not. An error should be returned if the trie
// state is internally corrupted.
StorageExists(addr common.Address, key []byte) (bool, error)
// UpdateAccount abstracts an account write to the trie. It encodes the // UpdateAccount abstracts an account write to the trie. It encodes the
// provided account object with associated algorithm and then updates it // provided account object with associated algorithm and then updates it
// in the trie with provided address. // in the trie with provided address.

@ -48,6 +48,10 @@ type Reader interface {
// - The returned storage slot is safe to modify after the call // - The returned storage slot is safe to modify after the call
Storage(addr common.Address, slot common.Hash) (common.Hash, error) Storage(addr common.Address, slot common.Hash) (common.Hash, error)
// StorageExists implements Reader, returning a flag indicating whether the
// requested storage slot exists.
StorageExists(addr common.Address, slot common.Hash) (bool, error)
// Copy returns a deep-copied state reader. // Copy returns a deep-copied state reader.
Copy() Reader Copy() Reader
} }
@ -126,6 +130,14 @@ func (r *stateReader) Storage(addr common.Address, key common.Hash) (common.Hash
return value, nil return value, nil
} }
// StorageExists implements Reader, returning a flag indicating whether the
// requested storage slot exists.
func (r *stateReader) StorageExists(addr common.Address, key common.Hash) (bool, error) {
addrHash := crypto.HashData(r.buff, addr.Bytes())
slotHash := crypto.HashData(r.buff, key.Bytes())
return r.snap.StorageExists(addrHash, slotHash)
}
// Copy implements Reader, returning a deep-copied snap reader. // Copy implements Reader, returning a deep-copied snap reader.
func (r *stateReader) Copy() Reader { func (r *stateReader) Copy() Reader {
return &stateReader{ return &stateReader{
@ -230,6 +242,41 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
return value, nil return value, nil
} }
// StorageExists implements Reader, returning a flag indicating whether the
// requested storage slot exists. An error will be returned if the trie data
// is internally corrupted.
func (r *trieReader) StorageExists(addr common.Address, key common.Hash) (bool, error) {
var (
tr Trie
found bool
)
if r.db.IsVerkle() {
tr = r.mainTrie
} else {
tr, found = r.subTries[addr]
if !found {
root, ok := r.subRoots[addr]
// The storage slot is accessed without account caching. It's unexpected
// behavior but try to resolve the account first anyway.
if !ok {
_, err := r.Account(addr)
if err != nil {
return false, err
}
root = r.subRoots[addr]
}
var err error
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.HashData(r.buff, addr.Bytes()), root), r.db)
if err != nil {
return false, err
}
r.subTries[addr] = tr
}
}
return tr.StorageExists(addr, key.Bytes())
}
// Copy implements Reader, returning a deep-copied trie reader. // Copy implements Reader, returning a deep-copied trie reader.
func (r *trieReader) Copy() Reader { func (r *trieReader) Copy() Reader {
tries := make(map[common.Address]Trie) tries := make(map[common.Address]Trie)
@ -301,6 +348,20 @@ func (r *multiReader) Storage(addr common.Address, slot common.Hash) (common.Has
return common.Hash{}, errors.Join(errs...) return common.Hash{}, errors.Join(errs...)
} }
// StorageExists implements Reader, returning a flag indicating whether the
// requested storage slot exists.
func (r *multiReader) StorageExists(addr common.Address, slot common.Hash) (bool, error) {
var errs []error
for _, reader := range r.readers {
exists, err := reader.StorageExists(addr, slot)
if err == nil {
return exists, nil
}
errs = append(errs, err)
}
return false, errors.Join(errs...)
}
// Copy implementing Reader interface, returning a deep-copied state reader. // Copy implementing Reader interface, returning a deep-copied state reader.
func (r *multiReader) Copy() Reader { func (r *multiReader) Copy() Reader {
var readers []Reader var readers []Reader

@ -365,6 +365,37 @@ func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro
return dl.storage(accountHash, storageHash, 0) return dl.storage(accountHash, storageHash, 0)
} }
// StorageExists returns a flag indicating whether the requested storage slot is
// existent or not.
func (dl *diffLayer) StorageExists(accountHash, storageHash common.Hash) (bool, error) {
// Check the bloom filter first whether there's even a point in reaching into
// all the maps in all the layers below
dl.lock.RLock()
// Check staleness before reaching further.
if dl.Stale() {
dl.lock.RUnlock()
return false, ErrSnapshotStale
}
hit := dl.diffed.ContainsHash(storageBloomHash(accountHash, storageHash))
if !hit {
hit = dl.diffed.ContainsHash(destructBloomHash(accountHash))
}
var origin *diskLayer
if !hit {
origin = dl.origin // extract origin while holding the lock
}
dl.lock.RUnlock()
// If the bloom filter misses, don't even bother with traversing the memory
// diff layers, reach straight into the bottom persistent disk layer
if origin != nil {
snapshotBloomStorageMissMeter.Mark(1)
return origin.StorageExists(accountHash, storageHash)
}
// The bloom filter hit, start poking in the internal maps
return dl.StorageExists(accountHash, storageHash)
}
// storage is an internal version of Storage that skips the bloom filter checks // storage is an internal version of Storage that skips the bloom filter checks
// and uses the internal maps to try and retrieve the data. It's meant to be // and uses the internal maps to try and retrieve the data. It's meant to be
// used if a higher layer's bloom filter hit already. // used if a higher layer's bloom filter hit already.

@ -169,6 +169,37 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro
return blob, nil return blob, nil
} }
// StorageExists returns a flag indicating whether the requested storage slot is
// existent or not.
func (dl *diskLayer) StorageExists(accountHash, storageHash common.Hash) (bool, error) {
dl.lock.RLock()
defer dl.lock.RUnlock()
// If the layer was flattened into, consider it invalid (any live reference to
// the original should be marked as unusable).
if dl.stale {
return false, ErrSnapshotStale
}
key := append(accountHash[:], storageHash[:]...)
// If the layer is being generated, ensure the requested hash has already been
// covered by the generator.
if dl.genMarker != nil && bytes.Compare(key, dl.genMarker) > 0 {
return false, ErrNotCoveredYet
}
// If we're in the disk layer, all diff layers missed
snapshotDirtyStorageMissMeter.Mark(1)
// Try to retrieve the storage slot from the memory cache
if blob, found := dl.cache.HasGet(nil, key); found {
snapshotCleanStorageHitMeter.Mark(1)
snapshotCleanStorageReadMeter.Mark(int64(len(blob)))
return true, nil
}
snapshotCleanStorageMissMeter.Mark(1)
return rawdb.HasStorageSnapshot(dl.diskdb, accountHash, storageHash), nil
}
// Update creates a new layer on top of the existing snapshot diff tree with // Update creates a new layer on top of the existing snapshot diff tree with
// the specified data items. Note, the maps are retained by the method to avoid // the specified data items. Note, the maps are retained by the method to avoid
// copying everything. // copying everything.

@ -572,3 +572,51 @@ func TestDiskSeek(t *testing.T) {
} }
} }
} }
func TestDiskStorageExists(t *testing.T) {
// Create some accounts in the disk layer
db := rawdb.NewMemoryDatabase()
defer db.Close()
// fill storage slot with zero size
rawdb.WriteStorageSnapshot(db, common.Hash{0x1}, common.Hash{0x1}, []byte{})
rawdb.WriteStorageSnapshot(db, common.Hash{0x1}, common.Hash{0x2}, nil)
rawdb.WriteStorageSnapshot(db, common.Hash{0x1}, common.Hash{0x3}, []byte{0x1})
dl := &diskLayer{
diskdb: db,
cache: fastcache.New(500 * 1024),
root: randomHash(),
}
// Test some different seek positions
type testcase struct {
key common.Hash
expect bool
}
var cases = []testcase{
{
common.Hash{0x0}, false,
},
{
common.Hash{0x1}, true,
},
{
common.Hash{0x2}, true,
},
{
common.Hash{0x3}, true,
},
{
common.Hash{0x4}, false,
},
}
for i, tc := range cases {
result, err := dl.StorageExists(common.Hash{0x1}, tc.key)
if err != nil {
t.Fatalf("Failed to query disk layer: %v", err)
}
if result != tc.expect {
t.Fatalf("%d, unexpected result, want %t, got: %t", i, tc.expect, result)
}
}
}

@ -112,6 +112,10 @@ type Snapshot interface {
// Storage directly retrieves the storage data associated with a particular hash, // Storage directly retrieves the storage data associated with a particular hash,
// within a particular account. // within a particular account.
Storage(accountHash, storageHash common.Hash) ([]byte, error) Storage(accountHash, storageHash common.Hash) ([]byte, error)
// StorageExists returns a flag indicating whether the requested storage slot is
// existent or not.
StorageExists(accountHash, storageHash common.Hash) (bool, error)
} }
// snapshot is the internal version of the snapshot data layer that supports some // snapshot is the internal version of the snapshot data layer that supports some

@ -113,6 +113,16 @@ func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) {
return content, err return content, err
} }
// StorageExists implements state.Trie, returning a flag indicating whether the
// requested storage slot is existent or not.
func (t *StateTrie) StorageExists(addr common.Address, key []byte) (bool, error) {
data, err := t.GetStorage(addr, key)
if err != nil {
return false, nil
}
return len(data) != 0, nil
}
// GetAccount attempts to retrieve an account with provided account address. // GetAccount attempts to retrieve an account with provided account address.
// If the specified account is not in the trie, nil will be returned. // If the specified account is not in the trie, nil will be returned.
// If a trie node is not found in the database, a MissingNodeError is returned. // If a trie node is not found in the database, a MissingNodeError is returned.

@ -147,3 +147,36 @@ func TestStateTrieConcurrency(t *testing.T) {
// Wait for all threads to finish // Wait for all threads to finish
pend.Wait() pend.Wait()
} }
func TestSecureStorageExists(t *testing.T) {
trie := newEmptySecure()
// Zero size value
trie.MustUpdate([]byte("foo"), []byte(""))
exists, err := trie.StorageExists(common.Address{}, []byte("foo"))
if err != nil {
t.Fatalf("trie is corrupted: %v", err)
}
if exists {
t.Fatal("Unexpected trie element")
}
// Non-existent value
exists, err = trie.StorageExists(common.Address{}, []byte("dead"))
if err != nil {
t.Fatalf("trie is corrupted: %v", err)
}
if exists {
t.Fatal("Unexpected trie element")
}
// Non-zero size value
trie.MustUpdate([]byte("foo"), []byte("bar"))
exists, err = trie.StorageExists(common.Address{}, []byte("foo"))
if err != nil {
t.Fatalf("trie is corrupted: %v", err)
}
if !exists {
t.Fatal("Trie element is missing")
}
}

@ -121,6 +121,17 @@ func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error)
return common.TrimLeftZeroes(val), nil return common.TrimLeftZeroes(val), nil
} }
// StorageExists implements state.Trie, returning a flag indicating whether the
// requested storage slot is existent or not.
func (t *VerkleTrie) StorageExists(addr common.Address, key []byte) (bool, error) {
k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key)
val, err := t.root.Get(k, t.nodeResolver)
if err != nil {
return false, err
}
return len(val) != 0, nil
}
// UpdateAccount implements state.Trie, writing the provided account into the tree. // UpdateAccount implements state.Trie, writing the provided account into the tree.
// If the tree is corrupted, an error will be returned. // If the tree is corrupted, an error will be returned.
func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error { func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {

Loading…
Cancel
Save