dcrd/blockchain/process.go
Dave Collins fd92d7c85a
blockchain: Add invalidate/reconsider infrastruct.
This adds infrastructure to support invalidating arbitrary blocks as if
they had violated a consensus rule as well as reconsidering arbitrary
blocks for validation under the current consensus rules.

It also includes comprehensive tests to ensure proper functionality.

Example use cases this enables:

- Revalidating blocks under new consensus rules
  - Consider the case of old software rejecting blocks due to new
    consensus rules activating on the network and then upgrading to a
    new version that supports the new rules
- Possibility of manually generating snapshots of historical state such
  as live tickets and available utxos
- Manually recovering from unexpected circumstances
- Easier chain reorganization testing
2021-01-07 17:48:11 -06:00

848 lines
34 KiB
Go

// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2020 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 (
"errors"
"fmt"
"github.com/decred/dcrd/blockchain/stake/v4"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database/v2"
"github.com/decred/dcrd/dcrutil/v4"
"github.com/decred/dcrd/wire"
)
// BehaviorFlags is a bitmask defining tweaks to the normal behavior when
// performing chain processing and consensus rules checks.
type BehaviorFlags uint32
const (
// BFFastAdd may be set to indicate that several checks can be avoided
// for the block since it is already known to fit into the chain due to
// already proving it correct links into the chain up to a known
// checkpoint. This is primarily used for headers-first mode.
BFFastAdd BehaviorFlags = 1 << iota
// BFNoPoWCheck may be set to indicate the proof of work check which
// ensures a block hashes to a value less than the required target will
// not be performed.
BFNoPoWCheck
// BFNone is a convenience value to specifically indicate no flags.
BFNone BehaviorFlags = 0
)
// checkKnownInvalidBlock returns an appropriate error when the provided block
// is known to be invalid either due to failing validation itself or due to
// having a known invalid ancestor (aka being part of an invalid branch).
//
// This function is safe for concurrent access.
func (b *BlockChain) checkKnownInvalidBlock(node *blockNode) error {
status := b.index.NodeStatus(node)
if status.KnownValidateFailed() {
str := fmt.Sprintf("block %s is known to be invalid", node.hash)
return ruleError(ErrKnownInvalidBlock, str)
}
if status.KnownInvalidAncestor() {
str := fmt.Sprintf("block %s is known to be part of an invalid branch",
node.hash)
return ruleError(ErrInvalidAncestorBlock, str)
}
return nil
}
// maybeAcceptBlockHeader potentially accepts the header to the block index and,
// if accepted, returns the block node associated with the header. It performs
// several context independent checks as well as those which depend on its
// position within the chain. It should be noted that some of the header fields
// require the full block data to be available in order to be able to validate
// them, so those fields are not included here. This provides support for full
// headers-first semantics.
//
// The flag for check header sanity allows the additional header sanity checks
// to be skipped which is useful for the full block processing path which checks
// the sanity of the entire block, including the header, before attempting to
// accept its header in order to quickly eliminate blocks that are obviously
// incorrect.
//
// In the case the block header is already known, the associated block node is
// examined to determine if the block is already known to be invalid, in which
// case an appropriate error will be returned. Otherwise, the block node is
// returned.
//
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to checkBlockHeaderSanity and
// checkBlockHeaderPositional.
//
// This function MUST be called with the chain lock held (for writes).
func (b *BlockChain) maybeAcceptBlockHeader(header *wire.BlockHeader, flags BehaviorFlags, checkHeaderSanity bool) (*blockNode, error) {
// Avoid validating the header again if its validation status is already
// known. Invalid headers are never added to the block index, so if there
// is an entry for the block hash, the header itself is known to be valid.
// However, it might have since been marked invalid either due to the
// associated block, or an ancestor, later failing validation.
hash := header.BlockHash()
if node := b.index.LookupNode(&hash); node != nil {
if err := b.checkKnownInvalidBlock(node); err != nil {
return nil, err
}
return node, nil
}
// Perform context-free sanity checks on the block header.
if checkHeaderSanity {
err := checkBlockHeaderSanity(header, b.timeSource, flags, b.chainParams)
if err != nil {
return nil, err
}
}
// Orphan headers are not allowed and this function should never be called
// with the genesis block.
prevHash := &header.PrevBlock
prevNode := b.index.LookupNode(prevHash)
if prevNode == nil {
str := fmt.Sprintf("previous block %s is not known", prevHash)
return nil, ruleError(ErrMissingParent, str)
}
// There is no need to validate the header if an ancestor is already known
// to be invalid.
prevNodeStatus := b.index.NodeStatus(prevNode)
if prevNodeStatus.KnownInvalid() {
str := fmt.Sprintf("previous block %s is known to be invalid", prevHash)
return nil, ruleError(ErrInvalidAncestorBlock, str)
}
// The block header must pass all of the validation rules which depend on
// its position within the block chain.
err := b.checkBlockHeaderPositional(header, prevNode, flags)
if err != nil {
return nil, err
}
// Create a new block node for the block and add it to the block index.
//
// Note that the additional information for the actual votes, tickets, and
// revocations in the block can't be populated until the full block data is
// known since that information is not available in the header.
newNode := newBlockNode(header, prevNode)
newNode.status = statusNone
b.index.AddNode(newNode)
// Potentially update the most recently known checkpoint to this block
// header.
b.maybeUpdateMostRecentCheckpoint(newNode)
return newNode, nil
}
// ProcessBlockHeader is the main workhorse for handling insertion of new block
// headers into the block chain using headers-first semantics. It includes
// functionality such as rejecting headers that do not connect to an existing
// known header, ensuring headers follow all rules that do not depend on having
// all ancestor block data available, and insertion into the block index.
//
// Block headers that have already been inserted are ignored, unless they have
// subsequently been marked invalid, in which case an appropriate error is
// returned.
//
// It should be noted that this function intentionally does not accept block
// headers that do not connect to an existing known header or to headers which
// are already known to be a part of an invalid branch. This means headers must
// be processed in order.
//
// This function is safe for concurrent access.
func (b *BlockChain) ProcessBlockHeader(header *wire.BlockHeader, flags BehaviorFlags) error {
b.processLock.Lock()
defer b.processLock.Unlock()
// Potentially accept the header to the block index. When the header
// already exists in the block index, this acts as a lookup of the existing
// node along with a status check to avoid additional work when possible.
//
// On the other hand, when the header does not already exist in the block
// index, validate it according to both context free and context dependent
// positional checks, and create a block index entry for it.
b.chainLock.Lock()
const checkHeaderSanity = true
_, err := b.maybeAcceptBlockHeader(header, flags, checkHeaderSanity)
if err != nil {
b.chainLock.Unlock()
return err
}
// Write any modified block index entries to the database since any new
// headers will have added a new entry.
if err := b.flushBlockIndex(); err != nil {
b.chainLock.Unlock()
return err
}
b.chainLock.Unlock()
return nil
}
// maybeAcceptBlockData potentially accepts the data for the given block into
// the database, updates the block index state to account for the full data now
// being available, and returns a list of all descendant blocks that already
// have their respective data available and are now therefore eligible for
// validation.
//
// The block is only accepted if it passes several validation checks which
// depend on its position within the block chain and having the headers of all
// ancestors available. This function does not, and must not, rely on having
// the full block data of all ancestors available.
//
// Note that this currently expects that it is only ever called from
// ProcessBlock which already checked the block sanity. Care must be taken if
// the code is changed to violate that assumption.
//
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to checkBlockPositional.
//
// This function MUST be called with the chain lock held (for writes).
func (b *BlockChain) maybeAcceptBlockData(node *blockNode, block *dcrutil.Block, flags BehaviorFlags) ([]*blockNode, error) {
// Nothing more to do if the block data is already available. Note that
// this function is never called when the data is already available at the
// time this comment was written, but it's a fast check and will prevent
// incorrect behavior if that changes at some point in the future.
if b.index.NodeStatus(node).HaveData() {
return nil, nil
}
// Populate the prunable information that is related to tickets and votes.
ticketInfo := stake.FindSpentTicketsInBlock(block.MsgBlock())
b.index.PopulateTicketInfo(node, ticketInfo)
// The block must pass all of the validation rules which depend on the
// position of the block within the block chain. Not that this only checks
// the block data, not including the header, because the header was already
// checked when it was accepted to the block index.
err := b.checkBlockDataPositional(block, node.parent, flags)
if err != nil {
b.index.MarkBlockFailedValidation(node)
return nil, err
}
// Prune stake nodes that are no longer needed.
b.pruner.pruneChainIfNeeded()
// Insert the block into the database if it's not already there. Even
// though it is possible the block will ultimately fail to connect, it has
// already passed all proof-of-work and validity tests which means it would
// be prohibitively expensive for an attacker to fill up the disk with a
// bunch of blocks that fail to connect. This is necessary since it allows
// block download to be decoupled from the much more expensive connection
// logic. It also has some other nice properties such as making blocks that
// never become part of the main chain or blocks that fail to connect
// available for further analysis.
err = b.db.Update(func(dbTx database.Tx) error {
return dbMaybeStoreBlock(dbTx, block)
})
if err != nil {
return nil, err
}
b.index.SetStatusFlags(node, statusDataStored)
// Update the block index state to account for the full data for the block
// now being available. This might result in the block, and any others that
// are descendants of it, becoming fully linked (meaning a block has all of
// its own data available and all of its ancestors also have their data
// available) which makes them eligible for full validation.
tip := b.bestChain.Tip()
linkedBlocks := b.index.AcceptBlockData(node, tip)
return linkedBlocks, nil
}
// maybeAcceptBlocks tentatively accepts the given blocks, which must have
// already been determined to be fully linked by the caller, to the chain if
// they pass several validation checks which depend on having the full block
// data for all of their ancestors available and updates the block index state
// to account for any that fail validation.
//
// It returns those that were accepted along with an error that applies to the
// first one that failed validation (if any). This is sufficient because the
// provided blocks must all be descendants of previous ones which means all of
// the remaining ones after a validation failure are not eligible for further
// processing and acceptance because they have an invalid ancestor.
//
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to checkBlockContext.
//
// This function MUST be called with the chain lock held (for writes).
func (b *BlockChain) maybeAcceptBlocks(curTip *blockNode, nodes []*blockNode, flags BehaviorFlags) ([]*blockNode, error) {
isCurrent := b.isCurrent(curTip)
for i, n := range nodes {
var err error
linkedBlock, err := b.fetchBlockByNode(n)
if err != nil {
return nodes[:i], err
}
// The block must pass all of the validation rules which depend on
// having the full block data for all of its ancestors available.
if err := b.checkBlockContext(linkedBlock, n.parent, flags); err != nil {
var rErr RuleError
if errors.As(err, &rErr) {
b.index.MarkBlockFailedValidation(n)
}
return nodes[:i], err
}
// Cache the block and mark it as recently checked to avoid loading and
// checking it again when connecting it in the typical case. Since the
// cache is limited in size, it is technically possible that a large
// enough chain of blocks becoming linked at once will end up evicting
// some of the early ones, but the only effect in that case is
// potentially having to load the block and run the context checks again
// later. That said, in practice, eviction of items essentially never
// happens under normal operation, especially once the chain is fully
// synced.
b.addRecentBlock(linkedBlock)
b.recentContextChecks.Add(n.hash)
// Notify the caller when the block intends to extend the main chain,
// the chain believes it is current, and the block has passed all of the
// sanity and contextual checks, such as having valid proof of work,
// valid merkle and stake roots, and only containing allowed votes and
// revocations.
//
// This allows the block to be relayed before doing the more expensive
// connection checks, because even though the block might still fail to
// connect and become the new main chain tip, that is quite rare in
// practice since a lot of work was expended to create a block that
// satisifies the proof of work requirement.
//
// Notice that the chain lock is not released before sending the
// notification. This is intentional and must not be changed without
// understanding why!
if n.parent == curTip && isCurrent {
b.sendNotification(NTNewTipBlockChecked, linkedBlock)
}
}
return nodes, nil
}
// ProcessBlock is the main workhorse for handling insertion of new blocks into
// the block chain. It includes functionality such as rejecting duplicate
// blocks, ensuring blocks follow all rules, and insertion into the block chain
// along with best chain selection and reorganization.
//
// This function permits blocks to be processed out of order so long as their
// header has already been successfully processed via ProcessBlockHeader which
// itself requires the headers to properly connect. In other words, orphan
// blocks are rejected and thus is up to the caller to either ensure that the
// blocks are processed in order or that the headers for the blocks have already
// been successfully processed.
//
// Upon return, the best chain tip will be whatever branch tip has the most
// proof of work and also passed all validation checks. Due to this, it is also
// worth noting that the best chain tip might be updated even in the case of
// processing a block that ultimately fails validation.
//
// Additionally, due to the ability to process blocks out of order, and the fact
// blocks can only be fully validated once all of their ancestors have the block
// data available, it is to be expected that no error is returned immediately
// for blocks that are valid enough to make it to the point they require the
// remaining ancestor block data to be fully validated even though they might
// ultimately end up failing validation. Similarly, because the data for a
// block becoming available makes any of its direct descendants that already
// have their data available eligible for validation, an error being returned
// does not necessarily mean the block being processed is the one that failed
// validation.
//
// When no errors occurred during processing, the first return value indicates
// the length of the fork the block extended. In the case it either extended
// the best chain or is now the tip of the best chain due to causing a
// reorganize, the fork length will be 0.
//
// This function is safe for concurrent access.
func (b *BlockChain) ProcessBlock(block *dcrutil.Block, flags BehaviorFlags) (int64, error) {
// Since the chain lock is periodically released to send notifications,
// protect the overall processing of blocks with a separate mutex.
b.processLock.Lock()
defer b.processLock.Unlock()
// The block must not already exist in the main chain or side chains.
blockHash := block.Hash()
if b.index.HaveBlock(blockHash) {
str := fmt.Sprintf("already have block %v", blockHash)
return 0, ruleError(ErrDuplicateBlock, str)
}
b.chainLock.Lock()
defer b.chainLock.Unlock()
// Reject blocks that are already known to be invalid immediately to avoid
// additional work when possible.
node := b.index.LookupNode(block.Hash())
if node != nil {
if err := b.checkKnownInvalidBlock(node); err != nil {
return 0, err
}
}
// Perform preliminary sanity checks on the block and its transactions.
// This is done prior to any attempts to accept the block data and connect
// the block to quickly eliminate blocks that are obviously incorrect and
// significantly increase the cost to attackers. Of particular note is that
// the checks include proof-of-work validation which means a significant
// amount of work must have been done in order to pass this check.
err := checkBlockSanity(block, b.timeSource, flags, b.chainParams)
if err != nil {
// When there is a block index entry for the block, which will be the
// case if the header was previously seen and passed all validation,
// mark it as having failed validation and all of its descendants as
// having an invalid ancestor.
if node != nil {
b.index.MarkBlockFailedValidation(node)
}
return 0, err
}
// Potentially accept the header to the block index when it does not already
// exist.
//
// This entails fully validating it according to both context independent
// and context dependent checks and creating a block index entry for it.
//
// Note that the header sanity checks are skipped because they were just
// performed above as part of the full block sanity checks.
if node == nil {
const checkHeaderSanity = false
header := &block.MsgBlock().Header
node, err = b.maybeAcceptBlockHeader(header, flags, checkHeaderSanity)
if err != nil {
return 0, err
}
}
// Enable skipping some of the more expensive validation checks when the
// block is an ancestor of a known good checkpoint.
//
// NOTE: The fast add flag from the caller is used as a standin for now
// since that validation is currently handled by the calling code. In the
// future, the calling code should be updated to process all headers through
// this package and then the ability to specify the fast add flag should be
// removed along with the fast add portion of this check here so that it is
// solely determined internally.
if flags&BFFastAdd == BFFastAdd || b.isKnownCheckpointAncestor(node) {
b.index.SetStatusFlags(node, statusValidated)
flags |= BFFastAdd
}
// Potentially accept the block data into the database and update the block
// index state to account for the full data now being available.
//
// This consists of performing several validation checks which depend on the
// block's position within the block chain and determining if the block, and
// any descendants of it are now eligible for full validation due to being
// fully linked (meaning a block has all of its own data available and all
// of its ancestors also have their data available).
//
// The returned linked block nodes are for those aforementioned blocks that
// are now eligible for validation.
linkedNodes, err := b.maybeAcceptBlockData(node, block, flags)
if err != nil {
return 0, err
}
// Write any modified block index entries to the database since any new
// headers will have added a new entry and the block will be marked as now
// having its data stored.
if err := b.flushBlockIndex(); err != nil {
return 0, err
}
// Tentatively accept the linked blocks to the chain if they pass several
// validation checks which depend on having the full block data for all of
// their ancestors available and update the block index state to account for
// any that fail validation.
//
// Note that this is done here because it allows any blocks that fail this
// level of validation to be detected and discounted early before doing more
// work.
//
// Also, any blocks that do not ultimately end up becoming part of the best
// chain would otherwise not have contextual checks run on them, which is
// required before accepting them, without somewhat more complicated logic
// later to detect them.
var finalErr error
currentTip := b.bestChain.Tip()
b.addRecentBlock(block)
acceptedNodes, err := b.maybeAcceptBlocks(currentTip, linkedNodes, flags)
if err != nil {
finalErr = err
// This intentionally falls through since the code below must run
// whether or not any blocks were accepted.
}
// Determine what the expected effects of the block, in terms of forks and
// reorganizations, will be on the chain and log it.
//
// 1) There is no effect if the block is not able to be validated yet
// 2) The block is causing a reorg when the new current best tip is not an
// ancestor of the new target tip
// 3) The block is either forking the best chain or extending an existing
// fork of it when it does not cause a reorg and it is not an ancestor
// of the new target tip
target := b.index.FindBestChainCandidate()
if b.index.CanValidate(node) {
triggersReorg := target.Ancestor(currentTip.height) != currentTip
if triggersReorg {
log.Infof("REORGANIZE: Block %v is causing a reorganize", node.hash)
} else if target.Ancestor(node.height) != node {
fork := b.bestChain.FindFork(node)
if fork == node.parent {
log.Infof("FORK: Block %v (height %v) forks the chain at "+
"height %d/block %v, but does not cause a reorganize",
node.hash, node.height, fork.height, fork.hash)
} else {
log.Infof("EXTEND FORK: Block %v (height %v) extends a side "+
"chain which forks the chain at height %d/block %v",
node.hash, node.height, fork.height, fork.hash)
}
}
}
// Find the best chain candidate and attempt to reorganize the chain to it.
// This will have no effect when the target is the same as the current best
// chain tip.
//
// Note that any errors that take place in the reorg will be attributed to
// the block being processed. The calling code currently depends on this
// behavior, so care must be taken if this behavior is changed.
reorgErr := b.reorganizeChain(target)
switch {
// The final error is just the reorg error in the case there was no error
// carried forward from above.
case reorgErr != nil && finalErr == nil:
finalErr = reorgErr
// The final error is a multi error when there is a reorg error and an error
// was carried forward from above. Additionally, in the case the reorg
// error is itself a multi error, combine it into a single multi error
// rather than wrapping it inside another one.
case reorgErr != nil && finalErr != nil:
var mErr MultiError
if errors.As(reorgErr, &mErr) {
combined := make([]error, 0, len(mErr)+1)
combined = append(combined, finalErr)
combined = append(combined, mErr...)
finalErr = MultiError(combined)
} else {
finalErr = MultiError{finalErr, reorgErr}
}
}
// Notify the caller about any blocks that are now linked and were accepted
// to the block chain. The caller would typically want to react by relaying
// the inventory to other peers unless it was already relayed above via
// NTNewTipBlockChecked.
//
// Note that this intentionally waits until after the chain reorganization
// above so that the information is relative to the final best chain after
// validation.
newTip := b.bestChain.Tip()
b.chainLock.Unlock()
for _, n := range acceptedNodes {
// Skip any blocks which either themselves failed validation or are
// descenants of one that failed.
if b.index.NodeStatus(n).KnownInvalid() {
continue
}
var forkLen int64
if fork := b.bestChain.FindFork(n); fork != nil {
forkLen = n.height - fork.height
}
b.sendNotification(NTBlockAccepted, &BlockAcceptedNtfnsData{
BestHeight: newTip.height,
ForkLen: forkLen,
Block: block,
})
}
b.chainLock.Lock()
var forkLen int64
if finalErr == nil {
if fork := b.bestChain.FindFork(node); fork != nil {
forkLen = node.height - fork.height
}
}
return forkLen, finalErr
}
// InvalidateBlock manually invalidates the provided block as if the block had
// violated a consensus rule and marks all of its descendants as having a known
// invalid ancestor. It then reorganizes the chain as necessary so the branch
// with the most cumulative proof of work that is still valid becomes the main
// chain.
func (b *BlockChain) InvalidateBlock(hash *chainhash.Hash) error {
b.processLock.Lock()
defer b.processLock.Unlock()
// Unable to invalidate a block that does not exist.
node := b.index.LookupNode(hash)
if node == nil {
return unknownBlockError(hash)
}
// Disallow invalidation of the genesis block.
if node.height == 0 {
str := "invalidating the genesis block is not allowed"
return contextError(ErrInvalidateGenesisBlock, str)
}
// Nothing to do if the block is already known to have failed validation.
// Notice that this is intentionally not considering the case when the block
// is marked invalid due to having a known invalid ancestor so the block is
// still manually marked as having failed validation in that case.
if b.index.NodeStatus(node).KnownValidateFailed() {
return nil
}
// Simply mark the block being invalidated as having failed validation and
// all of its descendants as having an invalid ancestor when it is not part
// of the current best chain.
b.recentContextChecks.Delete(node.hash)
if !b.bestChain.Contains(node) {
b.index.MarkBlockFailedValidation(node)
b.chainLock.Lock()
b.flushBlockIndexWarnOnly()
b.chainLock.Unlock()
return nil
}
log.Infof("Rolling the chain back to block %s (height %d) due to manual "+
"invalidation", node.parent.hash, node.parent.height)
// At this point, the invalidated block is part of the current best chain,
// so start by reorganizing the chain back to its parent and marking it as
// having failed validation along with all of its descendants as having an
// invalid ancestor.
b.chainLock.Lock()
if err := b.reorganizeChain(node.parent); err != nil {
b.flushBlockIndexWarnOnly()
b.chainLock.Unlock()
return err
}
b.index.MarkBlockFailedValidation(node)
// Reset whether or not the chain believes it is current since the best
// chain was just invalidated.
newTip := b.bestChain.Tip()
b.isCurrentLatch = false
b.maybeUpdateIsCurrent(newTip)
b.chainLock.Unlock()
// The new best chain tip is probably no longer in the best chain candidates
// since it was likely removed due to previously having less work, so scour
// the block tree in order repopulate the best chain candidates.
b.index.Lock()
b.index.addBestChainCandidate(newTip)
b.index.forEachChainTip(func(tip *blockNode) error {
// Chain tips that have less work than the new tip are not best chain
// candidates nor are any of their ancestors since they have even less
// work.
if tip.workSum.Cmp(newTip.workSum) < 0 {
return nil
}
// Find the first ancestor of the tip that is not known to be invalid
// and can be validated. Then add it as a candidate to potentially
// become the best chain tip if it has the same or more work than the
// current one.
n := tip
for n != nil && (n.status.KnownInvalid() || !b.index.canValidate(n)) {
n = n.parent
}
if n != nil && n != newTip && n.workSum.Cmp(newTip.workSum) >= 0 {
b.index.addBestChainCandidate(n)
}
return nil
})
b.index.Unlock()
// Find the current best chain candidate and attempt to reorganize the chain
// to it. The most common case is for the candidate to extend the current
// best chain, however, it might also be a candidate that would cause a
// reorg or be the current main chain tip, which will be the case when the
// passed block is on a side chain.
b.chainLock.Lock()
targetTip := b.index.FindBestChainCandidate()
err := b.reorganizeChain(targetTip)
b.flushBlockIndexWarnOnly()
b.chainLock.Unlock()
return err
}
// blockNodeInSlice return whether a given block node is an element in a slice
// of them.
func blockNodeInSlice(node *blockNode, slice []*blockNode) bool {
for _, child := range slice {
if child == node {
return true
}
}
return false
}
// ReconsiderBlock removes the known invalid status of the provided block and
// all of its ancestors along with the known invalid ancestor status from all of
// its descendants that are neither themselves marked as having failed
// validation nor descendants of another such block. Therefore, it allows the
// affected blocks to be reconsidered under the current consensus rules. It
// then potentially reorganizes the chain as necessary so the block with the
// most cumulative proof of work that is valid becomes the tip of the main
// chain.
func (b *BlockChain) ReconsiderBlock(hash *chainhash.Hash) error {
b.processLock.Lock()
defer b.processLock.Unlock()
// Unable to reconsider a block that does not exist.
node := b.index.LookupNode(hash)
if node == nil {
return unknownBlockError(hash)
}
log.Infof("Reconsidering block %s (height %d)", node.hash, node.height)
// Remove invalidity flags from the block to be reconsidered and all of its
// ancestors while tracking the earliest such block that is marked as having
// failed validation since all descendants of that block need to have their
// invalid ancestor flag removed.
//
// Also, add any that are eligible for validation as candidates to
// potentially become the best chain when they have the same or more work
// than the current best chain tip and remove any cached validation-related
// state for them to ensure they undergo full revalidation should it be
// necessary.
//
// Finally, add any that are not already fully linked and have their data
// available to the map of unlinked blocks that are eligible for connection
// when they are not already present.
curBestTip := b.bestChain.Tip()
vfNode := node
b.index.Lock()
for n := node; n != nil && n.height > 0; n = n.parent {
if n.status.KnownInvalid() {
if n.status.KnownValidateFailed() {
vfNode = n
}
b.index.unsetStatusFlags(n, statusValidateFailed|statusInvalidAncestor)
b.recentContextChecks.Delete(n.hash)
}
if b.index.canValidate(n) && n.workSum.Cmp(curBestTip.workSum) >= 0 {
b.index.addBestChainCandidate(n)
}
if !n.isFullyLinked && n.status.HaveData() && n.parent != nil {
unlinked := b.index.unlinkedChildrenOf[n.parent]
if !blockNodeInSlice(n, unlinked) {
b.index.unlinkedChildrenOf[n.parent] = append(unlinked, n)
}
}
}
// Remove the known invalid ancestor flag from all blocks that descend from
// the earliest failed block to be reconsidered that are neither themselves
// marked as having failed validation nor descendants of another such block.
//
// Also, add any that are eligible for validation as candidates to
// potentially become the best chain when they have the same or more work
// than the current best chain tip and remove any cached validation-related
// state for them to ensure they undergo full revalidation should it be
// necessary.
//
// Finally, add any that are not already fully linked and have their data
// available to the map of unlinked blocks that are eligible for connection
// when they are not already present.
//
// Chain tips at the same or lower heights than the earliest failed block to
// be reconsidered can't possibly be descendants of it, so use it as the
// lower height bound filter when iterating chain tips.
b.index.forEachChainTipAfterHeight(vfNode, func(tip *blockNode) error {
// Nothing to do if the earliest failed block to be reconsidered is not
// an ancestor of this chain tip.
if tip.Ancestor(vfNode.height) != vfNode {
return nil
}
// Find the final descendant that is not known to descend from another
// one that failed validation since all descendants after that point
// need to retain their known invalid ancestor status.
finalNotKnownInvalidDescendant := tip
for n := tip; n != vfNode; n = n.parent {
if n.status.KnownValidateFailed() {
finalNotKnownInvalidDescendant = n.parent
}
}
for n := finalNotKnownInvalidDescendant; n != vfNode; n = n.parent {
b.index.unsetStatusFlags(n, statusInvalidAncestor)
b.recentContextChecks.Delete(n.hash)
if b.index.canValidate(n) && n.workSum.Cmp(curBestTip.workSum) >= 0 {
b.index.addBestChainCandidate(n)
}
if !n.isFullyLinked && n.status.HaveData() && n.parent != nil {
unlinked := b.index.unlinkedChildrenOf[n.parent]
if !blockNodeInSlice(n, unlinked) {
b.index.unlinkedChildrenOf[n.parent] = append(unlinked, n)
}
}
}
return nil
})
// Update the best known invalid block (as determined by having the most
// cumulative work) and best header that is not known to be invalid as
// needed.
//
// Note this is separate from the above iteration because all tips must be
// considered as opposed to just those that are possible descendants of the
// node being reconsidered.
b.index.bestInvalid = nil
b.index.forEachChainTip(func(tip *blockNode) error {
if tip.status.KnownInvalid() {
b.index.maybeUpdateBestInvalid(tip)
}
b.index.maybeUpdateBestHeaderForTip(tip)
return nil
})
b.index.Unlock()
// Reset whether or not the chain believes it is current, find the best
// chain candidate, and attempt to reorganize the chain to it.
b.chainLock.Lock()
b.isCurrentLatch = false
targetTip := b.index.FindBestChainCandidate()
err := b.reorganizeChain(targetTip)
b.flushBlockIndexWarnOnly()
b.chainLock.Unlock()
// Force pruning of the cached chain tips since it's fairly likely the best
// tip has experienced a sudden change and is higher given how this function
// is typically used and the logic which only periodically prunes tips is
// optimized for steady state operation.
b.index.Lock()
b.index.pruneCachedTips(b.bestChain.Tip())
b.index.Unlock()
return err
}