// 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 }