• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 23483544962

24 Mar 2026 09:57AM UTC coverage: 85.685% (-0.02%) from 85.708%
23483544962

push

github

web-flow
Merge pull request #7029 from wileyj/chore/update_markdown

chore: Update link to page in SECURITY.md

186521 of 217682 relevant lines covered (85.69%)

17172874.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.35
/stacks-node/src/syncctl.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2024 Stacks Open Internet Foundation
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

17
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
18
use std::sync::Arc;
19

20
use stacks::burnchains::{Burnchain, Error as burnchain_error};
21
use stacks_common::util::{get_epoch_time_secs, sleep_ms};
22

23
use crate::burnchains::BurnchainTip;
24
use crate::Config;
25

26
#[derive(Clone)]
27
pub struct PoxSyncWatchdogComms {
28
    /// how many passes in the p2p state machine have taken place since startup?
29
    p2p_state_passes: Arc<AtomicU64>,
30
    /// how many times have we done an inv sync?
31
    inv_sync_passes: Arc<AtomicU64>,
32
    /// how many times have we done a download pass?
33
    download_passes: Arc<AtomicU64>,
34
    /// What's our last IBD status?
35
    last_ibd: Arc<AtomicBool>,
36
    /// Should keep running?
37
    should_keep_running: Arc<AtomicBool>,
38
}
39

40
impl PoxSyncWatchdogComms {
41
    pub fn new(should_keep_running: Arc<AtomicBool>) -> PoxSyncWatchdogComms {
509✔
42
        PoxSyncWatchdogComms {
509✔
43
            p2p_state_passes: Arc::new(AtomicU64::new(0)),
509✔
44
            inv_sync_passes: Arc::new(AtomicU64::new(0)),
509✔
45
            download_passes: Arc::new(AtomicU64::new(0)),
509✔
46
            last_ibd: Arc::new(AtomicBool::new(true)),
509✔
47
            should_keep_running,
509✔
48
        }
509✔
49
    }
509✔
50

51
    pub fn get_p2p_state_passes(&self) -> u64 {
×
52
        self.p2p_state_passes.load(Ordering::SeqCst)
×
53
    }
×
54

55
    pub fn get_inv_sync_passes(&self) -> u64 {
×
56
        self.inv_sync_passes.load(Ordering::SeqCst)
×
57
    }
×
58

59
    pub fn get_download_passes(&self) -> u64 {
×
60
        self.download_passes.load(Ordering::SeqCst)
×
61
    }
×
62

63
    pub fn get_ibd(&self) -> bool {
2,073,317✔
64
        self.last_ibd.load(Ordering::SeqCst)
2,073,317✔
65
    }
2,073,317✔
66

67
    fn interruptable_sleep(&self, secs: u64) -> Result<(), burnchain_error> {
11,859✔
68
        let deadline = secs + get_epoch_time_secs();
11,859✔
69
        while get_epoch_time_secs() < deadline {
23,635✔
70
            sleep_ms(1000);
11,859✔
71
            if !self.should_keep_running() {
11,859✔
72
                return Err(burnchain_error::CoordinatorClosed);
83✔
73
            }
11,776✔
74
        }
75
        Ok(())
11,776✔
76
    }
11,859✔
77

78
    pub fn should_keep_running(&self) -> bool {
11,857✔
79
        self.should_keep_running.load(Ordering::SeqCst)
11,857✔
80
    }
11,857✔
81

82
    pub fn notify_p2p_state_pass(&mut self) {
897,288✔
83
        self.p2p_state_passes.fetch_add(1, Ordering::SeqCst);
897,288✔
84
    }
897,288✔
85

86
    pub fn notify_inv_sync_pass(&mut self) {
522,034✔
87
        self.inv_sync_passes.fetch_add(1, Ordering::SeqCst);
522,034✔
88
    }
522,034✔
89

90
    pub fn notify_download_pass(&mut self) {
176,827✔
91
        self.download_passes.fetch_add(1, Ordering::SeqCst);
176,827✔
92
    }
176,827✔
93

94
    pub fn set_ibd(&mut self, value: bool) {
376,560✔
95
        self.last_ibd.store(value, Ordering::SeqCst);
376,560✔
96
    }
376,560✔
97
}
98

99
/// Monitor the state of the Stacks blockchain as the peer network and relay threads download and
100
/// proces Stacks blocks.  Don't allow the node to process the next PoX reward cycle's sortitions
101
/// unless it's reasonably sure that it has processed all Stacks blocks for this reward cycle.
102
/// This struct monitors the Stacks chainstate to make this determination.
103
pub struct PoxSyncWatchdog {
104
    /// time between burnchain syncs in steady state
105
    steady_state_burnchain_sync_interval: u64,
106
    /// handle to relayer thread that informs the watchdog when the P2P state-machine does stuff
107
    relayer_comms: PoxSyncWatchdogComms,
108
    /// should this sync watchdog always download? used in integration tests.
109
    unconditionally_download: bool,
110
}
111

