github.com/MetalBlockchain/subnet-evm@v0.4.9/core/state/trie_prefetcher.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc.
     2  //
     3  // This file is a derived work, based on the go-ethereum library whose original
     4  // notices appear below.
     5  //
     6  // It is distributed under a license compatible with the licensing terms of the
     7  // original code from which it is derived.
     8  //
     9  // Much love to the original authors for their work.
    10  // **********
    11  // Copyright 2020 The go-ethereum Authors
    12  // This file is part of the go-ethereum library.
    13  //
    14  // The go-ethereum library is free software: you can redistribute it and/or modify
    15  // it under the terms of the GNU Lesser General Public License as published by
    16  // the Free Software Foundation, either version 3 of the License, or
    17  // (at your option) any later version.
    18  //
    19  // The go-ethereum library is distributed in the hope that it will be useful,
    20  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    21  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    22  // GNU Lesser General Public License for more details.
    23  //
    24  // You should have received a copy of the GNU Lesser General Public License
    25  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    26  
    27  package state
    28  
    29  import (
    30  	"sync"
    31  
    32  	"github.com/MetalBlockchain/subnet-evm/metrics"
    33  	"github.com/ethereum/go-ethereum/common"
    34  	"github.com/ethereum/go-ethereum/log"
    35  )
    36  
    37  var (
    38  	// triePrefetchMetricsPrefix is the prefix under which to publish the metrics.
    39  	triePrefetchMetricsPrefix = "trie/prefetch/"
    40  )
    41  
    42  // triePrefetcher is an active prefetcher, which receives accounts or storage
    43  // items and does trie-loading of them. The goal is to get as much useful content
    44  // into the caches as possible.
    45  //
    46  // Note, the prefetcher's API is not thread safe.
    47  type triePrefetcher struct {
    48  	db       Database               // Database to fetch trie nodes through
    49  	root     common.Hash            // Root hash of the account trie for metrics
    50  	fetches  map[string]Trie        // Partially or fully fetcher tries
    51  	fetchers map[string]*subfetcher // Subfetchers for each trie
    52  
    53  	deliveryCopyMissMeter    metrics.Meter
    54  	deliveryRequestMissMeter metrics.Meter
    55  	deliveryWaitMissMeter    metrics.Meter
    56  
    57  	accountLoadMeter  metrics.Meter
    58  	accountDupMeter   metrics.Meter
    59  	accountSkipMeter  metrics.Meter
    60  	accountWasteMeter metrics.Meter
    61  	storageLoadMeter  metrics.Meter
    62  	storageDupMeter   metrics.Meter
    63  	storageSkipMeter  metrics.Meter
    64  	storageWasteMeter metrics.Meter
    65  }
    66  
    67  func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
    68  	prefix := triePrefetchMetricsPrefix + namespace
    69  	p := &triePrefetcher{
    70  		db:       db,
    71  		root:     root,
    72  		fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
    73  
    74  		deliveryCopyMissMeter:    metrics.GetOrRegisterMeter(prefix+"/deliverymiss/copy", nil),
    75  		deliveryRequestMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss/request", nil),
    76  		deliveryWaitMissMeter:    metrics.GetOrRegisterMeter(prefix+"/deliverymiss/wait", nil),
    77  
    78  		accountLoadMeter:  metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
    79  		accountDupMeter:   metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
    80  		accountSkipMeter:  metrics.GetOrRegisterMeter(prefix+"/account/skip", nil),
    81  		accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
    82  		storageLoadMeter:  metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
    83  		storageDupMeter:   metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
    84  		storageSkipMeter:  metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil),
    85  		storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
    86  	}
    87  	return p
    88  }
    89  
    90  // close iterates over all the subfetchers, aborts any that were left spinning
    91  // and reports the stats to the metrics subsystem.
    92  func (p *triePrefetcher) close() {
    93  	for _, fetcher := range p.fetchers {
    94  		fetcher.abort() // safe to do multiple times
    95  
    96  		if metrics.Enabled {
    97  			if fetcher.root == p.root {
    98  				p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
    99  				p.accountDupMeter.Mark(int64(fetcher.dups))
   100  				p.accountSkipMeter.Mark(int64(len(fetcher.tasks)))
   101  
   102  				for _, key := range fetcher.used {
   103  					delete(fetcher.seen, string(key))
   104  				}
   105  				p.accountWasteMeter.Mark(int64(len(fetcher.seen)))
   106  			} else {
   107  				p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
   108  				p.storageDupMeter.Mark(int64(fetcher.dups))
   109  				p.storageSkipMeter.Mark(int64(len(fetcher.tasks)))
   110  
   111  				for _, key := range fetcher.used {
   112  					delete(fetcher.seen, string(key))
   113  				}
   114  				p.storageWasteMeter.Mark(int64(len(fetcher.seen)))
   115  			}
   116  		}
   117  	}
   118  	// Clear out all fetchers (will crash on a second call, deliberate)
   119  	p.fetchers = nil
   120  }
   121  
   122  // copy creates a deep-but-inactive copy of the trie prefetcher. Any trie data
   123  // already loaded will be copied over, but no goroutines will be started. This
   124  // is mostly used in the miner which creates a copy of it's actively mutated
   125  // state to be sealed while it may further mutate the state.
   126  func (p *triePrefetcher) copy() *triePrefetcher {
   127  	copy := &triePrefetcher{
   128  		db:      p.db,
   129  		root:    p.root,
   130  		fetches: make(map[string]Trie), // Active prefetchers use the fetches map
   131  
   132  		deliveryCopyMissMeter:    p.deliveryCopyMissMeter,
   133  		deliveryRequestMissMeter: p.deliveryRequestMissMeter,
   134  		deliveryWaitMissMeter:    p.deliveryWaitMissMeter,
   135  
   136  		accountLoadMeter:  p.accountLoadMeter,
   137  		accountDupMeter:   p.accountDupMeter,
   138  		accountSkipMeter:  p.accountSkipMeter,
   139  		accountWasteMeter: p.accountWasteMeter,
   140  		storageLoadMeter:  p.storageLoadMeter,
   141  		storageDupMeter:   p.storageDupMeter,
   142  		storageSkipMeter:  p.storageSkipMeter,
   143  		storageWasteMeter: p.storageWasteMeter,
   144  	}
   145  	// If the prefetcher is already a copy, duplicate the data
   146  	if p.fetches != nil {
   147  		for root, fetch := range p.fetches {
   148  			copy.fetches[root] = p.db.CopyTrie(fetch)
   149  		}
   150  		return copy
   151  	}
   152  	// Otherwise we're copying an active fetcher, retrieve the current states
   153  	for id, fetcher := range p.fetchers {
   154  		copy.fetches[id] = fetcher.peek()
   155  	}
   156  	return copy
   157  }
   158  
   159  // prefetch schedules a batch of trie items to prefetch.
   160  func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, keys [][]byte) {
   161  	// If the prefetcher is an inactive one, bail out
   162  	if p.fetches != nil {
   163  		return
   164  	}
   165  	// Active fetcher, schedule the retrievals
   166  	id := p.trieID(owner, root)
   167  	fetcher := p.fetchers[id]
   168  	if fetcher == nil {
   169  		fetcher = newSubfetcher(p.db, owner, root)
   170  		p.fetchers[id] = fetcher
   171  	}
   172  	fetcher.schedule(keys)
   173  }
   174  
   175  // trie returns the trie matching the root hash, or nil if the prefetcher doesn't
   176  // have it.
   177  func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie {
   178  	// If the prefetcher is inactive, return from existing deep copies
   179  	id := p.trieID(owner, root)
   180  	if p.fetches != nil {
   181  		trie := p.fetches[id]
   182  		if trie == nil {
   183  			p.deliveryCopyMissMeter.Mark(1)
   184  			return nil
   185  		}
   186  		return p.db.CopyTrie(trie)
   187  	}
   188  	// Otherwise the prefetcher is active, bail if no trie was prefetched for this root
   189  	fetcher := p.fetchers[id]
   190  	if fetcher == nil {
   191  		p.deliveryRequestMissMeter.Mark(1)
   192  		return nil
   193  	}
   194  	// Interrupt the prefetcher if it's by any chance still running and return
   195  	// a copy of any pre-loaded trie.
   196  	fetcher.abort() // safe to do multiple times
   197  
   198  	trie := fetcher.peek()
   199  	if trie == nil {
   200  		p.deliveryWaitMissMeter.Mark(1)
   201  		return nil
   202  	}
   203  	return trie
   204  }
   205  
   206  // used marks a batch of state items used to allow creating statistics as to
   207  // how useful or wasteful the prefetcher is.
   208  func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) {
   209  	if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil {
   210  		fetcher.used = used
   211  	}
   212  }
   213  
   214  // trieID returns an unique trie identifier consists the trie owner and root hash.
   215  func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string {
   216  	return string(append(owner.Bytes(), root.Bytes()...))
   217  }
   218  
   219  // subfetcher is a trie fetcher goroutine responsible for pulling entries for a
   220  // single trie. It is spawned when a new root is encountered and lives until the
   221  // main prefetcher is paused and either all requested items are processed or if
   222  // the trie being worked on is retrieved from the prefetcher.
   223  type subfetcher struct {
   224  	db    Database    // Database to load trie nodes through
   225  	owner common.Hash // Owner of the trie, usually account hash
   226  	root  common.Hash // Root hash of the trie to prefetch
   227  	trie  Trie        // Trie being populated with nodes
   228  
   229  	tasks [][]byte   // Items queued up for retrieval
   230  	lock  sync.Mutex // Lock protecting the task queue
   231  
   232  	wake chan struct{}  // Wake channel if a new task is scheduled
   233  	stop chan struct{}  // Channel to interrupt processing
   234  	term chan struct{}  // Channel to signal interruption
   235  	copy chan chan Trie // Channel to request a copy of the current trie
   236  
   237  	seen map[string]struct{} // Tracks the entries already loaded
   238  	dups int                 // Number of duplicate preload tasks
   239  	used [][]byte            // Tracks the entries used in the end
   240  }
   241  
   242  // newSubfetcher creates a goroutine to prefetch state items belonging to a
   243  // particular root hash.
   244  func newSubfetcher(db Database, owner common.Hash, root common.Hash) *subfetcher {
   245  	sf := &subfetcher{
   246  		db:    db,
   247  		owner: owner,
   248  		root:  root,
   249  		wake:  make(chan struct{}, 1),
   250  		stop:  make(chan struct{}),
   251  		term:  make(chan struct{}),
   252  		copy:  make(chan chan Trie),
   253  		seen:  make(map[string]struct{}),
   254  	}
   255  	go sf.loop()
   256  	return sf
   257  }
   258  
   259  // schedule adds a batch of trie keys to the queue to prefetch.
   260  func (sf *subfetcher) schedule(keys [][]byte) {
   261  	// Append the tasks to the current queue
   262  	sf.lock.Lock()
   263  	sf.tasks = append(sf.tasks, keys...)
   264  	sf.lock.Unlock()
   265  
   266  	// Notify the prefetcher, it's fine if it's already terminated
   267  	select {
   268  	case sf.wake <- struct{}{}:
   269  	default:
   270  	}
   271  }
   272  
   273  // peek tries to retrieve a deep copy of the fetcher's trie in whatever form it
   274  // is currently.
   275  func (sf *subfetcher) peek() Trie {
   276  	ch := make(chan Trie)
   277  	select {
   278  	case sf.copy <- ch:
   279  		// Subfetcher still alive, return copy from it
   280  		return <-ch
   281  
   282  	case <-sf.term:
   283  		// Subfetcher already terminated, return a copy directly
   284  		if sf.trie == nil {
   285  			return nil
   286  		}
   287  		return sf.db.CopyTrie(sf.trie)
   288  	}
   289  }
   290  
   291  // abort interrupts the subfetcher immediately. It is safe to call abort multiple
   292  // times but it is not thread safe.
   293  func (sf *subfetcher) abort() {
   294  	select {
   295  	case <-sf.stop:
   296  	default:
   297  		close(sf.stop)
   298  	}
   299  	<-sf.term
   300  }
   301  
   302  // loop waits for new tasks to be scheduled and keeps loading them until it runs
   303  // out of tasks or its underlying trie is retrieved for committing.
   304  func (sf *subfetcher) loop() {
   305  	// No matter how the loop stops, signal anyone waiting that it's terminated
   306  	defer close(sf.term)
   307  
   308  	// Start by opening the trie and stop processing if it fails
   309  	if sf.owner == (common.Hash{}) {
   310  		trie, err := sf.db.OpenTrie(sf.root)
   311  		if err != nil {
   312  			log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
   313  			return
   314  		}
   315  		sf.trie = trie
   316  	} else {
   317  		trie, err := sf.db.OpenStorageTrie(sf.owner, sf.root)
   318  		if err != nil {
   319  			log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
   320  			return
   321  		}
   322  		sf.trie = trie
   323  	}
   324  
   325  	// Trie opened successfully, keep prefetching items
   326  	for {
   327  		select {
   328  		case <-sf.wake:
   329  			// Subfetcher was woken up, retrieve any tasks to avoid spinning the lock
   330  			sf.lock.Lock()
   331  			tasks := sf.tasks
   332  			sf.tasks = nil
   333  			sf.lock.Unlock()
   334  
   335  			// Prefetch any tasks until the loop is interrupted
   336  			for i, task := range tasks {
   337  				select {
   338  				case <-sf.stop:
   339  					// If termination is requested, add any leftover back and return
   340  					sf.lock.Lock()
   341  					sf.tasks = append(sf.tasks, tasks[i:]...)
   342  					sf.lock.Unlock()
   343  					return
   344  
   345  				case ch := <-sf.copy:
   346  					// Somebody wants a copy of the current trie, grant them
   347  					ch <- sf.db.CopyTrie(sf.trie)
   348  
   349  				default:
   350  					// No termination request yet, prefetch the next entry
   351  					if _, ok := sf.seen[string(task)]; ok {
   352  						sf.dups++
   353  					} else {
   354  						var err error
   355  						if len(task) == len(common.Address{}) {
   356  							_, err = sf.trie.TryGetAccount(task)
   357  						} else {
   358  							_, err = sf.trie.TryGet(task)
   359  						}
   360  						if err != nil {
   361  							log.Error("Trie prefetcher failed fetching", "root", sf.root, "err", err)
   362  						}
   363  						sf.seen[string(task)] = struct{}{}
   364  					}
   365  				}
   366  			}
   367  
   368  		case ch := <-sf.copy:
   369  			// Somebody wants a copy of the current trie, grant them
   370  			ch <- sf.db.CopyTrie(sf.trie)
   371  
   372  		case <-sf.stop:
   373  			// Termination is requested, abort and leave remaining tasks
   374  			return
   375  		}
   376  	}
   377  }