github.com/luckypickle/go-ethereum-vet@v1.14.2/core/chain_indexer.go (about) 1 // Copyright 2017 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 core 18 19 import ( 20 "context" 21 "encoding/binary" 22 "fmt" 23 "sync" 24 "sync/atomic" 25 "time" 26 27 "github.com/luckypickle/go-ethereum-vet/common" 28 "github.com/luckypickle/go-ethereum-vet/core/rawdb" 29 "github.com/luckypickle/go-ethereum-vet/core/types" 30 "github.com/luckypickle/go-ethereum-vet/ethdb" 31 "github.com/luckypickle/go-ethereum-vet/event" 32 "github.com/luckypickle/go-ethereum-vet/log" 33 ) 34 35 // ChainIndexerBackend defines the methods needed to process chain segments in 36 // the background and write the segment results into the database. These can be 37 // used to create filter blooms or CHTs. 38 type ChainIndexerBackend interface { 39 // Reset initiates the processing of a new chain segment, potentially terminating 40 // any partially completed operations (in case of a reorg). 41 Reset(ctx context.Context, section uint64, prevHead common.Hash) error 42 43 // Process crunches through the next header in the chain segment. The caller 44 // will ensure a sequential order of headers. 45 Process(ctx context.Context, header *types.Header) error 46 47 // Commit finalizes the section metadata and stores it into the database. 48 Commit() error 49 } 50 51 // ChainIndexerChain interface is used for connecting the indexer to a blockchain 52 type ChainIndexerChain interface { 53 // CurrentHeader retrieves the latest locally known header. 54 CurrentHeader() *types.Header 55 56 // SubscribeChainEvent subscribes to new head header notifications. 57 SubscribeChainEvent(ch chan<- ChainEvent) event.Subscription 58 } 59 60 // ChainIndexer does a post-processing job for equally sized sections of the 61 // canonical chain (like BlooomBits and CHT structures). A ChainIndexer is 62 // connected to the blockchain through the event system by starting a 63 // ChainEventLoop in a goroutine. 64 // 65 // Further child ChainIndexers can be added which use the output of the parent 66 // section indexer. These child indexers receive new head notifications only 67 // after an entire section has been finished or in case of rollbacks that might 68 // affect already finished sections. 69 type ChainIndexer struct { 70 chainDb ethdb.Database // Chain database to index the data from 71 indexDb ethdb.Database // Prefixed table-view of the db to write index metadata into 72 backend ChainIndexerBackend // Background processor generating the index data content 73 children []*ChainIndexer // Child indexers to cascade chain updates to 74 75 active uint32 // Flag whether the event loop was started 76 update chan struct{} // Notification channel that headers should be processed 77 quit chan chan error // Quit channel to tear down running goroutines 78 ctx context.Context 79 ctxCancel func() 80 81 sectionSize uint64 // Number of blocks in a single chain segment to process 82 confirmsReq uint64 // Number of confirmations before processing a completed segment 83 84 storedSections uint64 // Number of sections successfully indexed into the database 85 knownSections uint64 // Number of sections known to be complete (block wise) 86 cascadedHead uint64 // Block number of the last completed section cascaded to subindexers 87 88 throttling time.Duration // Disk throttling to prevent a heavy upgrade from hogging resources 89 90 log log.Logger 91 lock sync.RWMutex 92 } 93 94 // NewChainIndexer creates a new chain indexer to do background processing on 95 // chain segments of a given size after certain number of confirmations passed. 96 // The throttling parameter might be used to prevent database thrashing. 97 func NewChainIndexer(chainDb, indexDb ethdb.Database, backend ChainIndexerBackend, section, confirm uint64, throttling time.Duration, kind string) *ChainIndexer { 98 c := &ChainIndexer{ 99 chainDb: chainDb, 100 indexDb: indexDb, 101 backend: backend, 102 update: make(chan struct{}, 1), 103 quit: make(chan chan error), 104 sectionSize: section, 105 confirmsReq: confirm, 106 throttling: throttling, 107 log: log.New("type", kind), 108 } 109 // Initialize database dependent fields and start the updater 110 c.loadValidSections() 111 c.ctx, c.ctxCancel = context.WithCancel(context.Background()) 112 113 go c.updateLoop() 114 115 return c 116 } 117 118 // AddKnownSectionHead marks a new section head as known/processed if it is newer 119 // than the already known best section head 120 func (c *ChainIndexer) AddKnownSectionHead(section uint64, shead common.Hash) { 121 c.lock.Lock() 122 defer c.lock.Unlock() 123 124 if section < c.storedSections { 125 return 126 } 127 c.setSectionHead(section, shead) 128 c.setValidSections(section + 1) 129 } 130 131 // Start creates a goroutine to feed chain head events into the indexer for 132 // cascading background processing. Children do not need to be started, they 133 // are notified about new events by their parents. 134 func (c *ChainIndexer) Start(chain ChainIndexerChain) { 135 events := make(chan ChainEvent, 10) 136 sub := chain.SubscribeChainEvent(events) 137 138 go c.eventLoop(chain.CurrentHeader(), events, sub) 139 } 140 141 // Close tears down all goroutines belonging to the indexer and returns any error 142 // that might have occurred internally. 143 func (c *ChainIndexer) Close() error { 144 var errs []error 145 146 c.ctxCancel() 147 148 // Tear down the primary update loop 149 errc := make(chan error) 150 c.quit <- errc 151 if err := <-errc; err != nil { 152 errs = append(errs, err) 153 } 154 // If needed, tear down the secondary event loop 155 if atomic.LoadUint32(&c.active) != 0 { 156 c.quit <- errc 157 if err := <-errc; err != nil { 158 errs = append(errs, err) 159 } 160 } 161 // Close all children 162 for _, child := range c.children { 163 if err := child.Close(); err != nil { 164 errs = append(errs, err) 165 } 166 } 167 // Return any failures 168 switch { 169 case len(errs) == 0: 170 return nil 171 172 case len(errs) == 1: 173 return errs[0] 174 175 default: 176 return fmt.Errorf("%v", errs) 177 } 178 } 179 180 // eventLoop is a secondary - optional - event loop of the indexer which is only 181 // started for the outermost indexer to push chain head events into a processing 182 // queue. 183 func (c *ChainIndexer) eventLoop(currentHeader *types.Header, events chan ChainEvent, sub event.Subscription) { 184 // Mark the chain indexer as active, requiring an additional teardown 185 atomic.StoreUint32(&c.active, 1) 186 187 defer sub.Unsubscribe() 188 189 // Fire the initial new head event to start any outstanding processing 190 c.newHead(currentHeader.Number.Uint64(), false) 191 192 var ( 193 prevHeader = currentHeader 194 prevHash = currentHeader.Hash() 195 ) 196 for { 197 select { 198 case errc := <-c.quit: 199 // Chain indexer terminating, report no failure and abort 200 errc <- nil 201 return 202 203 case ev, ok := <-events: 204 // Received a new event, ensure it's not nil (closing) and update 205 if !ok { 206 errc := <-c.quit 207 errc <- nil 208 return 209 } 210 header := ev.Block.Header() 211 if header.ParentHash != prevHash { 212 // Reorg to the common ancestor (might not exist in light sync mode, skip reorg then) 213 // TODO(karalabe, zsfelfoldi): This seems a bit brittle, can we detect this case explicitly? 214 215 // TODO(karalabe): This operation is expensive and might block, causing the event system to 216 // potentially also lock up. We need to do with on a different thread somehow. 217 if h := rawdb.FindCommonAncestor(c.chainDb, prevHeader, header); h != nil { 218 c.newHead(h.Number.Uint64(), true) 219 } 220 } 221 c.newHead(header.Number.Uint64(), false) 222 223 prevHeader, prevHash = header, header.Hash() 224 } 225 } 226 } 227 228 // newHead notifies the indexer about new chain heads and/or reorgs. 229 func (c *ChainIndexer) newHead(head uint64, reorg bool) { 230 c.lock.Lock() 231 defer c.lock.Unlock() 232 233 // If a reorg happened, invalidate all sections until that point 234 if reorg { 235 // Revert the known section number to the reorg point 236 changed := head / c.sectionSize 237 if changed < c.knownSections { 238 c.knownSections = changed 239 } 240 // Revert the stored sections from the database to the reorg point 241 if changed < c.storedSections { 242 c.setValidSections(changed) 243 } 244 // Update the new head number to the finalized section end and notify children 245 head = changed * c.sectionSize 246 247 if head < c.cascadedHead { 248 c.cascadedHead = head 249 for _, child := range c.children { 250 child.newHead(c.cascadedHead, true) 251 } 252 } 253 return 254 } 255 // No reorg, calculate the number of newly known sections and update if high enough 256 var sections uint64 257 if head >= c.confirmsReq { 258 sections = (head + 1 - c.confirmsReq) / c.sectionSize 259 if sections > c.knownSections { 260 c.knownSections = sections 261 262 select { 263 case c.update <- struct{}{}: 264 default: 265 } 266 } 267 } 268 } 269 270 // updateLoop is the main event loop of the indexer which pushes chain segments 271 // down into the processing backend. 272 func (c *ChainIndexer) updateLoop() { 273 var ( 274 updating bool 275 updated time.Time 276 ) 277 278 for { 279 select { 280 case errc := <-c.quit: 281 // Chain indexer terminating, report no failure and abort 282 errc <- nil 283 return 284 285 case <-c.update: 286 // Section headers completed (or rolled back), update the index 287 c.lock.Lock() 288 if c.knownSections > c.storedSections { 289 // Periodically print an upgrade log message to the user 290 if time.Since(updated) > 8*time.Second { 291 if c.knownSections > c.storedSections+1 { 292 updating = true 293 c.log.Info("Upgrading chain index", "percentage", c.storedSections*100/c.knownSections) 294 } 295 updated = time.Now() 296 } 297 // Cache the current section count and head to allow unlocking the mutex 298 section := c.storedSections 299 var oldHead common.Hash 300 if section > 0 { 301 oldHead = c.SectionHead(section - 1) 302 } 303 // Process the newly defined section in the background 304 c.lock.Unlock() 305 newHead, err := c.processSection(section, oldHead) 306 if err != nil { 307 select { 308 case <-c.ctx.Done(): 309 <-c.quit <- nil 310 return 311 default: 312 } 313 c.log.Error("Section processing failed", "error", err) 314 } 315 c.lock.Lock() 316 317 // If processing succeeded and no reorgs occcurred, mark the section completed 318 if err == nil && oldHead == c.SectionHead(section-1) { 319 c.setSectionHead(section, newHead) 320 c.setValidSections(section + 1) 321 if c.storedSections == c.knownSections && updating { 322 updating = false 323 c.log.Info("Finished upgrading chain index") 324 } 325 326 c.cascadedHead = c.storedSections*c.sectionSize - 1 327 for _, child := range c.children { 328 c.log.Trace("Cascading chain index update", "head", c.cascadedHead) 329 child.newHead(c.cascadedHead, false) 330 } 331 } else { 332 // If processing failed, don't retry until further notification 333 c.log.Debug("Chain index processing failed", "section", section, "err", err) 334 c.knownSections = c.storedSections 335 } 336 } 337 // If there are still further sections to process, reschedule 338 if c.knownSections > c.storedSections { 339 time.AfterFunc(c.throttling, func() { 340 select { 341 case c.update <- struct{}{}: 342 default: 343 } 344 }) 345 } 346 c.lock.Unlock() 347 } 348 } 349 } 350 351 // processSection processes an entire section by calling backend functions while 352 // ensuring the continuity of the passed headers. Since the chain mutex is not 353 // held while processing, the continuity can be broken by a long reorg, in which 354 // case the function returns with an error. 355 func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (common.Hash, error) { 356 c.log.Trace("Processing new chain section", "section", section) 357 358 // Reset and partial processing 359 360 if err := c.backend.Reset(c.ctx, section, lastHead); err != nil { 361 c.setValidSections(0) 362 return common.Hash{}, err 363 } 364 365 for number := section * c.sectionSize; number < (section+1)*c.sectionSize; number++ { 366 hash := rawdb.ReadCanonicalHash(c.chainDb, number) 367 if hash == (common.Hash{}) { 368 return common.Hash{}, fmt.Errorf("canonical block #%d unknown", number) 369 } 370 header := rawdb.ReadHeader(c.chainDb, hash, number) 371 if header == nil { 372 return common.Hash{}, fmt.Errorf("block #%d [%x…] not found", number, hash[:4]) 373 } else if header.ParentHash != lastHead { 374 return common.Hash{}, fmt.Errorf("chain reorged during section processing") 375 } 376 if err := c.backend.Process(c.ctx, header); err != nil { 377 return common.Hash{}, err 378 } 379 lastHead = header.Hash() 380 } 381 if err := c.backend.Commit(); err != nil { 382 return common.Hash{}, err 383 } 384 return lastHead, nil 385 } 386 387 // Sections returns the number of processed sections maintained by the indexer 388 // and also the information about the last header indexed for potential canonical 389 // verifications. 390 func (c *ChainIndexer) Sections() (uint64, uint64, common.Hash) { 391 c.lock.Lock() 392 defer c.lock.Unlock() 393 394 return c.storedSections, c.storedSections*c.sectionSize - 1, c.SectionHead(c.storedSections - 1) 395 } 396 397 // AddChildIndexer adds a child ChainIndexer that can use the output of this one 398 func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) { 399 c.lock.Lock() 400 defer c.lock.Unlock() 401 402 c.children = append(c.children, indexer) 403 404 // Cascade any pending updates to new children too 405 if c.storedSections > 0 { 406 indexer.newHead(c.storedSections*c.sectionSize-1, false) 407 } 408 } 409 410 // loadValidSections reads the number of valid sections from the index database 411 // and caches is into the local state. 412 func (c *ChainIndexer) loadValidSections() { 413 data, _ := c.indexDb.Get([]byte("count")) 414 if len(data) == 8 { 415 c.storedSections = binary.BigEndian.Uint64(data[:]) 416 } 417 } 418 419 // setValidSections writes the number of valid sections to the index database 420 func (c *ChainIndexer) setValidSections(sections uint64) { 421 // Set the current number of valid sections in the database 422 var data [8]byte 423 binary.BigEndian.PutUint64(data[:], sections) 424 c.indexDb.Put([]byte("count"), data[:]) 425 426 // Remove any reorged sections, caching the valids in the mean time 427 for c.storedSections > sections { 428 c.storedSections-- 429 c.removeSectionHead(c.storedSections) 430 } 431 c.storedSections = sections // needed if new > old 432 } 433 434 // SectionHead retrieves the last block hash of a processed section from the 435 // index database. 436 func (c *ChainIndexer) SectionHead(section uint64) common.Hash { 437 var data [8]byte 438 binary.BigEndian.PutUint64(data[:], section) 439 440 hash, _ := c.indexDb.Get(append([]byte("shead"), data[:]...)) 441 if len(hash) == len(common.Hash{}) { 442 return common.BytesToHash(hash) 443 } 444 return common.Hash{} 445 } 446 447 // setSectionHead writes the last block hash of a processed section to the index 448 // database. 449 func (c *ChainIndexer) setSectionHead(section uint64, hash common.Hash) { 450 var data [8]byte 451 binary.BigEndian.PutUint64(data[:], section) 452 453 c.indexDb.Put(append([]byte("shead"), data[:]...), hash.Bytes()) 454 } 455 456 // removeSectionHead removes the reference to a processed section from the index 457 // database. 458 func (c *ChainIndexer) removeSectionHead(section uint64) { 459 var data [8]byte 460 binary.BigEndian.PutUint64(data[:], section) 461 462 c.indexDb.Delete(append([]byte("shead"), data[:]...)) 463 }