github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/store/mpt/trie_prefetcher.go (about)

     1  package mpt
     2  
     3  import (
     4  	"sync"
     5  
     6  	"github.com/ethereum/go-ethereum/common"
     7  	ethstate "github.com/ethereum/go-ethereum/core/state"
     8  	"github.com/ethereum/go-ethereum/log"
     9  	"github.com/ethereum/go-ethereum/metrics"
    10  )
    11  
    12  var (
    13  	// triePrefetchMetricsPrefix is the prefix under which to publis the metrics.
    14  	triePrefetchMetricsPrefix = "trie/prefetch/"
    15  )
    16  
    17  // TriePrefetcher is an active prefetcher, which receives accounts or storage
    18  // items and does trie-loading of them. The goal is to get as much useful content
    19  // into the caches as possible.
    20  //
    21  // Note, the prefetcher's API is not thread safe.
    22  type TriePrefetcher struct {
    23  	db       ethstate.Database             // Database to fetch trie nodes through
    24  	root     common.Hash                   // Root hash of the account trie for metrics
    25  	fetches  map[common.Hash]ethstate.Trie // Partially or fully fetcher tries
    26  	fetchers map[common.Hash]*subfetcher   // Subfetchers for each trie
    27  
    28  	deliveryMissMeter metrics.Meter
    29  	accountLoadMeter  metrics.Meter
    30  	accountDupMeter   metrics.Meter
    31  	accountSkipMeter  metrics.Meter
    32  	accountWasteMeter metrics.Meter
    33  	storageLoadMeter  metrics.Meter
    34  	storageDupMeter   metrics.Meter
    35  	storageSkipMeter  metrics.Meter
    36  	storageWasteMeter metrics.Meter
    37  }
    38  
    39  // NewTriePrefetcher
    40  func NewTriePrefetcher(db ethstate.Database, root common.Hash, namespace string) *TriePrefetcher {
    41  	prefix := triePrefetchMetricsPrefix + namespace
    42  	p := &TriePrefetcher{
    43  		db:       db,
    44  		root:     root,
    45  		fetchers: make(map[common.Hash]*subfetcher), // Active prefetchers use the fetchers map
    46  
    47  		deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
    48  		accountLoadMeter:  metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
    49  		accountDupMeter:   metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
    50  		accountSkipMeter:  metrics.GetOrRegisterMeter(prefix+"/account/skip", nil),
    51  		accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
    52  		storageLoadMeter:  metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
    53  		storageDupMeter:   metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
    54  		storageSkipMeter:  metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil),
    55  		storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
    56  	}
    57  	return p
    58  }
    59  
    60  // Close iterates over all the subfetchers, aborts any that were left spinning
    61  // and reports the stats to the metrics subsystem.
    62  func (p *TriePrefetcher) Close() {
    63  	for _, fetcher := range p.fetchers {
    64  		fetcher.abort() // safe to do multiple times
    65  
    66  		if metrics.Enabled {
    67  			if fetcher.root == p.root {
    68  				p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
    69  				p.accountDupMeter.Mark(int64(fetcher.dups))
    70  				p.accountSkipMeter.Mark(int64(len(fetcher.tasks)))
    71  
    72  				for _, key := range fetcher.used {
    73  					delete(fetcher.seen, string(key))
    74  				}
    75  				p.accountWasteMeter.Mark(int64(len(fetcher.seen)))
    76  			} else {
    77  				p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
    78  				p.storageDupMeter.Mark(int64(fetcher.dups))
    79  				p.storageSkipMeter.Mark(int64(len(fetcher.tasks)))
    80  
    81  				for _, key := range fetcher.used {
    82  					delete(fetcher.seen, string(key))
    83  				}
    84  				p.storageWasteMeter.Mark(int64(len(fetcher.seen)))
    85  			}
    86  		}
    87  	}
    88  	// Clear out all fetchers (will crash on a second call, deliberate)
    89  	p.fetchers = nil
    90  }
    91  
    92  // copy creates a deep-but-inactive copy of the trie prefetcher. Any trie data
    93  // already loaded will be copied over, but no goroutines will be started. This
    94  // is mostly used in the miner which creates a copy of it's actively mutated
    95  // state to be sealed while it may further mutate the state.
    96  func (p *TriePrefetcher) Copy() *TriePrefetcher {
    97  	copy := &TriePrefetcher{
    98  		db:      p.db,
    99  		root:    p.root,
   100  		fetches: make(map[common.Hash]ethstate.Trie), // Active prefetchers use the fetches map
   101  
   102  		deliveryMissMeter: p.deliveryMissMeter,
   103  		accountLoadMeter:  p.accountLoadMeter,
   104  		accountDupMeter:   p.accountDupMeter,
   105  		accountSkipMeter:  p.accountSkipMeter,
   106  		accountWasteMeter: p.accountWasteMeter,
   107  		storageLoadMeter:  p.storageLoadMeter,
   108  		storageDupMeter:   p.storageDupMeter,
   109  		storageSkipMeter:  p.storageSkipMeter,
   110  		storageWasteMeter: p.storageWasteMeter,
   111  	}
   112  	// If the prefetcher is already a copy, duplicate the data
   113  	if p.fetches != nil {
   114  		for root, fetch := range p.fetches {
   115  			copy.fetches[root] = p.db.CopyTrie(fetch)
   116  		}
   117  		return copy
   118  	}
   119  	// Otherwise we're copying an active fetcher, retrieve the current states
   120  	for root, fetcher := range p.fetchers {
   121  		copy.fetches[root] = fetcher.peek()
   122  	}
   123  	return copy
   124  }
   125  
   126  // prefetch schedules a batch of trie items to prefetch.
   127  func (p *TriePrefetcher) Prefetch(root common.Hash, keys [][]byte) {
   128  	// If the prefetcher is an inactive one, bail out
   129  	if p.fetches != nil || p.fetchers == nil{
   130  		return
   131  	}
   132  	// Active fetcher, schedule the retrievals
   133  	fetcher := p.fetchers[root]
   134  	if fetcher == nil {
   135  		fetcher = newSubfetcher(p.db, root)
   136  		p.fetchers[root] = fetcher
   137  	}
   138  	fetcher.schedule(keys)
   139  }
   140  
   141  // trie returns the trie matching the root hash, or nil if the prefetcher doesn't
   142  // have it.
   143  func (p *TriePrefetcher) Trie(root common.Hash) ethstate.Trie {
   144  	// If the prefetcher is inactive, return from existing deep copies
   145  	if p.fetches != nil {
   146  		trie := p.fetches[root]
   147  		if trie == nil {
   148  			p.deliveryMissMeter.Mark(1)
   149  			return nil
   150  		}
   151  		return p.db.CopyTrie(trie)
   152  	}
   153  	// Otherwise the prefetcher is active, bail if no trie was prefetched for this root
   154  	fetcher := p.fetchers[root]
   155  	if fetcher == nil {
   156  		p.deliveryMissMeter.Mark(1)
   157  		return nil
   158  	}
   159  	// Interrupt the prefetcher if it's by any chance still running and return
   160  	// a copy of any pre-loaded trie.
   161  	fetcher.abort() // safe to do multiple times
   162  
   163  	trie := fetcher.peek()
   164  	if trie == nil {
   165  		p.deliveryMissMeter.Mark(1)
   166  		return nil
   167  	}
   168  	return trie
   169  }
   170  
   171  // used marks a batch of state items used to allow creating statistics as to
   172  // how useful or wasteful the prefetcher is.
   173  func (p *TriePrefetcher) Used(root common.Hash, used [][]byte) {
   174  	if fetcher := p.fetchers[root]; fetcher != nil {
   175  		fetcher.used = used
   176  	}
   177  }
   178  
   179  // subfetcher is a trie fetcher goroutine responsible for pulling entries for a
   180  // single trie. It is spawned when a new root is encountered and lives until the
   181  // main prefetcher is paused and either all requested items are processed or if
   182  // the trie being worked on is retrieved from the prefetcher.
   183  type subfetcher struct {
   184  	db   ethstate.Database // Database to load trie nodes through
   185  	root common.Hash       // Root hash of the trie to prefetch
   186  	trie ethstate.Trie     // Trie being populated with nodes
   187  
   188  	tasks [][]byte   // Items queued up for retrieval
   189  	lock  sync.Mutex // Lock protecting the task queue
   190  
   191  	wake chan struct{}           // Wake channel if a new task is scheduled
   192  	stop chan struct{}           // Channel to interrupt processing
   193  	term chan struct{}           // Channel to signal iterruption
   194  	copy chan chan ethstate.Trie // Channel to request a copy of the current trie
   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 ethstate.Database, root common.Hash) *subfetcher {
   204  	sf := &subfetcher{
   205  		db:   db,
   206  		root: root,
   207  		wake: make(chan struct{}, 1),
   208  		stop: make(chan struct{}),
   209  		term: make(chan struct{}),
   210  		copy: make(chan chan ethstate.Trie),
   211  		seen: make(map[string]struct{}),
   212  	}
   213  	go sf.loop()
   214  	return sf
   215  }
   216  
   217  // schedule adds a batch of trie keys to the queue to prefetch.
   218  func (sf *subfetcher) schedule(keys [][]byte) {
   219  	// Append the tasks to the current queue
   220  	sf.lock.Lock()
   221  	sf.tasks = append(sf.tasks, keys...)
   222  	sf.lock.Unlock()
   223  
   224  	// Notify the prefetcher, it's fine if it's already terminated
   225  	select {
   226  	case sf.wake <- struct{}{}:
   227  	default:
   228  	}
   229  }
   230  
   231  // peek tries to retrieve a deep copy of the fetcher's trie in whatever form it
   232  // is currently.
   233  func (sf *subfetcher) peek() ethstate.Trie {
   234  	ch := make(chan ethstate.Trie)
   235  	select {
   236  	case sf.copy <- ch:
   237  		// Subfetcher still alive, return copy from it
   238  		return <-ch
   239  
   240  	case <-sf.term:
   241  		// Subfetcher already terminated, return a copy directly
   242  		if sf.trie == nil {
   243  			return nil
   244  		}
   245  		return sf.db.CopyTrie(sf.trie)
   246  	}
   247  }
   248  
   249  // abort interrupts the subfetcher immediately. It is safe to call abort multiple
   250  // times but it is not thread safe.
   251  func (sf *subfetcher) abort() {
   252  	select {
   253  	case <-sf.stop:
   254  	default:
   255  		close(sf.stop)
   256  	}
   257  	<-sf.term
   258  }
   259  
   260  // loop waits for new tasks to be scheduled and keeps loading them until it runs
   261  // out of tasks or its underlying trie is retrieved for committing.
   262  func (sf *subfetcher) loop() {
   263  	// No matter how the loop stops, signal anyone waiting that it's terminated
   264  	defer close(sf.term)
   265  
   266  	// Start by opening the trie and stop processing if it fails
   267  	trie, err := sf.db.OpenTrie(sf.root)
   268  	if err != nil {
   269  		log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
   270  		return
   271  	}
   272  	sf.trie = trie
   273  
   274  	// Trie opened successfully, keep prefetching items
   275  	for {
   276  		select {
   277  		case <-sf.wake:
   278  			// Subfetcher was woken up, retrieve any tasks to avoid spinning the lock
   279  			sf.lock.Lock()
   280  			tasks := sf.tasks
   281  			sf.tasks = nil
   282  			sf.lock.Unlock()
   283  
   284  			// Prefetch any tasks until the loop is interrupted
   285  			for i, task := range tasks {
   286  				select {
   287  				case <-sf.stop:
   288  					// If termination is requested, add any leftover back and return
   289  					sf.lock.Lock()
   290  					sf.tasks = append(sf.tasks, tasks[i:]...)
   291  					sf.lock.Unlock()
   292  					return
   293  
   294  				case ch := <-sf.copy:
   295  					// Somebody wants a copy of the current trie, grant them
   296  					ch <- sf.db.CopyTrie(sf.trie)
   297  
   298  				default:
   299  					// No termination request yet, prefetch the next entry
   300  					if _, ok := sf.seen[string(task)]; ok {
   301  						sf.dups++
   302  					} else {
   303  						sf.trie.TryGet(task)
   304  						sf.seen[string(task)] = struct{}{}
   305  					}
   306  				}
   307  			}
   308  
   309  		case ch := <-sf.copy:
   310  			// Somebody wants a copy of the current trie, grant them
   311  			ch <- sf.db.CopyTrie(sf.trie)
   312  
   313  		case <-sf.stop:
   314  			// Termination is requested, abort and leave remaining tasks
   315  			return
   316  		}
   317  	}
   318  }