mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
core, trie: extend state reader interface with StorageExists
This commit is contained in:
parent
a4fdf71de0
commit
f54ee58920
10 changed files with 241 additions and 0 deletions
|
|
@ -98,6 +98,13 @@ func ReadStorageSnapshot(db ethdb.KeyValueReader, accountHash, storageHash commo
|
|||
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.
|
||||
func WriteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash common.Hash, entry []byte) {
|
||||
if err := db.Put(storageSnapshotKey(accountHash, storageHash), entry); err != nil {
|
||||
|
|
|
|||
|
|
@ -95,6 +95,11 @@ type Trie interface {
|
|||
// a trie.MissingNodeError is returned.
|
||||
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
|
||||
// provided account object with associated algorithm and then updates it
|
||||
// in the trie with provided address.
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ type Reader interface {
|
|||
// - The returned storage slot is safe to modify after the call
|
||||
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() Reader
|
||||
}
|
||||
|
|
@ -126,6 +130,14 @@ func (r *stateReader) Storage(addr common.Address, key common.Hash) (common.Hash
|
|||
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.
|
||||
func (r *stateReader) Copy() Reader {
|
||||
return &stateReader{
|
||||
|
|
@ -230,6 +242,41 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
|
|||
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.
|
||||
func (r *trieReader) Copy() Reader {
|
||||
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...)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (r *multiReader) Copy() Reader {
|
||||
var readers []Reader
|
||||
|
|
|
|||
|
|
@ -365,6 +365,37 @@ func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro
|
|||
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
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -169,6 +169,37 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro
|
|||
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
|
||||
// the specified data items. Note, the maps are retained by the method to avoid
|
||||
// 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,
|
||||
// within a particular account.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -113,6 +113,16 @@ func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) {
|
|||
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.
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -147,3 +147,36 @@ func TestStateTrieConcurrency(t *testing.T) {
|
|||
// Wait for all threads to finish
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
// If the tree is corrupted, an error will be returned.
|
||||
func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error {
|
||||
|
|
|
|||
Loading…
Reference in a new issue