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 }