What changed, and why it matters
This commit adds a new internal function to btcd that accepts block headers into a separate 'header chain' before downloading full blocks. It is a feature/refactoring change for a 'headers-first' download mode. There is no indication in the commit that it fixes a security bug or introduces a vulnerability; it appears to be normal protocol implementation work.
No security action required. Review as part of normal code review for consensus-correctness if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces maybeAcceptBlockHeader in blockchain/accept.go. It validates a wire.BlockHeader against the existing header chain, runs CheckBlockHeaderSanity and CheckBlockHeaderContext, adds a new block node with statusHeaderStored, flushes the index, and updates bestHeader if the header extends the most-work chain. The function is a header-only counterpart to the existing maybeAcceptBlock. No existing logic is removed or altered; the change is purely additive.
Changed components
blockchain/accept.goInspect captured patch +133 / −0
diff --git a/blockchain/accept.go b/blockchain/accept.go
index 4adc2f6..6a99bac 100644
--- a/blockchain/accept.go
+++ b/blockchain/accept.go
@@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/database"
+ "github.com/btcsuite/btcd/wire"
)
// maybeAcceptBlock potentially accepts a block into the block chain and, if
@@ -92,3 +93,135 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
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
+}
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.