示例#1
0
文件: manager.go 项目: decred/dcrd
// 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
}
示例#2
0
文件: upgrade.go 项目: decred/dcrd
// 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
}