github.com/ethereum/go-ethereum@v1.14.4-0.20240516095835-473ee8fc07a3/core/state/trie_prefetcher.go (about)

     1  // Copyright 2020 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package state
    18  
    19  import (
    20  	"errors"
    21  	"sync"
    22  
    23  	"github.com/ethereum/go-ethereum/common"
    24  	"github.com/ethereum/go-ethereum/log"
    25  	"github.com/ethereum/go-ethereum/metrics"
    26  )
    27  
    28  var (
    29  	// triePrefetchMetricsPrefix is the prefix under which to publish the metrics.
    30  	triePrefetchMetricsPrefix = "trie/prefetch/"
    31  
    32  	// errTerminated is returned if a fetcher is attempted to be operated after it
    33  	// has already terminated.
    34  	errTerminated = errors.New("fetcher is already terminated")
    35  )
    36  
    37  // triePrefetcher is an active prefetcher, which receives accounts or storage
    38  // items and does trie-loading of them. The goal is to get as much useful content
    39  // into the caches as possible.
    40  //
    41  // Note, the prefetcher's API is not thread safe.
    42  type triePrefetcher struct {
    43  	db       Database               // Database to fetch trie nodes through
    44  	root     common.Hash            // Root hash of the account trie for metrics
    45  	fetchers map[string]*subfetcher // Subfetchers for each trie
    46  	term     chan struct{}          // Channel to signal interruption
    47  
    48  	deliveryMissMeter metrics.Meter
    49  	accountLoadMeter  metrics.Meter
    50  	accountDupMeter   metrics.Meter
    51  	accountWasteMeter metrics.Meter
    52  	storageLoadMeter  metrics.Meter
    53  	storageDupMeter   metrics.Meter
    54  	storageWasteMeter metrics.Meter
    55  }
    56  
    57  func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
    58  	prefix := triePrefetchMetricsPrefix + namespace
    59  	return &triePrefetcher{
    60  		db:       db,
    61  		root:     root,
    62  		fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
    63  		term:     make(chan struct{}),
    64  
    65  		deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
    66  		accountLoadMeter:  metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
    67  		accountDupMeter:   metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
    68  		accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
    69  		storageLoadMeter:  metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
    70  		storageDupMeter:   metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
    71  		storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
    72  	}
    73  }
    74  
    75  // terminate iterates over all the subfetchers and issues a terminateion request
    76  // to all of them. Depending on the async parameter, the method will either block
    77  // until all subfetchers spin down, or return immediately.
    78  func (p *triePrefetcher) terminate(async bool) {
    79  	// Short circuit if the fetcher is already closed
    80  	select {
    81  	case <-p.term:
    82  		return
    83  	default:
    84  	}
    85  	// Termiante all sub-fetchers, sync or async, depending on the request
    86  	for _, fetcher := range p.fetchers {
    87  		fetcher.terminate(async)
    88  	}
    89  	close(p.term)
    90  }
    91  
    92  // report aggregates the pre-fetching and usage metrics and reports them.
    93  func (p *triePrefetcher) report() {
    94  	if !metrics.Enabled {
    95  		return
    96  	}
    97  	for _, fetcher := range p.fetchers {
    98  		fetcher.wait() // ensure the fetcher's idle before poking in its internals
    99  
   100  		if fetcher.root == p.root {
   101  			p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
   102  			p.accountDupMeter.Mark(int64(fetcher.dups))
   103  			for _, key := range fetcher.used {
   104  				delete(fetcher.seen, string(key))
   105  			}
   106  			p.accountWasteMeter.Mark(int64(len(fetcher.seen)))
   107  		} else {
   108  			p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
   109  			p.storageDupMeter.Mark(int64(fetcher.dups))
   110  			for _, key := range fetcher.used {
   111  				delete(fetcher.seen, string(key))
   112  			}
   113  			p.storageWasteMeter.Mark(int64(len(fetcher.seen)))
   114  		}
   115  	}
   116  }
   117  
   118  // prefetch schedules a batch of trie items to prefetch. After the prefetcher is
   119  // closed, all the following tasks scheduled will not be executed and an error
   120  // will be returned.
   121  //
   122  // prefetch is called from two locations:
   123  //
   124  //  1. Finalize of the state-objects storage roots. This happens at the end
   125  //     of every transaction, meaning that if several transactions touches
   126  //     upon the same contract, the parameters invoking this method may be
   127  //     repeated.
   128  //  2. Finalize of the main account trie. This happens only once per block.
   129  func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte) error {
   130  	// Ensure the subfetcher is still alive
   131  	select {
   132  	case <-p.term:
   133  		return errTerminated
   134  	default:
   135  	}
   136  	id := p.trieID(owner, root)
   137  	fetcher := p.fetchers[id]
   138  	if fetcher == nil {
   139  		fetcher = newSubfetcher(p.db, p.root, owner, root, addr)
   140  		p.fetchers[id] = fetcher
   141  	}
   142  	return fetcher.schedule(keys)
   143  }
   144  
   145  // trie returns the trie matching the root hash, blocking until the fetcher of
   146  // the given trie terminates. If no fetcher exists for the request, nil will be
   147  // returned.
   148  func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) (Trie, error) {
   149  	// Bail if no trie was prefetched for this root
   150  	fetcher := p.fetchers[p.trieID(owner, root)]
   151  	if fetcher == nil {
   152  		log.Error("Prefetcher missed to load trie", "owner", owner, "root", root)
   153  		p.deliveryMissMeter.Mark(1)
   154  		return nil, nil
   155  	}
   156  	// Subfetcher exists, retrieve its trie
   157  	return fetcher.peek(), nil
   158  }
   159  
   160  // used marks a batch of state items used to allow creating statistics as to
   161  // how useful or wasteful the fetcher is.
   162  func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) {
   163  	if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil {
   164  		fetcher.wait() // ensure the fetcher's idle before poking in its internals
   165  		fetcher.used = used
   166  	}
   167  }
   168  
   169  // trieID returns an unique trie identifier consists the trie owner and root hash.
   170  func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string {
   171  	trieID := make([]byte, common.HashLength*2)
   172  	copy(trieID, owner.Bytes())
   173  	copy(trieID[common.HashLength:], root.Bytes())
   174  	return string(trieID)
   175  }
   176  
   177  // subfetcher is a trie fetcher goroutine responsible for pulling entries for a
   178  // single trie. It is spawned when a new root is encountered and lives until the
   179  // main prefetcher is paused and either all requested items are processed or if
   180  // the trie being worked on is retrieved from the prefetcher.
   181  type subfetcher struct {
   182  	db    Database       // Database to load trie nodes through
   183  	state common.Hash    // Root hash of the state to prefetch
   184  	owner common.Hash    // Owner of the trie, usually account hash
   185  	root  common.Hash    // Root hash of the trie to prefetch
   186  	addr  common.Address // Address of the account that the trie belongs to
   187  	trie  Trie           // Trie being populated with nodes
   188  
   189  	tasks [][]byte   // Items queued up for retrieval
   190  	lock  sync.Mutex // Lock protecting the task queue
   191  
   192  	wake chan struct{} // Wake channel if a new task is scheduled
   193  	stop chan struct{} // Channel to interrupt processing
   194  	term chan struct{} // Channel to signal interruption
   195  
   196  	seen map[string]struct{} // Tracks the entries already loaded
   197  	dups int                 // Number of duplicate preload tasks
   198  	used [][]byte            // Tracks the entries used in the end
   199  }
   200  
   201  // newSubfetcher creates a goroutine to prefetch state items belonging to a
   202  // particular root hash.
   203  func newSubfetcher(db Database, state common.Hash, owner common.Hash, root common.Hash, addr common.Address) *subfetcher {
   204  	sf := &subfetcher{
   205  		db:    db,
   206  		state: state,
   207  		owner: owner,
   208  		root:  root,
   209  		addr:  addr,
   210  		wake:  make(chan struct{}, 1),
   211  		stop:  make(chan struct{}),
   212  		term:  make(chan struct{}),
   213  		seen:  make(map[string]struct{}),
   214  	}
   215  	go sf.loop()
   216  	return sf
   217  }
   218  
   219  // schedule adds a batch of trie keys to the queue to prefetch.
   220  func (sf *subfetcher) schedule(keys [][]byte) error {
   221  	// Ensure the subfetcher is still alive
   222  	select {
   223  	case <-sf.term:
   224  		return errTerminated
   225  	default:
   226  	}
   227  	// Append the tasks to the current queue
   228  	sf.lock.Lock()
   229  	sf.tasks = append(sf.tasks, keys...)
   230  	sf.lock.Unlock()
   231  
   232  	// Notify the background thread to execute scheduled tasks
   233  	select {
   234  	case sf.wake <- struct{}{}:
   235  		// Wake signal sent
   236  	default:
   237  		// Wake signal not sent as a previous is already queued
   238  	}
   239  	return nil
   240  }
   241  
   242  // wait blocks until the subfetcher terminates. This method is used to block on
   243  // an async termination before accessing internal fields from the fetcher.
   244  func (sf *subfetcher) wait() {
   245  	<-sf.term
   246  }
   247  
   248  // peek retrieves the fetcher's trie, populated with any pre-fetched data. The
   249  // returned trie will be a shallow copy, so modifying it will break subsequent
   250  // peeks for the original data. The method will block until all the scheduled
   251  // data has been loaded and the fethcer terminated.
   252  func (sf *subfetcher) peek() Trie {
   253  	// Block until the fertcher terminates, then retrieve the trie
   254  	sf.wait()
   255  	return sf.trie
   256  }
   257  
   258  // terminate requests the subfetcher to stop accepting new tasks and spin down
   259  // as soon as everything is loaded. Depending on the async parameter, the method
   260  // will either block until all disk loads finish or return immediately.
   261  func (sf *subfetcher) terminate(async bool) {
   262  	select {
   263  	case <-sf.stop:
   264  	default:
   265  		close(sf.stop)
   266  	}
   267  	if async {
   268  		return
   269  	}
   270  	<-sf.term
   271  }
   272  
   273  // loop loads newly-scheduled trie tasks as they are received and loads them, stopping
   274  // when requested.
   275  func (sf *subfetcher) loop() {
   276  	// No matter how the loop stops, signal anyone waiting that it's terminated
   277  	defer close(sf.term)
   278  
   279  	// Start by opening the trie and stop processing if it fails
   280  	if sf.owner == (common.Hash{}) {
   281  		trie, err := sf.db.OpenTrie(sf.root)
   282  		if err != nil {
   283  			log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
   284  			return
   285  		}
   286  		sf.trie = trie
   287  	} else {
   288  		trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil)
   289  		if err != nil {
   290  			log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
   291  			return
   292  		}
   293  		sf.trie = trie
   294  	}
   295  	// Trie opened successfully, keep prefetching items
   296  	for {
   297  		select {
   298  		case <-sf.wake:
   299  			// Execute all remaining tasks in single run
   300  			sf.lock.Lock()
   301  			tasks := sf.tasks
   302  			sf.tasks = nil
   303  			sf.lock.Unlock()
   304  
   305  			for _, task := range tasks {
   306  				if _, ok := sf.seen[string(task)]; ok {
   307  					sf.dups++
   308  					continue
   309  				}
   310  				if len(task) == common.AddressLength {
   311  					sf.trie.GetAccount(common.BytesToAddress(task))
   312  				} else {
   313  					sf.trie.GetStorage(sf.addr, task)
   314  				}
   315  				sf.seen[string(task)] = struct{}{}
   316  			}
   317  
   318  		case <-sf.stop:
   319  			// Termination is requested, abort if no more tasks are pending. If
   320  			// there are some, exhaust them first.
   321  			sf.lock.Lock()
   322  			done := sf.tasks == nil
   323  			sf.lock.Unlock()
   324  
   325  			if done {
   326  				return
   327  			}
   328  			// Some tasks are pending, loop and pick them up (that wake branch
   329  			// will be selected eventually, whilst stop remains closed to this
   330  			// branch will also run afterwards).
   331  		}
   332  	}
   333  }