dcrd/blockchain/blockindex.go
Dave Collins 68fc0c5d4e
blockchain: Consolidate best header access.
This adds an exported method to the block index to retrieve the best
header node under the block index mutex and updates the various call
sites that do it manually to use the newly exported function instead.
2022-05-13 02:13:25 -05:00

1414 lines
53 KiB
Go

// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2018-2022 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 (
"bytes"
"encoding/binary"
"math/big"
"sort"
"sync"
"time"
"github.com/decred/dcrd/blockchain/stake/v5"
"github.com/decred/dcrd/blockchain/standalone/v2"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database/v3"
"github.com/decred/dcrd/wire"
)
// blockStatus is a bit field representing the validation state of the block.
type blockStatus byte
// The following constants specify possible status bit flags for a block.
//
// NOTE: This section specifically does not use iota since the block status is
// serialized and must be stable for long-term storage.
const (
// statusNone indicates that the block has no validation state flags set.
statusNone blockStatus = 0
// statusDataStored indicates that the block's payload is stored on disk.
statusDataStored blockStatus = 1 << 0
// statusValidated indicates that the block has been fully validated. It
// also means that all of its ancestors have also been validated.
statusValidated blockStatus = 1 << 1
// statusValidateFailed indicates that the block has failed validation.
statusValidateFailed blockStatus = 1 << 2
// statusInvalidAncestor indicates that one of the ancestors of the block
// has failed validation, thus the block is also invalid.
statusInvalidAncestor blockStatus = 1 << 3
)
const (
// cachedTipsPruneInterval is the amount of time to wait in between pruning
// the cache that tracks the most recent chain tips.
cachedTipsPruneInterval = time.Minute * 5
// cachedTipsPruneDepth is the number of blocks before the provided best
// block hint to prune cached chain tips. This value is set based on the
// target block time for the main network such that there is approximately
// one hour of chain tips cached.
cachedTipsPruneDepth = 12
)
// HaveData returns whether the full block data is stored in the database. This
// will return false for a block node where only the header is downloaded or
// stored.
func (status blockStatus) HaveData() bool {
return status&statusDataStored != 0
}
// HasValidated returns whether the block is known to have been successfully
// validated. A return value of false in no way implies the block is invalid.
// Thus, this will return false for a valid block that has not been fully
// validated yet.
//
// NOTE: A block that is known to have been validated might also be marked as
// known invalid as well if the block is manually invalidated.
func (status blockStatus) HasValidated() bool {
return status&statusValidated != 0
}
// KnownInvalid returns whether either the block itself is known to be invalid
// or to have an invalid ancestor. A return value of false in no way implies
// the block is valid or only has valid ancestors. Thus, this will return false
// for invalid blocks that have not been proven invalid yet as well as return
// false for blocks with invalid ancestors that have not been proven invalid
// yet.
//
// NOTE: A block that is known invalid might also be marked as known to have
// been successfully validated as well if the block is manually invalidated.
func (status blockStatus) KnownInvalid() bool {
return status&(statusValidateFailed|statusInvalidAncestor) != 0
}
// KnownInvalidAncestor returns whether the block is known to have an invalid
// ancestor. A return value of false in no way implies the block only has valid
// ancestors. Thus, this will return false for blocks with invalid ancestors
// that have not been proven invalid yet.
func (status blockStatus) KnownInvalidAncestor() bool {
return status&(statusInvalidAncestor) != 0
}
// KnownValidateFailed returns whether the block is known to have failed
// validation. A return value of false in no way implies the block is valid.
// Thus, this will return false for blocks that have not been proven to fail
// validation yet.
func (status blockStatus) KnownValidateFailed() bool {
return status&(statusValidateFailed) != 0
}
// blockNode represents a block within the block chain and is primarily used to
// aid in selecting the best chain to be the main chain. The main chain is
// stored into the block database.
type blockNode struct {
// NOTE: Additions, deletions, or modifications to the order of the
// definitions in this struct should not be changed without considering
// how it affects alignment on 64-bit platforms. The current order is
// specifically crafted to result in minimal padding. There will be
// hundreds of thousands of these in memory, so a few extra bytes of
// padding adds up.
// parent is the parent block for this node.
parent *blockNode
// skipToAncestor is used to provide a skip list to significantly speed up
// traversal to ancestors deep in history.
skipToAncestor *blockNode
// hash is the hash of the block this node represents.
hash chainhash.Hash
// workSum is the total amount of work in the chain up to and including
// this node.
workSum *big.Int
// Some fields from block headers to aid in best chain selection and
// reconstructing headers from memory. These must be treated as
// immutable and are intentionally ordered to avoid padding on 64-bit
// platforms.
height int64
voteBits uint16
finalState [6]byte
blockVersion int32
voters uint16
freshStake uint8
revocations uint8
poolSize uint32
bits uint32
sbits int64
timestamp int64
merkleRoot chainhash.Hash
stakeRoot chainhash.Hash
blockSize uint32
nonce uint32
extraData [32]byte
stakeVersion uint32
// status is a bitfield representing the validation state of the block.
// This field, unlike most other fields, may be changed after the block
// node is created, so it must only be accessed or updated using the
// concurrent-safe NodeStatus, SetStatusFlags, and UnsetStatusFlags
// methods on blockIndex once the node has been added to the index.
status blockStatus
// isFullyLinked indicates whether or not this block builds on a branch
// that has the block data for all of its ancestors and is therefore
// eligible for validation.
//
// It is protected by the block index mutex and is not stored in the
// database.
isFullyLinked bool
// stakeNode contains all the consensus information required for the
// staking system. The node also caches information required to add or
// remove stake nodes, so that the stake node itself may be prunable
// to save memory while maintaining high throughput efficiency for the
// evaluation of sidechains.
stakeNode *stake.Node
newTickets []chainhash.Hash
ticketsVoted []chainhash.Hash
ticketsRevoked []chainhash.Hash
// Keep track of all vote version and bits in this block.
votes []stake.VoteVersionTuple
// receivedOrderID tracks the order block data was received for the node and
// is only stored in memory. It is set when the block data is received, and
// the block data for all parents is also already known, as opposed to when
// the header was received in order to ensure that no additional priority in
// terms of chain selection between competing branches can be gained by
// submitting the header first.
//
// It is protected by the block index mutex.
receivedOrderID uint32
}
// clearLowestOneBit clears the lowest set bit in the passed value.
func clearLowestOneBit(n int64) int64 {
return n & (n - 1)
}
// calcSkipListHeight calculates the height of an ancestor block to use when
// constructing the ancestor traversal skip list.
func calcSkipListHeight(height int64) int64 {
if height < 0 {
return 0
}
// Traditional skip lists create multiple levels to achieve expected average
// search, insert, and delete costs of O(log n). Since the blockchain is
// append only, there is no need to handle random insertions or deletions,
// so this takes advantage of that to effectively create a deterministic
// skip list with a single level that is reasonably close to O(log n) in
// order to reduce the number of pointers and implementation complexity.
//
// This calculation is definitely not the most optimal possible in terms of
// the number of steps in the worst case, however, it is predominantly
// logarithmic, easy to reason about, deterministic, blazing fast to
// calculate and can easily be shown to have a worst case performance of
// 420 steps for heights up to 4,294,967,296 (2^32) and 1580 steps for
// heights up to 2^63 - 1.
//
// Finally, it also satisfies the only real requirement for proper operation
// of the skip list which is for the calculated height to be less than the
// provided height.
return clearLowestOneBit(clearLowestOneBit(height))
}
// initBlockNode initializes a block node from the given header, initialization
// vector for the ticket lottery, and parent node. The workSum is calculated
// based on the parent, or, in the case no parent is provided, it will just be
// the work for the passed block.
//
// This function is NOT safe for concurrent access. It must only be called when
// initially creating a node.
func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parent *blockNode) {
*node = blockNode{
hash: blockHeader.BlockHash(),
workSum: standalone.CalcWork(blockHeader.Bits),
height: int64(blockHeader.Height),
blockVersion: blockHeader.Version,
voteBits: blockHeader.VoteBits,
finalState: blockHeader.FinalState,
voters: blockHeader.Voters,
freshStake: blockHeader.FreshStake,
poolSize: blockHeader.PoolSize,
bits: blockHeader.Bits,
sbits: blockHeader.SBits,
timestamp: blockHeader.Timestamp.Unix(),
merkleRoot: blockHeader.MerkleRoot,
stakeRoot: blockHeader.StakeRoot,
revocations: blockHeader.Revocations,
blockSize: blockHeader.Size,
nonce: blockHeader.Nonce,
extraData: blockHeader.ExtraData,
stakeVersion: blockHeader.StakeVersion,
status: statusNone,
}
if parent != nil {
node.parent = parent
node.skipToAncestor = parent.Ancestor(calcSkipListHeight(node.height))
node.workSum = node.workSum.Add(parent.workSum, node.workSum)
}
}
// newBlockNode returns a new block node for the given block header and parent
// node. The workSum is calculated based on the parent, or, in the case no
// parent is provided, it will just be the work for the passed block.
func newBlockNode(blockHeader *wire.BlockHeader, parent *blockNode) *blockNode {
var node blockNode
initBlockNode(&node, blockHeader, parent)
return &node
}
// Header constructs a block header from the node and returns it.
//
// This function is safe for concurrent access.
func (node *blockNode) Header() wire.BlockHeader {
// No lock is needed because all accessed fields are immutable.
prevHash := zeroHash
if node.parent != nil {
prevHash = &node.parent.hash
}
return wire.BlockHeader{
Version: node.blockVersion,
PrevBlock: *prevHash,
MerkleRoot: node.merkleRoot,
StakeRoot: node.stakeRoot,
VoteBits: node.voteBits,
FinalState: node.finalState,
Voters: node.voters,
FreshStake: node.freshStake,
Revocations: node.revocations,
PoolSize: node.poolSize,
Bits: node.bits,
SBits: node.sbits,
Height: uint32(node.height),
Size: node.blockSize,
Timestamp: time.Unix(node.timestamp, 0),
Nonce: node.nonce,
ExtraData: node.extraData,
StakeVersion: node.stakeVersion,
}
}
// lotteryIV returns the initialization vector for the deterministic PRNG used
// to determine winning tickets.
//
// This function is safe for concurrent access.
func (node *blockNode) lotteryIV() chainhash.Hash {
// Serialize the block header for use in calculating the initialization
// vector for the ticket lottery. The only way this can fail is if the
// process is out of memory in which case it would panic anyways, so
// although panics are generally frowned upon in package code, it is
// acceptable here.
buf := bytes.NewBuffer(make([]byte, 0, wire.MaxBlockHeaderPayload))
header := node.Header()
if err := header.Serialize(buf); err != nil {
panic(err)
}
return stake.CalcHash256PRNGIV(buf.Bytes())
}
// populateTicketInfo sets prunable ticket information in the provided block
// node.
//
// This function is NOT safe for concurrent access. It must only be called when
// initially creating a node or when protected by the block index lock.
func (node *blockNode) populateTicketInfo(spentTickets *stake.SpentTicketsInBlock) {
node.ticketsVoted = spentTickets.VotedTickets
node.ticketsRevoked = spentTickets.RevokedTickets
node.votes = spentTickets.Votes
}
// Ancestor returns the ancestor block node at the provided height by following
// the chain backwards from this node. The returned block will be nil when a
// height is requested that is after the height of the passed node or is less
// than zero.
//
// This function is safe for concurrent access.
func (node *blockNode) Ancestor(height int64) *blockNode {
if height < 0 || height > node.height {
return nil
}
n := node
for n != nil && n.height != height {
// Skip to the linked ancestor when it won't overshoot the target
// height.
if n.skipToAncestor != nil && calcSkipListHeight(n.height) >= height {
n = n.skipToAncestor
continue
}
n = n.parent
}
return n
}
// RelativeAncestor returns the ancestor block node a relative 'distance' blocks
// before this node. This is equivalent to calling Ancestor with the node's
// height minus provided distance.
//
// This function is safe for concurrent access.
func (node *blockNode) RelativeAncestor(distance int64) *blockNode {
return node.Ancestor(node.height - distance)
}
// IsAncestorOf returns whether or not this node is an ancestor of the provided
// target node.
//
// NOTE: Nodes are considered ancestors of themselves.
func (node *blockNode) IsAncestorOf(target *blockNode) bool {
return target.Ancestor(node.height) == node
}
// CalcPastMedianTime calculates the median time of the previous few blocks
// prior to, and including, the block node.
//
// This function is safe for concurrent access.
func (node *blockNode) CalcPastMedianTime() time.Time {
// Create a slice of the previous few block timestamps used to calculate
// the median per the number defined by the constant medianTimeBlocks.
timestamps := make([]int64, medianTimeBlocks)
numNodes := 0
iterNode := node
for i := 0; i < medianTimeBlocks && iterNode != nil; i++ {
timestamps[i] = iterNode.timestamp
numNodes++
iterNode = iterNode.parent
}
// Prune the slice to the actual number of available timestamps which
// will be fewer than desired near the beginning of the block chain
// and sort them.
timestamps = timestamps[:numNodes]
sort.Sort(timeSorter(timestamps))
// NOTE: The consensus rules incorrectly calculate the median for even
// numbers of blocks. A true median averages the middle two elements
// for a set with an even number of elements in it. Since the constant
// for the previous number of blocks to be used is odd, this is only an
// issue for a few blocks near the beginning of the chain. I suspect
// this is an optimization even though the result is slightly wrong for
// a few of the first blocks since after the first few blocks, there
// will always be an odd number of blocks in the set per the constant.
//
// This code follows suit to ensure the same rules are used, however, be
// aware that should the medianTimeBlocks constant ever be changed to an
// even number, this code will be wrong.
medianTimestamp := timestamps[numNodes/2]
return time.Unix(medianTimestamp, 0)
}
// compareHashesAsUint256LE compares two raw hashes treated as if they were
// little-endian uint256s in a way that is more efficient than converting them
// to big integers first. It returns 1 when a > b, -1 when a < b, and 0 when a
// == b.
func compareHashesAsUint256LE(a, b *chainhash.Hash) int {
// Find the index of the first byte that differs.
index := len(a) - 1
for ; index >= 0 && a[index] == b[index]; index-- {
// Nothing to do.
}
if index < 0 {
return 0
}
if a[index] > b[index] {
return 1
}
return -1
}
// betterCandidate returns whether node 'a' is a better candidate than 'b' for
// the purposes of best chain selection.
//
// The criteria for determining what constitutes a better candidate, in order of
// priority, is as follows:
//
// 1. More total cumulative work
// 2. Having block data available
// 3. Receiving data earlier
// 4. Hash that represents more work (smaller value as a little-endian uint256)
//
// This function MUST be called with the block index lock held (for reads).
func betterCandidate(a, b *blockNode) bool {
// First, sort by the total cumulative work.
//
// Blocks with more cumulative work are better candidates for best chain
// selection.
if workCmp := a.workSum.Cmp(b.workSum); workCmp != 0 {
return workCmp > 0
}
// Then sort according to block data availability.
//
// Blocks that already have all of their data available are better
// candidates than those that do not. They have the same priority if either
// both have their data available or neither do.
if aHasData := a.status.HaveData(); aHasData != b.status.HaveData() {
return aHasData
}
// Then sort according to blocks that received their data first. Note that
// the received order will be 0 for both in the case neither block has its
// data available.
//
// Blocks that receive their data earlier are better candidates.
if a.receivedOrderID != b.receivedOrderID {
// Using less than here because data that was received earlier will have
// a lower id.
return a.receivedOrderID < b.receivedOrderID
}
// Finally, fall back to sorting based on the hash in the case the work,
// block data availability, and received order are all the same. In
// practice, the order will typically only be the same for blocks loaded
// from disk since the received order is only stored in memory, however it
// can be the same when the block data for a given header is not yet known
// as well.
//
// Note that it is more difficult to find hashes with more leading zeros
// when treated as a little-endian uint256, so smaller values represent more
// work and are therefore better candidates.
return compareHashesAsUint256LE(&a.hash, &b.hash) < 0
}
// chainTipEntry defines an entry used to track the chain tips and is structured
// such that there is a single statically-allocated field to house a tip, and a
// dynamically-allocated slice for the rare case when there are multiple
// tips at the same height.
//
// This is done to reduce the number of allocations for the common case since
// there is typically only a single tip at a given height.
type chainTipEntry struct {
tip *blockNode
otherTips []*blockNode
}
// blockIndex provides facilities for keeping track of an in-memory index of the
// block chain. Although the name block chain suggests a single chain of
// blocks, it is actually a tree-shaped structure where any node can have
// multiple children. However, there can only be one active branch which does
// indeed form a chain from the tip all the way back to the genesis block.
type blockIndex 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.
db database.DB
// These following fields are protected by the embedded mutex.
//
// index and collisions jointly contain an entry for every block known to
// the block index such that index either contains the entry keyed by its
// shortened key directly when there is no collision or nil in the case
// there is a collision to indicate the entry is stored in the collisions
// map by its full key instead. This approach saves a substantial amount of
// memory by allowing shorter keys to be used for all non-colliding entries
// in exchange for a small bit of processing overhead to handle collisions.
// It should be noted that the current implementation uses 4 bytes for the
// short keys and thus a relatively low number of collisions are expected.
// More concretely, the expected number of collisions is n(n-1)/2^33, where
// n is the number of blocks. This implies the cutoff point where the
// overhead of the collisions is expected to reach just 10% of the savings
// gained is around 5800 years given the average mainnet block production
// rate.
//
// modified contains an entry for all nodes that have been modified
// since the last time the index was flushed to disk.
//
// chainTips contains an entry with the tip of all known side chains.
//
// totalTips tracks the total number of all known chain tips.
sync.RWMutex
index map[uint32]*blockNode
collisions map[chainhash.Hash]*blockNode
modified map[*blockNode]struct{}
chainTips map[int64]chainTipEntry
totalTips uint64
// These fields are related to selecting the best chain. They are protected
// by the embedded mutex.
//
// bestHeader tracks the highest work block node in the index that is not
// known to be invalid. This is not necessarily the same as the active best
// chain, especially when block data is not yet known. However, since block
// nodes are only added to the index for block headers that pass all sanity
// and positional checks, which include checking proof of work, it does
// represent the tip of the header chain with the highest known work that
// has a reasonably high chance of becoming the best chain tip and is useful
// for things such as reporting progress and discovering the most suitable
// blocks to download.
//
// bestInvalid tracks the highest work block node that was found to be
// invalid.
//
// bestChainCandidates tracks a set of block nodes in the block index that
// are potential candidates to become the best chain.
//
// unlinkedChildrenOf maps blocks that do not yet have the full block data
// available to any immediate children that do have the full block data
// available. It is used to efficiently discover all child blocks which
// might be eligible for connection when the full block data for a block
// becomes available.
//
// nextReceivedOrderID is assigned to block nodes and incremented each time
// block data is received in order to aid in chain selection. In
// particular, it helps ensure that no additional priority in terms of chain
// selection between competing branches can be gained by submitting the
// header first.
bestHeader *blockNode
bestInvalid *blockNode
bestChainCandidates map[*blockNode]struct{}
unlinkedChildrenOf map[*blockNode][]*blockNode
nextReceivedOrderID uint32
// These fields are related to caching the most recent chain tips. They are
// protected by the embedded mutex.
//
// cachedTips is similar to chainTips except that it only tracks chain tips
// starting at the height specified by cachedTipsStart. It is primarily
// used to optimize the block invalidation logic.
//
// cachedTipsStart is the starting height (inclusive) for which the cached
// chain tips are tracked.
//
// cachedTipsLastPruned is the last time the cached chain tips were pruned.
cachedTips map[chainhash.Hash]*blockNode
cachedTipsStart int64
cachedTipsLastPruned time.Time
}
// newBlockIndex returns a new empty instance of a block index. The index will
// be dynamically populated as block nodes are loaded from the database and
// manually added.
func newBlockIndex(db database.DB) *blockIndex {
// Notice the next received ID starts at one since all entries loaded from
// disk will be zero.
return &blockIndex{
db: db,
index: make(map[uint32]*blockNode),
collisions: make(map[chainhash.Hash]*blockNode),
modified: make(map[*blockNode]struct{}),
chainTips: make(map[int64]chainTipEntry),
cachedTips: make(map[chainhash.Hash]*blockNode),
bestChainCandidates: make(map[*blockNode]struct{}),
unlinkedChildrenOf: make(map[*blockNode][]*blockNode),
nextReceivedOrderID: 1,
}
}
// HaveBlock returns whether or not the block index contains the provided hash
// and the block data is available.
//
// This function is safe for concurrent access.
func (bi *blockIndex) HaveBlock(hash *chainhash.Hash) bool {
bi.RLock()
node := bi.lookupNode(hash)
hasBlock := node != nil && node.status.HaveData()
bi.RUnlock()
return hasBlock
}
// shortBlockKey generates a short identifier from a standard block hash for use
// as a key in the block index.
func shortBlockKey(hash *chainhash.Hash) uint32 {
// Use the first bytes of the hash directly since it is the result of a hash
// function that produces a uniformly-random distribution. It is also worth
// noting that the mining process reduces entropy by zeroing the bits at the
// other end of the array, but there would need to be effectively impossible
// to achieve hash rates exceeding ~2^215.77 hashes/sec (aka ~89.8 peta
// yotta yotta hashes/sec) in order to start zeroing out the bits used here.
// Not only is that impossible for all intents and purposes, the only effect
// would be reducing overall memory savings due to increased collisions
// among the shortened keys.
return binary.BigEndian.Uint32(hash[0:4])
}
// addNode adds the provided node to the block index. Duplicate entries are not
// checked so it is up to caller to avoid adding them.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) addNode(node *blockNode) {
// Add the node to the block index based on a shortened key with proper
// collision handling. Two maps are used to that end:
// 1) An index map keyed by the shortened key
// 2) A collisions map keyed by the full hash
//
// The entry in the index map is either a block node directly when there has
// not been any collisions for the shortened key, or nil when there has.
// The nil entry signals the block node is stored in the collisions map
// keyed by its full hash instead.
blockKey := shortBlockKey(&node.hash)
existingNode, ok := bi.index[blockKey]
switch {
// Add the new node directly to the index when there is no collision with
// the short key (aka there is no existing entry for it).
case !ok:
bi.index[blockKey] = node
// Add the new node to the collision map using the full hash as the key when
// there is a collision with the short key and the existing node has already
// been moved to the collisions map implying there was a previous collision.
// Note that this will replace the node when the full hash is the same.
case existingNode == nil:
bi.collisions[node.hash] = node
// At this point, there is an existing node for the shortened key indicating
// there were no previous collisions for it, so move that existing node to
// the collision map and nil out the entry in the index map to indicate the
// collision. Finally, add the new node to the collision map using its full
// hash as the key as well.
case existingNode.hash != node.hash:
bi.index[blockKey] = nil
bi.collisions[existingNode.hash] = existingNode
bi.collisions[node.hash] = node
// Replace the node in the index when there is an existing node for the
// shortened key indicating there were no previous collisions for it and the
// full hashes are the same.
default:
bi.index[blockKey] = node
}
// Since the block index does not support nodes that do not connect to
// an existing node (except the genesis block), all new nodes are either
// extending an existing chain or are on a side chain, but in either
// case, are a new chain tip. In the case the node is extending a
// chain, the parent is no longer a tip.
bi.addChainTip(node)
if node.parent != nil {
bi.removeChainTip(node.parent)
}
// Update the header with most known work that is also not known to be
// invalid to this node if needed.
if !node.status.KnownInvalid() && betterCandidate(node, bi.bestHeader) {
bi.bestHeader = node
}
}
// addNodeFromDB adds the provided node, which is expected to have come from
// storage, to the block index and also updates the unlinked block dependencies
// and best known invalid block as needed.
//
// This differs from addNode in that it performs the additional updates to the
// block index which only apply when nodes are first loaded from storage.
//
// This function is NOT safe for concurrent access and therefore must only be
// called during block index initialization.
func (bi *blockIndex) addNodeFromDB(node *blockNode) {
bi.addNode(node)
// Add this node to the map of unlinked blocks that are potentially eligible
// for connection when it is not already fully linked, but the data for it
// is already known and its parent is not already known to be invalid.
if !node.isFullyLinked && node.status.HaveData() && node.parent != nil &&
!node.parent.status.KnownInvalid() {
unlinkedChildren := bi.unlinkedChildrenOf[node.parent]
bi.unlinkedChildrenOf[node.parent] = append(unlinkedChildren, node)
}
// Set this node as the best known invalid block when it is invalid and has
// more work than the current one.
if node.status.KnownInvalid() {
bi.maybeUpdateBestInvalid(node)
}
}
// AddNode adds the provided node to the block index and marks it as modified.
// Duplicate entries are not checked so it is up to caller to avoid adding them.
//
// This function is safe for concurrent access.
func (bi *blockIndex) AddNode(node *blockNode) {
bi.Lock()
bi.addNode(node)
bi.modified[node] = struct{}{}
bi.Unlock()
}
// addChainTip adds the passed block node as a new chain tip.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) addChainTip(tip *blockNode) {
bi.totalTips++
bi.cachedTips[tip.hash] = tip
// When an entry does not already exist for the given tip height, add an
// entry to the map with the tip stored in the statically-allocated field.
entry, ok := bi.chainTips[tip.height]
if !ok {
bi.chainTips[tip.height] = chainTipEntry{tip: tip}
return
}
// Otherwise, an entry already exists for the given tip height, so store the
// tip in the dynamically-allocated slice.
entry.otherTips = append(entry.otherTips, tip)
bi.chainTips[tip.height] = entry
}
// removeChainTip removes the passed block node from the available chain tips.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) removeChainTip(tip *blockNode) {
// Remove it from the cached tips as needed.
delete(bi.cachedTips, tip.hash)
// Nothing to do if no tips exist at the given height.
entry, ok := bi.chainTips[tip.height]
if !ok {
return
}
// The most common case is a single tip at the given height, so handle the
// case where the tip that is being removed is the tip that is stored in the
// statically-allocated field first.
if entry.tip == tip {
bi.totalTips--
entry.tip = nil
// Remove the map entry altogether if there are no more tips left.
if len(entry.otherTips) == 0 {
delete(bi.chainTips, tip.height)
return
}
// There are still tips stored in the dynamically-allocated slice, so
// move the first tip from it to the statically-allocated field, nil the
// slice so it can be garbage collected when there are no more items in
// it, and update the map with the modified entry accordingly.
entry.tip = entry.otherTips[0]
entry.otherTips = entry.otherTips[1:]
if len(entry.otherTips) == 0 {
entry.otherTips = nil
}
bi.chainTips[tip.height] = entry
return
}
// The tip being removed is not the tip stored in the statically-allocated
// field, so attempt to remove it from the dyanimcally-allocated slice.
for i, n := range entry.otherTips {
if n == tip {
bi.totalTips--
copy(entry.otherTips[i:], entry.otherTips[i+1:])
entry.otherTips[len(entry.otherTips)-1] = nil
entry.otherTips = entry.otherTips[:len(entry.otherTips)-1]
if len(entry.otherTips) == 0 {
entry.otherTips = nil
}
bi.chainTips[tip.height] = entry
return
}
}
}
// forEachChainTip calls the provided function with each chain tip known to the
// block index. Returning an error from the provided function will stop the
// iteration early and return said error from this function.
//
// This function MUST be called with the block index lock held (for reads).
func (bi *blockIndex) forEachChainTip(f func(tip *blockNode) error) error {
for _, tipEntry := range bi.chainTips {
if err := f(tipEntry.tip); err != nil {
return err
}
for _, tip := range tipEntry.otherTips {
if err := f(tip); err != nil {
return err
}
}
}
return nil
}
// forEachChainTipAfterHeight calls the provided function with each chain tip
// known to the block index that has a height which is greater than the provided
// filter node.
//
// Providing a filter node also makes use of the recent chain tip cache when
// possible which typically further reduces the number of chain tips that need
// to be iterated since all old chain tips are pruned from the cache.
//
// Returning an error from the provided function will stop the iteration early
// and return said error from this function.
//
// This function MUST be called with the block index lock held (for reads).
func (bi *blockIndex) forEachChainTipAfterHeight(filter *blockNode, f func(tip *blockNode) error) error {
// Use the cached recent chain tips when the filter height permits it.
if filter.height >= bi.cachedTipsStart-1 {
for _, tip := range bi.cachedTips {
// Ignore any chain tips at the same or lower heights than the
// provided filter.
if tip.height <= filter.height {
continue
}
if err := f(tip); err != nil {
return err
}
}
return nil
}
// Fall back to iterating through all chain tips when the filter height is
// prior to the point the cached recent chain tips are tracking.
for tipHeight, tipEntry := range bi.chainTips {
// Ignore any chain tips at the same or lower heights than the provided
// filter.
if tipHeight <= filter.height {
continue
}
if err := f(tipEntry.tip); err != nil {
return err
}
for _, tip := range tipEntry.otherTips {
if err := f(tip); err != nil {
return err
}
}
}
return nil
}
// lookupNode returns the block node identified by the provided hash. It will
// return nil if there is no entry for the hash.
//
// This function MUST be called with the block index lock held (for reads).
func (bi *blockIndex) lookupNode(hash *chainhash.Hash) *blockNode {
// An existing non-nil entry for the shortened key means there was no
// previous collision when inserting nodes, but it still might collide with
// the requested hash, so ensure the full hash actually matches and return
// the node in the case it does.
blockKey := shortBlockKey(hash)
n, ok := bi.index[blockKey]
if n != nil && n.hash == *hash {
return n
}
// A nil entry for the shortened key (as opposed to no entry at all) means
// there was a previous collision when inserting nodes, so look up the node
// by its full hash in the collisions map.
if ok && n == nil {
return bi.collisions[*hash]
}
// There is no entry for the shortened key in the index at all or there is a
// non-nil entry for it, but it does not match the full hash. The requested
// node does not exist in either case.
return nil
}
// LookupNode returns the block node identified by the provided hash. It will
// return nil if there is no entry for the hash.
//
// This function is safe for concurrent access.
func (bi *blockIndex) LookupNode(hash *chainhash.Hash) *blockNode {
bi.RLock()
node := bi.lookupNode(hash)
bi.RUnlock()
return node
}
// PopulateTicketInfo sets prunable ticket information in the provided block
// node.
//
// This function is safe for concurrent access.
func (bi *blockIndex) PopulateTicketInfo(node *blockNode, spentTickets *stake.SpentTicketsInBlock) {
bi.Lock()
node.populateTicketInfo(spentTickets)
bi.modified[node] = struct{}{}
bi.Unlock()
}
// NodeStatus returns the status associated with the provided node.
//
// This function is safe for concurrent access.
func (bi *blockIndex) NodeStatus(node *blockNode) blockStatus {
bi.RLock()
status := node.status
bi.RUnlock()
return status
}
// setStatusFlags sets the provided status flags for the given block node
// regardless of their previous state. It does not unset any flags.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) setStatusFlags(node *blockNode, flags blockStatus) {
origStatus := node.status
node.status |= flags
if node.status != origStatus {
bi.modified[node] = struct{}{}
}
}
// SetStatusFlags sets the provided status flags for the given block node
// regardless of their previous state. It does not unset any flags.
//
// This function is safe for concurrent access.
func (bi *blockIndex) SetStatusFlags(node *blockNode, flags blockStatus) {
bi.Lock()
bi.setStatusFlags(node, flags)
bi.Unlock()
}
// unsetStatusFlags unsets the provided status flags for the given block node
// regardless of their previous state.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) unsetStatusFlags(node *blockNode, flags blockStatus) {
origStatus := node.status
node.status &^= flags
if node.status != origStatus {
bi.modified[node] = struct{}{}
}
}
// UnsetStatusFlags unsets the provided status flags for the given block node
// regardless of their previous state.
//
// This function is safe for concurrent access.
func (bi *blockIndex) UnsetStatusFlags(node *blockNode, flags blockStatus) {
bi.Lock()
bi.unsetStatusFlags(node, flags)
bi.Unlock()
}
// addBestChainCandidate adds the passed block node as a potential candidate
// for becoming the tip of the best chain.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) addBestChainCandidate(node *blockNode) {
bi.bestChainCandidates[node] = struct{}{}
}
// pruneCachedTips removes old cached chain tips used to optimize block
// invalidation by treating the passed best known block as a reference point.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) pruneCachedTips(bestNode *blockNode) {
// No blocks exist before height 0.
height := bestNode.height - cachedTipsPruneDepth
if height <= 0 {
bi.cachedTipsLastPruned = time.Now()
return
}
for hash, n := range bi.cachedTips {
if n.height < height {
delete(bi.cachedTips, hash)
}
}
bi.cachedTipsStart = height
bi.cachedTipsLastPruned = time.Now()
}
// MaybePruneCachedTips periodically removes old cached chain tips used to
// optimize block invalidation by treating the passed best known block as a
// reference point.
//
// This function is safe for concurrent access.
func (bi *blockIndex) MaybePruneCachedTips(bestNode *blockNode) {
bi.Lock()
if time.Since(bi.cachedTipsLastPruned) >= cachedTipsPruneInterval {
bi.pruneCachedTips(bestNode)
}
bi.Unlock()
}
// removeBestChainCandidate removes the passed block node from the potential
// candidates for becoming the tip of the best chain.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) removeBestChainCandidate(node *blockNode) {
delete(bi.bestChainCandidates, node)
}
// maybeUpdateBestInvalid potentially updates the best known invalid block, as
// determined by having the most cumulative work, by comparing the passed block
// node, which must have already been determined to be invalid, against the
// current one.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) maybeUpdateBestInvalid(invalidNode *blockNode) {
if bi.bestInvalid == nil || betterCandidate(invalidNode, bi.bestInvalid) {
bi.bestInvalid = invalidNode
}
}
// maybeUpdateBestHeaderForTip potentially updates the best known header that is
// not known to be invalid, as determined by having the most cumulative work.
// It works by walking backwards from the provided tip so long as those headers
// have more work than the current best header and selecting the first one that
// is not known to be invalid.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) maybeUpdateBestHeaderForTip(tip *blockNode) {
for n := tip; n != nil && betterCandidate(n, bi.bestHeader); n = n.parent {
if !n.status.KnownInvalid() {
bi.bestHeader = n
return
}
}
}
// BestHeader returns the header with the most cumulative work that is NOT known
// to be invalid. This is not necessarily the same as the active best chain,
// especially when block data is not yet known. However, since block nodes are
// only added to the index for block headers that pass all sanity and positional
// checks, which include checking proof of work, it does represent the tip of
// the header chain with the highest known work that has a reasonably high
// chance of becoming the best chain tip and is useful for things such as
// reporting progress and discovering the most suitable blocks to download.
//
// This function is safe for concurrent access.
func (bi *blockIndex) BestHeader() *blockNode {
bi.RLock()
bestHeader := bi.bestHeader
bi.RUnlock()
return bestHeader
}
// MarkBlockFailedValidation marks the passed node as having failed validation
// and then marks all of its descendants (if any) as having a failed ancestor.
//
// This function is safe for concurrent access.
func (bi *blockIndex) MarkBlockFailedValidation(node *blockNode) {
bi.Lock()
bi.setStatusFlags(node, statusValidateFailed)
bi.unsetStatusFlags(node, statusValidated)
bi.removeBestChainCandidate(node)
bi.maybeUpdateBestInvalid(node)
delete(bi.unlinkedChildrenOf, node)
// Mark all descendants of the failed block as having a failed ancestor.
//
// In order to fairly efficiently determine all of the descendants of the
// block without having to iterate the entire block index, walk through all
// of the known chain tips and check if the block being invalidated is an
// ancestor of the tip. In the case it is, then all blocks between that tip
// and the failed block are descendants. As an additional optimization, a
// cache of recent tips (those after a recent height) is maintained and used
// when possible to reduce the number of potential affected chain tips that
// need to be iterated.
//
// In order to help visualize the logic, consider the following block tree
// with several branches:
//
// 100 -> 101 -> 102 -> 103 -> 104 -> 105 -> 106 -> 107 -> 108
// \-> 101a -> 102a -> 103a -> 104a -> 105a \-> 107a
// \-> 101b ---- | \-> 105b -> 106b
// ^^ \-> 104c -> 105c -> 106c
// Failed \-> 104d -> 105d
//
// Further, assume block 102a failed validation. As can be seen, its
// descendants are 103a, 104a, 105a, 105b, 106b, 104c, 105c, 106c, 104d, and
// 105d, and the chain tips of this hypothetical block tree would be 101b,
// 105a, 105d, 106b, 106c, 107a, and 108.
//
// Since the failed block, 102a, is not an ancestor of tips 101b, 107a, or
// 108, those tips are ignored. Also notice that, of the remaining tips,
// 103a is a common ancestor to all of them, and 104a is a common ancestor
// to tips 105a and 106b.
//
// Given all of the above, the blocks would semantically be marked as having
// an invalid ancestor as follows:
//
// Tip 105a: 105a, 104a, 103a (102a is failed block, next)
// Tip 106b: 106b, 105b, 104a, 103a (102a is failed block, next)
// Tip 106c: 106c, 105c, 104c, 103a (102a is failed block, next)
// Tip 105d: 105d, 104d, 103a (102a is failed block, next)
//
// Note that it might be tempting to consider trying to optimize this to
// skip directly to the next tip once a node with a common invalid ancestor
// is found. However, that would result in incorrect behavior if a block is
// marked invalid deeper in a branch first and then an earlier block is
// later marked invalid as it would result in skipping the intermediate
// blocks thereby NOT marking them as invalid as they should be.
//
// For example, consider what happens if 105c is marked invalid prior to the
// block data for 102a becoming available and found to be invalid. Blocks
// 106c and 105c would already be marked invalid, and blocks 104c and 103a
// need to be marked invalid.
markDescendantsInvalid := func(node, tip *blockNode) {
// Nothing to do if the node is not an ancestor of the given chain tip.
if !node.IsAncestorOf(tip) {
return
}
// Set this chain tip as the best known invalid block when it has more
// work than the current one.
bi.maybeUpdateBestInvalid(tip)
// Mark everything that descends from the failed block as having an
// invalid ancestor.
for n := tip; n != node; n = n.parent {
// Skip blocks that are already known to have an invalid ancestor.
if n.status.KnownInvalidAncestor() {
continue
}
bi.setStatusFlags(n, statusInvalidAncestor)
bi.unsetStatusFlags(n, statusValidated)
bi.removeBestChainCandidate(n)
// Remove any children that depend on the failed block from the set
// of unlinked blocks accordingly since they are no longer eligible
// for connection even if the full block data for a block becomes
// available.
delete(bi.unlinkedChildrenOf, n)
}
}
// Chain tips at the same or lower heights than the failed block can't
// possibly be descendants of it, so use it as the lower height bound filter
// when iterating chain tips. Note that this will make use of the cache of
// recent tips when possible.
bi.forEachChainTipAfterHeight(node, func(tip *blockNode) error {
markDescendantsInvalid(node, tip)
return nil
})
// Update the best header if the current one is now invalid which will be
// the case when the best header is a descendant of the failed block.
if bi.bestHeader.status.KnownInvalid() {
// Use the first ancestor of the failed block that is not known to be
// invalid as the lower bound for the best header. This will typically
// be the parent of the failed block, but it might be some more distant
// ancestor when performing manual invalidation.
n := node.parent
for n != nil && n.status.KnownInvalid() {
n = n.parent
}
bi.bestHeader = n
// Scour the block tree to find a new best header.
//
// Note that all chain tips must be iterated versus filtering based on
// the current best header height because, while uncommon, it is
// possible for lower heights to have more work.
bi.forEachChainTip(func(tip *blockNode) error {
// Skip chain tips that are descendants of the failed block since
// none of the intermediate headers are eligible to become the best
// header given they all have an invalid ancestor.
if node.IsAncestorOf(tip) {
return nil
}
bi.maybeUpdateBestHeaderForTip(tip)
return nil
})
}
bi.Unlock()
}
// canValidate returns whether or not the block associated with the provided
// node can be validated. In order for a block to be validated, both it, and
// all of its ancestors, must have the block data available.
//
// This function MUST be called with the block index lock held (for reads).
func (bi *blockIndex) canValidate(node *blockNode) bool {
return node.isFullyLinked && node.status.HaveData()
}
// CanValidate returns whether or not the block associated with the provided
// node can be validated. In order for a block to be validated, both it, and
// all of its ancestors, must have the block data available.
//
// This function is safe for concurrent access.
func (bi *blockIndex) CanValidate(node *blockNode) bool {
bi.RLock()
canValidate := bi.canValidate(node)
bi.RUnlock()
return canValidate
}
// removeLessWorkCandidates removes all potential best chain candidates that
// have less work than the provided node, which is typically a newly connected
// best chain tip.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) removeLessWorkCandidates(node *blockNode) {
// Remove all best chain candidates that have less work than the passed
// node.
for n := range bi.bestChainCandidates {
if n.workSum.Cmp(node.workSum) < 0 {
bi.removeBestChainCandidate(n)
}
}
// The best chain candidates must always contain at least the current best
// chain tip. Assert this assumption is true.
if len(bi.bestChainCandidates) == 0 {
panicf("best chain candidates list is empty after removing less work " +
"candidates")
}
}
// RemoveLessWorkCandidates removes all potential best chain candidates that
// have less work than the provided node, which is typically a newly connected
// best chain tip.
//
// This function is safe for concurrent access.
func (bi *blockIndex) RemoveLessWorkCandidates(node *blockNode) {
bi.Lock()
bi.removeLessWorkCandidates(node)
bi.Unlock()
}
// linkBlockData marks the provided block as fully linked to indicate that both
// it and all of its ancestors have their data available and then determines if
// there are any unlinked blocks which depend on the passed block and links
// those as well until there are no more. It returns a list of blocks that were
// linked.
//
// It also accounts for the order that the blocks are linked and potentially
// adds the newly-linked blocks as best chain candidates if they have more
// cumulative work than the current best chain tip.
//
// This function MUST be called with the block index lock held (for writes).
func (bi *blockIndex) linkBlockData(node, tip *blockNode) []*blockNode {
// Start with processing at least the passed node.
//
// Note that no additional space is preallocated here because it is fairly
// rare (after the initial sync) for there to be more than the single block
// being linked and thus it will typically remain on the stack and avoid an
// allocation.
linkedNodes := []*blockNode{node}
for nodeIndex := 0; nodeIndex < len(linkedNodes); nodeIndex++ {
linkedNode := linkedNodes[nodeIndex]
// Mark the block as fully linked to indicate that both it and all of
// its ancestors have their data available.
linkedNode.isFullyLinked = true
// Keep track of the order in which the block data was received to
// ensure miners gain no advantage by advertising the header first.
linkedNode.receivedOrderID = bi.nextReceivedOrderID
bi.nextReceivedOrderID++
// The block is now a candidate to potentially become the best chain if
// it has the same or more work than the current best chain tip.
if linkedNode.workSum.Cmp(tip.workSum) >= 0 {
bi.addBestChainCandidate(linkedNode)
}
// Add any children of the block that was just linked to the list to be
// linked and remove them from the set of unlinked blocks accordingly.
// There will typically only be zero or one, but it could be more if
// multiple solutions are mined and broadcast around the same time.
unlinkedChildren := bi.unlinkedChildrenOf[linkedNode]
if len(unlinkedChildren) > 0 {
linkedNodes = append(linkedNodes, unlinkedChildren...)
delete(bi.unlinkedChildrenOf, linkedNode)
}
}
return linkedNodes
}
// AcceptBlockData updates the block index state to account for the full data
// for a block becoming available. For example, blocks that are currently not
// eligible for validation due to either not having the block data itself or not
// having all ancestor data available might become eligible for validation. It
// returns a list of all blocks that were linked, if any.
//
// NOTE: It is up to the caller to only call this function when the data was not
// previously available.
//
// This function is safe for concurrent access.
func (bi *blockIndex) AcceptBlockData(node, tip *blockNode) []*blockNode {
// The passed block, and any blocks that also have their data available, are
// now eligible for validation when the parent of the passed block is also
// eligible (or has already been validated).
var linkedBlocks []*blockNode
bi.Lock()
if bi.canValidate(node.parent) {
linkedBlocks = bi.linkBlockData(node, tip)
} else if !node.parent.status.KnownInvalid() {
unlinkedChildren := bi.unlinkedChildrenOf[node.parent]
bi.unlinkedChildrenOf[node.parent] = append(unlinkedChildren, node)
}
bi.Unlock()
return linkedBlocks
}
// FindBestChainCandidate searches the block index for the best potentially
// valid chain that contains the most cumulative work and returns its tip. In
// order to be potentially valid, all of the block data leading up to a block
// must have already been received and must not be part of a chain that is
// already known to be invalid. A chain that has not yet been fully validated,
// such as a side chain that has never been the main chain, is neither known to
// be valid nor invalid, so it is possible that the returned candidate will form
// a chain that is invalid.
//
// This function is safe for concurrent access.
func (bi *blockIndex) FindBestChainCandidate() *blockNode {
bi.RLock()
defer bi.RUnlock()
// Find the best candidate among the potential candidates as determined by
// having the highest cumulative work with fallback to the criteria
// described by the invoked function in the case of equal work.
//
// Note that the best candidate should never actually be nil in practice
// since the current best tip is always a candidate.
var bestCandidate *blockNode
for node := range bi.bestChainCandidates {
if bestCandidate == nil || betterCandidate(node, bestCandidate) {
bestCandidate = node
}
}
return bestCandidate
}
// Flush writes all of the modified block nodes to the database and clears the
// set of modified nodes if it succeeds.
//
// This function is safe for concurrent access.
func (bi *blockIndex) Flush() error {
// Nothing to flush if there are no modified nodes.
bi.Lock()
if len(bi.modified) == 0 {
bi.Unlock()
return nil
}
// Write all of the nodes in the set of modified nodes to the database.
err := bi.db.Update(func(dbTx database.Tx) error {
for node := range bi.modified {
err := dbPutBlockNode(dbTx, node)
if err != nil {
return err
}
}
return nil
})
if err != nil {
bi.Unlock()
return err
}
// Clear the set of modified nodes.
bi.modified = make(map[*blockNode]struct{})
bi.Unlock()
return nil
}