diff --git a/core/state/database.go b/core/state/database.go index de61dee036..b0e0ee7d74 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -87,10 +87,11 @@ type Trie interface { // be returned. 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, // 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 // provided account object with associated algorithm and then updates it diff --git a/core/state/reader.go b/core/state/reader.go index 6bddefc2a7..62b783b887 100644 --- a/core/state/reader.go +++ b/core/state/reader.go @@ -41,12 +41,13 @@ type Reader interface { Account(addr common.Address) (*types.StateAccount, error) // 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 error only if an unexpected issue occurs // - 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() 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 -// 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 // the requested storage slot is not yet covered by the snapshot. // // 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()) slotHash := crypto.HashData(r.buff, key.Bytes()) ret, err := r.snap.Storage(addrHash, slotHash) if err != nil { - return common.Hash{}, err + return false, common.Hash{}, err } 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 // snapshot. _, content, _, err := rlp.Split(ret) if err != nil { - return common.Hash{}, err + return false, common.Hash{}, err } var value common.Hash value.SetBytes(content) - return value, nil + return true, value, nil } // 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 // 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 ( tr Trie found bool @@ -212,24 +214,24 @@ func (r *trieReader) Storage(addr common.Address, key common.Hash) (common.Hash, if !ok { _, err := r.Account(addr) if err != nil { - return common.Hash{}, err + return false, common.Hash{}, 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 common.Hash{}, err + return false, common.Hash{}, err } r.subTries[addr] = tr } } - ret, err := tr.GetStorage(addr, key.Bytes()) + exist, ret, err := tr.GetStorage(addr, key.Bytes()) if err != nil { - return common.Hash{}, err + return false, common.Hash{}, err } value.SetBytes(ret) - return value, nil + return exist, value, nil } // 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 error only if an unexpected issue occurs // - 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 for _, reader := range r.readers { - slot, err := reader.Storage(addr, slot) + exist, slot, err := reader.Storage(addr, slot) if err == nil { - return slot, nil + return exist, slot, nil } 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. diff --git a/core/state/state_object.go b/core/state/state_object.go index 422badb19b..9fc7482742 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -190,7 +190,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { s.db.StorageLoaded++ start := time.Now() - value, err := s.db.reader.Storage(s.address, key) + _, value, err := s.db.reader.Storage(s.address, key) if err != nil { s.db.setError(err) return common.Hash{} @@ -207,6 +207,37 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { 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. func (s *stateObject) SetState(key, value common.Hash) { // If the new value is the same as old, don't set. Otherwise, track only the diff --git a/core/state/statedb.go b/core/state/statedb.go index 1a12f519a4..62f05af536 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" "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{} } +// 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. func (s *StateDB) Database() Database { 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 snap := s.db.Snapshot(); snap != nil { 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) } // Keep 128 diff layers in the memory, persistent layer is 129th. diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 91fd38269f..08098aee2d 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -104,13 +104,16 @@ func (t *StateTrie) MustGet(key []byte) []byte { // 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 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)) - if err != nil || len(enc) == 0 { - return nil, err + if err != nil { + return false, nil, err + } + if len(enc) == 0 { + return false, nil, nil } _, content, _, err := rlp.Split(enc) - return content, err + return true, content, err } // GetAccount attempts to retrieve an account with provided account address. diff --git a/trie/verkle.go b/trie/verkle.go index 6bd9d3d1af..a89c9d63be 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -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 // 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. -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) val, err := t.root.Get(k, t.nodeResolver) 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. diff --git a/trie/verkle_test.go b/trie/verkle_test.go index 4cd1717c0e..4da75d0743 100644 --- a/trie/verkle_test.go +++ b/trie/verkle_test.go @@ -80,7 +80,7 @@ func TestVerkleTreeReadWrite(t *testing.T) { t.Fatal("account is not matched") } for key, val := range storages[addr] { - stored, err := tr.GetStorage(addr, key.Bytes()) + _, stored, err := tr.GetStorage(addr, key.Bytes()) if err != nil { t.Fatalf("Failed to get storage, %v", err) } @@ -126,7 +126,7 @@ func TestVerkleRollBack(t *testing.T) { t.Fatal("account is not matched") } for key, val := range storages[addr] { - stored, err := tr.GetStorage(addr, key.Bytes()) + _, stored, err := tr.GetStorage(addr, key.Bytes()) if err != nil { t.Fatalf("Failed to get storage, %v", err) }