github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/core/state/trie_prefetcher.go (about)

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