// 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 }
// upgradeToVersion2 upgrades a version 1 blockchain to version 2, allowing // use of the new on-disk ticket database. func (b *BlockChain) upgradeToVersion2() error { log.Infof("Initializing upgrade to database version 2") best := b.BestSnapshot() progressLogger := progresslog.NewBlockProgressLogger("Upgraded", log) // The upgrade is atomic, so there is no need to set the flag that // the database is undergoing an upgrade here. Get the stake node // for the genesis block, and then begin connecting stake nodes // incrementally. err := b.db.Update(func(dbTx database.Tx) error { bestStakeNode, errLocal := stake.InitDatabaseState(dbTx, b.chainParams) if errLocal != nil { return errLocal } parent, errLocal := dbFetchBlockByHeight(dbTx, 0) if errLocal != nil { return errLocal } for i := int64(1); i <= best.Height; i++ { block, errLocal := dbFetchBlockByHeight(dbTx, i) if errLocal != nil { return errLocal } // If we need the tickets, fetch them too. var newTickets []chainhash.Hash if i >= b.chainParams.StakeEnabledHeight { matureHeight := i - int64(b.chainParams.TicketMaturity) matureBlock, errLocal := dbFetchBlockByHeight(dbTx, matureHeight) if errLocal != nil { return errLocal } for _, stx := range matureBlock.MsgBlock().STransactions { if is, _ := stake.IsSStx(stx); is { h := stx.TxSha() newTickets = append(newTickets, h) } } } // Iteratively connect the stake nodes in memory. header := block.MsgBlock().Header bestStakeNode, errLocal = bestStakeNode.ConnectNode(header, ticketsSpentInBlock(block), ticketsRevokedInBlock(block), newTickets) if errLocal != nil { return errLocal } // Write the top block stake node to the database. errLocal = stake.WriteConnectedBestNode(dbTx, bestStakeNode, *best.Hash) if errLocal != nil { return errLocal } // Write the best block node when we reach it. if i == best.Height { b.bestNode.stakeNode = bestStakeNode b.bestNode.stakeUndoData = bestStakeNode.UndoData() b.bestNode.newTickets = newTickets b.bestNode.ticketsSpent = ticketsSpentInBlock(block) b.bestNode.ticketsRevoked = ticketsRevokedInBlock(block) } progressLogger.LogBlockHeight(block, parent) parent = block } // Write the new database version. b.dbInfo.version = 2 errLocal = dbPutDatabaseInfo(dbTx, b.dbInfo) if errLocal != nil { return errLocal } return nil }) if err != nil { return err } log.Infof("Upgrade to new stake database was successful!") return nil }