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
|
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…
Reference in a new issue