What changed, and why it matters
This commit adds a new internal helper function called checkHeadersList to the Bitcoin network synchronization code, along with unit tests. It decides whether a block being downloaded during initial sync should use a faster verification path (BFFastAdd) and whether the block is a known checkpoint. There is no direct evidence in the commit or supplied references that this fixes an active security vulnerability; it appears to be a correctness or refactoring change.
No immediate action required. Treat as routine code/test addition. If this commit is part of a larger PR, review the follow-up commits that wire checkHeadersList into block handling to assess whether the combined change affects consensus or DoS resistance.
Security signals we found
New helper affects verification behavior flags (BFFastAdd) during initial block download
Checkpoint logic could influence how much validation is performed on blocks near checkpoints
Commit is additive only; no call sites in production code are changed in this diff
Evidence from the diff
The change introduces SyncManager.checkHeadersList, which returns (isCheckpointBlock, behaviorFlags). It only applies during headers-first initial block download (headersFirstMode). For headers already validated by chain.IsValidHeader and whose height is below the next checkpoint, it sets BFFastAdd. If the block hash equals the next checkpoint hash, it also marks it as a checkpoint block. The accompanying tests verify behavior for mainnet blocks 1-11 with a synthetic checkpoint at height 11. The function is added but not yet wired into handleBlockMsg in this commit, so it is a partial/introductory patch.
Changed components
btcd/netsync/manager.gobtcd/netsync/manager_test.gobtcd/netsync/testdata/blockheaders-mainnet-1-11.txtInspect captured patch +280 / −0
diff --git a/netsync/manager.go b/netsync/manager.go
index d10188d..480eead 100644
--- a/netsync/manager.go
+++ b/netsync/manager.go
@@ -690,6 +690,47 @@ func (sm *SyncManager) current() bool {
return true
}
+// checkHeadersList checks if the sync manager is in the initial block download
+// mode and returns if the given block hash is a checkpointed block and the
+// behavior flags for this block. If the block is still under the checkpoint,
+// then it's given the fast-add flag.
+func (sm *SyncManager) checkHeadersList(blockHash *chainhash.Hash) (
+ bool, blockchain.BehaviorFlags) {
+
+ // Always return false and BFNone if we're not in ibd mode.
+ if !sm.headersFirstMode {
+ return false, blockchain.BFNone
+ }
+
+ isCheckpointBlock := false
+ behaviorFlags := blockchain.BFNone
+
+ // If we don't already know this is a valid header, return false and
+ // BFNone.
+ if !sm.chain.IsValidHeader(blockHash) {
+ return false, blockchain.BFNone
+ }
+
+ height, err := sm.chain.HeaderHeightByHash(*blockHash)
+ if err != nil {
+ return false, blockchain.BFNone
+ }
+
+ // Since findNextHeaderCheckpoint returns the next checkpoint after the
+ // passed height, we do a -1 to include the current block.
+ checkpoint := sm.findNextHeaderCheckpoint(height - 1)
+ if checkpoint == nil {
+ return false, blockchain.BFNone
+ }
+
+ behaviorFlags |= blockchain.BFFastAdd
+ if blockHash.IsEqual(checkpoint.Hash) {
+ isCheckpointBlock = true
+ }
+
+ return isCheckpointBlock, behaviorFlags
+}
+
// handleBlockMsg handles block messages from all peers.
func (sm *SyncManager) handleBlockMsg(bmsg *blockMsg) {
peer := bmsg.peer
diff --git a/netsync/manager_test.go b/netsync/manager_test.go
new file mode 100644
index 0000000..ee84cea
--- /dev/null
+++ b/netsync/manager_test.go
@@ -0,0 +1,228 @@
+package netsync
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/btcsuite/btcd/blockchain"
+ "github.com/btcsuite/btcd/btcutil"
+ "github.com/btcsuite/btcd/chaincfg"
+ "github.com/btcsuite/btcd/chaincfg/chainhash"
+ "github.com/btcsuite/btcd/database"
+ _ "github.com/btcsuite/btcd/database/ffldb"
+ "github.com/btcsuite/btcd/mempool"
+ "github.com/btcsuite/btcd/peer"
+ "github.com/btcsuite/btcd/txscript"
+ "github.com/btcsuite/btcd/wire"
+ "github.com/stretchr/testify/require"
+)
+
+// noopPeerNotifier is a no-op implementation of PeerNotifier for tests.
+type noopPeerNotifier struct{}
+
+func (noopPeerNotifier) AnnounceNewTransactions([]*mempool.TxDesc) {}
+func (noopPeerNotifier) UpdatePeerHeights(*chainhash.Hash, int32, *peer.Peer) {}
+func (noopPeerNotifier) RelayInventory(*wire.InvVect, interface{}) {}
+func (noopPeerNotifier) TransactionConfirmed(*btcutil.Tx) {}
+
+// dbSetup is used to create a new db with the genesis block already inserted.
+// It returns a teardown function the caller should invoke when done testing to
+// clean up. The database is stored under t.TempDir() which is automatically
+// removed when the test finishes.
+func dbSetup(t *testing.T, params *chaincfg.Params) (database.DB, func(), error) {
+ dbPath := filepath.Join(t.TempDir(), "ffldb")
+ db, err := database.Create("ffldb", dbPath, params.Net)
+ if err != nil {
+ return nil, nil, fmt.Errorf("error creating db: %v", err)
+ }
+
+ teardown := func() {
+ db.Close()
+ }
+
+ return db, teardown, nil
+}
+
+// chainSetup is used to create a new db and chain instance with the genesis
+// block already inserted. In addition to the new chain instance, it returns
+// a teardown function the caller should invoke when done testing to clean up.
+func chainSetup(t *testing.T, params *chaincfg.Params) (
+ *blockchain.BlockChain, func(), error) {
+
+ db, teardown, err := dbSetup(t, params)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Copy the chain params to ensure any modifications the tests do to
+ // the chain parameters do not affect the global instance.
+ paramsCopy := *params
+
+ // Deep-copy deployment starters/enders so that parallel tests don't
+ // race on the shared blockClock field written by SynchronizeClock.
+ for i := range paramsCopy.Deployments {
+ d := ¶msCopy.Deployments[i]
+ if s, ok := d.DeploymentStarter.(*chaincfg.MedianTimeDeploymentStarter); ok {
+ d.DeploymentStarter = chaincfg.NewMedianTimeDeploymentStarter(
+ s.StartTime())
+ }
+ if e, ok := d.DeploymentEnder.(*chaincfg.MedianTimeDeploymentEnder); ok {
+ d.DeploymentEnder = chaincfg.NewMedianTimeDeploymentEnder(
+ e.EndTime())
+ }
+ }
+
+ // Create the main chain instance.
+ chain, err := blockchain.New(&blockchain.Config{
+ DB: db,
+ Checkpoints: paramsCopy.Checkpoints,
+ ChainParams: ¶msCopy,
+ TimeSource: blockchain.NewMedianTime(),
+ SigCache: txscript.NewSigCache(1000),
+ })
+ if err != nil {
+ teardown()
+ err := fmt.Errorf("failed to create chain instance: %v", err)
+ return nil, nil, err
+ }
+ return chain, teardown, nil
+}
+
+// loadHeaders loads headers from mainnet from 1 to 11.
+func loadHeaders(t *testing.T) []*wire.BlockHeader {
+ testFile := "blockheaders-mainnet-1-11.txt"
+ filename := filepath.Join("testdata/", testFile)
+
+ file, err := os.Open(filename)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ headers := make([]*wire.BlockHeader, 0, 10)
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ line := scanner.Text()
+ b, err := hex.DecodeString(line)
+ if err != nil {
+ t.Fatalf("failed to read block headers from file %v", testFile)
+ }
+
+ r := bytes.NewReader(b)
+ header := new(wire.BlockHeader)
+ header.Deserialize(r)
+
+ headers = append(headers, header)
+ }
+
+ return headers
+}
+
+func makeMockSyncManager(t *testing.T,
+ params *chaincfg.Params) (*SyncManager, func()) {
+
+ t.Helper()
+
+ chain, tearDown, err := chainSetup(t, params)
+ require.NoError(t, err)
+
+ sm, err := New(&Config{
+ PeerNotifier: noopPeerNotifier{},
+ Chain: chain,
+ ChainParams: params,
+ })
+ require.NoError(t, err)
+
+ return sm, tearDown
+}
+
+func TestCheckHeadersList(t *testing.T) {
+ // Set params to mainnet with a checkpoint at block 11.
+ params := chaincfg.MainNetParams
+ checkpointHeight := int32(11)
+ checkpointHash, err := chainhash.NewHashFromStr(
+ "0000000097be56d606cdd9c54b04d4747e957d3608abe69198c661f2add73073")
+ if err != nil {
+ t.Fatal(err)
+ }
+ params.Checkpoints = []chaincfg.Checkpoint{
+ {
+ Height: checkpointHeight,
+ Hash: checkpointHash,
+ },
+ }
+
+ // Create mock SyncManager.
+ sm, tearDown := makeMockSyncManager(t, ¶ms)
+ defer tearDown()
+
+ // Setup SyncManager with headers processed.
+ headers := loadHeaders(t)
+ for _, header := range headers {
+ isMainChain, err := sm.chain.ProcessBlockHeader(
+ header, blockchain.BFNone, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !isMainChain {
+ t.Fatalf("expected block header %v to be in the main chain",
+ header.BlockHash())
+ }
+ }
+
+ tests := []struct {
+ hash string
+ isCheckpointBlock bool
+ behaviorFlags blockchain.BehaviorFlags
+ }{
+ {
+ hash: chaincfg.MainNetParams.GenesisHash.String(),
+ isCheckpointBlock: false,
+ behaviorFlags: blockchain.BFFastAdd,
+ },
+ {
+ // Block 10.
+ hash: "000000002c05cc2e78923c34df87fd108b22221ac6076c18f3ade378a4d915e9",
+ isCheckpointBlock: false,
+ behaviorFlags: blockchain.BFFastAdd,
+ },
+ {
+ // Block 11.
+ hash: "0000000097be56d606cdd9c54b04d4747e957d3608abe69198c661f2add73073",
+ isCheckpointBlock: true,
+ behaviorFlags: blockchain.BFFastAdd,
+ },
+ {
+ // Block 12.
+ hash: "0000000027c2488e2510d1acf4369787784fa20ee084c258b58d9fbd43802b5e",
+ isCheckpointBlock: false,
+ behaviorFlags: blockchain.BFNone,
+ },
+ }
+
+ for _, test := range tests {
+ hash, err := chainhash.NewHashFromStr(test.hash)
+ if err != nil {
+ t.Errorf("NewHashFromStr: %v", err)
+ continue
+ }
+
+ // Make sure that when the headers-first mode is off, we always get
+ // false and BFNone.
+ sm.headersFirstMode = false
+ isCheckpoint, gotFlags := sm.checkHeadersList(hash)
+ require.Equal(t, false, isCheckpoint)
+ require.Equal(t, blockchain.BFNone, gotFlags)
+
+ // Now check that the test values are correct.
+ sm.headersFirstMode = true
+ isCheckpoint, gotFlags = sm.checkHeadersList(hash)
+ require.Equal(t, test.isCheckpointBlock, isCheckpoint)
+ require.Equal(t, test.behaviorFlags, gotFlags)
+ }
+}
diff --git a/netsync/testdata/blockheaders-mainnet-1-11.txt b/netsync/testdata/blockheaders-mainnet-1-11.txt
new file mode 100644
index 0000000..27b5f18
--- /dev/null
+++ b/netsync/testdata/blockheaders-mainnet-1-11.txt
@@ -0,0 +1,11 @@
+010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e36299
+010000004860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000d5fdcc541e25de1c7a5addedf24858b8bb665c9f36ef744ee42c316022c90f9bb0bc6649ffff001d08d2bd61
+01000000bddd99ccfda39da1b108ce1a5d70038d0a967bacb68b6b63065f626a0000000044f672226090d85db9a9f2fbfe5f0f9609b387af7be5b7fbb7a1767c831c9e995dbe6649ffff001d05e0ed6d
+010000004944469562ae1c2c74d9a535e00b6f3e40ffbad4f2fda3895501b582000000007a06ea98cd40ba2e3288262b28638cec5337c1456aaf5eedc8e9e5a20f062bdf8cc16649ffff001d2bfee0a9
+0100000085144a84488ea88d221c8bd6c059da090e88f8a2c99690ee55dbba4e00000000e11c48fecdd9e72510ca84f023370c9a38bf91ac5cae88019bee94d24528526344c36649ffff001d1d03e477
+01000000fc33f596f822a0a1951ffdbf2a897b095636ad871707bf5d3162729b00000000379dfb96a5ea8c81700ea4ac6b97ae9a9312b2d4301a29580e924ee6761a2520adc46649ffff001d189c4c97
+010000008d778fdc15a2d3fb76b7122a3b5582bea4f21f5a0c693537e7a03130000000003f674005103b42f984169c7d008370967e91920a6a5d64fd51282f75bc73a68af1c66649ffff001d39a59c86
+010000004494c8cf4154bdcc0720cd4a59d9c9b285e4b146d45f061d2b6c967100000000e3855ed886605b6d4a99d5fa2ef2e9b0b164e63df3c4136bebf2d0dac0f1f7a667c86649ffff001d1c4b5666
+01000000c60ddef1b7618ca2348a46e868afc26e3efc68226c78aa47f8488c4000000000c997a5e56e104102fa209c6a852dd90660a20b2d9c352423edce25857fcd37047fca6649ffff001d28404f53
+010000000508085c47cc849eb80ea905cc7800a3be674ffc57263cf210c59d8d00000000112ba175a1e04b14ba9e7ea5f76ab640affeef5ec98173ac9799a852fa39add320cd6649ffff001d1e2de565
+01000000e915d9a478e3adf3186c07c61a22228b10fd87df343c92782ecc052c000000006e06373c80de397406dc3d19c90d71d230058d28293614ea58d6a57f8f5d32f8b8ce6649ffff001d173807f8
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.