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