dcrd/blockchain/chain.go
Matheus Degiovani 893802fc06 multi: Use database/v3 module
This updates all users of the github.com/decred/dcrd/databasev/2 module
to use the recently introduced v3 version.

Replace directives are added as needed to ease development while the
final version of the database/v3 v3.0.0 isn't tagged.

The relevant documentation is also updated to use the new database/v3
module.
2021-08-02 09:13:45 -05:00

2319 lines
82 KiB
Go

// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockchain
import (
"context"
"errors"
"fmt"
"math/big"
"sync"
"time"
"github.com/decred/dcrd/blockchain/stake/v4"
"github.com/decred/dcrd/blockchain/standalone/v2"
"github.com/decred/dcrd/blockchain/v4/indexers"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrd/database/v3"
"github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/dcrd/gcs/v3"
"github.com/decred/dcrd/gcs/v3/blockcf2"
"github.com/decred/dcrd/lru"
"github.com/decred/dcrd/txscript/v4"
"github.com/decred/dcrd/wire"
)
const (
// minMemoryStakeNodes is the maximum height to keep stake nodes
// in memory for in their respective nodes. Beyond this height,
// they will need to be manually recalculated. This value should
// be at least the stake retarget interval.
minMemoryStakeNodes = 288
// recentBlockCacheSize is the number of recent blocks to keep in memory.
// This value is set based on the target block time for the main network
// such that there is approximately one hour of blocks cached. This could
// be made network independent and calculated based on the parameters, but
// that would result in larger caches than desired for other networks.
recentBlockCacheSize = 12
// contextCheckCacheSize is the number of recent successful contextual block
// check results to keep in memory.
contextCheckCacheSize = 25
)
// panicf is a convenience function that formats according to the given format
// specifier and arguments and then logs the result at the critical level and
// panics with it.
func panicf(format string, args ...interface{}) {
str := fmt.Sprintf(format, args...)
log.Critical(str)
panic(str)
}
// BlockLocator is used to help locate a specific block. The algorithm for
// building the block locator is to add the hashes in reverse order until
// the genesis block is reached. In order to keep the list of locator hashes
// to a reasonable number of entries, first the most recent previous 12 block
// hashes are added, then the step is doubled each loop iteration to
// exponentially decrease the number of hashes as a function of the distance
// from the block being located.
//
// For example, assume a block chain with a side chain as depicted below:
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
// \-> 16a -> 17a
//
// The block locator for block 17a would be the hashes of blocks:
// [17a 16a 15 14 13 12 11 10 9 8 7 6 4 genesis]
type BlockLocator []*chainhash.Hash
// BestState houses information about the current best block and other info
// related to the state of the main chain as it exists from the point of view of
// the current best block.
//
// The BestSnapshot method can be used to obtain access to this information
// in a concurrent safe manner and the data will not be changed out from under
// the caller when chain state changes occur as the function name implies.
// However, the returned snapshot must be treated as immutable since it is
// shared by all callers.
type BestState struct {
Hash chainhash.Hash // The hash of the block.
PrevHash chainhash.Hash // The previous block hash.
Height int64 // The height of the block.
Bits uint32 // The difficulty bits of the block.
NextPoolSize uint32 // The ticket pool size.
NextStakeDiff int64 // The next stake difficulty.
BlockSize uint64 // The size of the block.
NumTxns uint64 // The number of txns in the block.
TotalTxns uint64 // The total number of txns in the chain.
MedianTime time.Time // Median time as per CalcPastMedianTime.
TotalSubsidy int64 // The total subsidy for the chain.
NextWinningTickets []chainhash.Hash // The eligible tickets to vote on the next block.
MissedTickets []chainhash.Hash // The missed tickets set to be revoked.
NextFinalState [6]byte // The calculated state of the lottery for the next block.
}
// newBestState returns a new best stats instance for the given parameters.
func newBestState(node *blockNode, blockSize, numTxns, totalTxns uint64,
medianTime time.Time, totalSubsidy int64, nextPoolSize uint32,
nextStakeDiff int64, nextWinners, missed []chainhash.Hash,
nextFinalState [6]byte) *BestState {
prevHash := *zeroHash
if node.parent != nil {
prevHash = node.parent.hash
}
return &BestState{
Hash: node.hash,
PrevHash: prevHash,
Height: node.height,
Bits: node.bits,
NextPoolSize: nextPoolSize,
NextStakeDiff: nextStakeDiff,
BlockSize: blockSize,
NumTxns: numTxns,
TotalTxns: totalTxns,
MedianTime: medianTime,
TotalSubsidy: totalSubsidy,
NextWinningTickets: nextWinners,
MissedTickets: missed,
NextFinalState: nextFinalState,
}
}
// BlockChain provides functions for working with the Decred block chain. It
// includes functionality such as rejecting duplicate blocks, ensuring blocks
// follow all rules, checkpoint handling, and best chain selection with
// reorganization.
type BlockChain struct {
// The following fields are set when the instance is created and can't
// be changed afterwards, so there is no need to protect them with a
// separate mutex.
checkpoints []chaincfg.Checkpoint
checkpointsByHeight map[int64]*chaincfg.Checkpoint
deploymentVers map[string]uint32
db database.DB
dbInfo *databaseInfo
chainParams *chaincfg.Params
timeSource MedianTimeSource
notifications NotificationCallback
sigCache *txscript.SigCache
indexManager indexers.IndexManager
interrupt <-chan struct{}
utxoCache UtxoCacher
// subsidyCache is the cache that provides quick lookup of subsidy
// values.
subsidyCache *standalone.SubsidyCache
// processLock protects concurrent access to overall chain processing
// independent from the chain lock which is periodically released to
// send notifications.
processLock sync.Mutex
// chainLock protects concurrent access to the vast majority of the
// fields in this struct below this point.
chainLock sync.RWMutex
// This field is a configuration parameter that can be toggled at runtime.
// It is protected by the chain lock.
noVerify bool
// These fields are related to the memory block index. They both have
// their own locks, however they are often also protected by the chain
// lock to help prevent logic races when blocks are being processed.
//
// index houses the entire block index in memory. The block index is
// a tree-shaped structure.
//
// bestChain tracks the current active chain by making use of an
// efficient chain view into the block index.
index *blockIndex
bestChain *chainView
// isCurrentLatch tracks whether or not the chain believes it is current in
// such a way that once it becomes current it latches to that state unless
// the chain falls too far behind again which likely indicates it is forked
// from the network. It is protected by the chain lock.
isCurrentLatch bool
// These fields house caches for blocks to facilitate faster chain reorgs,
// block connection, and more efficient recent block serving.
//
// recentBlocks houses a block cache of block data that has been seen
// recently.
//
// recentContextChecks tracks recent blocks that have successfully passed
// all contextual checks and is primarily used as an optimization to avoid
// running the checks again when possible.
recentBlocks lru.KVCache
recentContextChecks lru.Cache
// These fields house a cached view that represents a block that votes
// against its parent and therefore contains all changes as a result
// of disconnecting all regular transactions in its parent. It is only
// lazily updated to the current tip when fetching a utxo view via the
// FetchUtxoView function with the flag indicating the block votes against
// the parent set.
disapprovedViewLock sync.Mutex
disapprovedView *UtxoViewpoint
// checkpointNode tracks the most recently known checkpoint. It will be nil
// when no checkpoints are known or are disabled. It is protected by the
// chain lock.
checkpointNode *blockNode
// The state is used as a fairly efficient way to cache information
// about the current best chain state that is returned to callers when
// requested. It operates on the principle of MVCC such that any time a
// new block becomes the best block, the state pointer is replaced with
// a new struct and the old state is left untouched. In this way,
// multiple callers can be pointing to different best chain states.
// This is acceptable for most callers because the state is only being
// queried at a specific point in time.
//
// In addition, some of the fields are stored in the database so the
// chain state can be quickly reconstructed on load.
stateLock sync.RWMutex
stateSnapshot *BestState
// The following caches are used to efficiently keep track of the
// current deployment threshold state of each rule change deployment.
//
// deploymentCaches caches the current deployment threshold state for
// blocks in each of the actively defined deployments.
deploymentCaches map[uint32][]thresholdStateCache
// pruner is the automatic pruner for block nodes and stake nodes,
// so that the memory may be restored by the garbage collector if
// it is unlikely to be referenced in the future.
pruner *chainPruner
// The following maps are various caches for the stake version/voting
// system. The goal of these is to reduce disk access to load blocks
// from disk. Measurements indicate that it is slightly more expensive
// so setup the cache (<10%) vs doing a straight chain walk. Every
// other subsequent call is >10x faster.
isVoterMajorityVersionCache map[[stakeMajorityCacheKeySize]byte]bool
isStakeMajorityVersionCache map[[stakeMajorityCacheKeySize]byte]bool
calcPriorStakeVersionCache map[[chainhash.HashSize]byte]uint32
calcVoterVersionIntervalCache map[[chainhash.HashSize]byte]uint32
calcStakeVersionCache map[[chainhash.HashSize]byte]uint32
}
const (
// stakeMajorityCacheKeySize is comprised of the stake version and the
// hash size. The stake version is a little endian uint32, hence we
// add 4 to the overall size.
stakeMajorityCacheKeySize = 4 + chainhash.HashSize
)
// StakeVersions is a condensed form of a dcrutil.Block that is used to prevent
// using gigabytes of memory.
type StakeVersions struct {
Hash chainhash.Hash
Height int64
BlockVersion int32
StakeVersion uint32
Votes []stake.VoteVersionTuple
}
// GetStakeVersions returns a cooked array of StakeVersions. We do this in
// order to not bloat memory by returning raw blocks.
func (b *BlockChain) GetStakeVersions(hash *chainhash.Hash, count int32) ([]StakeVersions, error) {
startNode := b.index.LookupNode(hash)
if startNode == nil || !b.index.CanValidate(startNode) {
return nil, unknownBlockError(hash)
}
// Nothing to do if no count requested.
if count == 0 {
return nil, nil
}
if count < 0 {
return nil, fmt.Errorf("count must not be less than zero - "+
"got %d", count)
}
// Limit the requested count to the max possible for the requested block.
if count > int32(startNode.height+1) {
count = int32(startNode.height + 1)
}
result := make([]StakeVersions, 0, count)
prevNode := startNode
for i := int32(0); prevNode != nil && i < count; i++ {
sv := StakeVersions{
Hash: prevNode.hash,
Height: prevNode.height,
BlockVersion: prevNode.blockVersion,
StakeVersion: prevNode.stakeVersion,
Votes: prevNode.votes,
}
result = append(result, sv)
prevNode = prevNode.parent
}
return result, nil
}
// VoteInfo represents information on agendas and their respective states for
// a consensus deployment.
type VoteInfo struct {
Agendas []chaincfg.ConsensusDeployment
AgendaStatus []ThresholdStateTuple
}
// GetVoteInfo returns information on consensus deployment agendas and their
// respective states at the provided hash, for the provided deployment version.
func (b *BlockChain) GetVoteInfo(hash *chainhash.Hash, version uint32) (*VoteInfo, error) {
deployments, ok := b.chainParams.Deployments[version]
if !ok {
str := fmt.Sprintf("stake version %d does not exist", version)
return nil, contextError(ErrUnknownDeploymentVersion, str)
}
vi := VoteInfo{
Agendas: make([]chaincfg.ConsensusDeployment,
0, len(deployments)),
AgendaStatus: make([]ThresholdStateTuple, 0, len(deployments)),
}
for _, deployment := range deployments {
vi.Agendas = append(vi.Agendas, deployment)
status, err := b.NextThresholdState(hash, version, deployment.Vote.Id)
if err != nil {
return nil, err
}
vi.AgendaStatus = append(vi.AgendaStatus, status)
}
return &vi, nil
}
// DisableVerify provides a mechanism to disable transaction script validation
// which you DO NOT want to do in production as it could allow double spends
// and other undesirable things. It is provided only for debug purposes since
// script validation is extremely intensive and when debugging it is sometimes
// nice to quickly get the chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) DisableVerify(disable bool) {
b.chainLock.Lock()
b.noVerify = disable
b.chainLock.Unlock()
}
// HaveHeader returns whether or not the chain instance has the block header
// represented by the passed hash. Note that this will return true for both the
// main chain and any side chains.
//
// This function is safe for concurrent access.
func (b *BlockChain) HaveHeader(hash *chainhash.Hash) bool {
return b.index.LookupNode(hash) != nil
}
// HaveBlock returns whether or not the chain instance has the block represented
// by the passed hash. This includes checking the various places a block can
// be like part of the main chain or on a side chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) HaveBlock(hash *chainhash.Hash) bool {
return b.index.HaveBlock(hash)
}
// ChainWork returns the total work up to and including the block of the
// provided block hash.
func (b *BlockChain) ChainWork(hash *chainhash.Hash) (*big.Int, error) {
node := b.index.LookupNode(hash)
if node == nil {
return nil, unknownBlockError(hash)
}
return node.workSum, nil
}
// TipGeneration returns the entire generation of blocks stemming from the
// parent of the current tip.
//
// The function is safe for concurrent access.
func (b *BlockChain) TipGeneration() ([]chainhash.Hash, error) {
var nodeHashes []chainhash.Hash
b.chainLock.Lock()
b.index.RLock()
entry := b.index.chainTips[b.bestChain.Tip().height]
if entry.tip != nil {
nodeHashes = make([]chainhash.Hash, 0, len(entry.otherTips)+1)
nodeHashes = append(nodeHashes, entry.tip.hash)
for _, n := range entry.otherTips {
nodeHashes = append(nodeHashes, n.hash)
}
}
b.index.RUnlock()
b.chainLock.Unlock()
return nodeHashes, nil
}
// addRecentBlock adds a block to the recent block LRU cache and evicts the
// least recently used item if needed.
//
// This function is safe for concurrent access.
func (b *BlockChain) addRecentBlock(block *dcrutil.Block) {
b.recentBlocks.Add(*block.Hash(), block)
}
// lookupRecentBlock attempts to return the requested block from the recent
// block LRU cache. When the block exists, it will be made the most recently
// used item.
//
// This function is safe for concurrent access.
func (b *BlockChain) lookupRecentBlock(hash *chainhash.Hash) (*dcrutil.Block, bool) {
block, ok := b.recentBlocks.Lookup(*hash)
if ok {
return block.(*dcrutil.Block), true
}
return nil, false
}
// fetchMainChainBlockByNode returns the block from the main chain associated
// with the given node. It first attempts to use cache and then falls back to
// loading it from the database.
//
// An error is returned if the block is either not found or not in the main
// chain.
//
// This function MUST be called with the chain lock held (for reads).
func (b *BlockChain) fetchMainChainBlockByNode(node *blockNode) (*dcrutil.Block, error) {
// Ensure the block is in the main chain.
if !b.bestChain.Contains(node) {
str := fmt.Sprintf("block %s is not in the main chain", node.hash)
return nil, errNotInMainChain(str)
}
// Attempt to load the block from the recent block cache.
block, ok := b.lookupRecentBlock(&node.hash)
if ok {
return block, nil
}
// Load the block from the database.
err := b.db.View(func(dbTx database.Tx) error {
var err error
block, err = dbFetchBlockByNode(dbTx, node)
return err
})
return block, err
}
// fetchBlockByNode returns the block associated with the given node all known
// sources such as the internal caches and the database. This function returns
// blocks regardless or whether or not they are part of the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) fetchBlockByNode(node *blockNode) (*dcrutil.Block, error) {
// Attempt to load the block from the recent block cache.
block, ok := b.lookupRecentBlock(&node.hash)
if ok {
return block, nil
}
// Load the block from the database.
err := b.db.View(func(dbTx database.Tx) error {
var err error
block, err = dbFetchBlockByNode(dbTx, node)
return err
})
return block, err
}
// pruneStakeNodes removes references to old stake nodes which should no
// longer be held in memory so as to keep the maximum memory usage down.
// It proceeds from the bestNode back to the determined minimum height node,
// finds all the relevant children, and then drops the stake nodes from
// them by assigning nil and allowing the memory to be recovered by GC.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) pruneStakeNodes() {
// Find the height to prune to.
pruneToNode := b.bestChain.Tip()
for i := int64(0); i < minMemoryStakeNodes-1 && pruneToNode != nil; i++ {
pruneToNode = pruneToNode.parent
}
// Nothing to do if there are not enough nodes.
if pruneToNode == nil || pruneToNode.parent == nil {
return
}
// Determine the nodes that need to be pruned. This will typically end up
// being a small number of nodes since the pruning interval currently
// coincides with the average block time.
pruneNodes := make([]*blockNode, 0, b.pruner.prunedPerIntervalHint)
for n := pruneToNode.parent; n != nil && n.stakeNode != nil; n = n.parent {
pruneNodes = append(pruneNodes, n)
}
// Loop through each node to prune from the oldest to the newest and prune
// the stake-related fields.
for i := len(pruneNodes) - 1; i >= 0; i-- {
node := pruneNodes[i]
node.stakeNode = nil
node.newTickets = nil
node.ticketsVoted = nil
node.ticketsRevoked = nil
}
}
// isMajorityVersion determines if a previous number of blocks in the chain
// starting with startNode are at least the minimum passed version.
//
// This function MUST be called with the chain state lock held (for reads).
func (b *BlockChain) isMajorityVersion(minVer int32, startNode *blockNode, numRequired uint64) bool {
numFound := uint64(0)
iterNode := startNode
for i := uint64(0); i < b.chainParams.BlockUpgradeNumToCheck &&
numFound < numRequired && iterNode != nil; i++ {
// This node has a version that is at least the minimum version.
if iterNode.blockVersion >= minVer {
numFound++
}
iterNode = iterNode.parent
}
return numFound >= numRequired
}
// connectBlock handles connecting the passed node/block to the end of the main
// (best) chain.
//
// This passed utxo view must have all referenced txos the block spends marked
// as spent and all of the new txos the block creates added to it. In addition,
// the passed stxos slice must be populated with all of the information for the
// spent txos. This approach is used because the connection validation that
// must happen prior to calling this function requires the same details, so
// it would be inefficient to repeat it.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) connectBlock(node *blockNode, block, parent *dcrutil.Block, view *UtxoViewpoint, stxos []spentTxOut, hdrCommitments *headerCommitmentData) error {
// Make sure it's extending the end of the best chain.
prevHash := block.MsgBlock().Header.PrevBlock
tip := b.bestChain.Tip()
if prevHash != tip.hash {
panicf("block %v (height %v) connects to block %v instead of "+
"extending the best chain (hash %v, height %v)", node.hash,
node.height, prevHash, tip.hash, tip.height)
}
isTreasuryEnabled, err := b.isTreasuryAgendaActive(node.parent)
if err != nil {
return err
}
// Create agenda flags for checking transactions based on which ones are
// active as of the block being connected.
checkTxFlags := AFNone
if isTreasuryEnabled {
checkTxFlags |= AFTreasuryEnabled
}
// Sanity check the correct number of stxos are provided.
if len(stxos) != countSpentOutputs(block, isTreasuryEnabled) {
panicf("provided %v stxos for block %v (height %v) which spends %v "+
"outputs", len(stxos), node.hash, node.height,
countSpentOutputs(block, isTreasuryEnabled))
}
// Write any modified block index entries to the database before
// updating the best state.
if err := b.flushBlockIndex(); err != nil {
return err
}
// Get the stake node for this node, filling in any data that
// may have yet to have been filled in. In all cases this
// should simply give a pointer to data already prepared, but
// run this anyway to be safe.
stakeNode, err := b.fetchStakeNode(node)
if err != nil {
return err
}
// Calculate the next stake difficulty.
nextStakeDiff, err := b.calcNextRequiredStakeDifficulty(node)
if err != nil {
return err
}
// NOTE: When more header commitments are added, the inclusion proofs
// will need to be generated and stored to the database here (when not
// already stored). There is no need to store them currently because
// there is only a single commitment which means there are no sibling
// hashes that typically form the inclusion proofs due to the fact a
// single leaf merkle tree reduces to having the same root as the leaf
// and therefore the proof only consists of checking the leaf hash
// itself against the commitment root.
// Generate a new best state snapshot that will be used to update the
// database and later memory if all database updates are successful.
b.stateLock.RLock()
curTotalTxns := b.stateSnapshot.TotalTxns
curTotalSubsidy := b.stateSnapshot.TotalSubsidy
b.stateLock.RUnlock()
subsidy := calculateAddedSubsidy(block, parent, isTreasuryEnabled)
numTxns := uint64(len(block.Transactions()) + len(block.STransactions()))
blockSize := uint64(block.MsgBlock().Header.Size)
state := newBestState(node, blockSize, numTxns, curTotalTxns+numTxns,
node.CalcPastMedianTime(), curTotalSubsidy+subsidy,
uint32(node.stakeNode.PoolSize()), nextStakeDiff,
node.stakeNode.Winners(), node.stakeNode.MissedTickets(),
node.stakeNode.FinalState())
// Atomically insert info into the database.
err = b.db.Update(func(dbTx database.Tx) error {
// Update best block state.
err := dbPutBestState(dbTx, state, node.workSum)
if err != nil {
return err
}
// Update the transaction spend journal by adding a record for
// the block that contains all txos spent by it.
err = dbPutSpendJournalEntry(dbTx, block.Hash(), stxos)
if err != nil {
return err
}
// Insert the block into the stake database.
err = stake.WriteConnectedBestNode(dbTx, stakeNode, node.hash)
if err != nil {
return err
}
// Insert the treasury information into the database.
if isTreasuryEnabled {
err = b.dbPutTreasuryBalance(dbTx, block, node)
if err != nil {
return err
}
err = b.dbPutTSpend(dbTx, block)
if err != nil {
return err
}
}
// Insert the GCS filter for the block into the database.
err = dbPutGCSFilter(dbTx, block.Hash(), hdrCommitments.filter)
if err != nil {
return err
}
// Allow the index manager to call each of the currently active
// optional indexes with the block being connected so they can
// update themselves accordingly.
if b.indexManager != nil {
err := b.indexManager.ConnectBlock(dbTx, block, parent,
view, isTreasuryEnabled)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
// Commit all entries in the view to the utxo cache. All entries in the view
// that are marked as modified and spent are removed from the view.
// Additionally, all entries that are added to the cache are removed from the
// view.
err = b.utxoCache.Commit(view)
if err != nil {
return err
}
// Conditionally flush the utxo cache to the database. Force a flush if the
// chain believes it is current since blocks are connected infrequently at
// that point. Only log the flush when the chain is not current as it is
// mostly useful to see the flush details when many blocks are being connected
// (and subsequently flushed) in quick succession.
isCurrent := b.isCurrent(node)
err = b.utxoCache.MaybeFlush(&node.hash, uint32(node.height), isCurrent,
!isCurrent)
if err != nil {
return err
}
// This node is now the end of the best chain.
b.bestChain.SetTip(node)
b.index.MaybePruneCachedTips(node)
// Update the state for the best block. Notice how this replaces the
// entire struct instead of updating the existing one. This effectively
// allows the old version to act as a snapshot which callers can use
// freely without needing to hold a lock for the duration. See the
// comments on the state variable for more details.
b.stateLock.Lock()
b.stateSnapshot = state
b.stateLock.Unlock()
// Notify the caller that the block was connected to the main chain.
// The caller would typically want to react with actions such as
// updating wallets.
b.chainLock.Unlock()
b.sendNotification(NTBlockConnected, &BlockConnectedNtfnsData{
Block: block,
ParentBlock: parent,
CheckTxFlags: checkTxFlags,
})
b.chainLock.Lock()
// Send stake notifications about the new block.
if node.height >= b.chainParams.StakeEnabledHeight {
nextStakeDiff, err := b.calcNextRequiredStakeDifficulty(node)
if err != nil {
return err
}
// Notify of spent and missed tickets.
b.sendNotification(NTSpentAndMissedTickets,
&TicketNotificationsData{
Hash: node.hash,
Height: node.height,
StakeDifficulty: nextStakeDiff,
TicketsSpent: node.stakeNode.SpentByBlock(),
TicketsMissed: node.stakeNode.MissedByBlock(),
TicketsNew: nil,
})
// Notify of new tickets.
b.sendNotification(NTNewTickets,
&TicketNotificationsData{
Hash: node.hash,
Height: node.height,
StakeDifficulty: nextStakeDiff,
TicketsSpent: nil,
TicketsMissed: nil,
TicketsNew: node.stakeNode.NewTickets(),
})
}
// Optimization: Before checkpoints, immediately dump the parent's stake
// node because we no longer need it.
var latestCheckpointHeight int64
if len(b.checkpoints) > 0 {
latestCheckpointHeight = b.checkpoints[len(b.checkpoints)-1].Height
}
if node.height < latestCheckpointHeight {
parent := b.bestChain.Tip().parent
parent.stakeNode = nil
parent.newTickets = nil
parent.ticketsVoted = nil
parent.ticketsRevoked = nil
}
b.addRecentBlock(block)
return nil
}
// disconnectBlock handles disconnecting the passed node/block from the end of
// the main (best) chain.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) disconnectBlock(node *blockNode, block, parent *dcrutil.Block, view *UtxoViewpoint) error {
// Make sure the node being disconnected is the end of the best chain.
tip := b.bestChain.Tip()
if node.hash != tip.hash {
panicf("block %v (height %v) is not the end of the best chain "+
"(hash %v, height %v)", node.hash, node.height, tip.hash,
tip.height)
}
isTreasuryEnabled, err := b.isTreasuryAgendaActive(node.parent)
if err != nil {
return err
}
// Create agenda flags for checking transactions based on which ones were
// active as of the block being disconnected.
checkTxFlags := AFNone
if isTreasuryEnabled {
checkTxFlags |= AFTreasuryEnabled
}
// Write any modified block index entries to the database before
// updating the best state.
if err := b.flushBlockIndex(); err != nil {
return err
}
// Prepare the information required to update the stake database
// contents.
childStakeNode, err := b.fetchStakeNode(node)
if err != nil {
return err
}
parentStakeNode, err := b.fetchStakeNode(node.parent)
if err != nil {
return err
}
// Generate a new best state snapshot that will be used to update the
// database and later memory if all database updates are successful.
b.stateLock.RLock()
curTotalTxns := b.stateSnapshot.TotalTxns
curTotalSubsidy := b.stateSnapshot.TotalSubsidy
b.stateLock.RUnlock()
parentBlockSize := uint64(parent.MsgBlock().Header.Size)
numParentTxns := uint64(len(parent.Transactions()) + len(parent.STransactions()))
numBlockTxns := uint64(len(block.Transactions()) + len(block.STransactions()))
newTotalTxns := curTotalTxns - numBlockTxns
subsidy := calculateAddedSubsidy(block, parent, isTreasuryEnabled)
newTotalSubsidy := curTotalSubsidy - subsidy
prevNode := node.parent
state := newBestState(prevNode, parentBlockSize, numParentTxns,
newTotalTxns, prevNode.CalcPastMedianTime(), newTotalSubsidy,
uint32(prevNode.stakeNode.PoolSize()), node.sbits,
prevNode.stakeNode.Winners(), prevNode.stakeNode.MissedTickets(),
prevNode.stakeNode.FinalState())
err = b.db.Update(func(dbTx database.Tx) error {
// Update best block state.
err := dbPutBestState(dbTx, state, node.workSum)
if err != nil {
return err
}
err = stake.WriteDisconnectedBestNode(dbTx, parentStakeNode,
node.parent.hash, childStakeNode.UndoData())
if err != nil {
return err
}
// NOTE: The GCS filter is intentionally not removed on disconnect to
// ensure that lightweight clients still have access to them if they
// happen to be on a side chain after coming back online after a reorg.
// Allow the index manager to call each of the currently active
// optional indexes with the block being disconnected so they
// can update themselves accordingly.
if b.indexManager != nil {
err := b.indexManager.DisconnectBlock(dbTx, block,
parent, view, isTreasuryEnabled)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
// Commit all entries in the view to the utxo cache. All entries in the view
// that are marked as modified and spent are removed from the view.
// Additionally, all entries that are added to the cache are removed from the
// view.
err = b.utxoCache.Commit(view)
if err != nil {
return err
}
// Force a utxo cache flush when blocks are being disconnected. A cache flush
// is forced here since the spend journal entry for the disconnected block
// will be removed below.
err = b.utxoCache.MaybeFlush(&node.parent.hash, uint32(node.parent.height),
true, false)
if err != nil {
return err
}
// Update the transaction spend journal by removing the record that contains
// all txos spent by the block. This is intentionally done AFTER the utxo
// cache has been force flushed since the spend journal information will no
// longer be available for the cache to use for recovery purposes after being
// removed.
err = b.db.Update(func(dbTx database.Tx) error {
return dbRemoveSpendJournalEntry(dbTx, block.Hash())
})
if err != nil {
return err
}
// This node's parent is now the end of the best chain.
b.bestChain.SetTip(node.parent)
// Update the state for the best block. Notice how this replaces the
// entire struct instead of updating the existing one. This effectively
// allows the old version to act as a snapshot which callers can use
// freely without needing to hold a lock for the duration. See the
// comments on the state variable for more details.
b.stateLock.Lock()
b.stateSnapshot = state
b.stateLock.Unlock()
// Notify the caller that the block was disconnected from the main
// chain. The caller would typically want to react with actions such as
// updating wallets.
b.chainLock.Unlock()
b.sendNotification(NTBlockDisconnected, &BlockDisconnectedNtfnsData{
Block: block,
ParentBlock: parent,
CheckTxFlags: checkTxFlags,
})
b.chainLock.Lock()
return nil
}
// countSpentRegularOutputs returns the number of utxos the regular transactions
// in the passed block spend.
func countSpentRegularOutputs(block *dcrutil.Block) int {
// Skip the coinbase since it has no inputs.
var numSpent int
for _, tx := range block.MsgBlock().Transactions[1:] {
numSpent += len(tx.TxIn)
}
return numSpent
}
// countSpentStakeOutputs returns the number of utxos the stake transactions in
// the passed block spend.
func countSpentStakeOutputs(block *dcrutil.Block, isTreasuryEnabled bool) int {
var numSpent int
for _, stx := range block.MsgBlock().STransactions {
// Exclude the vote stakebase since it has no input.
if stake.IsSSGen(stx, isTreasuryEnabled) {
numSpent++
continue
}
// Exclude TreasuryBase and TSpend.
if stake.IsTreasuryBase(stx) || stake.IsTSpend(stx) {
continue
}
numSpent += len(stx.TxIn)
}
return numSpent
}
// countSpentOutputs returns the number of utxos the passed block spends.
func countSpentOutputs(block *dcrutil.Block, isTreasuryEnabled bool) int {
return countSpentRegularOutputs(block) +
countSpentStakeOutputs(block, isTreasuryEnabled)
}
// loadOrCreateFilter attempts to load and return the version 2 GCS filter for
// the given block from the database and falls back to creating a new one in
// the case one has not previously been stored.
func (b *BlockChain) loadOrCreateFilter(block *dcrutil.Block, view *UtxoViewpoint) (*gcs.FilterV2, error) {
// Attempt to load and return the version 2 block filter for the given block
// from the database.
var filter *gcs.FilterV2
err := b.db.View(func(dbTx database.Tx) error {
var err error
filter, err = dbFetchGCSFilter(dbTx, block.Hash())
return err
})
if err != nil {
return nil, err
}
if filter != nil {
return filter, nil
}
// At this point the version 2 block filter has not been stored in the
// database for the block, so create and return one.
filter, err = blockcf2.Regular(block.MsgBlock(), view)
if err != nil {
return nil, ruleError(ErrMissingTxOut, err.Error())
}
return filter, nil
}
// reorganizeChainInternal attempts to reorganize the block chain to the given
// target without attempting to undo failed reorgs.
//
// The actions needed to reorganize the chain to the given target fall into
// three main cases:
//
// 1. The target is a descendant of the current best chain tip (most common)
// 2. The target is an ancestor of the current best chain tip (least common)
// 3. The target is neither of the above which means it is on another branch
// and that branch forks from the main chain at some ancestor of the current
// best chain tip
//
// For the first case, the blocks between the current best chain tip and the
// given target need to be connected (think pushed onto the end of the chain).
//
// For the second case, the blocks between the current best chain tip and the
// given target need to be disconnected in reverse order (think popped off the
// end of chain).
//
// The third case is essentially a combination of the first two. Namely, the
// blocks between the current best chain tip and the fork point between it and
// the given target need to be disconnected in reverse order and then the blocks
// between that fork point and the given target (aka the blocks that form the
// new branch) need to be connected in forwards order.
//
// This function may modify the validation state of nodes in the block index
// without flushing in the case the chain is not able to reorganize due to a
// block failing to connect.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) reorganizeChainInternal(target *blockNode) error {
// Find the fork point between the current tip and target block.
tip := b.bestChain.Tip()
fork := b.bestChain.FindFork(target)
// Disconnect all of the blocks back to the point of the fork. This entails
// loading the blocks and their associated spent txos from the database and
// using that information to unspend all of the spent txos and remove the
// utxos created by the blocks. In addition, if a block votes against its
// parent, the regular transactions are reconnected.
view := NewUtxoViewpoint(b.utxoCache)
view.SetBestHash(&tip.hash)
var nextBlockToDetach *dcrutil.Block
for tip != nil && tip != fork {
select {
case <-b.interrupt:
return errInterruptRequested
default:
}
// Grab the block to detach based on the node. Use the fact that the
// blocks are being detached in reverse order, so the parent of the
// current block being detached is the next one being detached.
n := tip
block := nextBlockToDetach
if block == nil {
var err error
block, err = b.fetchMainChainBlockByNode(n)
if err != nil {
return err
}
}
if n.hash != *block.Hash() {
panicf("detach block node hash %v (height %v) does not match "+
"previous parent block hash %v", &n.hash, n.height,
block.Hash())
}
// Grab the parent of the current block and also save a reference to it
// as the next block to detach so it doesn't need to be loaded again on
// the next iteration.
parent, err := b.fetchMainChainBlockByNode(n.parent)
if err != nil {
return err
}
nextBlockToDetach = parent
// Determine if treasury agenda is active.
isTreasuryEnabled, err := b.isTreasuryAgendaActive(n.parent)
if err != nil {
return err
}
// Load all of the spent txos for the block from the spend journal.
var stxos []spentTxOut
err = b.db.View(func(dbTx database.Tx) error {
stxos, err = dbFetchSpendJournalEntry(dbTx, block, isTreasuryEnabled)
return err
})
if err != nil {
return err
}
// Update the view to unspend all of the spent txos and remove the utxos
// created by the block. Also, if the block votes against its parent,
// reconnect all of the regular transactions.
err = view.disconnectBlock(block, parent, stxos, isTreasuryEnabled)
if err != nil {
return err
}
// Update the database and chain state.
err = b.disconnectBlock(n, block, parent, view)
if err != nil {
return err
}
log.Tracef("Disconnected block %s (height %d) from main chain", n.hash,
n.height)
tip = n.parent
}
// Determine the blocks to attach after the fork point. Each block is added
// to the slice from back to front so they are attached in the appropriate
// order when iterating the slice below.
attachNodes := make([]*blockNode, target.height-fork.height)
for n := target; n != nil && n != fork; n = n.parent {
attachNodes[n.height-fork.height-1] = n
}
// Load the fork block if there are blocks to attach and its not already
// loaded which will be the case if no nodes were detached. The fork block
// is used as the parent to the first node to be attached below.
forkBlock := nextBlockToDetach
if len(attachNodes) > 0 && forkBlock == nil {
var err error
forkBlock, err = b.fetchMainChainBlockByNode(tip)
if err != nil {
return err
}
}
// Attempt to connect each block that needs to be attached to the main
// chain. This entails performing several checks to verify each block can
// be connected without violating any consensus rules and updating the
// relevant information related to the current chain state.
var prevBlockAttached *dcrutil.Block
for i, n := range attachNodes {
select {
case <-b.interrupt:
return errInterruptRequested
default:
}
// Grab the block to attach based on the node. Use the fact that the
// parent of the block is either the fork point for the first node being
// attached or the previous one that was attached for subsequent blocks
// to optimize.
block, err := b.fetchBlockByNode(n)
if err != nil {
return err
}
parent := forkBlock
if i > 0 {
parent = prevBlockAttached
}
if n.parent.hash != *parent.Hash() {
panicf("attach block node hash %v (height %v) parent hash %v does "+
"not match previous parent block hash %v", &n.hash, n.height,
&n.parent.hash, parent.Hash())
}
// Store the loaded block as parent of next iteration.
prevBlockAttached = block
// Determine if treasury agenda is active.
isTreasuryEnabled, err := b.isTreasuryAgendaActive(n.parent)
if err != nil {
return err
}
// Skip validation if the block has already been validated. However,
// the utxo view still needs to be updated and the stxos and header
// commitment data are still needed.
numSpentOutputs := countSpentOutputs(block, isTreasuryEnabled)
stxos := make([]spentTxOut, 0, numSpentOutputs)
var hdrCommitments headerCommitmentData
if b.index.NodeStatus(n).HasValidated() {
// Update the view to mark all utxos referenced by the block as
// spent and add all transactions being created by this block to it.
// In the case the block votes against the parent, also disconnect
// all of the regular transactions in the parent block. Finally,
// provide an stxo slice so the spent txout details are generated.
err := view.connectBlock(b.db, block, parent, &stxos,
isTreasuryEnabled)
if err != nil {
return err
}
filter, err := b.loadOrCreateFilter(block, view)
if err != nil {
return err
}
hdrCommitments.filter = filter
} else {
// The block must pass all of the validation rules which depend on
// having the full block data for all of its ancestors available.
if err := b.checkBlockContext(block, n.parent, BFNone); err != nil {
var rerr RuleError
if errors.As(err, &rerr) {
b.index.MarkBlockFailedValidation(n)
}
return err
}
// Mark the block as recently checked to avoid checking it again
// when processing.
b.recentContextChecks.Add(n.hash)
// In the case the block is determined to be invalid due to a rule
// violation, mark it as invalid and mark all of its descendants as
// having an invalid ancestor.
err = b.checkConnectBlock(n, block, parent, view, &stxos,
&hdrCommitments)
if err != nil {
var rerr RuleError
if errors.As(err, &rerr) {
b.index.MarkBlockFailedValidation(n)
}
return err
}
b.index.SetStatusFlags(n, statusValidated)
}
// Update the database and chain state.
err = b.connectBlock(n, block, parent, view, stxos, &hdrCommitments)
if err != nil {
return err
}
log.Tracef("Connected block %s (height %d) to main chain", n.hash,
n.height)
// Remove any best chain candidates that have less work than the new
// tip.
b.index.RemoveLessWorkCandidates(n)
}
return nil
}
// reorganizeChain attempts to reorganize the block chain to the given target
// with additional handling for failed reorgs.
//
// When the given target is already known to be invalid, or is determined to be
// invalid during the process, the chain will be reorganized to the best valid
// block as determined by having the most cumulative proof of work instead.
//
// This is most commonly called with a target that is a descendant of the
// current best chain. However, it supports arbitrary targets.
//
// See reorganizeChainInternal for more details on the various actions needed to
// reorganize the chain.
//
// This function may modify the validation state of nodes in the block index
// without flushing.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) reorganizeChain(target *blockNode) error {
// Nothing to do if there is no target specified or it is already the
// current best chain tip.
tip := b.bestChain.Tip()
if target == nil || tip == target {
return nil
}
origTip := tip
var sentReorgingNtfn bool
var reorgErrs []error
for ; target != nil && tip != target; tip = b.bestChain.Tip() {
select {
case <-b.interrupt:
return errInterruptRequested
default:
}
// Determine if the chain is being reorganized to a competing branch.
// This is the case when the current tip is not an ancestor of the
// target tip.
if !sentReorgingNtfn && target.Ancestor(tip.height) != tip {
// Send a notification announcing the start of the chain
// reorganization.
//
// Notice that the chain lock is not released before sending the
// notification. This is intentional and must not be changed
// without understanding why!
b.sendNotification(NTChainReorgStarted, nil)
sentReorgingNtfn = true
defer func() {
// Send a notification announcing the end of the chain
// reorganization.
//
// Notice that the chain lock is not released before sending the
// notification. This is intentional and must not be changed
// without understanding why!
b.sendNotification(NTChainReorgDone, nil)
}()
}
// Attempt to reorganize the chain to the new tip. In the case it
// fails, attempt to reorganize to the best valid block with the most
// cumulative proof of work instead.
err := b.reorganizeChainInternal(target)
if err != nil {
// Shutting down.
if errors.Is(err, errInterruptRequested) {
return err
}
// Typically, if a reorganize fails, there will only be a single
// error due to the block that caused the failure. However, it is
// possible that several candidate branches might fail. Thus, track
// them all so they can potentially be converted to a multi error
// later if needed.
reorgErrs = append(reorgErrs, err)
// Determine a new best candidate since the reorg failed. This
// should realistically always result in a different target than the
// current one unless there is some type of unrecoverable error,
// such as a disk failure. In that case, bail out to avoid
// attempting to do the same reorg over and over.
newTarget := b.index.FindBestChainCandidate()
if newTarget == target {
break
}
target = newTarget
}
}
// Potentially update whether or not the chain believes it is current based
// on the new tip. Notice that the tip is reset to whatever the best chain
// actually is here versus using the one from above since it might not match
// reality if there were errors while reorganizing.
newTip := b.bestChain.Tip()
wasLatched := b.isCurrentLatch
b.maybeUpdateIsCurrent(newTip)
// If the chain just latched to current, force the UTXO cache to flush to the
// database. This ensures that the full UTXO set is always flushed to the
// database when the chain becomes current, which allows for fetching
// up-to-date UTXO set stats.
if !wasLatched && b.isCurrentLatch {
err := b.utxoCache.MaybeFlush(&newTip.hash, uint32(newTip.height), true,
true)
if err != nil {
return err
}
}
// Log chain reorganizations and send a notification as needed.
if sentReorgingNtfn && newTip != origTip {
// Send a notification that a chain reorganization took place.
//
// Notice that the chain lock is not released before sending the
// notification. This is intentional and must not be changed without
// understanding why!
b.sendNotification(NTReorganization, &ReorganizationNtfnsData{
OldHash: origTip.hash,
OldHeight: origTip.height,
NewHash: newTip.hash,
NewHeight: newTip.height,
})
// Log the point where the chain forked and old and new best chain tips.
if fork := b.bestChain.FindFork(origTip); fork != nil {
log.Infof("REORGANIZE: Chain forks at %v (height %v)", fork.hash,
fork.height)
}
log.Infof("REORGANIZE: Old best chain tip was %v (height %v)",
&origTip.hash, origTip.height)
log.Infof("REORGANIZE: New best chain tip is %v (height %v)",
&newTip.hash, newTip.height)
}
// Determine if there were any reorg errors and either extract and return
// the error directly when there was only a single error or return them all
// as a multi error when there are more.
var finalErr error
switch {
case len(reorgErrs) == 1:
finalErr = reorgErrs[0]
case len(reorgErrs) > 1:
finalErr = MultiError(reorgErrs)
}
return finalErr
}
// forceHeadReorganization forces a reorganization of the block chain to the
// block hash requested, so long as it matches up with the current organization
// of the best chain.
//
// This function may modify the validation state of nodes in the block index
// without flushing.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) forceHeadReorganization(formerBest chainhash.Hash, newBest chainhash.Hash) error {
// Don't try to reorganize to the same block.
if formerBest == newBest {
str := "tried to force reorg to the same block"
return ruleError(ErrForceReorgSameBlock, str)
}
// Don't allow a reorganize when the former best is not the current best
// chain tip.
formerBestNode := b.bestChain.Tip()
if formerBestNode.hash != formerBest {
str := "tried to force reorg on wrong chain"
return ruleError(ErrForceReorgWrongChain, str)
}
// Child to reorganize to is missing.
newBestNode := b.index.LookupNode(&newBest)
if newBestNode == nil || newBestNode.parent != formerBestNode.parent {
str := "missing child of common parent for forced reorg"
return ruleError(ErrForceReorgMissingChild, str)
}
// Don't allow a reorganize to a known invalid chain.
newBestNodeStatus := b.index.NodeStatus(newBestNode)
if newBestNodeStatus.KnownInvalid() {
str := "block is known to be invalid"
return ruleError(ErrKnownInvalidBlock, str)
}
// Don't try to reorganize to a block when its data is not available.
if !newBestNodeStatus.HaveData() {
return ruleError(ErrNoBlockData, "block data is not available")
}
// Reorganize the chain and flush any potential unsaved changes to the
// block index to the database. It is safe to ignore any flushing
// errors here as the only time the index will be modified is if the
// block failed to connect.
err := b.reorganizeChain(newBestNode)
b.flushBlockIndexWarnOnly()
return err
}
// ForceHeadReorganization forces a reorganization of the block chain to the
// block hash requested, so long as it matches up with the current organization
// of the best chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) ForceHeadReorganization(formerBest chainhash.Hash, newBest chainhash.Hash) error {
b.processLock.Lock()
b.chainLock.Lock()
err := b.forceHeadReorganization(formerBest, newBest)
b.chainLock.Unlock()
b.processLock.Unlock()
return err
}
// flushBlockIndex populates any ticket data that has been pruned from modified
// block nodes, writes those nodes to the database and clears the set of
// modified nodes if it succeeds.
//
// This function MUST be called with the chain lock held (for writes).
func (b *BlockChain) flushBlockIndex() error {
// Ensure that any ticket information that has been pruned is reloaded
// before flushing modified nodes.
//
// Note that a separate slice is created for the modified nodes that
// potentially need the ticket information reloaded as opposed to doing it
// directly in the loop over the modified nodes because reloading the ticket
// information is shared code that locks the index to mark the entry
// modified. Therefore, it has to be called without the index lock.
b.index.RLock()
maybePruned := make([]*blockNode, 0, len(b.index.modified))
for node := range b.index.modified {
if !b.index.canValidate(node) {
continue
}
maybePruned = append(maybePruned, node)
}
b.index.RUnlock()
for _, node := range maybePruned {
if err := b.maybeFetchTicketInfo(node); err != nil {
return err
}
}
return b.index.flush()
}
// flushBlockIndexWarnOnly attempts to flush any modified block index nodes to
// the database and will log a warning if it fails.
//
// NOTE: This MUST only be used in the specific circumstances where failure to
// flush only results in a worst case scenario of requiring one or more blocks
// to be validated again. All other cases must directly call the function on
// the block index and check the error return accordingly.
//
// This function MUST be called with the chain lock held (for writes).
func (b *BlockChain) flushBlockIndexWarnOnly() {
if err := b.flushBlockIndex(); err != nil {
log.Warnf("Unable to flush block index changes to db: %v", err)
}
}
// isOldTimestamp returns whether the given node has a timestamp too far in
// history for the purposes of determining if the chain should be considered
// current.
func (b *BlockChain) isOldTimestamp(node *blockNode) bool {
minus24Hours := b.timeSource.AdjustedTime().Add(-24 * time.Hour).Unix()
return node.timestamp < minus24Hours
}
// maybeUpdateIsCurrent potentially updates whether or not the chain believes it
// is current using the provided best chain tip.
//
// It makes use of a latching approach such that once the chain becomes current
// it will only switch back to false in the case no new blocks have been seen
// for an extended period of time.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) maybeUpdateIsCurrent(curBest *blockNode) {
// Do some additional checks when the chain is not already latched to being
// current.
if !b.isCurrentLatch {
// Not current if the latest best block has a cumulative work less than
// the minimum known work specified by the network parameters.
minKnownWork := b.chainParams.MinKnownChainWork
if minKnownWork != nil && curBest.workSum.Cmp(minKnownWork) < 0 {
return
}
// Not current if the best block is not synced to the header with the
// most cumulative work that is not known to be invalid.
b.index.RLock()
bestHeader := b.index.bestHeader
b.index.RUnlock()
syncedToBestHeader := curBest.height == bestHeader.height ||
curBest.Ancestor(bestHeader.height) == bestHeader
if !syncedToBestHeader {
return
}
}
// Not current if the latest best block has too old of a timestamp.
//
// The chain appears to be current if none of the checks reported otherwise.
wasLatched := b.isCurrentLatch
b.isCurrentLatch = !b.isOldTimestamp(curBest)
if !wasLatched && b.isCurrentLatch {
log.Debugf("Chain latched to current at block %s (height %d)",
curBest.hash, curBest.height)
}
}
// MaybeUpdateIsCurrent potentially updates whether or not the chain believes it
// is current.
//
// It makes use of a latching approach such that once the chain becomes current
// it will only switch back to false in the case no new blocks have been seen
// for an extended period of time.
//
// This function is safe for concurrent access.
func (b *BlockChain) MaybeUpdateIsCurrent() {
b.chainLock.Lock()
b.maybeUpdateIsCurrent(b.bestChain.Tip())
b.chainLock.Unlock()
}
// isCurrent returns whether or not the chain believes it is current based on
// the current latched state and an additional check which returns false in the
// case no new blocks have been seen for an extended period of time.
//
// This function MUST be called with the chain state lock held (for reads).
func (b *BlockChain) isCurrent(curBest *blockNode) bool {
return b.isCurrentLatch && !b.isOldTimestamp(curBest)
}
// IsCurrent returns whether or not the chain believes it is current based on
// the current latched state and an additional check which returns false in the
// case no new blocks have been seen for an extended period of time.
//
// The initial factors that are used to latch the state to current are:
// - Total amount of cumulative work is more than the minimum known work
// specified by the parameters for the network
// - The best chain is synced to the header with the most cumulative work that
// is not known to be invalid
// - Latest block has a timestamp newer than 24 hours ago
//
// This function is safe for concurrent access.
func (b *BlockChain) IsCurrent() bool {
b.chainLock.RLock()
isCurrent := b.isCurrent(b.bestChain.Tip())
b.chainLock.RUnlock()
return isCurrent
}
// BestSnapshot returns information about the current best chain block and
// related state as of the current point in time. The returned instance must be
// treated as immutable since it is shared by all callers.
//
// This function is safe for concurrent access.
func (b *BlockChain) BestSnapshot() *BestState {
b.stateLock.RLock()
snapshot := b.stateSnapshot
b.stateLock.RUnlock()
return snapshot
}
// MaximumBlockSize returns the maximum permitted block size for the block
// AFTER the given node.
//
// This function MUST be called with the chain state lock held (for reads).
func (b *BlockChain) maxBlockSize(prevNode *blockNode) (int64, error) {
// Determine the correct deployment version for the block size consensus
// vote or treat it as active when voting is not enabled for the current
// network.
const deploymentID = chaincfg.VoteIDMaxBlockSize
deploymentVer, ok := b.deploymentVers[deploymentID]
if !ok {
return int64(b.chainParams.MaximumBlockSizes[0]), nil
}
// Return the larger block size if the stake vote for the max block size
// increase agenda is active.
//
// NOTE: The choice field of the return threshold state is not examined
// here because there is only one possible choice that can be active
// for the agenda, which is yes, so there is no need to check it.
maxSize := int64(b.chainParams.MaximumBlockSizes[0])
state, err := b.deploymentState(prevNode, deploymentVer, deploymentID)
if err != nil {
return maxSize, err
}
if state.State == ThresholdActive {
return int64(b.chainParams.MaximumBlockSizes[1]), nil
}
// The max block size is not changed in any other cases.
return maxSize, nil
}
// MaxBlockSize returns the maximum permitted block size for the block AFTER
// the provided block hash.
//
// This function is safe for concurrent access.
func (b *BlockChain) MaxBlockSize(hash *chainhash.Hash) (int64, error) {
node := b.index.LookupNode(hash)
if node == nil || !b.index.CanValidate(node) {
return 0, unknownBlockError(hash)
}
b.chainLock.Lock()
maxSize, err := b.maxBlockSize(node)
b.chainLock.Unlock()
return maxSize, err
}
// HeaderByHash returns the block header identified by the given hash or an
// error if it doesn't exist. Note that this will return headers from both the
// main chain and any side chains.
//
// This function is safe for concurrent access.
func (b *BlockChain) HeaderByHash(hash *chainhash.Hash) (wire.BlockHeader, error) {
node := b.index.LookupNode(hash)
if node == nil {
return wire.BlockHeader{}, unknownBlockError(hash)
}
return node.Header(), nil
}
// HeaderByHeight returns the block header at the given height in the main
// chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) HeaderByHeight(height int64) (wire.BlockHeader, error) {
node := b.bestChain.NodeByHeight(height)
if node == nil {
str := fmt.Sprintf("no block at height %d exists", height)
return wire.BlockHeader{}, errNotInMainChain(str)
}
return node.Header(), nil
}
// BlockByHash searches the internal chain block stores and the database in an
// attempt to find the requested block and returns it. This function returns
// blocks regardless of whether or not they are part of the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockByHash(hash *chainhash.Hash) (*dcrutil.Block, error) {
node := b.index.LookupNode(hash)
if node == nil || !b.index.NodeStatus(node).HaveData() {
return nil, unknownBlockError(hash)
}
// Return the block from either cache or the database.
return b.fetchBlockByNode(node)
}
// BlockByHeight returns the block at the given height in the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockByHeight(height int64) (*dcrutil.Block, error) {
// Lookup the block height in the best chain.
node := b.bestChain.NodeByHeight(height)
if node == nil {
str := fmt.Sprintf("no block at height %d exists", height)
return nil, errNotInMainChain(str)
}
// Return the block from either cache or the database. Note that this is
// not using fetchMainChainBlockByNode since the main chain check has
// already been done.
return b.fetchBlockByNode(node)
}
// MainChainHasBlock returns whether or not the block with the given hash is in
// the main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) MainChainHasBlock(hash *chainhash.Hash) bool {
node := b.index.LookupNode(hash)
return node != nil && b.bestChain.Contains(node)
}
// MedianTimeByHash returns the median time of a block by the given hash or an
// error if it doesn't exist. Note that this will return times from both the
// main chain and any side chains.
//
// This function is safe for concurrent access.
func (b *BlockChain) MedianTimeByHash(hash *chainhash.Hash) (time.Time, error) {
node := b.index.LookupNode(hash)
if node == nil {
return time.Time{}, unknownBlockError(hash)
}
return node.CalcPastMedianTime(), nil
}
// BlockHeightByHash returns the height of the block with the given hash in the
// main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockHeightByHash(hash *chainhash.Hash) (int64, error) {
node := b.index.LookupNode(hash)
if node == nil || !b.bestChain.Contains(node) {
str := fmt.Sprintf("block %s is not in the main chain", hash)
return 0, errNotInMainChain(str)
}
return node.height, nil
}
// BlockHashByHeight returns the hash of the block at the given height in the
// main chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockHashByHeight(height int64) (*chainhash.Hash, error) {
node := b.bestChain.NodeByHeight(height)
if node == nil {
str := fmt.Sprintf("no block at height %d exists", height)
return nil, errNotInMainChain(str)
}
return &node.hash, nil
}
// HeightRange returns a range of block hashes for the given start and end
// heights. It is inclusive of the start height and exclusive of the end
// height. In other words, it is the half open range [startHeight, endHeight).
//
// The end height will be limited to the current main chain height.
//
// This function is safe for concurrent access.
func (b *BlockChain) HeightRange(startHeight, endHeight int64) ([]chainhash.Hash, error) {
// Ensure requested heights are sane.
if startHeight < 0 {
return nil, fmt.Errorf("start height of fetch range must not "+
"be less than zero - got %d", startHeight)
}
if endHeight < startHeight {
return nil, fmt.Errorf("end height of fetch range must not "+
"be less than the start height - got start %d, end %d",
startHeight, endHeight)
}
// There is nothing to do when the start and end heights are the same,
// so return now to avoid extra work.
if startHeight == endHeight {
return nil, nil
}
// When the requested start height is after the most recent best chain
// height, there is nothing to do.
latestHeight := b.bestChain.Tip().height
if startHeight > latestHeight {
return nil, nil
}
// Limit the ending height to the latest height of the chain.
if endHeight > latestHeight+1 {
endHeight = latestHeight + 1
}
// Fetch as many as are available within the specified range.
hashes := make([]chainhash.Hash, endHeight-startHeight)
iterNode := b.bestChain.NodeByHeight(endHeight - 1)
for i := startHeight; i < endHeight; i++ {
// Since the desired result is from the starting node to the
// ending node in forward order, but they are iterated in
// reverse, add them in reverse order.
hashes[endHeight-i-1] = iterNode.hash
iterNode = iterNode.parent
}
return hashes, nil
}
// locateInventory returns the node of the block after the first known block in
// the locator along with the number of subsequent nodes needed to either reach
// the provided stop hash or the provided max number of entries.
//
// In addition, there are two special cases:
//
// - When no locators are provided, the stop hash is treated as a request for
// that block, so it will either return the node associated with the stop hash
// if it is known, or nil if it is unknown
// - When locators are provided, but none of them are known, nodes starting
// after the genesis block will be returned
//
// This is primarily a helper function for the locateBlocks and locateHeaders
// functions.
//
// This function MUST be called with the chain state lock held (for reads).
func (b *BlockChain) locateInventory(locator BlockLocator, hashStop *chainhash.Hash, maxEntries uint32) (*blockNode, uint32) {
// There are no block locators so a specific block is being requested
// as identified by the stop hash.
stopNode := b.index.LookupNode(hashStop)
if len(locator) == 0 {
if stopNode == nil {
// No blocks with the stop hash were found so there is
// nothing to do.
return nil, 0
}
return stopNode, 1
}
// Find the most recent locator block hash in the main chain. In the
// case none of the hashes in the locator are in the main chain, fall
// back to the genesis block.
startNode := b.bestChain.Genesis()
for _, hash := range locator {
node := b.index.LookupNode(hash)
if node != nil && b.bestChain.Contains(node) {
startNode = node
break
}
}
// Start at the block after the most recently known block. When there
// is no next block it means the most recently known block is the tip of
// the best chain, so there is nothing more to do.
startNode = b.bestChain.Next(startNode)
if startNode == nil {
return nil, 0
}
// Calculate how many entries are needed.
total := uint32((b.bestChain.Tip().height - startNode.height) + 1)
if stopNode != nil && b.bestChain.Contains(stopNode) &&
stopNode.height >= startNode.height {
total = uint32((stopNode.height - startNode.height) + 1)
}
if total > maxEntries {
total = maxEntries
}
return startNode, total
}
// locateBlocks returns the hashes of the blocks after the first known block in
// the locator until the provided stop hash is reached, or up to the provided
// max number of block hashes.
//
// See the comment on the exported function for more details on special cases.
//
// This function MUST be called with the chain state lock held (for reads).
func (b *BlockChain) locateBlocks(locator BlockLocator, hashStop *chainhash.Hash, maxHashes uint32) []chainhash.Hash {
// Find the node after the first known block in the locator and the
// total number of nodes after it needed while respecting the stop hash
// and max entries.
node, total := b.locateInventory(locator, hashStop, maxHashes)
if total == 0 {
return nil
}
// Populate and return the found hashes.
hashes := make([]chainhash.Hash, 0, total)
for i := uint32(0); i < total; i++ {
hashes = append(hashes, node.hash)
node = b.bestChain.Next(node)
}
return hashes
}
// LocateBlocks returns the hashes of the blocks after the first known block in
// the locator until the provided stop hash is reached, or up to the provided
// max number of block hashes.
//
// In addition, there are two special cases:
//
// - When no locators are provided, the stop hash is treated as a request for
// that block, so it will either return the stop hash itself if it is known,
// or nil if it is unknown
// - When locators are provided, but none of them are known, hashes starting
// after the genesis block will be returned
//
// This function is safe for concurrent access.
func (b *BlockChain) LocateBlocks(locator BlockLocator, hashStop *chainhash.Hash, maxHashes uint32) []chainhash.Hash {
b.chainLock.RLock()
hashes := b.locateBlocks(locator, hashStop, maxHashes)
b.chainLock.RUnlock()
return hashes
}
// locateHeaders returns the headers of the blocks after the first known block
// in the locator until the provided stop hash is reached, or up to the provided
// max number of block headers.
//
// See the comment on the exported function for more details on special cases.
//
// This function MUST be called with the chain state lock held (for reads).
func (b *BlockChain) locateHeaders(locator BlockLocator, hashStop *chainhash.Hash, maxHeaders uint32) []wire.BlockHeader {
// Find the node after the first known block in the locator and the
// total number of nodes after it needed while respecting the stop hash
// and max entries.
node, total := b.locateInventory(locator, hashStop, maxHeaders)
if total == 0 {
return nil
}
// Populate and return the found headers.
headers := make([]wire.BlockHeader, 0, total)
for i := uint32(0); i < total; i++ {
headers = append(headers, node.Header())
node = b.bestChain.Next(node)
}
return headers
}
// LocateHeaders returns the headers of the blocks after the first known block
// in the locator until the provided stop hash is reached, or up to a max of
// wire.MaxBlockHeadersPerMsg headers.
//
// In addition, there are two special cases:
//
// - When no locators are provided, the stop hash is treated as a request for
// that header, so it will either return the header for the stop hash itself
// if it is known, or nil if it is unknown
// - When locators are provided, but none of them are known, headers starting
// after the genesis block will be returned
//
// This function is safe for concurrent access.
func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Hash) []wire.BlockHeader {
b.chainLock.RLock()
headers := b.locateHeaders(locator, hashStop, wire.MaxBlockHeadersPerMsg)
b.chainLock.RUnlock()
return headers
}
// BlockLocatorFromHash returns a block locator for the passed block hash.
// See BlockLocator for details on the algorithm used to create a block locator.
//
// In addition to the general algorithm referenced above, this function will
// return the block locator for the latest known tip of the main (best) chain if
// the passed hash is not currently known.
//
// This function is safe for concurrent access.
func (b *BlockChain) BlockLocatorFromHash(hash *chainhash.Hash) BlockLocator {
b.chainLock.RLock()
node := b.index.LookupNode(hash)
locator := b.bestChain.BlockLocator(node)
b.chainLock.RUnlock()
return locator
}
// LatestBlockLocator returns a block locator for the latest known tip of the
// main (best) chain.
//
// This function is safe for concurrent access.
func (b *BlockChain) LatestBlockLocator() BlockLocator {
b.chainLock.RLock()
locator := b.bestChain.BlockLocator(nil)
b.chainLock.RUnlock()
return locator
}
// extractDeploymentIDVersions returns a map of all deployment IDs within the
// provided params to the deployment version for which they are defined. An
// error is returned if a duplicate ID is encountered.
func extractDeploymentIDVersions(params *chaincfg.Params) (map[string]uint32, error) {
// Generate a deployment ID to version map from the provided params.
deploymentVers := make(map[string]uint32)
for version, deployments := range params.Deployments {
for _, deployment := range deployments {
id := deployment.Vote.Id
if _, ok := deploymentVers[id]; ok {
str := fmt.Sprintf("deployment ID %s exists in more than one "+
"deployment", id)
return nil, contextError(ErrDuplicateDeployment, str)
}
deploymentVers[id] = version
}
}
return deploymentVers, nil
}
// stxosToScriptSource uses the provided block and spent txo information to
// create a source of previous transaction scripts and versions spent by the
// block.
func stxosToScriptSource(block *dcrutil.Block, stxos []spentTxOut, isTreasuryEnabled bool, chainParams *chaincfg.Params) scriptSource {
source := make(scriptSource)
msgBlock := block.MsgBlock()
// TSpends can only be added to TVI blocks so don't look for them
// except in those blocks.
isTVI := standalone.IsTreasuryVoteInterval(uint64(msgBlock.Header.Height),
chainParams.TreasuryVoteInterval)
// Loop through all of the transaction inputs in the stake transaction
// tree (except for the stakebases, treasurybases and treasuryspends
// which have no inputs) and add the scripts and associated script
// versions from the referenced txos to the script source.
//
// Note that transactions in the stake tree are spent before transactions in
// the regular tree when originally creating the spend journal entry, thus
// the spent txous need to be processed in the same order.
var stxoIdx int
for i, tx := range msgBlock.STransactions {
// Ignore treasury base and tspends since they have no inputs.
isTreasuryBase := isTreasuryEnabled && i == 0
isTSpend := isTreasuryEnabled && i > 0 && isTVI && stake.IsTSpend(tx)
if isTreasuryBase || isTSpend {
continue
}
isVote := stake.IsSSGen(tx, isTreasuryEnabled)
for txInIdx, txIn := range tx.TxIn {
// Ignore stakebase since it has no input.
if isVote && txInIdx == 0 {
continue
}
// Ensure the spent txout index is incremented to stay in sync with
// the transaction input.
stxo := &stxos[stxoIdx]
stxoIdx++
// Create an output for the referenced script and version using the
// stxo data from the spend journal if it doesn't already exist in
// the view.
prevOut := &txIn.PreviousOutPoint
source[*prevOut] = scriptSourceEntry{
version: stxo.scriptVersion,
script: stxo.pkScript,
}
}
}
// Loop through all of the transaction inputs in the regular transaction
// tree (except for the coinbase which has no inputs) and add the scripts
// and associated script versions from the referenced txos to the script
// source.
for _, tx := range msgBlock.Transactions[1:] {
for _, txIn := range tx.TxIn {
// Ensure the spent txout index is incremented to stay in sync with
// the transaction input.
stxo := &stxos[stxoIdx]
stxoIdx++
// Create an output for the referenced script and version using the
// stxo data from the spend journal if it doesn't already exist in
// the view.
prevOut := &txIn.PreviousOutPoint
source[*prevOut] = scriptSourceEntry{
version: stxo.scriptVersion,
script: stxo.pkScript,
}
}
}
return source
}
// chainQueryerAdapter provides an adapter from a BlockChain instance to the
// indexers.ChainQueryer interface.
type chainQueryerAdapter struct {
*BlockChain
}
// BestHeight returns the height of the current best block. It is equivalent to
// the Height field of the BestSnapshot method, however, it is needed to satisfy
// the indexers.ChainQueryer interface.
//
// It is defined via a separate internal struct to avoid polluting the public
// API of the BlockChain type itself.
func (q *chainQueryerAdapter) BestHeight() int64 {
return q.BestSnapshot().Height
}
// IsTreasuryEnabled returns true if the treasury agenda is enabled as of the
// provided block.
func (q *chainQueryerAdapter) IsTreasuryEnabled(hash *chainhash.Hash) (bool, error) {
return q.IsTreasuryAgendaActive(hash)
}
// PrevScripts returns a source of previous transaction scripts and their
// associated versions spent by the given block by using the spend journal.
//
// It is defined via a separate internal struct to avoid polluting the public
// API of the BlockChain type itself.
//
// This is part of the indexers.ChainQueryer interface.
func (q *chainQueryerAdapter) PrevScripts(dbTx database.Tx, block *dcrutil.Block) (indexers.PrevScripter, error) {
prevHash := &block.MsgBlock().Header.PrevBlock
isTreasuryEnabled, err := q.IsTreasuryAgendaActive(prevHash)
if err != nil {
return nil, err
}
// Load all of the spent transaction output data from the database.
stxos, err := dbFetchSpendJournalEntry(dbTx, block, isTreasuryEnabled)
if err != nil {
return nil, err
}
prevScripts := stxosToScriptSource(block, stxos, isTreasuryEnabled,
q.chainParams)
return prevScripts, nil
}
// Config is a descriptor which specifies the blockchain instance configuration.
type Config struct {
// DB defines the database which houses the blocks and will be used to
// store all metadata created by this package outside of the UTXO set, which
// is stored in a separate database.
//
// This field is required.
DB database.DB
// UtxoBackend defines the backend which houses the UTXO set.
//
// This field is required.
UtxoBackend UtxoBackend
// ChainParams identifies which chain parameters the chain is associated
// with.
//
// This field is required.
ChainParams *chaincfg.Params
// Checkpoints specifies caller-defined checkpoints that are typically the
// default checkpoints in ChainParams or additional checkpoints added to
// them. Checkpoints must be sorted by height.
//
// This field can be nil if the caller does not wish to specify any
// checkpoints.
Checkpoints []chaincfg.Checkpoint
// TimeSource defines the median time source to use for things such as
// block processing and determining whether or not the chain is current.
//
// The caller is expected to keep a reference to the time source as well
// and add time samples from other peers on the network so the local
// time is adjusted to be in agreement with other peers.
TimeSource MedianTimeSource
// Notifications defines a callback to which notifications will be sent
// when various events take place. See the documentation for
// Notification and NotificationType for details on the types and
// contents of notifications.
//
// This field can be nil if the caller is not interested in receiving
// notifications.
Notifications NotificationCallback
// SigCache defines a signature cache to use when validating signatures.
// This is typically most useful when individual transactions are
// already being validated prior to their inclusion in a block such as
// what is usually done via a transaction memory pool.
//
// This field can be nil if the caller is not interested in using a
// signature cache.
SigCache *txscript.SigCache
// SubsidyCache defines a subsidy cache to use when calculating and
// validating block and vote subsidies.
//
// This field can be nil if the caller is not interested in using a
// subsidy cache.
SubsidyCache *standalone.SubsidyCache
// IndexManager defines an index manager to use when initializing the
// chain and connecting and disconnecting blocks.
//
// This field can be nil if the caller does not wish to make use of an
// index manager.
IndexManager indexers.IndexManager
// UtxoCache defines a utxo cache that sits on top of the utxo set database.
// All utxo reads and writes go through the cache, and never read or write to
// the database directly.
//
// This field is required.
UtxoCache UtxoCacher
}
// New returns a BlockChain instance using the provided configuration details.
func New(ctx context.Context, config *Config) (*BlockChain, error) {
// Enforce required config fields.
if config.DB == nil {
return nil, AssertError("blockchain.New database is nil")
}
if config.UtxoBackend == nil {
return nil, AssertError("blockchain.New UTXO backend is nil")
}
if config.ChainParams == nil {
return nil, AssertError("blockchain.New chain parameters nil")
}
// Generate a checkpoint by height map from the provided checkpoints.
params := config.ChainParams
var checkpointsByHeight map[int64]*chaincfg.Checkpoint
var prevCheckpointHeight int64
if len(config.Checkpoints) > 0 {
checkpointsByHeight = make(map[int64]*chaincfg.Checkpoint)
for i := range config.Checkpoints {
checkpoint := &config.Checkpoints[i]
if checkpoint.Height <= prevCheckpointHeight {
return nil, AssertError("blockchain.New checkpoints are not " +
"sorted by height")
}
checkpointsByHeight[checkpoint.Height] = checkpoint
prevCheckpointHeight = checkpoint.Height
}
}
// Generate a deployment ID to version map from the provided params.
deploymentVers, err := extractDeploymentIDVersions(params)
if err != nil {
return nil, err
}
// Either use the subsidy cache provided by the caller or create a new
// one when one was not provided.
subsidyCache := config.SubsidyCache
if subsidyCache == nil {
subsidyCache = standalone.NewSubsidyCache(params)
}
b := BlockChain{
checkpoints: config.Checkpoints,
checkpointsByHeight: checkpointsByHeight,
deploymentVers: deploymentVers,
db: config.DB,
chainParams: params,
timeSource: config.TimeSource,
notifications: config.Notifications,
sigCache: config.SigCache,
indexManager: config.IndexManager,
interrupt: ctx.Done(),
subsidyCache: subsidyCache,
index: newBlockIndex(config.DB),
bestChain: newChainView(nil),
recentBlocks: lru.NewKVCache(recentBlockCacheSize),
recentContextChecks: lru.NewCache(contextCheckCacheSize),
deploymentCaches: newThresholdCaches(params),
isVoterMajorityVersionCache: make(map[[stakeMajorityCacheKeySize]byte]bool),
isStakeMajorityVersionCache: make(map[[stakeMajorityCacheKeySize]byte]bool),
calcPriorStakeVersionCache: make(map[[chainhash.HashSize]byte]uint32),
calcVoterVersionIntervalCache: make(map[[chainhash.HashSize]byte]uint32),
calcStakeVersionCache: make(map[[chainhash.HashSize]byte]uint32),
utxoCache: config.UtxoCache,
}
b.pruner = newChainPruner(&b)
// Initialize the chain state from the passed database. When the db
// does not yet contain any chain state, both it and the chain state
// will be initialized to contain only the genesis block.
if err := b.initChainState(ctx, config.UtxoBackend); err != nil {
return nil, err
}
// Initialize the UTXO state. This entails running any database migrations as
// necessary as well as initializing the UTXO cache.
if err := b.utxoCache.Initialize(ctx, &b, b.bestChain.tip()); err != nil {
return nil, err
}
// Initialize and catch up all of the currently active optional indexes
// as needed.
queryAdapter := chainQueryerAdapter{BlockChain: &b}
if config.IndexManager != nil {
err := config.IndexManager.Init(ctx, &queryAdapter)
if err != nil {
return nil, err
}
}
log.Infof("Blockchain database version info: chain: %d, compression: "+
"%d, block index: %d, spend journal: %d", b.dbInfo.version,
b.dbInfo.compVer, b.dbInfo.bidxVer, b.dbInfo.stxoVer)
// Fetch and log the UTXO backend versioning info.
utxoDbInfo, err := config.UtxoBackend.FetchInfo()
if err != nil {
return nil, err
}
log.Infof("UTXO database version info: version: %d, compression: %d, utxo "+
"set: %d", utxoDbInfo.version, utxoDbInfo.compVer, utxoDbInfo.utxoVer)
b.index.RLock()
bestHdr := b.index.bestHeader
b.index.RUnlock()
log.Infof("Best known header: height %d, hash %v", bestHdr.height,
bestHdr.hash)
tip := b.bestChain.Tip()
log.Infof("Chain state: height %d, hash %v, total transactions %d, work "+
"%v, progress %0.2f%%", tip.height, tip.hash,
b.stateSnapshot.TotalTxns, tip.workSum, b.VerifyProgress())
return &b, nil
}