What changed, and why it matters
This commit adds a new helper function called isInIBDMode to btcd's network synchronization code. It simply checks whether the local node is still catching up to the rest of the Bitcoin network (Initial Block Download mode). The change is purely additive: it introduces the helper and its unit tests, but does not change any existing behavior or fix any visible bug on its own.
No immediate action required. Treat as routine code/test addition. Review the follow-up commits that actually call isInIBDMode to determine whether the new logic is used safely.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch adds isInIBDMode() to netsync/manager.go. The function returns true unless sm.chain.IsCurrent() is true and no peer advertises a higher block than our best snapshot. It also adds TestIsInIBDMode in netsync/manager_test.go with four cases covering the four combinations of IsCurrent and higher peers, plus a mockTimeSource to force IsCurrent() true. No callers of isInIBDMode are added or modified in this commit, so it is a preparatory refactor/test addition rather than a behavioral fix.
Changed components
netsync/manager.gonetsync/manager_test.goInspect captured patch +172 / −0
diff --git a/netsync/manager.go b/netsync/manager.go
index 626cf49..5f40ff8 100644
--- a/netsync/manager.go
+++ b/netsync/manager.go
@@ -272,6 +272,18 @@ func (sm *SyncManager) fetchHigherPeers(height int32) []*peerpkg.Peer {
return higherPeers
}
+// isInIBDMode returns true if there's more blocks needed to be downloaded to
+// catch up to the latest chain tip.
+func (sm *SyncManager) isInIBDMode() bool {
+ best := sm.chain.BestSnapshot()
+ higherPeers := sm.fetchHigherPeers(best.Height)
+ if sm.chain.IsCurrent() && len(higherPeers) == 0 {
+ return false
+ }
+
+ return true
+}
+
// startSync will choose the best peer among the available candidate peers to
// download/sync the blockchain from. When syncing is already running, it
// simply returns. It also examines the candidates for any which are no longer
diff --git a/netsync/manager_test.go b/netsync/manager_test.go
index bc84bc7..8bf1d68 100644
--- a/netsync/manager_test.go
+++ b/netsync/manager_test.go
@@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"testing"
+ "time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcutil"
@@ -284,3 +285,162 @@ func TestFetchHigherPeers(t *testing.T) {
}
}
}
+
+// mockTimeSource is used to trick the BlockChain instance to think that we're
+// in the past. This is so that we can force it to return true for isCurrent().
+type mockTimeSource struct {
+ adjustedTime time.Time
+}
+
+// AdjustedTime returns the internal adjustedTime.
+//
+// Part of the MedianTimeSource interface implementation.
+func (m *mockTimeSource) AdjustedTime() time.Time {
+ return m.adjustedTime
+}
+
+// AddTimeSample isn't relevant so we just leave it as emtpy.
+//
+// Part of the MedianTimeSource interface implementation.
+func (m *mockTimeSource) AddTimeSample(id string, timeVal time.Time) {
+ // purposely left empty
+}
+
+// Offset isn't relevant so we just return 0.
+//
+// Part of the MedianTimeSource interface implementation.
+func (m *mockTimeSource) Offset() time.Duration {
+ return 0
+}
+
+func TestIsInIBDMode(t *testing.T) {
+ tests := []struct {
+ peerState map[*peer.Peer]*peerSyncState
+ params *chaincfg.Params
+ timesource *mockTimeSource
+ isIBDMode bool
+ }{
+ // Is not current, higher peers.
+ {
+ params: &chaincfg.MainNetParams,
+ peerState: func() map[*peer.Peer]*peerSyncState {
+ ps := make(map[*peer.Peer]*peerSyncState)
+ peer := peer.NewInboundPeer(&peer.Config{})
+ peer.UpdateLastBlockHeight(900_000)
+ ps[peer] = &peerSyncState{
+ syncCandidate: true,
+ requestedTxns: make(map[chainhash.Hash]struct{}),
+ requestedBlocks: make(map[chainhash.Hash]struct{}),
+ }
+ return ps
+ }(),
+ timesource: nil,
+ isIBDMode: true,
+ },
+ // Is not current, no higher peers.
+ {
+ params: &chaincfg.MainNetParams,
+ peerState: func() map[*peer.Peer]*peerSyncState {
+ ps := make(map[*peer.Peer]*peerSyncState)
+ peer := peer.NewInboundPeer(&peer.Config{})
+ peer.UpdateLastBlockHeight(0)
+ ps[peer] = &peerSyncState{
+ syncCandidate: true,
+ requestedTxns: make(map[chainhash.Hash]struct{}),
+ requestedBlocks: make(map[chainhash.Hash]struct{}),
+ }
+ return ps
+ }(),
+ timesource: nil,
+ isIBDMode: true,
+ },
+ // Is current, higher peers.
+ {
+ params: func() *chaincfg.Params {
+ params := chaincfg.MainNetParams
+ params.Checkpoints = nil
+ return ¶ms
+ }(),
+ peerState: func() map[*peer.Peer]*peerSyncState {
+ ps := make(map[*peer.Peer]*peerSyncState)
+ peer := peer.NewInboundPeer(&peer.Config{})
+ peer.UpdateLastBlockHeight(900_000)
+ ps[peer] = &peerSyncState{
+ syncCandidate: true,
+ requestedTxns: make(map[chainhash.Hash]struct{}),
+ requestedBlocks: make(map[chainhash.Hash]struct{}),
+ }
+ return ps
+ }(),
+ timesource: &mockTimeSource{
+ chaincfg.MainNetParams.GenesisBlock.Header.Timestamp,
+ },
+ isIBDMode: true,
+ },
+ // Is current, no higher peers.
+ {
+ params: func() *chaincfg.Params {
+ params := chaincfg.MainNetParams
+ params.Checkpoints = nil
+ return ¶ms
+ }(),
+ peerState: func() map[*peer.Peer]*peerSyncState {
+ ps := make(map[*peer.Peer]*peerSyncState)
+ peer := peer.NewInboundPeer(&peer.Config{})
+ peer.UpdateLastBlockHeight(0)
+ ps[peer] = &peerSyncState{
+ syncCandidate: true,
+ requestedTxns: make(map[chainhash.Hash]struct{}),
+ requestedBlocks: make(map[chainhash.Hash]struct{}),
+ }
+ return ps
+ }(),
+ timesource: &mockTimeSource{
+ chaincfg.MainNetParams.GenesisBlock.Header.Timestamp,
+ },
+ isIBDMode: false,
+ },
+ }
+
+ for i, test := range tests {
+ db, tearDown, err := dbSetup(
+ fmt.Sprintf("TestIsInIBDMode-%v", i),
+ test.params)
+ if err != nil {
+ tearDown()
+ t.Fatal(err)
+ }
+
+ timesource := blockchain.NewMedianTime()
+ if test.timesource != nil {
+ timesource = test.timesource
+ }
+
+ // Create the main chain instance.
+ chain, err := blockchain.New(&blockchain.Config{
+ DB: db,
+ Checkpoints: test.params.Checkpoints,
+ ChainParams: test.params,
+ TimeSource: timesource,
+ SigCache: txscript.NewSigCache(1000),
+ })
+ if err != nil {
+ tearDown()
+ t.Fatal(err)
+ }
+ sm, err := New(&Config{
+ Chain: chain,
+ ChainParams: test.params,
+ })
+ if err != nil {
+ tearDown()
+ t.Fatal(err)
+ }
+
+ // Run test and assert.
+ sm.peerStates = test.peerState
+ got := sm.isInIBDMode()
+ require.Equal(t, test.isIBDMode, got)
+ tearDown()
+ }
+}
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.