Skip to content

Commit e321f28

Browse files
committed
netsync: Always request initial non-mining state.
Currently, the existing logic to avoid requesting the initial mining state is slightly incorrect since the initial state was extended to include additional data that is not specifically related to the mining state. Concretely, it currently does not request any initial state at all when the no mining state synchronization flag is set instead of only avoiding the data specific to the mining state. Since the flag is not set by default, most nodes would not encounter this condition, but the result for any nodes with the no mining sync flag set is that any unmined treasury spends that are currently being voted on would not immediately be accessible to nodes initially coming online. This resolves the aforementioned by modifying the relevant logic to only avoid requesting the data specific to the mining state. While here, it also changes the logic to prevent duplicate initial state requests to use a sync primitive that will ultimately allow requests to be run in parallel instead of requiring them all to go through a single goroutine to protect concurrent access.
1 parent f98d08e commit e321f28

File tree

1 file changed

+54
-49
lines changed

1 file changed

+54
-49
lines changed

internal/netsync/manager.go

Lines changed: 54 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) 2013-2016 The btcsuite developers
2-
// Copyright (c) 2015-2024 The Decred developers
2+
// Copyright (c) 2015-2025 The Decred developers
33
// Use of this source code is governed by an ISC
44
// license that can be found in the LICENSE file.
55

@@ -213,9 +213,9 @@ type Peer struct {
213213
requestedBlocks map[chainhash.Hash]struct{}
214214
requestedMixMsgs map[chainhash.Hash]struct{}
215215

216-
// initialStateRequested tracks whether or not the initial state data has
217-
// been requested from the peer.
218-
initialStateRequested bool
216+
// requestInitialStateOnce is used to ensure the initial state data is only
217+
// requested from the peer once.
218+
requestInitialStateOnce sync.Once
219219

220220
// numConsecutiveOrphanHeaders tracks the number of consecutive header
221221
// messages sent by the peer that contain headers which do not connect. It
@@ -258,6 +258,49 @@ func NewPeer(peer *peerpkg.Peer) *Peer {
258258
}
259259
}
260260

261+
// maybeRequestInitialState potentially requests initial state information from
262+
// the peer by sending it an appropriate initial state sync message dependending
263+
// on the protocol version.
264+
//
265+
// The request will not be sent more than once or when the peer is in the
266+
// process of being removed.
267+
//
268+
// This function is safe for concurrent access.
269+
func (peer *Peer) maybeRequestInitialState(includeMiningState bool) {
270+
// Don't request the initial state more than once or when the peer is in the
271+
// process of being removed.
272+
if !peer.Connected() {
273+
return
274+
}
275+
peer.requestInitialStateOnce.Do(func() {
276+
// Choose which initial state sync p2p messages to use based on the
277+
// protocol version.
278+
//
279+
// Protocol versions prior to the init state version use getminingstate
280+
// and miningstate while those after use getinitstate and initstate.
281+
if peer.ProtocolVersion() < wire.InitStateVersion {
282+
if includeMiningState {
283+
peer.QueueMessage(wire.NewMsgGetMiningState(), nil)
284+
}
285+
return
286+
}
287+
288+
// Always request treasury spends for newer protocol versions.
289+
types := make([]string, 0, 3)
290+
types = append(types, wire.InitStateTSpends)
291+
if includeMiningState {
292+
types = append(types, wire.InitStateHeadBlocks)
293+
types = append(types, wire.InitStateHeadBlockVotes)
294+
}
295+
msg := wire.NewMsgGetInitState()
296+
if err := msg.AddTypes(types...); err != nil {
297+
log.Errorf("Failed to build getinitstate msg: %v", err)
298+
return
299+
}
300+
peer.QueueMessage(msg, nil)
301+
})
302+
}
303+
261304
// headerSyncState houses the state used to track the header sync progress and
262305
// related stall handling.
263306
type headerSyncState struct {
@@ -613,55 +656,17 @@ func (m *SyncManager) startSync() {
613656
}
614657
}
615658

616-
// maybeRequestInitialState potentially requests initial state information from
617-
// the provided peer by sending it an appropriate initial state sync message
618-
// dependending on the protocol version.
619-
//
620-
// The request will not be sent more than once or when the peer is in the
621-
// process of being removed.
622-
func maybeRequestInitialState(peer *Peer) {
623-
// Don't request the initial state more than once or when the peer is in the
624-
// process of being removed.
625-
if peer.initialStateRequested || !peer.Connected() {
626-
return
627-
}
628-
629-
// Choose which initial state sync p2p messages to use based on the protocol
630-
// version. Protocol versions prior to the init state version use
631-
// getminingstate and miningstate while those after use getinitstate and
632-
// initstate.
633-
var msg wire.Message
634-
if peer.ProtocolVersion() < wire.InitStateVersion {
635-
msg = wire.NewMsgGetMiningState()
636-
} else {
637-
m := wire.NewMsgGetInitState()
638-
err := m.AddTypes(wire.InitStateHeadBlocks,
639-
wire.InitStateHeadBlockVotes,
640-
wire.InitStateTSpends)
641-
if err != nil {
642-
log.Errorf("Unexpected error building getinitstate msg: %v", err)
643-
return
644-
}
645-
msg = m
646-
}
647-
peer.QueueMessage(msg, nil)
648-
649-
peer.initialStateRequested = true
650-
}
651-
652659
// onInitialChainSyncDone is invoked when the initial chain sync process
653660
// completes.
654661
func (m *SyncManager) onInitialChainSyncDone() {
655662
best := m.cfg.Chain.BestSnapshot()
656663
log.Infof("Initial chain sync complete (hash %s, height %d)",
657664
best.Hash, best.Height)
658665

659-
// Request initial state from all peers that are marked as needing it now
660-
// that the initial chain sync is done when enabled.
661-
if !m.cfg.NoMiningStateSync {
662-
for peer := range m.peers {
663-
maybeRequestInitialState(peer)
664-
}
666+
// Request initial state from all peers that still need it now that the
667+
// initial chain sync is done.
668+
for peer := range m.peers {
669+
peer.maybeRequestInitialState(!m.cfg.NoMiningStateSync)
665670
}
666671
}
667672

@@ -698,11 +703,11 @@ func (m *SyncManager) handlePeerConnectedMsg(ctx context.Context, peer *Peer) {
698703
m.startSync()
699704
}
700705

701-
// Request the initial state from this peer now when enabled and the manager
706+
// Potentially request the initial state from this peer now when the manager
702707
// believes the chain is fully synced. Otherwise, it will be requested when
703708
// the initial chain sync process is complete.
704-
if !m.cfg.NoMiningStateSync && m.IsCurrent() {
705-
maybeRequestInitialState(peer)
709+
if m.IsCurrent() {
710+
peer.maybeRequestInitialState(!m.cfg.NoMiningStateSync)
706711
}
707712
}
708713

0 commit comments

Comments
 (0)