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 }