// Init initializes the enabled indexes. This is called during chain // initialization and primarily consists of catching up all indexes to the // current best chain tip. This is necessary since each index can be disabled // and re-enabled at any time and attempting to catch-up indexes at the same // time new blocks are being downloaded would lead to an overall longer time to // catch up due to the I/O contention. // // This is part of the blockchain.IndexManager interface. func (m *Manager) Init(chain *blockchain.BlockChain) error { // Nothing to do when no indexes are enabled. if len(m.enabledIndexes) == 0 { return nil } // Finish and drops that were previously interrupted. if err := m.maybeFinishDrops(); err != nil { return err } // Create the initial state for the indexes as needed. err := m.db.Update(func(dbTx database.Tx) error { // Create the bucket for the current tips as needed. meta := dbTx.Metadata() _, err := meta.CreateBucketIfNotExists(indexTipsBucketName) if err != nil { return err } return m.maybeCreateIndexes(dbTx) }) if err != nil { return err } // Initialize each of the enabled indexes. for _, indexer := range m.enabledIndexes { if err := indexer.Init(); err != nil { return err } } // Rollback indexes to the main chain if their tip is an orphaned fork. // This is fairly unlikely, but it can happen if the chain is // reorganized while the index is disabled. This has to be done in // reverse order because later indexes can depend on earlier ones. var cachedBlock *dcrutil.Block for i := len(m.enabledIndexes); i > 0; i-- { indexer := m.enabledIndexes[i-1] // Fetch the current tip for the index. var height uint32 var hash *chainhash.Hash err := m.db.View(func(dbTx database.Tx) error { idxKey := indexer.Key() hash, height, err = dbFetchIndexerTip(dbTx, idxKey) if err != nil { return err } return nil }) if err != nil { return err } // Nothing to do if the index does not have any entries yet. if height == 0 { continue } // Loop until the tip is a block that exists in the main chain. initialHeight := height err = m.db.Update(func(dbTx database.Tx) error { for { if blockchain.DBMainChainHasBlock(dbTx, hash) { break } // Get the block, unless it's already cached. var block *dcrutil.Block if cachedBlock == nil && height > 0 { block, err = blockchain.DBFetchBlockByHeight(dbTx, int64(height)) if err != nil { return err } } else { block = cachedBlock } // Load the parent block for the height since it is // required to remove it. parent, err := blockchain.DBFetchBlockByHeight(dbTx, int64(height)-1) if err != nil { return err } cachedBlock = parent // When the index requires all of the referenced // txouts they need to be retrieved from the // transaction index. var view *blockchain.UtxoViewpoint if indexNeedsInputs(indexer) { var err error view, err = makeUtxoView(dbTx, block, parent) if err != nil { return err } } // Remove all of the index entries associated // with the block and update the indexer tip. err = dbIndexDisconnectBlock(dbTx, indexer, block, parent, view) if err != nil { return err } // Update the tip to the previous block. hash = &block.MsgBlock().Header.PrevBlock height-- } return nil }) if err != nil { return err } if initialHeight != height { log.Infof("Removed %d orphaned blocks from %s "+ "(heights %d to %d)", initialHeight-height, indexer.Name(), height+1, initialHeight) } } // Fetch the current tip heights for each index along with tracking the // lowest one so the catchup code only needs to start at the earliest // block and is able to skip connecting the block for the indexes that // don't need it. bestHeight := chain.BestSnapshot().Height lowestHeight := bestHeight indexerHeights := make([]uint32, len(m.enabledIndexes)) err = m.db.View(func(dbTx database.Tx) error { for i, indexer := range m.enabledIndexes { idxKey := indexer.Key() hash, height, err := dbFetchIndexerTip(dbTx, idxKey) if err != nil { return err } log.Debugf("Current %s tip (height %d, hash %v)", indexer.Name(), height, hash) indexerHeights[i] = height if height < uint32(lowestHeight) { lowestHeight = int64(height) } } return nil }) if err != nil { return err } // Nothing to index if all of the indexes are caught up. if lowestHeight == bestHeight { return nil } // Create a progress logger for the indexing process below. progressLogger := progresslog.NewBlockProgressLogger("Indexed", log) // At this point, one or more indexes are behind the current best chain // tip and need to be caught up, so log the details and loop through // each block that needs to be indexed. log.Infof("Catching up indexes from height %d to %d", lowestHeight, bestHeight) var cachedParent *dcrutil.Block for height := lowestHeight + 1; height <= bestHeight; height++ { var block, parent *dcrutil.Block err = m.db.Update(func(dbTx database.Tx) error { // Get the parent of the block, unless it's already cached. if cachedParent == nil && height > 0 { parent, err = blockchain.DBFetchBlockByHeight(dbTx, height-1) if err != nil { return err } } else { parent = cachedParent } // Load the block for the height since it is required to index // it. block, err = blockchain.DBFetchBlockByHeight(dbTx, height) if err != nil { return err } cachedParent = block // Connect the block for all indexes that need it. var view *blockchain.UtxoViewpoint for i, indexer := range m.enabledIndexes { // Skip indexes that don't need to be updated with this // block. if indexerHeights[i] >= uint32(height) { continue } // When the index requires all of the referenced // txouts and they haven't been loaded yet, they // need to be retrieved from the transaction // index. if view == nil && indexNeedsInputs(indexer) { var errMakeView error view, errMakeView = makeUtxoView(dbTx, block, parent) if errMakeView != nil { return errMakeView } } errLocal := dbIndexConnectBlock(dbTx, indexer, block, parent, view) if errLocal != nil { return errLocal } indexerHeights[i] = uint32(height) } return nil }) if err != nil { return err } progressLogger.LogBlockHeight(block, parent) } log.Infof("Indexes caught up to height %d", bestHeight) return nil }
// findCandidates searches the chain backwards for checkpoint candidates and // returns a slice of found candidates, if any. It also stops searching for // candidates at the last checkpoint that is already hard coded into chain // since there is no point in finding candidates before already existing // checkpoints. func findCandidates(chain *blockchain.BlockChain, latestHash *chainhash.Hash) ([]*chaincfg.Checkpoint, error) { // Start with the latest block of the main chain. block, err := chain.BlockByHash(latestHash) if err != nil { return nil, err } // Get the latest known checkpoint. latestCheckpoint := chain.LatestCheckpoint() if latestCheckpoint == nil { // Set the latest checkpoint to the genesis block if there isn't // already one. latestCheckpoint = &chaincfg.Checkpoint{ Hash: activeNetParams.GenesisHash, Height: 0, } } // The latest known block must be at least the last known checkpoint // plus required checkpoint confirmations. checkpointConfirmations := int64(blockchain.CheckpointConfirmations) requiredHeight := latestCheckpoint.Height + checkpointConfirmations if block.Height() < requiredHeight { return nil, fmt.Errorf("the block database is only at height "+ "%d which is less than the latest checkpoint height "+ "of %d plus required confirmations of %d", block.Height(), latestCheckpoint.Height, checkpointConfirmations) } // For the first checkpoint, the required height is any block after the // genesis block, so long as the chain has at least the required number // of confirmations (which is enforced above). if len(activeNetParams.Checkpoints) == 0 { requiredHeight = 1 } // Indeterminate progress setup. numBlocksToTest := block.Height() - requiredHeight progressInterval := (numBlocksToTest / 100) + 1 // min 1 fmt.Print("Searching for candidates") defer fmt.Println() // Loop backwards through the chain to find checkpoint candidates. candidates := make([]*chaincfg.Checkpoint, 0, cfg.NumCandidates) numTested := int64(0) for len(candidates) < cfg.NumCandidates && block.Height() > requiredHeight { // Display progress. if numTested%progressInterval == 0 { fmt.Print(".") } // Determine if this block is a checkpoint candidate. isCandidate, err := chain.IsCheckpointCandidate(block) if err != nil { return nil, err } // All checks passed, so this node seems like a reasonable // checkpoint candidate. if isCandidate { checkpoint := chaincfg.Checkpoint{ Height: block.Height(), Hash: block.Sha(), } candidates = append(candidates, &checkpoint) } prevHash := &block.MsgBlock().Header.PrevBlock block, err = chain.BlockByHash(prevHash) if err != nil { return nil, err } numTested++ } return candidates, nil }