core/state: add flag for inidicating the existence of storage

pull/30405/head
Gary Rong 3 weeks ago
parent 623b17ba20
commit 88dfd15522
  1. 5
      core/state/database.go
  2. 38
      core/state/reader.go
  3. 33
      core/state/state_object.go
  4. 30
      core/state/statedb.go
  5. 11
      trie/secure_trie.go
  6. 9
      trie/verkle.go
  7. 4
      trie/verkle_test.go

@ -87,10 +87,11 @@ type Trie interface {
// be returned. // be returned.
GetAccount(address common.Address) (*types.StateAccount, error) GetAccount(address common.Address) (*types.StateAccount, error)
// GetStorage returns the value for key stored in the trie. The value bytes // GetStorage returns the value for key stored in the trie along with a flag
// whether the requested storage slot is existent or not. The value bytes
// must not be modified by the caller. If a node was not found in the database, // must not be modified by the caller. If a node was not found in the database,
// a trie.MissingNodeError is returned. // a trie.MissingNodeError is returned.
GetStorage(addr common.Address, key []byte) ([]byte, error) GetStorage(addr common.Address, key []byte) (bool, []byte, 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

@ -41,12 +41,13 @@ type Reader interface {
Account(addr common.Address) (*types.StateAccount, error) Account(addr common.Address) (*types.StateAccount, error)
// Storage retrieves the storage slot associated with a particular account // Storage retrieves the storage slot associated with a particular account
// address and slot key. // address and slot key along with a flag indicating whether the slot is
// existent or not.
// //
// - Returns an empty slot if it does not exist // - Returns an empty slot if it does not exist
// - Returns an error only if an unexpected issue occurs // - Returns an error only if an unexpected issue occurs
// - 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) (bool, common.Hash, error)
// Copy returns a deep-copied state reader. // Copy returns a deep-copied state reader.
Copy() Reader Copy() Reader
@ -101,31 +102,32 @@ func (r *stateReader) Account(addr common.Address) (*types.StateAccount, error)
} }
// Storage implements Reader, retrieving the storage slot specified by the // Storage implements Reader, retrieving the storage slot specified by the
// address and slot key. // address and slot key along with a flag whether the requested storage slot
// is existent or not.
// //
// An error will be returned if the associated snapshot is already stale or // An error will be returned if the associated snapshot is already stale or
// the requested storage slot is not yet covered by the snapshot. // the requested storage slot is not yet covered by the snapshot.
// //
// The returned storage slot might be empty if it's not existent. // The returned storage slot might be empty if it's not existent.
func (r *stateReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) { func (r *stateReader) Storage(addr common.Address, key common.Hash) (bool, common.Hash, error) {
addrHash := crypto.HashData(r.buff, addr.Bytes()) addrHash := crypto.HashData(r.buff, addr.Bytes())
slotHash := crypto.HashData(r.buff, key.Bytes()) slotHash := crypto.HashData(r.buff, key.Bytes())
ret, err := r.snap.Storage(addrHash, slotHash) ret, err := r.snap.Storage(addrHash, slotHash)
if err != nil { if err != nil {
return common.Hash{}, err return false, common.Hash{}, err
} }
if len(ret) == 0 { if len(ret) == 0 {
return common.Hash{}, nil return false, common.Hash{}, nil
} }
// Perform the rlp-decode as the slot value is RLP-encoded in the state // Perform the rlp-decode as the slot value is RLP-encoded in the state
// snapshot. // snapshot.
_, content, _, err := rlp.Split(ret) _, content, _, err := rlp.Split(ret)
if err != nil { if err != nil {
return common.Hash{}, err return false, common.Hash{}, err
} }
var value common.Hash var value common.Hash
value.SetBytes(content) value.SetBytes(content)
return value, nil return true, value, nil
} }
// Copy implements Reader, returning a deep-copied snap reader. // Copy implements Reader, returning a deep-copied snap reader.
@ -194,7 +196,7 @@ func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
// //
// An error will be returned if the trie state is corrupted. An empty storage // An error will be returned if the trie state is corrupted. An empty storage
// slot will be returned if it's not existent in the trie. // slot will be returned if it's not existent in the trie.
func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash, error) { func (r *trieReader) Storage(addr common.Address, key common.Hash) (bool, common.Hash, error) {
var ( var (
tr Trie tr Trie
found bool found bool
@ -212,24 +214,24 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash,
if !ok { if !ok {
_, err := r.Account(addr) _, err := r.Account(addr)
if err != nil { if err != nil {
return common.Hash{}, err return false, common.Hash{}, err
} }
root = r.subRoots[addr] root = r.subRoots[addr]
} }
var err error var err error
tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.HashData(r.buff, addr.Bytes()), root), r.db) tr, err = trie.NewStateTrie(trie.StorageTrieID(r.root, crypto.HashData(r.buff, addr.Bytes()), root), r.db)
if err != nil { if err != nil {
return common.Hash{}, err return false, common.Hash{}, err
} }
r.subTries[addr] = tr r.subTries[addr] = tr
} }
} }
ret, err := tr.GetStorage(addr, key.Bytes()) exist, ret, err := tr.GetStorage(addr, key.Bytes())
if err != nil { if err != nil {
return common.Hash{}, err return false, common.Hash{}, err
} }
value.SetBytes(ret) value.SetBytes(ret)
return value, nil return exist, value, nil
} }
// Copy implements Reader, returning a deep-copied trie reader. // Copy implements Reader, returning a deep-copied trie reader.
@ -291,16 +293,16 @@ func (r *multiReader) Account(addr common.Address) (*types.StateAccount, error)
// - Returns an empty slot if it does not exist // - Returns an empty slot if it does not exist
// - Returns an error only if an unexpected issue occurs // - Returns an error only if an unexpected issue occurs
// - The returned storage slot is safe to modify after the call // - The returned storage slot is safe to modify after the call
func (r *multiReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { func (r *multiReader) Storage(addr common.Address, slot common.Hash) (bool, common.Hash, error) {
var errs []error var errs []error
for _, reader := range r.readers { for _, reader := range r.readers {
slot, err := reader.Storage(addr, slot) exist, slot, err := reader.Storage(addr, slot)
if err == nil { if err == nil {
return slot, nil return exist, slot, nil
} }
errs = append(errs, err) errs = append(errs, err)
} }
return common.Hash{}, errors.Join(errs...) return false, common.Hash{}, errors.Join(errs...)
} }
// Copy implementing Reader interface, returning a deep-copied state reader. // Copy implementing Reader interface, returning a deep-copied state reader.

@ -190,7 +190,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
s.db.StorageLoaded++ s.db.StorageLoaded++
start := time.Now() start := time.Now()
value, err := s.db.reader.Storage(s.address, key) _, value, err := s.db.reader.Storage(s.address, key)
if err != nil { if err != nil {
s.db.setError(err) s.db.setError(err)
return common.Hash{} return common.Hash{}
@ -207,6 +207,37 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
return value return value
} }
// StateExists returns the flag indicating the storage slot is existent or not.
func (s *stateObject) StateExists(key common.Hash) bool {
// Short circuit if the slot can be found in either dirty map (modified within
// the current transaction) or the pending storage (modified within the current
// block).
//
// Note, the slot is regarded as existent even they are marked as deleted,
// specifically the value is [32]byte{}, as there is no delete notion in verkle
// world anymore and the deletions will be translated to a special marker
// writing ultimately.
//
// Note, there is no account deletion since cancun fork (as for verkle
// world), the account destruct checking can be skipped.
if _, dirty := s.dirtyStorage[key]; dirty {
return true
}
if _, pending := s.pendingStorage[key]; pending {
return true
}
start := time.Now()
exists, _, err := s.db.reader.Storage(s.address, key)
if err != nil {
s.db.setError(err)
return false
}
s.db.StorageLoaded++
s.db.StorageReads += time.Since(start)
return exists
}
// SetState updates a value in account storage. // SetState updates a value in account storage.
func (s *stateObject) SetState(key, value common.Hash) { func (s *stateObject) SetState(key, value common.Hash) {
// If the new value is the same as old, don't set. Otherwise, track only the // If the new value is the same as old, don't set. Otherwise, track only the

@ -36,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate" "github.com/ethereum/go-ethereum/trie/triestate"
@ -392,6 +393,16 @@ func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) commo
return common.Hash{} return common.Hash{}
} }
// StateExists returns the flag indicating whether the storage slot specified by
// address and storage key is existent.
func (s *StateDB) StateExists(addr common.Address, key common.Hash) bool {
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.StateExists(key)
}
return false
}
// Database retrieves the low level database supporting the lower level trie ops. // Database retrieves the low level database supporting the lower level trie ops.
func (s *StateDB) Database() Database { func (s *StateDB) Database() Database {
return s.db return s.db
@ -1267,7 +1278,24 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU
// If snapshotting is enabled, update the snapshot tree with this new version // If snapshotting is enabled, update the snapshot tree with this new version
if snap := s.db.Snapshot(); snap != nil { if snap := s.db.Snapshot(); snap != nil {
start := time.Now() start := time.Now()
if err := snap.Update(ret.root, ret.originRoot, ret.destructs, ret.accounts, ret.storages); err != nil {
storages := ret.storages
if s.db.TrieDB().IsVerkle() {
zero, _ := rlp.EncodeToBytes(common.Hash{}.Bytes())
storages = make(map[common.Hash]map[common.Hash][]byte)
for addrHash, slots := range ret.storages {
subset := make(map[common.Hash][]byte)
storages[addrHash] = subset
for slotHash, slot := range slots {
if len(slot) == 0 {
subset[slotHash] = zero
} else {
subset[slotHash] = slot
}
}
}
}
if err := snap.Update(ret.root, ret.originRoot, ret.destructs, ret.accounts, storages); err != nil {
log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err) log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err)
} }
// Keep 128 diff layers in the memory, persistent layer is 129th. // Keep 128 diff layers in the memory, persistent layer is 129th.

@ -104,13 +104,16 @@ func (t *StateTrie) MustGet(key []byte) []byte {
// and slot key. The value bytes must not be modified by the caller. // and slot key. The value bytes must not be modified by the caller.
// If the specified storage slot is not in the trie, nil will be returned. // If the specified storage slot 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.
func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) { func (t *StateTrie) GetStorage(_ common.Address, key []byte) (bool, []byte, error) {
enc, err := t.trie.Get(t.hashKey(key)) enc, err := t.trie.Get(t.hashKey(key))
if err != nil || len(enc) == 0 { if err != nil {
return nil, err return false, nil, err
}
if len(enc) == 0 {
return false, nil, nil
} }
_, content, _, err := rlp.Split(enc) _, content, _, err := rlp.Split(enc)
return content, err return true, content, err
} }
// GetAccount attempts to retrieve an account with provided account address. // GetAccount attempts to retrieve an account with provided account address.

@ -112,13 +112,16 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error
// GetStorage implements state.Trie, retrieving the storage slot with the specified // GetStorage implements state.Trie, retrieving the storage slot with the specified
// account address and storage key. If the specified slot is not in the verkle tree, // account address and storage key. If the specified slot is not in the verkle tree,
// nil will be returned. If the tree is corrupted, an error will be returned. // nil will be returned. If the tree is corrupted, an error will be returned.
func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) { func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) (bool, []byte, error) {
k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key) k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key)
val, err := t.root.Get(k, t.nodeResolver) val, err := t.root.Get(k, t.nodeResolver)
if err != nil { if err != nil {
return nil, err return false, nil, err
}
if len(val) == 0 {
return false, nil, nil
} }
return common.TrimLeftZeroes(val), nil return true, common.TrimLeftZeroes(val), nil
} }
// UpdateAccount implements state.Trie, writing the provided account into the tree. // UpdateAccount implements state.Trie, writing the provided account into the tree.

@ -80,7 +80,7 @@ func TestVerkleTreeReadWrite(t *testing.T) {
t.Fatal("account is not matched") t.Fatal("account is not matched")
} }
for key, val := range storages[addr] { for key, val := range storages[addr] {
stored, err := tr.GetStorage(addr, key.Bytes()) _, stored, err := tr.GetStorage(addr, key.Bytes())
if err != nil { if err != nil {
t.Fatalf("Failed to get storage, %v", err) t.Fatalf("Failed to get storage, %v", err)
} }
@ -126,7 +126,7 @@ func TestVerkleRollBack(t *testing.T) {
t.Fatal("account is not matched") t.Fatal("account is not matched")
} }
for key, val := range storages[addr] { for key, val := range storages[addr] {
stored, err := tr.GetStorage(addr, key.Bytes()) _, stored, err := tr.GetStorage(addr, key.Bytes())
if err != nil { if err != nil {
t.Fatalf("Failed to get storage, %v", err) t.Fatalf("Failed to get storage, %v", err)
} }

Loading…
Cancel
Save