github.com/dim4egster/coreth@v0.10.2/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/dim4egster/coreth/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  	deliveryMissMeter metrics.Meter
    54  	accountLoadMeter  metrics.Meter
    55  	accountDupMeter   metrics.Meter
    56  	accountSkipMeter  metrics.Meter
    57  	accountWasteMeter metrics.Meter
    58  	storageLoadMeter  metrics.Meter
    59  	storageDupMeter   metrics.Meter
    60  	storageSkipMeter  metrics.Meter
    61  	storageWasteMeter metrics.Meter
    62  }
    63  
    64  func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
    65  	prefix := triePrefetchMetricsPrefix + namespace
    66  	p := &triePrefetcher{
    67  		db:       db,
    68  		root:     root,
    69  		fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
    70  
    71  		deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
    72  		accountLoadMeter:  metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
    73  		accountDupMeter:   metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
    74  		accountSkipMeter:  metrics.GetOrRegisterMeter(prefix+"/account/skip", nil),
    75  		accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
    76  		storageLoadMeter:  metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
    77  		storageDupMeter:   metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
    78  		storageSkipMeter:  metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil),
    79  		storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
    80  	}
    81  	return p
    82  }
    83  
    84  // close iterates over all the subfetchers, aborts any that were left spinning
    85  // and reports the stats to the metrics subsystem.
    86  func (p *triePrefetcher) close() {
    87  	for _, fetcher := range p.fetchers {
    88  		fetcher.abort() // safe to do multiple times
    89  
    90  		if metrics.Enabled {
    91  			if fetcher.root == p.root {
    92  				p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
    93  				p.accountDupMeter.Mark(int64(fetcher.dups))
    94  				p.accountSkipMeter.Mark(int64(len(fetcher.tasks)))
    95  
    96  				for _, key := range fetcher.used {
    97  					delete(fetcher.seen, string(key))
    98  				}
    99  				p.accountWasteMeter.Mark(int64(len(fetcher.seen)))
   100  			} else {
   101  				p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
   102  				p.storageDupMeter.Mark(int64(fetcher.dups))
   103  				p.storageSkipMeter.Mark(int64(len(fetcher.tasks)))
   104  
   105  				for _, key := range fetcher.used {
   106  					delete(fetcher.seen, string(key))
   107  				}
   108  				p.storageWasteMeter.Mark(int64(len(fetcher.seen)))
   109  			}
   110  		}
   111  	}
   112  	// Clear out all fetchers (will crash on a second call, deliberate)
   113  	p.fetchers = nil
   114  }
   115  
   116  // copy creates a deep-but-inactive copy of the trie prefetcher. Any trie data
   117  // already loaded will be copied over, but no goroutines will be started. This
   118  // is mostly used in the miner which creates a copy of it's actively mutated
   119  // state to be sealed while it may further mutate the state.
   120  func (p *triePrefetcher) copy() *triePrefetcher {
   121  	copy := &triePrefetcher{
   122  		db:      p.db,
   123  		root:    p.root,
   124  		fetches: make(map[string]Trie), // Active prefetchers use the fetches map
   125  
   126  		deliveryMissMeter: p.deliveryMissMeter,
   127  		accountLoadMeter:  p.accountLoadMeter,
   128  		accountDupMeter:   p.accountDupMeter,
   129  		accountSkipMeter:  p.accountSkipMeter,
   130  		accountWasteMeter: p.accountWasteMeter,
   131  		storageLoadMeter:  p.storageLoadMeter,
   132  		storageDupMeter:   p.storageDupMeter,
   133  		storageSkipMeter:  p.storageSkipMeter,
   134  		storageWasteMeter: p.storageWasteMeter,
   135  	}
   136  	// If the prefetcher is already a copy, duplicate the data
   137  	if p.fetches != nil {
   138  		for root, fetch := range p.fetches {
   139  			copy.fetches[root] = p.db.CopyTrie(fetch)
   140  		}
   141  		return copy
   142  	}
   143  	// Otherwise we're copying an active fetcher, retrieve the current states
   144  	for id, fetcher := range p.fetchers {
   145  		copy.fetches[id] = fetcher.peek()
   146  	}
   147  	return copy
   148  }
   149  
   150  // prefetch schedules a batch of trie items to prefetch.
   151  func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, keys [][]byte) {
   152  	// If the prefetcher is an inactive one, bail out
   153  	if p.fetches != nil {
   154  		return
   155  	}
   156  	// Active fetcher, schedule the retrievals
   157  	id := p.trieID(owner, root)
   158  	fetcher := p.fetchers[id]
   159  	if fetcher == nil {
   160  		fetcher = newSubfetcher(p.db, owner, root)
   161  		p.fetchers[id] = fetcher
   162  	}
   163  	fetcher.schedule(keys)
   164  }
   165  
   166  // trie returns the trie matching the root hash, or nil if the prefetcher doesn't
   167  // have it.
   168  func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie {
   169  	// If the prefetcher is inactive, return from existing deep copies
   170  	id := p.trieID(owner, root)
   171  	if p.fetches != nil {
   172  		trie := p.fetches[id]
   173  		if trie == nil {
   174  			p.deliveryMissMeter.Mark(1)
   175  			return nil
   176  		}
   177  		return p.db.CopyTrie(trie)
   178  	}
   179  	// Otherwise the prefetcher is active, bail if no trie was prefetched for this root
   180  	fetcher := p.fetchers[id]
   181  	if fetcher == nil {
   182  		p.deliveryMissMeter.Mark(1)
   183  		return nil
   184  	}
   185  	// Interrupt the prefetcher if it's by any chance still running and return
   186  	// a copy of any pre-loaded trie.
   187  	fetcher.abort() // safe to do multiple times
   188  
   189  	trie := fetcher.peek()
   190  	if trie == nil {
   191  		p.deliveryMissMeter.Mark(1)
   192  		return nil
   193  	}
   194  	return trie
   195  }
   196  
   197  // used marks a batch of state items used to allow creating statistics as to
   198  // how useful or wasteful the prefetcher is.
   199  func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) {
   200  	if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil {
   201  		fetcher.used = used
   202  	}
   203  }
   204  
   205  // trieID returns an unique trie identifier consists the trie owner and root hash.
   206  func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string {
   207  	return string(append(owner.Bytes(), root.Bytes()...))
   208  }
   209  
   210  // subfetcher is a trie fetcher goroutine responsible for pulling entries for a
   211  // single trie. It is spawned when a new root is encountered and lives until the
   212  // main prefetcher is paused and either all requested items are processed or if
   213  // the trie being worked on is retrieved from the prefetcher.
   214  type subfetcher struct {
   215  	db    Database    // Database to load trie nodes through
   216  	owner common.Hash // Owner of the trie, usually account hash
   217  	root  common.Hash // Root hash of the trie to prefetch
   218  	trie  Trie        // Trie being populated with nodes
   219  
   220  	tasks [][]byte   // Items queued up for retrieval
   221  	lock  sync.Mutex // Lock protecting the task queue
   222  
   223  	wake chan struct{}  // Wake channel if a new task is scheduled
   224  	stop chan struct{}  // Channel to interrupt processing
   225  	term chan struct{}  // Channel to signal interruption
   226  	copy chan chan Trie // Channel to request a copy of the current trie
   227  
   228  	seen map[string]struct{} // Tracks the entries already loaded
   229  	dups int                 // Number of duplicate preload tasks
   230  	used [][]byte            // Tracks the entries used in the end
   231  }
   232  
   233  // newSubfetcher creates a goroutine to prefetch state items belonging to a
   234  // particular root hash.
   235  func newSubfetcher(db Database, owner common.Hash, root common.Hash) *subfetcher {
   236  	sf := &subfetcher{
   237  		db:    db,
   238  		owner: owner,
   239  		root:  root,
   240  		wake:  make(chan struct{}, 1),
   241  		stop:  make(chan struct{}),
   242  		term:  make(chan struct{}),
   243  		copy:  make(chan chan Trie),
   244  		seen:  make(map[string]struct{}),
   245  	}
   246  	go sf.loop()
   247  	return sf
   248  }
   249  
   250  // schedule adds a batch of trie keys to the queue to prefetch.
   251  func (sf *subfetcher) schedule(keys [][]byte) {
   252  	// Append the tasks to the current queue
   253  	sf.lock.Lock()
   254  	sf.tasks = append(sf.tasks, keys...)
   255  	sf.lock.Unlock()
   256  
   257  	// Notify the prefetcher, it's fine if it's already terminated
   258  	select {
   259  	case sf.wake <- struct{}{}:
   260  	default:
   261  	}
   262  }
   263  
   264  // peek tries to retrieve a deep copy of the fetcher's trie in whatever form it
   265  // is currently.
   266  func (sf *subfetcher) peek() Trie {
   267  	ch := make(chan Trie)
   268  	select {
   269  	case sf.copy <- ch:
   270  		// Subfetcher still alive, return copy from it
   271  		return <-ch
   272  
   273  	case <-sf.term:
   274  		// Subfetcher already terminated, return a copy directly
   275  		if sf.trie == nil {
   276  			return nil
   277  		}
   278  		return sf.db.CopyTrie(sf.trie)
   279  	}
   280  }
   281  
   282  // abort interrupts the subfetcher immediately. It is safe to call abort multiple
   283  // times but it is not thread safe.
   284  func (sf *subfetcher) abort() {
   285  	select {
   286  	case <-sf.stop:
   287  	default:
   288  		close(sf.stop)
   289  	}
   290  	<-sf.term
   291  }
   292  
   293  // loop waits for new tasks to be scheduled and keeps loading them until it runs
   294  // out of tasks or its underlying trie is retrieved for committing.
   295  func (sf *subfetcher) loop() {
   296  	// No matter how the loop stops, signal anyone waiting that it's terminated
   297  	defer close(sf.term)
   298  
   299  	// Start by opening the trie and stop processing if it fails
   300  	if sf.owner == (common.Hash{}) {
   301  		trie, err := sf.db.OpenTrie(sf.root)
   302  		if err != nil {
   303  			log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
   304  			return
   305  		}
   306  		sf.trie = trie
   307  	} else {
   308  		trie, err := sf.db.OpenStorageTrie(sf.owner, sf.root)
   309  		if err != nil {
   310  			log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
   311  			return
   312  		}
   313  		sf.trie = trie
   314  	}
   315  
   316  	// Trie opened successfully, keep prefetching items
   317  	for {
   318  		select {
   319  		case <-sf.wake:
   320  			// Subfetcher was woken up, retrieve any tasks to avoid spinning the lock
   321  			sf.lock.Lock()
   322  			tasks := sf.tasks
   323  			sf.tasks = nil
   324  			sf.lock.Unlock()
   325  
   326  			// Prefetch any tasks until the loop is interrupted
   327  			for i, task := range tasks {
   328  				select {
   329  				case <-sf.stop:
   330  					// If termination is requested, add any leftover back and return
   331  					sf.lock.Lock()
   332  					sf.tasks = append(sf.tasks, tasks[i:]...)
   333  					sf.lock.Unlock()
   334  					return
   335  
   336  				case ch := <-sf.copy:
   337  					// Somebody wants a copy of the current trie, grant them
   338  					ch <- sf.db.CopyTrie(sf.trie)
   339  
   340  				default:
   341  					// No termination request yet, prefetch the next entry
   342  					if _, ok := sf.seen[string(task)]; ok {
   343  						sf.dups++
   344  					} else {
   345  						var err error
   346  						if len(task) == len(common.Address{}) {
   347  							_, err = sf.trie.TryGetAccount(task)
   348  						} else {
   349  							_, err = sf.trie.TryGet(task)
   350  						}
   351  						if err != nil {
   352  							log.Error("Trie prefetcher failed fetching", "root", sf.root, "err", err)
   353  						}
   354  						sf.seen[string(task)] = struct{}{}
   355  					}
   356  				}
   357  			}
   358  
   359  		case ch := <-sf.copy:
   360  			// Somebody wants a copy of the current trie, grant them
   361  			ch <- sf.db.CopyTrie(sf.trie)
   362  
   363  		case <-sf.stop:
   364  			// Termination is requested, abort and leave remaining tasks
   365  			return
   366  		}
   367  	}
   368  }