mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 01:41:36 +00:00
core/state: remove trie prefetcher and witness from stateDB
This commit is contained in:
parent
e2c00d6c96
commit
9daaef1923
3 changed files with 18 additions and 208 deletions
|
|
@ -119,11 +119,6 @@ func (s *stateObject) addrHash() common.Hash {
|
|||
return *s.addressHash
|
||||
}
|
||||
|
||||
// storageTrieID returns the unique identifier for this account's storage trie.
|
||||
func (s *stateObject) storageTrieID() trie.ID {
|
||||
return *trie.StorageTrieID(s.db.originalRoot, s.addrHash(), s.data.Root)
|
||||
}
|
||||
|
||||
func (s *stateObject) markSelfdestructed() {
|
||||
s.selfDestructed = true
|
||||
}
|
||||
|
|
@ -149,23 +144,6 @@ func (s *stateObject) getTrie() (Trie, error) {
|
|||
return s.trie, nil
|
||||
}
|
||||
|
||||
// getPrefetchedTrie returns the associated trie, as populated by the prefetcher
|
||||
// if it's available.
|
||||
//
|
||||
// Note, opposed to getTrie, this method will *NOT* blindly cache the resulting
|
||||
// trie in the state object. The caller might want to do that, but it's cleaner
|
||||
// to break the hidden interdependency between retrieving tries from the db or
|
||||
// from the prefetcher.
|
||||
func (s *stateObject) getPrefetchedTrie() Trie {
|
||||
// If there's nothing to meaningfully return, let the user figure it out by
|
||||
// pulling the trie from disk.
|
||||
if (s.data.Root == types.EmptyRootHash && !s.db.db.TrieDB().IsVerkle()) || s.db.prefetcher == nil {
|
||||
return nil
|
||||
}
|
||||
// Attempt to retrieve the trie from the prefetcher
|
||||
return s.db.prefetcher.trie(s.storageTrieID())
|
||||
}
|
||||
|
||||
// GetState retrieves a value associated with the given storage key.
|
||||
func (s *stateObject) GetState(key common.Hash) common.Hash {
|
||||
value, _ := s.getState(key)
|
||||
|
|
@ -226,12 +204,6 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
|
|||
}
|
||||
s.db.StorageReads += time.Since(start)
|
||||
|
||||
// Schedule the resolved storage slots for prefetching if it's enabled.
|
||||
if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash {
|
||||
if err = s.db.prefetcher.prefetchStorage(s.storageTrieID(), s.address, []common.Hash{key}, true); err != nil {
|
||||
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
|
||||
}
|
||||
}
|
||||
s.originStorage[key] = value
|
||||
return value
|
||||
}
|
||||
|
|
@ -265,7 +237,6 @@ func (s *stateObject) setState(key common.Hash, value common.Hash, origin common
|
|||
// finalise moves all dirty storage slots into the pending area to be hashed or
|
||||
// committed later. It is invoked at the end of every transaction.
|
||||
func (s *stateObject) finalise() {
|
||||
slotsToPrefetch := make([]common.Hash, 0, len(s.dirtyStorage))
|
||||
for key, value := range s.dirtyStorage {
|
||||
if origin, exist := s.uncommittedStorage[key]; exist && origin == value {
|
||||
// The slot is reverted to its original value, delete the entry
|
||||
|
|
@ -278,7 +249,6 @@ func (s *stateObject) finalise() {
|
|||
// The slot is different from its original value and hasn't been
|
||||
// tracked for commit yet.
|
||||
s.uncommittedStorage[key] = s.GetCommittedState(key)
|
||||
slotsToPrefetch = append(slotsToPrefetch, key) // Copy needed for closure
|
||||
}
|
||||
// Aggregate the dirty storage slots into the pending area. It might
|
||||
// be possible that the value of tracked slot here is same with the
|
||||
|
|
@ -288,11 +258,6 @@ func (s *stateObject) finalise() {
|
|||
// byzantium fork) and entry is necessary to modify the value back.
|
||||
s.pendingStorage[key] = value
|
||||
}
|
||||
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
|
||||
if err := s.db.prefetcher.prefetchStorage(s.storageTrieID(), s.address, slotsToPrefetch, false); err != nil {
|
||||
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
|
||||
}
|
||||
}
|
||||
if len(s.dirtyStorage) > 0 {
|
||||
s.dirtyStorage = make(Storage)
|
||||
}
|
||||
|
|
@ -311,34 +276,16 @@ func (s *stateObject) finalise() {
|
|||
//
|
||||
// It assumes all the dirty storage slots have been finalized before.
|
||||
func (s *stateObject) updateTrie() (Trie, error) {
|
||||
// Short circuit if nothing was accessed, don't trigger a prefetcher warning
|
||||
if len(s.uncommittedStorage) == 0 {
|
||||
// Nothing was written, so we could stop early. Unless we have both reads
|
||||
// and witness collection enabled, in which case we need to fetch the trie.
|
||||
if s.db.witness == nil || len(s.originStorage) == 0 {
|
||||
return s.trie, nil
|
||||
}
|
||||
}
|
||||
// Retrieve a pretecher populated trie, or fall back to the database. This will
|
||||
// block until all prefetch tasks are done, which are needed for witnesses even
|
||||
// for unmodified state objects.
|
||||
tr := s.getPrefetchedTrie()
|
||||
if tr != nil {
|
||||
// Prefetcher returned a live trie, swap it out for the current one
|
||||
s.trie = tr
|
||||
} else {
|
||||
// Fetcher not running or empty trie, fallback to the database trie
|
||||
var err error
|
||||
tr, err = s.getTrie()
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Short circuit if nothing changed, don't bother with hashing anything
|
||||
// Short circuit if nothing was accessed
|
||||
if len(s.uncommittedStorage) == 0 {
|
||||
return s.trie, nil
|
||||
}
|
||||
// Fetcher not running or empty trie, fallback to the database trie
|
||||
tr, err := s.getTrie()
|
||||
if err != nil {
|
||||
s.db.setError(err)
|
||||
return nil, err
|
||||
}
|
||||
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
|
||||
// in circumstances similar to the following:
|
||||
//
|
||||
|
|
@ -351,7 +298,6 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
|
||||
var (
|
||||
deletions []common.Hash
|
||||
used = make([]common.Hash, 0, len(s.uncommittedStorage))
|
||||
)
|
||||
for key, origin := range s.uncommittedStorage {
|
||||
// Skip noop changes, persist actual changes
|
||||
|
|
@ -373,8 +319,6 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
} else {
|
||||
deletions = append(deletions, key)
|
||||
}
|
||||
// Cache the items for preloading
|
||||
used = append(used, key) // Copy needed for closure
|
||||
}
|
||||
for _, key := range deletions {
|
||||
if err := tr.DeleteStorage(s.address, key[:]); err != nil {
|
||||
|
|
@ -383,9 +327,6 @@ func (s *stateObject) updateTrie() (Trie, error) {
|
|||
}
|
||||
s.db.StorageDeleted.Add(1)
|
||||
}
|
||||
if s.db.prefetcher != nil {
|
||||
s.db.prefetcher.used(s.storageTrieID(), nil, used)
|
||||
}
|
||||
s.uncommittedStorage = make(Storage) // empties the commit markers
|
||||
return tr, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
|
|
@ -75,10 +74,9 @@ func (m *mutation) isDelete() bool {
|
|||
// must be created with new root and updated database for accessing post-
|
||||
// commit states.
|
||||
type StateDB struct {
|
||||
db Database
|
||||
prefetcher *triePrefetcher
|
||||
reader Reader
|
||||
trie Trie // it's resolved on first access
|
||||
db Database
|
||||
reader Reader
|
||||
trie Trie // it's resolved on first access
|
||||
|
||||
// originalRoot is the pre-state root, before any changes were made.
|
||||
// It will be updated when the Commit is called.
|
||||
|
|
@ -133,9 +131,6 @@ type StateDB struct {
|
|||
// Snapshot and RevertToSnapshot.
|
||||
journal *journal
|
||||
|
||||
// State witness if cross validation is needed
|
||||
witness *stateless.Witness
|
||||
|
||||
// Measurements gathered during execution for debugging purposes
|
||||
AccountReads time.Duration
|
||||
AccountHashes time.Duration
|
||||
|
|
@ -200,47 +195,11 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro
|
|||
// state trie concurrently while the state is mutated so that when we reach the
|
||||
// commit phase, most of the needed data is already hot.
|
||||
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) {
|
||||
// Terminate any previously running prefetcher
|
||||
s.StopPrefetcher()
|
||||
|
||||
// Enable witness collection if requested
|
||||
s.witness = witness
|
||||
|
||||
// With the switch to the Proof-of-Stake consensus algorithm, block production
|
||||
// rewards are now handled at the consensus layer. Consequently, a block may
|
||||
// have no state transitions if it contains no transactions and no withdrawals.
|
||||
// In such cases, the account trie won't be scheduled for prefetching, leading
|
||||
// to unnecessary error logs.
|
||||
//
|
||||
// To prevent this, the account trie is always scheduled for prefetching once
|
||||
// the prefetcher is constructed. For more details, see:
|
||||
// https://github.com/ethereum/go-ethereum/issues/29880
|
||||
opener := func(id trie.ID, addr common.Address) (Trie, error) {
|
||||
if s.db.TrieDB().IsVerkle() {
|
||||
return s.db.OpenTrie(id.StateRoot)
|
||||
}
|
||||
if id.Owner != (common.Hash{}) {
|
||||
return s.db.OpenStorageTrie(id.StateRoot, addr, id.Root, nil)
|
||||
}
|
||||
return s.db.OpenTrie(id.StateRoot)
|
||||
}
|
||||
s.prefetcher = newTriePrefetcher(opener, s.originalRoot, namespace, witness == nil)
|
||||
|
||||
id := trie.StateTrieID(s.originalRoot)
|
||||
if err := s.prefetcher.prefetchAccounts(*id, nil, false); err != nil {
|
||||
log.Error("Failed to prefetch account trie", "root", s.originalRoot, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// StopPrefetcher terminates a running prefetcher and reports any leftover stats
|
||||
// from the gathered metrics.
|
||||
func (s *StateDB) StopPrefetcher() {
|
||||
if s.prefetcher != nil {
|
||||
s.prefetcher.terminate(false)
|
||||
s.prefetcher.report()
|
||||
s.prefetcher = nil
|
||||
}
|
||||
}
|
||||
func (s *StateDB) StopPrefetcher() {}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
func (s *StateDB) setError(err error) {
|
||||
|
|
@ -368,9 +327,6 @@ func (s *StateDB) TxIndex() int {
|
|||
func (s *StateDB) GetCode(addr common.Address) []byte {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
if s.witness != nil {
|
||||
s.witness.AddCode(stateObject.Code())
|
||||
}
|
||||
return stateObject.Code()
|
||||
}
|
||||
return nil
|
||||
|
|
@ -379,9 +335,6 @@ func (s *StateDB) GetCode(addr common.Address) []byte {
|
|||
func (s *StateDB) GetCodeSize(addr common.Address) int {
|
||||
stateObject := s.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
if s.witness != nil {
|
||||
s.witness.AddCode(stateObject.Code())
|
||||
}
|
||||
return stateObject.CodeSize()
|
||||
}
|
||||
return 0
|
||||
|
|
@ -612,13 +565,6 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
|||
if acct == nil {
|
||||
return nil
|
||||
}
|
||||
// Schedule the resolved account for prefetching if it's enabled.
|
||||
if s.prefetcher != nil {
|
||||
id := trie.StateTrieID(s.originalRoot)
|
||||
if err = s.prefetcher.prefetchAccounts(*id, []common.Address{addr}, true); err != nil {
|
||||
log.Error("Failed to prefetch account", "addr", addr, "err", err)
|
||||
}
|
||||
}
|
||||
// Insert into the live set
|
||||
obj := newObject(s, addr, acct)
|
||||
s.setStateObject(obj)
|
||||
|
|
@ -710,9 +656,6 @@ func (s *StateDB) Copy() *StateDB {
|
|||
if s.trie != nil {
|
||||
state.trie = mustCopyTrie(s.trie)
|
||||
}
|
||||
if s.witness != nil {
|
||||
state.witness = s.witness.Copy()
|
||||
}
|
||||
if s.accessEvents != nil {
|
||||
state.accessEvents = s.accessEvents.Copy()
|
||||
}
|
||||
|
|
@ -797,7 +740,6 @@ func (s *StateDB) LogsForBurnAccounts() []*types.Log {
|
|||
// the journal as well as the refunds. Finalise, however, will not push any updates
|
||||
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
||||
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||
addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties))
|
||||
for addr := range s.journal.dirties {
|
||||
obj, exist := s.stateObjects[addr]
|
||||
if !exist {
|
||||
|
|
@ -822,16 +764,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
|||
obj.finalise()
|
||||
s.markUpdate(addr)
|
||||
}
|
||||
// At this point, also ship the address off to the precacher. The precacher
|
||||
// will start loading tries, and when the change is eventually committed,
|
||||
// the commit-phase will be a lot faster
|
||||
addressesToPrefetch = append(addressesToPrefetch, addr) // Copy needed for closure
|
||||
}
|
||||
if s.prefetcher != nil && len(addressesToPrefetch) > 0 {
|
||||
id := trie.StateTrieID(s.originalRoot)
|
||||
if err := s.prefetcher.prefetchAccounts(*id, addressesToPrefetch, false); err != nil {
|
||||
log.Error("Failed to prefetch addresses", "addresses", len(addressesToPrefetch), "err", err)
|
||||
}
|
||||
}
|
||||
// Invalidate journal because reverting across transactions is not allowed.
|
||||
s.clearJournalAndRefund()
|
||||
|
|
@ -858,15 +790,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
}
|
||||
s.trie = tr
|
||||
}
|
||||
// If there was a trie prefetcher operating, terminate it async so that the
|
||||
// individual storage tries can be updated as soon as the disk load finishes.
|
||||
if s.prefetcher != nil {
|
||||
s.prefetcher.terminate(true)
|
||||
defer func() {
|
||||
s.prefetcher.report()
|
||||
s.prefetcher = nil // Pre-byzantium, unset any used up prefetcher
|
||||
}()
|
||||
}
|
||||
// Process all storage updates concurrently. The state object update root
|
||||
// method will internally call a blocking trie fetch from the prefetcher,
|
||||
// so there's no need to explicitly wait for the prefetchers to finish.
|
||||
|
|
@ -927,49 +850,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
obj := s.stateObjects[addr] // closure for the task runner below
|
||||
workers.Go(func() error {
|
||||
obj.updateRoot()
|
||||
|
||||
// If witness building is enabled and the state object has a trie,
|
||||
// gather the witnesses for its specific storage trie
|
||||
if s.witness != nil && obj.trie != nil {
|
||||
s.witness.AddState(obj.trie.Witness(), obj.addrHash())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
// If witness building is enabled, gather all the read-only accesses.
|
||||
// Skip witness collection in Verkle mode, they will be gathered
|
||||
// together at the end.
|
||||
if s.witness != nil && !s.db.TrieDB().IsVerkle() {
|
||||
// Pull in anything that has been accessed before destruction
|
||||
for _, obj := range s.stateObjectsDestruct {
|
||||
// Skip any objects that haven't touched their storage
|
||||
if len(obj.originStorage) == 0 {
|
||||
continue
|
||||
}
|
||||
if trie := obj.getPrefetchedTrie(); trie != nil {
|
||||
s.witness.AddState(trie.Witness(), obj.addrHash())
|
||||
} else if obj.trie != nil {
|
||||
s.witness.AddState(obj.trie.Witness(), obj.addrHash())
|
||||
}
|
||||
}
|
||||
// Pull in only-read and non-destructed trie witnesses
|
||||
for _, obj := range s.stateObjects {
|
||||
// Skip any objects that have been updated
|
||||
if _, ok := s.mutations[obj.address]; ok {
|
||||
continue
|
||||
}
|
||||
// Skip any objects that haven't touched their storage
|
||||
if len(obj.originStorage) == 0 {
|
||||
continue
|
||||
}
|
||||
if trie := obj.getPrefetchedTrie(); trie != nil {
|
||||
s.witness.AddState(trie.Witness(), obj.addrHash())
|
||||
} else if obj.trie != nil {
|
||||
s.witness.AddState(obj.trie.Witness(), obj.addrHash())
|
||||
}
|
||||
}
|
||||
}
|
||||
workers.Wait()
|
||||
s.StorageUpdates += time.Since(start)
|
||||
|
||||
|
|
@ -981,14 +865,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
// only a single trie is used for state hashing. Replacing a non-nil verkle tree
|
||||
// here could result in losing uncommitted changes from storage.
|
||||
start = time.Now()
|
||||
if s.prefetcher != nil {
|
||||
id := trie.StateTrieID(s.originalRoot)
|
||||
if trie := s.prefetcher.trie(*id); trie == nil {
|
||||
log.Error("Failed to retrieve account pre-fetcher trie")
|
||||
} else {
|
||||
s.trie = trie
|
||||
}
|
||||
}
|
||||
|
||||
// Perform updates before deletions. This prevents resolution of unnecessary trie nodes
|
||||
// in circumstances similar to the following:
|
||||
//
|
||||
|
|
@ -1000,7 +877,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
// into a shortnode. This requires `B` to be resolved from disk.
|
||||
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
|
||||
var (
|
||||
usedAddrs []common.Address
|
||||
deletedAddrs []common.Address
|
||||
)
|
||||
for addr, op := range s.mutations {
|
||||
|
|
@ -1022,7 +898,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
s.CodeUpdateBytes += len(obj.code)
|
||||
}
|
||||
}
|
||||
usedAddrs = append(usedAddrs, addr) // Copy needed for closure
|
||||
}
|
||||
for _, deletedAddr := range deletedAddrs {
|
||||
s.deleteStateObject(deletedAddr)
|
||||
|
|
@ -1030,20 +905,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
|||
}
|
||||
s.AccountUpdates += time.Since(start)
|
||||
|
||||
if s.prefetcher != nil {
|
||||
id := trie.StateTrieID(s.originalRoot)
|
||||
s.prefetcher.used(*id, usedAddrs, nil)
|
||||
}
|
||||
// Track the amount of time wasted on hashing the account trie
|
||||
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
|
||||
|
||||
hash := s.trie.Hash()
|
||||
|
||||
// If witness building is enabled, gather the account trie witness
|
||||
if s.witness != nil {
|
||||
s.witness.AddState(s.trie.Witness(), common.Hash{})
|
||||
}
|
||||
return hash
|
||||
return s.trie.Hash()
|
||||
}
|
||||
|
||||
// SetTxContext sets the current transaction hash and index which are
|
||||
|
|
@ -1476,7 +1341,7 @@ func (s *StateDB) markUpdate(addr common.Address) {
|
|||
|
||||
// Witness retrieves the current state witness being collected.
|
||||
func (s *StateDB) Witness() *stateless.Witness {
|
||||
return s.witness
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StateDB) AccessEvents() *AccessEvents {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import (
|
|||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
|
||||
//lint:file-ignore U1000 this file intentionally keeps unused helpers for future use
|
||||
|
||||
var (
|
||||
// triePrefetchMetricsPrefix is the prefix under which to publish the metrics.
|
||||
triePrefetchMetricsPrefix = "trie/prefetch/"
|
||||
|
|
@ -111,6 +113,7 @@ func (p *triePrefetcher) terminate(async bool) {
|
|||
}
|
||||
|
||||
// report aggregates the pre-fetching and usage metrics and reports them.
|
||||
// nolint:unused
|
||||
func (p *triePrefetcher) report() {
|
||||
if !metrics.Enabled() {
|
||||
return
|
||||
|
|
@ -205,6 +208,7 @@ func (p *triePrefetcher) trie(id trie.ID) Trie {
|
|||
|
||||
// used marks a batch of state items used to allow creating statistics as to
|
||||
// how useful or wasteful the fetcher is.
|
||||
// nolint:unused
|
||||
func (p *triePrefetcher) used(id trie.ID, usedAddr []common.Address, usedSlot []common.Hash) {
|
||||
if fetcher := p.fetchers[id]; fetcher != nil {
|
||||
fetcher.wait() // ensure the fetcher's idle before poking in its internals
|
||||
|
|
|
|||
Loading…
Reference in a new issue