github.com/decred/dcrd/blockchain@v1.2.1/indexers/manager.go (about) 1 // Copyright (c) 2016 The btcsuite developers 2 // Copyright (c) 2016-2017 The Decred developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 package indexers 7 8 import ( 9 "bytes" 10 "fmt" 11 12 "github.com/decred/dcrd/blockchain" 13 "github.com/decred/dcrd/blockchain/internal/progresslog" 14 "github.com/decred/dcrd/blockchain/stake" 15 "github.com/decred/dcrd/chaincfg" 16 "github.com/decred/dcrd/chaincfg/chainhash" 17 "github.com/decred/dcrd/database" 18 "github.com/decred/dcrd/dcrutil" 19 "github.com/decred/dcrd/wire" 20 ) 21 22 var ( 23 // indexTipsBucketName is the name of the db bucket used to house the 24 // current tip of each index. 25 indexTipsBucketName = []byte("idxtips") 26 ) 27 28 // ----------------------------------------------------------------------------- 29 // The index manager tracks the current tip and version of each index by using a 30 // parent bucket that contains an entry for index and a separate entry for its 31 // version. 32 // 33 // The serialized format for an index tip is: 34 // 35 // [<block hash><block height>],... 36 // 37 // Field Type Size 38 // block hash chainhash.Hash chainhash.HashSize 39 // block height uint32 4 bytes 40 // 41 // The serialized format for an index version is: 42 // 43 // [<version>] 44 // 45 // Field Type Size 46 // index version uint32 4 bytes 47 // ----------------------------------------------------------------------------- 48 49 // dbPutIndexerTip uses an existing database transaction to update or add the 50 // current tip for the given index to the provided values. 51 func dbPutIndexerTip(dbTx database.Tx, idxKey []byte, hash *chainhash.Hash, height int32) error { 52 serialized := make([]byte, chainhash.HashSize+4) 53 copy(serialized, hash[:]) 54 byteOrder.PutUint32(serialized[chainhash.HashSize:], uint32(height)) 55 56 indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) 57 return indexesBucket.Put(idxKey, serialized) 58 } 59 60 // dbFetchIndexerTip uses an existing database transaction to retrieve the 61 // hash and height of the current tip for the provided index. 62 func dbFetchIndexerTip(dbTx database.Tx, idxKey []byte) (*chainhash.Hash, int32, error) { 63 indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) 64 serialized := indexesBucket.Get(idxKey) 65 if len(serialized) < chainhash.HashSize+4 { 66 return nil, 0, database.Error{ 67 ErrorCode: database.ErrCorruption, 68 Description: fmt.Sprintf("unexpected end of data for "+ 69 "index %q tip", string(idxKey)), 70 } 71 } 72 73 var hash chainhash.Hash 74 copy(hash[:], serialized[:chainhash.HashSize]) 75 height := int32(byteOrder.Uint32(serialized[chainhash.HashSize:])) 76 return &hash, height, nil 77 } 78 79 // indexVersionKey returns the key for an index which houses the current version 80 // of the index. 81 func indexVersionKey(idxKey []byte) []byte { 82 verKey := make([]byte, len(idxKey)+1) 83 verKey[0] = 'v' 84 copy(verKey[1:], idxKey) 85 return verKey 86 } 87 88 // dbPutIndexerVersion uses an existing database transaction to update the 89 // version for the given index to the provided value. 90 func dbPutIndexerVersion(dbTx database.Tx, idxKey []byte, version uint32) error { 91 serialized := make([]byte, 4) 92 byteOrder.PutUint32(serialized[0:4], version) 93 94 indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) 95 return indexesBucket.Put(indexVersionKey(idxKey), serialized) 96 } 97 98 // dbFetchIndexerVersion uses an existing database transaction to retrieve the 99 // version of the provided index. It will return one if the version has not 100 // previously been stored. 101 func dbFetchIndexerVersion(dbTx database.Tx, idxKey []byte) (uint32, error) { 102 indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) 103 serialized := indexesBucket.Get(indexVersionKey(idxKey)) 104 if len(serialized) == 0 { 105 return 1, nil 106 } 107 108 if len(serialized) < 4 { 109 return 0, database.Error{ 110 ErrorCode: database.ErrCorruption, 111 Description: fmt.Sprintf("unexpected end of data for "+ 112 "index %q version", string(idxKey)), 113 } 114 } 115 116 version := byteOrder.Uint32(serialized) 117 return version, nil 118 } 119 120 // dbIndexConnectBlock adds all of the index entries associated with the 121 // given block using the provided indexer and updates the tip of the indexer 122 // accordingly. An error will be returned if the current tip for the indexer is 123 // not the previous block for the passed block. 124 func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error { 125 // Assert that the block being connected properly connects to the 126 // current tip of the index. 127 idxKey := indexer.Key() 128 curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey) 129 if err != nil { 130 return err 131 } 132 if !curTipHash.IsEqual(&block.MsgBlock().Header.PrevBlock) { 133 return AssertError(fmt.Sprintf("dbIndexConnectBlock must be "+ 134 "called with a block that extends the current index "+ 135 "tip (%s, tip %s, block %s)", indexer.Name(), 136 curTipHash, block.Hash())) 137 } 138 139 // Notify the indexer with the connected block so it can index it. 140 if err := indexer.ConnectBlock(dbTx, block, parent, view); err != nil { 141 return err 142 } 143 144 // Update the current index tip. 145 return dbPutIndexerTip(dbTx, idxKey, block.Hash(), int32(block.Height())) 146 } 147 148 // dbIndexDisconnectBlock removes all of the index entries associated with the 149 // given block using the provided indexer and updates the tip of the indexer 150 // accordingly. An error will be returned if the current tip for the indexer is 151 // not the passed block. 152 func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error { 153 // Assert that the block being disconnected is the current tip of the 154 // index. 155 idxKey := indexer.Key() 156 curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey) 157 if err != nil { 158 return err 159 } 160 if !curTipHash.IsEqual(block.Hash()) { 161 return AssertError(fmt.Sprintf("dbIndexDisconnectBlock must "+ 162 "be called with the block at the current index tip "+ 163 "(%s, tip %s, block %s)", indexer.Name(), 164 curTipHash, block.Hash())) 165 } 166 167 // Notify the indexer with the disconnected block so it can remove all 168 // of the appropriate entries. 169 if err := indexer.DisconnectBlock(dbTx, block, parent, view); err != nil { 170 return err 171 } 172 173 // Update the current index tip. 174 prevHash := &block.MsgBlock().Header.PrevBlock 175 return dbPutIndexerTip(dbTx, idxKey, prevHash, int32(block.Height()-1)) 176 } 177 178 // Manager defines an index manager that manages multiple optional indexes and 179 // implements the blockchain.IndexManager interface so it can be seamlessly 180 // plugged into normal chain processing. 181 type Manager struct { 182 params *chaincfg.Params 183 db database.DB 184 enabledIndexes []Indexer 185 } 186 187 // Ensure the Manager type implements the blockchain.IndexManager interface. 188 var _ blockchain.IndexManager = (*Manager)(nil) 189 190 // indexDropKey returns the key for an index which indicates it is in the 191 // process of being dropped. 192 func indexDropKey(idxKey []byte) []byte { 193 dropKey := make([]byte, len(idxKey)+1) 194 dropKey[0] = 'd' 195 copy(dropKey[1:], idxKey) 196 return dropKey 197 } 198 199 // maybeFinishDrops determines if each of the enabled indexes are in the middle 200 // of being dropped and finishes dropping them when the are. This is necessary 201 // because dropping and index has to be done in several atomic steps rather than 202 // one big atomic step due to the massive number of entries. 203 func (m *Manager) maybeFinishDrops(interrupt <-chan struct{}) error { 204 indexNeedsDrop := make([]bool, len(m.enabledIndexes)) 205 err := m.db.View(func(dbTx database.Tx) error { 206 // None of the indexes needs to be dropped if the index tips 207 // bucket hasn't been created yet. 208 indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) 209 if indexesBucket == nil { 210 return nil 211 } 212 213 // Mark the indexer as requiring a drop if one is already in 214 // progress. 215 for i, indexer := range m.enabledIndexes { 216 dropKey := indexDropKey(indexer.Key()) 217 if indexesBucket.Get(dropKey) != nil { 218 indexNeedsDrop[i] = true 219 } 220 } 221 222 return nil 223 }) 224 if err != nil { 225 return err 226 } 227 228 if interruptRequested(interrupt) { 229 return errInterruptRequested 230 } 231 232 // Finish dropping any of the enabled indexes that are already in the 233 // middle of being dropped. 234 for i, indexer := range m.enabledIndexes { 235 if !indexNeedsDrop[i] { 236 continue 237 } 238 239 log.Infof("Resuming %s drop", indexer.Name()) 240 241 switch d := indexer.(type) { 242 case IndexDropper: 243 err := d.DropIndex(m.db, interrupt) 244 if err != nil { 245 return err 246 } 247 default: 248 err := dropIndex(m.db, indexer.Key(), indexer.Name()) 249 if err != nil { 250 return err 251 } 252 } 253 } 254 255 return nil 256 } 257 258 // maybeCreateIndexes determines if each of the enabled indexes have already 259 // been created and creates them if not. 260 func (m *Manager) maybeCreateIndexes() error { 261 return m.db.Update(func(dbTx database.Tx) error { 262 // Create the bucket for the current tips as needed. 263 meta := dbTx.Metadata() 264 indexesBucket, err := meta.CreateBucketIfNotExists(indexTipsBucketName) 265 if err != nil { 266 return err 267 } 268 269 for _, indexer := range m.enabledIndexes { 270 // Nothing to do if the index tip already exists. 271 idxKey := indexer.Key() 272 if indexesBucket.Get(idxKey) != nil { 273 continue 274 } 275 276 // Store the index version. 277 err := dbPutIndexerVersion(dbTx, idxKey, indexer.Version()) 278 if err != nil { 279 return err 280 } 281 282 // The tip for the index does not exist, so create it and 283 // invoke the create callback for the index so it can perform 284 // any one-time initialization it requires. 285 if err := indexer.Create(dbTx); err != nil { 286 return err 287 } 288 289 // Set the tip for the index to values which represent an 290 // uninitialized index (the genesis block hash and height). 291 genesisBlockHash := m.params.GenesisBlock.BlockHash() 292 err = dbPutIndexerTip(dbTx, idxKey, &genesisBlockHash, 0) 293 if err != nil { 294 return err 295 } 296 } 297 298 return nil 299 }) 300 } 301 302 // upgradeIndexes determines if each of the enabled indexes need to be upgraded 303 // and drops them when they do. 304 func (m *Manager) upgradeIndexes(interrupt <-chan struct{}) error { 305 indexNeedsDrop := make([]bool, len(m.enabledIndexes)) 306 err := m.db.View(func(dbTx database.Tx) error { 307 // None of the indexes needs to be updated if the index tips bucket 308 // hasn't been created yet. 309 indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) 310 if indexesBucket == nil { 311 return nil 312 } 313 314 for i, indexer := range m.enabledIndexes { 315 idxKey := indexer.Key() 316 version, err := dbFetchIndexerVersion(dbTx, idxKey) 317 if err != nil { 318 return err 319 } 320 321 // Upgrade is not needed if the index hasn't been created yet. 322 if version < indexer.Version() { 323 indexNeedsDrop[i] = true 324 } 325 } 326 327 return nil 328 }) 329 if err != nil { 330 return err 331 } 332 333 if interruptRequested(interrupt) { 334 return errInterruptRequested 335 } 336 337 // Drop any of the enabled indexes that have bumped their version. 338 for i, indexer := range m.enabledIndexes { 339 if !indexNeedsDrop[i] { 340 continue 341 } 342 343 log.Infof("Dropping %s due to new version", indexer.Name()) 344 345 switch d := indexer.(type) { 346 case IndexDropper: 347 err := d.DropIndex(m.db, interrupt) 348 if err != nil { 349 return err 350 } 351 default: 352 err := dropIndex(m.db, indexer.Key(), indexer.Name()) 353 if err != nil { 354 return err 355 } 356 } 357 } 358 359 // Create the initial state for the indexes that were dropped as needed. 360 return m.maybeCreateIndexes() 361 } 362 363 // dbFetchBlockByHash uses an existing database transaction to retrieve the raw 364 // block for the provided hash, deserialize it, and return a dcrutil.Block. 365 func dbFetchBlockByHash(dbTx database.Tx, hash *chainhash.Hash) (*dcrutil.Block, error) { 366 blockBytes, err := dbTx.FetchBlock(hash) 367 if err != nil { 368 return nil, err 369 } 370 return dcrutil.NewBlockFromBytes(blockBytes) 371 } 372 373 // Init initializes the enabled indexes. This is called during chain 374 // initialization and primarily consists of catching up all indexes to the 375 // current best chain tip. This is necessary since each index can be disabled 376 // and re-enabled at any time and attempting to catch-up indexes at the same 377 // time new blocks are being downloaded would lead to an overall longer time to 378 // catch up due to the I/O contention. 379 // 380 // This is part of the blockchain.IndexManager interface. 381 func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{}) error { 382 // Nothing to do when no indexes are enabled. 383 if len(m.enabledIndexes) == 0 { 384 return nil 385 } 386 387 if interruptRequested(interrupt) { 388 return errInterruptRequested 389 } 390 391 // Finish any drops that were previously interrupted. 392 if err := m.maybeFinishDrops(interrupt); err != nil { 393 return err 394 } 395 396 // Create the initial state for the indexes as needed. 397 if err := m.maybeCreateIndexes(); err != nil { 398 return err 399 } 400 401 // Upgrade the indexes as needed. 402 if err := m.upgradeIndexes(interrupt); err != nil { 403 return err 404 } 405 406 // Initialize each of the enabled indexes. 407 for _, indexer := range m.enabledIndexes { 408 if err := indexer.Init(); err != nil { 409 return err 410 } 411 } 412 413 // Rollback indexes to the main chain if their tip is an orphaned fork. 414 // This is fairly unlikely, but it can happen if the chain is 415 // reorganized while the index is disabled. This has to be done in 416 // reverse order because later indexes can depend on earlier ones. 417 var err error 418 var cachedBlock *dcrutil.Block 419 for i := len(m.enabledIndexes); i > 0; i-- { 420 indexer := m.enabledIndexes[i-1] 421 422 // Fetch the current tip for the index. 423 var height int32 424 var hash *chainhash.Hash 425 err := m.db.View(func(dbTx database.Tx) error { 426 idxKey := indexer.Key() 427 hash, height, err = dbFetchIndexerTip(dbTx, idxKey) 428 return err 429 }) 430 if err != nil { 431 return err 432 } 433 434 // Nothing to do if the index does not have any entries yet. 435 if height == 0 { 436 continue 437 } 438 439 // Loop until the tip is a block that exists in the main chain. 440 var interrupted bool 441 initialHeight := height 442 err = m.db.Update(func(dbTx database.Tx) error { 443 for !chain.MainChainHasBlock(hash) { 444 // Get the block, unless it's already cached. 445 var block *dcrutil.Block 446 if cachedBlock == nil && height > 0 { 447 block, err = dbFetchBlockByHash(dbTx, hash) 448 if err != nil { 449 return err 450 } 451 } else { 452 block = cachedBlock 453 } 454 455 // Load the parent block for the height since it is 456 // required to remove it. 457 parentHash := &block.MsgBlock().Header.PrevBlock 458 parent, err := dbFetchBlockByHash(dbTx, parentHash) 459 if err != nil { 460 return err 461 } 462 cachedBlock = parent 463 464 // When the index requires all of the referenced 465 // txouts they need to be retrieved from the 466 // transaction index. 467 var view *blockchain.UtxoViewpoint 468 if indexNeedsInputs(indexer) { 469 var err error 470 view, err = makeUtxoView(dbTx, block, 471 interrupt) 472 if err != nil { 473 return err 474 } 475 } 476 477 // Remove all of the index entries associated 478 // with the block and update the indexer tip. 479 err = dbIndexDisconnectBlock(dbTx, indexer, 480 block, parent, view) 481 if err != nil { 482 return err 483 } 484 485 // Update the tip to the previous block. 486 hash = &block.MsgBlock().Header.PrevBlock 487 height-- 488 489 // NOTE: This does not return as it does 490 // elsewhere since it would cause the 491 // database transaction to rollback and 492 // undo all work that has been done. 493 if interruptRequested(interrupt) { 494 interrupted = true 495 break 496 } 497 } 498 499 return nil 500 }) 501 if err != nil { 502 return err 503 } 504 if interrupted { 505 return errInterruptRequested 506 } 507 508 if initialHeight != height { 509 log.Infof("Removed %d orphaned blocks from %s "+ 510 "(heights %d to %d)", initialHeight-height, 511 indexer.Name(), height+1, initialHeight) 512 } 513 } 514 515 // Fetch the current tip heights for each index along with tracking the 516 // lowest one so the catchup code only needs to start at the earliest 517 // block and is able to skip connecting the block for the indexes that 518 // don't need it. 519 bestHeight := int32(chain.BestSnapshot().Height) 520 lowestHeight := bestHeight 521 indexerHeights := make([]int32, len(m.enabledIndexes)) 522 err = m.db.View(func(dbTx database.Tx) error { 523 for i, indexer := range m.enabledIndexes { 524 idxKey := indexer.Key() 525 hash, height, err := dbFetchIndexerTip(dbTx, idxKey) 526 if err != nil { 527 return err 528 } 529 530 log.Debugf("Current %s tip (height %d, hash %v)", 531 indexer.Name(), height, hash) 532 indexerHeights[i] = height 533 if height < lowestHeight { 534 lowestHeight = height 535 } 536 } 537 return nil 538 }) 539 if err != nil { 540 return err 541 } 542 543 // Nothing to index if all of the indexes are caught up. 544 if lowestHeight == bestHeight { 545 return nil 546 } 547 548 // Create a progress logger for the indexing process below. 549 progressLogger := progresslog.NewBlockProgressLogger("Indexed", log) 550 551 // At this point, one or more indexes are behind the current best chain 552 // tip and need to be caught up, so log the details and loop through 553 // each block that needs to be indexed. 554 log.Infof("Catching up indexes from height %d to %d", lowestHeight, 555 bestHeight) 556 557 var cachedParent *dcrutil.Block 558 for height := lowestHeight + 1; height <= bestHeight; height++ { 559 if interruptRequested(interrupt) { 560 return errInterruptRequested 561 } 562 563 var block, parent *dcrutil.Block 564 err = m.db.Update(func(dbTx database.Tx) error { 565 // Get the parent of the block, unless it's already cached. 566 if cachedParent == nil && height > 0 { 567 parentHash, err := chain.BlockHashByHeight(int64(height - 1)) 568 if err != nil { 569 return err 570 } 571 parent, err = dbFetchBlockByHash(dbTx, parentHash) 572 if err != nil { 573 return err 574 } 575 } else { 576 parent = cachedParent 577 } 578 579 // Load the block for the height since it is required to index 580 // it. 581 hash, err := chain.BlockHashByHeight(int64(height)) 582 if err != nil { 583 return err 584 } 585 block, err = dbFetchBlockByHash(dbTx, hash) 586 if err != nil { 587 return err 588 } 589 cachedParent = block 590 591 if interruptRequested(interrupt) { 592 return errInterruptRequested 593 } 594 595 // Connect the block for all indexes that need it. 596 var view *blockchain.UtxoViewpoint 597 for i, indexer := range m.enabledIndexes { 598 // Skip indexes that don't need to be updated with this 599 // block. 600 if indexerHeights[i] >= height { 601 continue 602 } 603 604 // When the index requires all of the referenced 605 // txouts and they haven't been loaded yet, they 606 // need to be retrieved from the transaction 607 // index. 608 if view == nil && indexNeedsInputs(indexer) { 609 var errMakeView error 610 view, errMakeView = makeUtxoView(dbTx, block, interrupt) 611 if errMakeView != nil { 612 return errMakeView 613 } 614 } 615 err = dbIndexConnectBlock(dbTx, indexer, block, 616 parent, view) 617 if err != nil { 618 return err 619 } 620 621 indexerHeights[i] = height 622 } 623 624 return nil 625 }) 626 if err != nil { 627 return err 628 } 629 progressLogger.LogBlockHeight(block.MsgBlock(), parent.MsgBlock()) 630 } 631 632 log.Infof("Indexes caught up to height %d", bestHeight) 633 return nil 634 } 635 636 // indexNeedsInputs returns whether or not the index needs access to the txouts 637 // referenced by the transaction inputs being indexed. 638 func indexNeedsInputs(index Indexer) bool { 639 if idx, ok := index.(NeedsInputser); ok { 640 return idx.NeedsInputs() 641 } 642 643 return false 644 } 645 646 // dbFetchTx looks up the passed transaction hash in the transaction index and 647 // loads it from the database. 648 func dbFetchTx(dbTx database.Tx, hash *chainhash.Hash) (*wire.MsgTx, error) { 649 // Look up the location of the transaction. 650 entry, err := dbFetchTxIndexEntry(dbTx, hash) 651 if err != nil { 652 return nil, err 653 } 654 if entry == nil { 655 return nil, fmt.Errorf("transaction %v not found in the txindex", hash) 656 } 657 658 // Load the raw transaction bytes from the database. 659 txBytes, err := dbTx.FetchBlockRegion(&entry.BlockRegion) 660 if err != nil { 661 return nil, err 662 } 663 664 // Deserialize the transaction. 665 var msgTx wire.MsgTx 666 err = msgTx.Deserialize(bytes.NewReader(txBytes)) 667 if err != nil { 668 return nil, err 669 } 670 671 return &msgTx, nil 672 } 673 674 // makeUtxoView creates a mock unspent transaction output view by using the 675 // transaction index in order to look up all inputs referenced by the 676 // transactions in the block. This is sometimes needed when catching indexes up 677 // because many of the txouts could actually already be spent however the 678 // associated scripts are still required to index them. 679 func makeUtxoView(dbTx database.Tx, block *dcrutil.Block, interrupt <-chan struct{}) (*blockchain.UtxoViewpoint, error) { 680 view := blockchain.NewUtxoViewpoint() 681 processTxns := func(txns []*dcrutil.Tx, regularTree bool) error { 682 for txIdx, tx := range txns { 683 // Coinbases do not reference any inputs. Since the block is 684 // required to have already gone through full validation, it has 685 // already been proven on the first transaction in the block is a 686 // coinbase. 687 if regularTree && txIdx == 0 { 688 continue 689 } 690 msgTx := tx.MsgTx() 691 isVote := !regularTree && stake.IsSSGen(msgTx) 692 693 // Use the transaction index to load all of the referenced inputs 694 // and add their outputs to the view. 695 for txInIdx, txIn := range msgTx.TxIn { 696 // Ignore stakebase since it has no input. 697 if isVote && txInIdx == 0 { 698 continue 699 } 700 701 // Skip already fetched outputs. 702 originOut := &txIn.PreviousOutPoint 703 if view.LookupEntry(&originOut.Hash) != nil { 704 continue 705 } 706 707 originTx, err := dbFetchTx(dbTx, &originOut.Hash) 708 if err != nil { 709 return err 710 } 711 712 view.AddTxOuts(dcrutil.NewTx(originTx), int64(txIn.BlockHeight), 713 txIn.BlockIndex) 714 } 715 716 if interruptRequested(interrupt) { 717 return errInterruptRequested 718 } 719 } 720 721 return nil 722 } 723 724 if err := processTxns(block.STransactions(), false); err != nil { 725 return nil, err 726 } 727 if err := processTxns(block.Transactions(), true); err != nil { 728 return nil, err 729 } 730 731 return view, nil 732 } 733 734 // ConnectBlock must be invoked when a block is extending the main chain. It 735 // keeps track of the state of each index it is managing, performs some sanity 736 // checks, and invokes each indexer. 737 // 738 // This is part of the blockchain.IndexManager interface. 739 func (m *Manager) ConnectBlock(dbTx database.Tx, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error { 740 // Call each of the currently active optional indexes with the block 741 // being connected so they can update accordingly. 742 for _, index := range m.enabledIndexes { 743 err := dbIndexConnectBlock(dbTx, index, block, parent, view) 744 if err != nil { 745 return err 746 } 747 } 748 return nil 749 } 750 751 // DisconnectBlock must be invoked when a block is being disconnected from the 752 // end of the main chain. It keeps track of the state of each index it is 753 // managing, performs some sanity checks, and invokes each indexer to remove 754 // the index entries associated with the block. 755 // 756 // This is part of the blockchain.IndexManager interface. 757 func (m *Manager) DisconnectBlock(dbTx database.Tx, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error { 758 // Call each of the currently active optional indexes with the block 759 // being disconnected so they can update accordingly. 760 for _, index := range m.enabledIndexes { 761 err := dbIndexDisconnectBlock(dbTx, index, block, parent, view) 762 if err != nil { 763 return err 764 } 765 } 766 return nil 767 } 768 769 // NewManager returns a new index manager with the provided indexes enabled. 770 // 771 // The manager returned satisfies the blockchain.IndexManager interface and thus 772 // cleanly plugs into the normal blockchain processing path. 773 func NewManager(db database.DB, enabledIndexes []Indexer, params *chaincfg.Params) *Manager { 774 return &Manager{ 775 db: db, 776 enabledIndexes: enabledIndexes, 777 params: params, 778 } 779 } 780 781 // existsIndex returns whether the index keyed by idxKey exists in the database. 782 func existsIndex(db database.DB, idxKey []byte, idxName string) (bool, error) { 783 var exists bool 784 err := db.View(func(dbTx database.Tx) error { 785 indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) 786 if indexesBucket != nil && indexesBucket.Get(idxKey) != nil { 787 exists = true 788 } 789 return nil 790 }) 791 return exists, err 792 } 793 794 // markIndexDeletion marks the index identified by idxKey for deletion. Marking 795 // an index for deletion allows deletion to resume next startup if an 796 // incremental deletion was interrupted. 797 func markIndexDeletion(db database.DB, idxKey []byte) error { 798 return db.Update(func(dbTx database.Tx) error { 799 indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName) 800 return indexesBucket.Put(indexDropKey(idxKey), idxKey) 801 }) 802 } 803 804 // incrementalFlatDrop uses multiple database updates to remove key/value pairs 805 // saved to a flat index. 806 func incrementalFlatDrop(db database.DB, idxKey []byte, idxName string, interrupt <-chan struct{}) error { 807 const maxDeletions = 2000000 808 var totalDeleted uint64 809 for numDeleted := maxDeletions; numDeleted == maxDeletions; { 810 numDeleted = 0 811 err := db.Update(func(dbTx database.Tx) error { 812 bucket := dbTx.Metadata().Bucket(idxKey) 813 cursor := bucket.Cursor() 814 for ok := cursor.First(); ok; ok = cursor.Next() && 815 numDeleted < maxDeletions { 816 817 if err := cursor.Delete(); err != nil { 818 return err 819 } 820 numDeleted++ 821 } 822 return nil 823 }) 824 if err != nil { 825 return err 826 } 827 828 if numDeleted > 0 { 829 totalDeleted += uint64(numDeleted) 830 log.Infof("Deleted %d keys (%d total) from %s", 831 numDeleted, totalDeleted, idxName) 832 } 833 834 if interruptRequested(interrupt) { 835 return errInterruptRequested 836 } 837 } 838 return nil 839 } 840 841 // dropIndexMetadata drops the passed index from the database by removing the 842 // top level bucket for the index, the index tip, and any in-progress drop flag. 843 func dropIndexMetadata(db database.DB, idxKey []byte, idxName string) error { 844 return db.Update(func(dbTx database.Tx) error { 845 meta := dbTx.Metadata() 846 indexesBucket := meta.Bucket(indexTipsBucketName) 847 err := indexesBucket.Delete(idxKey) 848 if err != nil { 849 return err 850 } 851 852 err = meta.DeleteBucket(idxKey) 853 if err != nil && !database.IsError(err, database.ErrBucketNotFound) { 854 return err 855 } 856 857 err = indexesBucket.Delete(indexVersionKey(idxKey)) 858 if err != nil { 859 return err 860 } 861 862 return indexesBucket.Delete(indexDropKey(idxKey)) 863 }) 864 } 865 866 // dropFlatIndex incrementally drops the passed index from the database. Since 867 // indexes can be massive, it deletes the index in multiple database 868 // transactions in order to keep memory usage to reasonable levels. For this 869 // algorithm to work, the index must be "flat" (have no nested buckets). It 870 // also marks the drop in progress so the drop can be resumed if it is stopped 871 // before it is done before the index can be used again. 872 func dropFlatIndex(db database.DB, idxKey []byte, idxName string, interrupt <-chan struct{}) error { 873 // Nothing to do if the index doesn't already exist. 874 exists, err := existsIndex(db, idxKey, idxName) 875 if err != nil { 876 return err 877 } 878 if !exists { 879 log.Infof("Not dropping %s because it does not exist", idxName) 880 return nil 881 } 882 883 log.Infof("Dropping all %s entries. This might take a while...", 884 idxName) 885 886 // Mark that the index is in the process of being dropped so that it 887 // can be resumed on the next start if interrupted before the process is 888 // complete. 889 err = markIndexDeletion(db, idxKey) 890 if err != nil { 891 return err 892 } 893 894 // Since the indexes can be so large, attempting to simply delete 895 // the bucket in a single database transaction would result in massive 896 // memory usage and likely crash many systems due to ulimits. In order 897 // to avoid this, use a cursor to delete a maximum number of entries out 898 // of the bucket at a time. 899 err = incrementalFlatDrop(db, idxKey, idxName, interrupt) 900 if err != nil { 901 return err 902 } 903 904 // Remove the index tip, version, bucket, and in-progress drop flag now that 905 // all index entries have been removed. 906 err = dropIndexMetadata(db, idxKey, idxName) 907 if err != nil { 908 return err 909 } 910 911 log.Infof("Dropped %s", idxName) 912 return nil 913 } 914 915 // dropIndex drops the passed index from the database without using incremental 916 // deletion. This should be used to drop indexes containing nested buckets, 917 // which can not be deleted with dropFlatIndex. 918 func dropIndex(db database.DB, idxKey []byte, idxName string) error { 919 // Nothing to do if the index doesn't already exist. 920 exists, err := existsIndex(db, idxKey, idxName) 921 if err != nil { 922 return err 923 } 924 if !exists { 925 log.Infof("Not dropping %s because it does not exist", idxName) 926 return nil 927 } 928 929 log.Infof("Dropping all %s entries. This might take a while...", 930 idxName) 931 932 // Mark that the index is in the process of being dropped so that it 933 // can be resumed on the next start if interrupted before the process is 934 // complete. 935 err = markIndexDeletion(db, idxKey) 936 if err != nil { 937 return err 938 } 939 940 // Remove the index tip, version, bucket, and in-progress drop flag. 941 // Removing the index bucket also recursively removes all values saved to 942 // the index. 943 err = dropIndexMetadata(db, idxKey, idxName) 944 if err != nil { 945 return err 946 } 947 948 log.Infof("Dropped %s", idxName) 949 return nil 950 }