github.com/ethereum/go-ethereum@v1.14.4-0.20240516095835-473ee8fc07a3/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 "errors" 21 "sync" 22 23 "github.com/ethereum/go-ethereum/common" 24 "github.com/ethereum/go-ethereum/log" 25 "github.com/ethereum/go-ethereum/metrics" 26 ) 27 28 var ( 29 // triePrefetchMetricsPrefix is the prefix under which to publish the metrics. 30 triePrefetchMetricsPrefix = "trie/prefetch/" 31 32 // errTerminated is returned if a fetcher is attempted to be operated after it 33 // has already terminated. 34 errTerminated = errors.New("fetcher is already terminated") 35 ) 36 37 // triePrefetcher is an active prefetcher, which receives accounts or storage 38 // items and does trie-loading of them. The goal is to get as much useful content 39 // into the caches as possible. 40 // 41 // Note, the prefetcher's API is not thread safe. 42 type triePrefetcher struct { 43 db Database // Database to fetch trie nodes through 44 root common.Hash // Root hash of the account trie for metrics 45 fetchers map[string]*subfetcher // Subfetchers for each trie 46 term chan struct{} // Channel to signal interruption 47 48 deliveryMissMeter metrics.Meter 49 accountLoadMeter metrics.Meter 50 accountDupMeter metrics.Meter 51 accountWasteMeter metrics.Meter 52 storageLoadMeter metrics.Meter 53 storageDupMeter metrics.Meter 54 storageWasteMeter metrics.Meter 55 } 56 57 func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher { 58 prefix := triePrefetchMetricsPrefix + namespace 59 return &triePrefetcher{ 60 db: db, 61 root: root, 62 fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map 63 term: make(chan struct{}), 64 65 deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil), 66 accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil), 67 accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil), 68 accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil), 69 storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil), 70 storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil), 71 storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil), 72 } 73 } 74 75 // terminate iterates over all the subfetchers and issues a terminateion request 76 // to all of them. Depending on the async parameter, the method will either block 77 // until all subfetchers spin down, or return immediately. 78 func (p *triePrefetcher) terminate(async bool) { 79 // Short circuit if the fetcher is already closed 80 select { 81 case <-p.term: 82 return 83 default: 84 } 85 // Termiante all sub-fetchers, sync or async, depending on the request 86 for _, fetcher := range p.fetchers { 87 fetcher.terminate(async) 88 } 89 close(p.term) 90 } 91 92 // report aggregates the pre-fetching and usage metrics and reports them. 93 func (p *triePrefetcher) report() { 94 if !metrics.Enabled { 95 return 96 } 97 for _, fetcher := range p.fetchers { 98 fetcher.wait() // ensure the fetcher's idle before poking in its internals 99 100 if fetcher.root == p.root { 101 p.accountLoadMeter.Mark(int64(len(fetcher.seen))) 102 p.accountDupMeter.Mark(int64(fetcher.dups)) 103 for _, key := range fetcher.used { 104 delete(fetcher.seen, string(key)) 105 } 106 p.accountWasteMeter.Mark(int64(len(fetcher.seen))) 107 } else { 108 p.storageLoadMeter.Mark(int64(len(fetcher.seen))) 109 p.storageDupMeter.Mark(int64(fetcher.dups)) 110 for _, key := range fetcher.used { 111 delete(fetcher.seen, string(key)) 112 } 113 p.storageWasteMeter.Mark(int64(len(fetcher.seen))) 114 } 115 } 116 } 117 118 // prefetch schedules a batch of trie items to prefetch. After the prefetcher is 119 // closed, all the following tasks scheduled will not be executed and an error 120 // will be returned. 121 // 122 // prefetch is called from two locations: 123 // 124 // 1. Finalize of the state-objects storage roots. This happens at the end 125 // of every transaction, meaning that if several transactions touches 126 // upon the same contract, the parameters invoking this method may be 127 // repeated. 128 // 2. Finalize of the main account trie. This happens only once per block. 129 func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte) error { 130 // Ensure the subfetcher is still alive 131 select { 132 case <-p.term: 133 return errTerminated 134 default: 135 } 136 id := p.trieID(owner, root) 137 fetcher := p.fetchers[id] 138 if fetcher == nil { 139 fetcher = newSubfetcher(p.db, p.root, owner, root, addr) 140 p.fetchers[id] = fetcher 141 } 142 return fetcher.schedule(keys) 143 } 144 145 // trie returns the trie matching the root hash, blocking until the fetcher of 146 // the given trie terminates. If no fetcher exists for the request, nil will be 147 // returned. 148 func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) (Trie, error) { 149 // Bail if no trie was prefetched for this root 150 fetcher := p.fetchers[p.trieID(owner, root)] 151 if fetcher == nil { 152 log.Error("Prefetcher missed to load trie", "owner", owner, "root", root) 153 p.deliveryMissMeter.Mark(1) 154 return nil, nil 155 } 156 // Subfetcher exists, retrieve its trie 157 return fetcher.peek(), nil 158 } 159 160 // used marks a batch of state items used to allow creating statistics as to 161 // how useful or wasteful the fetcher is. 162 func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) { 163 if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil { 164 fetcher.wait() // ensure the fetcher's idle before poking in its internals 165 fetcher.used = used 166 } 167 } 168 169 // trieID returns an unique trie identifier consists the trie owner and root hash. 170 func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string { 171 trieID := make([]byte, common.HashLength*2) 172 copy(trieID, owner.Bytes()) 173 copy(trieID[common.HashLength:], root.Bytes()) 174 return string(trieID) 175 } 176 177 // subfetcher is a trie fetcher goroutine responsible for pulling entries for a 178 // single trie. It is spawned when a new root is encountered and lives until the 179 // main prefetcher is paused and either all requested items are processed or if 180 // the trie being worked on is retrieved from the prefetcher. 181 type subfetcher struct { 182 db Database // Database to load trie nodes through 183 state common.Hash // Root hash of the state to prefetch 184 owner common.Hash // Owner of the trie, usually account hash 185 root common.Hash // Root hash of the trie to prefetch 186 addr common.Address // Address of the account that the trie belongs to 187 trie Trie // Trie being populated with nodes 188 189 tasks [][]byte // Items queued up for retrieval 190 lock sync.Mutex // Lock protecting the task queue 191 192 wake chan struct{} // Wake channel if a new task is scheduled 193 stop chan struct{} // Channel to interrupt processing 194 term chan struct{} // Channel to signal interruption 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 Database, state common.Hash, owner common.Hash, root common.Hash, addr common.Address) *subfetcher { 204 sf := &subfetcher{ 205 db: db, 206 state: state, 207 owner: owner, 208 root: root, 209 addr: addr, 210 wake: make(chan struct{}, 1), 211 stop: make(chan struct{}), 212 term: make(chan struct{}), 213 seen: make(map[string]struct{}), 214 } 215 go sf.loop() 216 return sf 217 } 218 219 // schedule adds a batch of trie keys to the queue to prefetch. 220 func (sf *subfetcher) schedule(keys [][]byte) error { 221 // Ensure the subfetcher is still alive 222 select { 223 case <-sf.term: 224 return errTerminated 225 default: 226 } 227 // Append the tasks to the current queue 228 sf.lock.Lock() 229 sf.tasks = append(sf.tasks, keys...) 230 sf.lock.Unlock() 231 232 // Notify the background thread to execute scheduled tasks 233 select { 234 case sf.wake <- struct{}{}: 235 // Wake signal sent 236 default: 237 // Wake signal not sent as a previous is already queued 238 } 239 return nil 240 } 241 242 // wait blocks until the subfetcher terminates. This method is used to block on 243 // an async termination before accessing internal fields from the fetcher. 244 func (sf *subfetcher) wait() { 245 <-sf.term 246 } 247 248 // peek retrieves the fetcher's trie, populated with any pre-fetched data. The 249 // returned trie will be a shallow copy, so modifying it will break subsequent 250 // peeks for the original data. The method will block until all the scheduled 251 // data has been loaded and the fethcer terminated. 252 func (sf *subfetcher) peek() Trie { 253 // Block until the fertcher terminates, then retrieve the trie 254 sf.wait() 255 return sf.trie 256 } 257 258 // terminate requests the subfetcher to stop accepting new tasks and spin down 259 // as soon as everything is loaded. Depending on the async parameter, the method 260 // will either block until all disk loads finish or return immediately. 261 func (sf *subfetcher) terminate(async bool) { 262 select { 263 case <-sf.stop: 264 default: 265 close(sf.stop) 266 } 267 if async { 268 return 269 } 270 <-sf.term 271 } 272 273 // loop loads newly-scheduled trie tasks as they are received and loads them, stopping 274 // when requested. 275 func (sf *subfetcher) loop() { 276 // No matter how the loop stops, signal anyone waiting that it's terminated 277 defer close(sf.term) 278 279 // Start by opening the trie and stop processing if it fails 280 if sf.owner == (common.Hash{}) { 281 trie, err := sf.db.OpenTrie(sf.root) 282 if err != nil { 283 log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) 284 return 285 } 286 sf.trie = trie 287 } else { 288 trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil) 289 if err != nil { 290 log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) 291 return 292 } 293 sf.trie = trie 294 } 295 // Trie opened successfully, keep prefetching items 296 for { 297 select { 298 case <-sf.wake: 299 // Execute all remaining tasks in single run 300 sf.lock.Lock() 301 tasks := sf.tasks 302 sf.tasks = nil 303 sf.lock.Unlock() 304 305 for _, task := range tasks { 306 if _, ok := sf.seen[string(task)]; ok { 307 sf.dups++ 308 continue 309 } 310 if len(task) == common.AddressLength { 311 sf.trie.GetAccount(common.BytesToAddress(task)) 312 } else { 313 sf.trie.GetStorage(sf.addr, task) 314 } 315 sf.seen[string(task)] = struct{}{} 316 } 317 318 case <-sf.stop: 319 // Termination is requested, abort if no more tasks are pending. If 320 // there are some, exhaust them first. 321 sf.lock.Lock() 322 done := sf.tasks == nil 323 sf.lock.Unlock() 324 325 if done { 326 return 327 } 328 // Some tasks are pending, loop and pick them up (that wake branch 329 // will be selected eventually, whilst stop remains closed to this 330 // branch will also run afterwards). 331 } 332 } 333 }