// 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. for i := len(m.enabledIndexes); i > 0; i-- { indexer := m.enabledIndexes[i-1] // Fetch the current tip for the index. var height int32 var hash *wire.ShaHash 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 == -1 { continue } // Loop until the tip is a block that exists in the main chain. initialHeight := height for { exists, err := chain.MainChainHasBlock(hash) if err != nil { return err } if exists { break } // At this point the index tip is orphaned, so load the // orphaned block from the database directly and // disconnect it from the index. The block has to be // loaded directly since it is no longer in the main // chain and thus the chain.BlockByHash function would // error. err = m.db.Update(func(dbTx database.Tx) error { blockBytes, err := dbTx.FetchBlock(hash) if err != nil { return err } block, err := btcutil.NewBlockFromBytes(blockBytes) if err != nil { return err } block.SetHeight(height) // 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) 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, 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([]int32, 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 < lowestHeight { lowestHeight = 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 := 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) for height := lowestHeight + 1; height <= bestHeight; height++ { // Load the block for the height since it is required to index // it. block, err := chain.BlockByHeight(height) if err != nil { return err } // 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] >= height { continue } err := m.db.Update(func(dbTx database.Tx) error { // 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 err error view, err = makeUtxoView(dbTx, block) if err != nil { return err } } return dbIndexConnectBlock(dbTx, indexer, block, view) }) if err != nil { return err } indexerHeights[i] = height } // Log indexing progress. progressLogger.LogBlockHeight(block) } 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 btcchain // since there is no point in finding candidates before already existing // checkpoints. func findCandidates(chain *blockchain.BlockChain, latestHash *wire.ShaHash) ([]*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 := int32(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 := int32(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 }