btcd/blockchain/accept.go
2026-05-14 18:05:33 -07:00

236 lines
8.6 KiB
Go

// Copyright (c) 2013-2017 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockchain
import (
"fmt"
"github.com/btcsuite/btcd/btcutil/v2"
"github.com/btcsuite/btcd/database"
"github.com/btcsuite/btcd/wire/v2"
)
// maybeAcceptBlock potentially accepts a block into the block chain and, if
// accepted, returns whether or not it is on the main chain. It performs
// several validation checks which depend on its position within the block chain
// before adding it. The block is expected to have already gone through
// ProcessBlock before calling this function with it.
//
// The flags are also passed to checkBlockContext and connectBestChain. See
// their documentation for how the flags modify their behavior.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) (bool, error) {
// The height of this block is one more than the referenced previous
// block.
prevHash := &block.MsgBlock().Header.PrevBlock
prevNode := b.index.LookupNode(prevHash)
if prevNode == nil {
str := fmt.Sprintf("previous block %s is unknown", prevHash)
return false, ruleError(ErrPreviousBlockUnknown, str)
} else if b.index.NodeStatus(prevNode).KnownInvalid() {
str := fmt.Sprintf("previous block %s is known to be invalid", prevHash)
return false, ruleError(ErrInvalidAncestorBlock, str)
}
blockHeight := prevNode.height + 1
block.SetHeight(blockHeight)
// The block must pass all of the validation rules which depend on the
// position of the block within the block chain.
err := b.checkBlockContext(block, prevNode, flags)
if err != nil {
return false, err
}
// 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 dbStoreBlock(dbTx, block)
})
if err != nil {
return false, err
}
// Create a new block node for the block and add it to the node index. Even
// if the block ultimately gets connected to the main chain, it starts out
// on a side chain.
//
// If a header-only node already exists (from maybeAcceptBlockHeader),
// upgrade its status rather than creating a new node. Creating a new
// node would overwrite the index entry, orphaning the pointer held by
// bestHeader's chainView and breaking Contains checks.
newNode := b.index.LookupNode(block.Hash())
if newNode != nil {
b.index.SetStatusFlags(newNode, statusDataStored)
} else {
blockHeader := &block.MsgBlock().Header
newNode = newBlockNode(blockHeader, prevNode)
newNode.status = statusDataStored | statusHeaderStored
b.index.AddNode(newNode)
}
err = b.index.flushToDB()
if err != nil {
return false, err
}
// Connect the passed block to the chain while respecting proper chain
// selection according to the chain with the most proof of work. This
// also handles validation of the transaction scripts.
isMainChain, err := b.connectBestChain(newNode, block, flags)
if err != nil {
return false, err
}
// Notify the caller that the new block was accepted into the block
// chain. The caller would typically want to react by relaying the
// inventory to other peers.
func() {
b.chainLock.Unlock()
defer b.chainLock.Lock()
b.sendNotification(NTBlockAccepted, block)
}()
return isMainChain, nil
}
// maybeAcceptBlockHeader potentially accepts the header to the block index and,
// if accepted, returns a bool indicating if the header extended the best chain
// of headers. It also performs several context independent checks as well as
// those which depend on its position within the header chain.
//
// The flags are passed to CheckBlockHeaderSanity and CheckBlockHeaderContext
// which allow the skipping of PoW check or the check for the block difficulty,
// median time check, and the BIP94 check.
//
// The skipCheckpoint boolean allows skipping of the check for if the header is
// part of the existing checkpoints.
//
// 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.
//
// This function MUST be called with the chain lock held (for writes).
func (b *BlockChain) maybeAcceptBlockHeader(header *wire.BlockHeader,
flags BehaviorFlags, skipCheckpoint bool) (bool, error) {
// 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 false, ruleError(ErrPreviousBlockUnknown, str)
}
// This header is invalid if its previous node is invalid.
if b.index.NodeStatus(prevNode).KnownInvalid() {
str := fmt.Sprintf(
"previous block %s is known to be invalid", prevHash)
return false, ruleError(ErrInvalidAncestorBlock, str)
}
// 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.
hash := header.BlockHash()
node := b.index.LookupNode(&hash)
if node != nil {
nodeStatus := b.index.NodeStatus(node)
if nodeStatus&statusValidateFailed != 0 {
str := fmt.Sprintf("block %s is known to be invalid", hash)
return false, ruleError(ErrKnownInvalidBlock, str)
} else if nodeStatus&statusInvalidAncestor != 0 {
str := fmt.Sprintf("block %s has an invalid ancestor", hash)
return false, ruleError(ErrInvalidAncestorBlock, str)
}
// If the node is in the bestHeaders chainview, it's in the main chain.
// If it isn't, then we'll go through the verification process below.
if b.bestHeader.Contains(node) {
return true, nil
}
}
// Perform context-free sanity checks on the block header.
err := CheckBlockHeaderSanity(
header, b.chainParams.PowLimit, b.timeSource, flags)
if err != nil {
return false, err
}
// The block must pass all of the validation rules which depend on the
// position of the block within the block chain.
err = CheckBlockHeaderContext(header, prevNode, flags, b, skipCheckpoint)
if err != nil {
return false, err
}
// Create a new block node for the block and add it to the block index.
//
// Note that the additional information for the actual transactions and
// witnesses in the block can't be populated until the full block data is
// known since that information is not available in the header.
if node == nil {
node = newBlockNode(header, prevNode)
node.status = statusHeaderStored
b.index.AddNode(node)
}
// Flush the block index to database at this point since we added the
// node.
err = b.index.flushToDB()
if err != nil {
return false, err
}
// Check if the header extends the best header tip.
isMainChain := false
parentHash := &header.PrevBlock
if parentHash.IsEqual(&b.bestHeader.Tip().hash) {
log.Debugf("accepted header %v as the new header tip", node.hash)
// This header is now the end of the best headers.
b.bestHeader.SetTip(node)
isMainChain = true
return isMainChain, nil
}
// We're extending (or creating) a side chain, but the cumulative
// work for this new side chain is not enough to make it the new chain.
if node.workSum.Cmp(b.bestHeader.Tip().workSum) <= 0 {
// Log information about how the header is forking the chain.
fork := b.bestHeader.FindFork(node)
if fork.hash.IsEqual(parentHash) {
log.Infof("FORK: BlockHeader %v(%v) forks the chain at block %v(%v) "+
"but did not have enough work to be the "+
"main chain", node.hash, node.height, fork.hash, fork.height)
} else {
log.Infof("EXTEND FORK: BlockHeader %v(%v) extends a side chain "+
"which forks the chain at block %v(%v)",
node.hash, node.height, fork.hash, fork.height)
}
return false, nil
}
prevTip := b.bestHeader.Tip()
log.Infof("NEW BEST HEADER CHAIN: BlockHeader %v(%v) is now a longer "+
"PoW chain than the previous header tip of %v(%v).",
node.hash, node.height,
prevTip.hash, prevTip.height)
b.bestHeader.SetTip(node)
isMainChain = true
return isMainChain, nil
}