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