112
impl PoxSyncWatchdog {
113
    pub fn new(
270✔
114
        config: &Config,
270✔
115
        watchdog_comms: PoxSyncWatchdogComms,
270✔
116
    ) -> Result<PoxSyncWatchdog, String> {
270✔
117
        let burnchain_poll_time = config.burnchain.poll_time_secs;
270✔
118
        let unconditionally_download = config.node.pox_sync_sample_secs == 0;
270✔
119

120
        Ok(PoxSyncWatchdog {
270✔
121
            unconditionally_download,
270✔
122
            steady_state_burnchain_sync_interval: burnchain_poll_time,
270✔
123
            relayer_comms: watchdog_comms,
270✔
124
        })
270✔
125
    }
270✔
126

127
    pub fn make_comms_handle(&self) -> PoxSyncWatchdogComms {
×
128
        self.relayer_comms.clone()
×
129
    }
×
130

131
    /// Are we in the initial burnchain block download? i.e. is the burn tip snapshot far enough away
132
    /// from the burnchain height that we should be eagerly downloading snapshots?
133
    fn infer_initial_burnchain_block_download(
357,483✔
134
        burnchain: &Burnchain,
357,483✔
135
        last_processed_height: u64,
357,483✔
136
        burnchain_height: u64,
357,483✔
137
    ) -> bool {
357,483✔
138
        let ibd =
357,483✔
139
            last_processed_height + (burnchain.stable_confirmations as u64) < burnchain_height;
357,483✔
140
        if ibd {
357,483✔
141
            debug!(
3,326✔
142
                "PoX watchdog: {last_processed_height} + {} < {burnchain_height}, so initial block download",
143
                burnchain.stable_confirmations
144
            );
145
        } else {
146
            debug!(
354,157✔
147
                "PoX watchdog: {last_processed_height} + {} >= {burnchain_height}, so steady-state",
148
                burnchain.stable_confirmations
149
            );
150
        }
151
        ibd
357,483✔
152
    }
357,483✔
153

154
    /// Wait until the next PoX anchor block arrives.
155
    /// We know for a fact that they all exist for Epochs 2.5 and earlier, in both mainnet and
156
    /// testnet.
157
    /// Return (still-in-ibd?, maximum-burnchain-sync-height) on success.
158
    pub fn pox_sync_wait(
357,483✔
159
        &mut self,
357,483✔
160
        burnchain: &Burnchain,
357,483✔
161
        burnchain_tip: &BurnchainTip, // this is the highest burnchain snapshot we've sync'ed to
357,483✔
162
        burnchain_height: u64,        // this is the absolute burnchain block height
357,483✔
163
    ) -> Result<(bool, u64), burnchain_error> {
357,483✔
164
        let burnchain_rc = burnchain
357,483✔
165
            .block_height_to_reward_cycle(burnchain_height)
357,483✔
166
            .expect("FATAL: burnchain height is before system start");
357,483✔
167

168
        let sortition_rc = burnchain
357,483✔
169
            .block_height_to_reward_cycle(burnchain_tip.block_snapshot.block_height)
357,483✔
170
            .expect("FATAL: sortition height is before system start");
357,483✔
171

172
        let ibbd = PoxSyncWatchdog::infer_initial_burnchain_block_download(
357,483✔
173
            burnchain,
357,483✔
174
            burnchain_tip.block_snapshot.block_height,
357,483✔
175
            burnchain_height,
357,483✔
176
        );
177

178
        let max_sync_height = if sortition_rc < burnchain_rc {
357,483✔
179
            burnchain
3,543✔
180
                .reward_cycle_to_block_height(sortition_rc + 1)
3,543✔
181
                .min(burnchain_height)
3,543✔
182
        } else {
183
            burnchain_tip
353,940✔
184
                .block_snapshot
353,940✔
185
                .block_height
353,940✔
186
                .max(burnchain_height)
353,940✔
187
        };
188

189
        self.relayer_comms.set_ibd(ibbd);
357,483✔
190
        if !self.unconditionally_download {
357,483✔
191
            self.relayer_comms
11,859✔
192
                .interruptable_sleep(self.steady_state_burnchain_sync_interval)?;
11,859✔
193
        }
345,624✔
194

195
        Ok((ibbd, max_sync_height))
357,402✔
196
    }
357,483✔
197
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc