dcrd/blockchain/blockindex.go
Dave Collins f5663dd603
blockchain: Use new ancestor of convenience func.
This updates the various blockchain call sites that deal with
determining if a node is an ancestor (or descendant) to make use of the
new IsAncestorOf convenience func.
2021-10-29 12:37:24 -05:00

1395 lines
52 KiB
Go

// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2018-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 (
"bytes"
"encoding/binary"
"math/big"
"sort"
"sync"
"time"
"github.com/decred/dcrd/blockchain/stake/v4"
"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
}
}
}
// 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.
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
}