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