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

stacks-network / stacks-core / 23943169302

03 Apr 2026 10:28AM UTC coverage: 77.573% (-8.1%) from 85.712%
23943169302

Pull #7076

github

7f2377
web-flow
Merge bb87ecec2 into c529ad924
Pull Request #7076: feat: sortition side-table copy and validation

3743 of 4318 new or added lines in 19 files covered. (86.68%)

19304 existing lines in 182 files now uncovered.

172097 of 221852 relevant lines covered (77.57%)

7722182.76 hits per line

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

8.74
/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 {
1✔
42
        PoxSyncWatchdogComms {
1✔
43
            p2p_state_passes: Arc::new(AtomicU64::new(0)),
1✔
44
            inv_sync_passes: Arc::new(AtomicU64::new(0)),
1✔
45
            download_passes: Arc::new(AtomicU64::new(0)),
1✔
46
            last_ibd: Arc::new(AtomicBool::new(true)),
1✔
47
            should_keep_running,
1✔
48
        }
1✔
49
    }
1✔
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

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

UNCOV
63
    pub fn get_ibd(&self) -> bool {
×
UNCOV
64
        self.last_ibd.load(Ordering::SeqCst)
×
UNCOV
65
    }
×
66

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

UNCOV
78
    pub fn should_keep_running(&self) -> bool {
×
UNCOV
79
        self.should_keep_running.load(Ordering::SeqCst)
×
UNCOV
80
    }
×
81

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

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

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

UNCOV
94
    pub fn set_ibd(&mut self, value: bool) {
×
UNCOV
95
        self.last_ibd.store(value, Ordering::SeqCst);
×
UNCOV
96
    }
×
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 {
UNCOV
113
    pub fn new(
×
UNCOV
114
        config: &Config,
×
UNCOV
115
        watchdog_comms: PoxSyncWatchdogComms,
×
UNCOV
116
    ) -> Result<PoxSyncWatchdog, String> {
×
UNCOV
117
        let burnchain_poll_time = config.burnchain.poll_time_secs;
×
UNCOV
118
        let unconditionally_download = config.node.pox_sync_sample_secs == 0;
×
119

UNCOV
120
        Ok(PoxSyncWatchdog {
×
UNCOV
121
            unconditionally_download,
×
UNCOV
122
            steady_state_burnchain_sync_interval: burnchain_poll_time,
×
UNCOV
123
            relayer_comms: watchdog_comms,
×
UNCOV
124
        })
×
UNCOV
125
    }
×
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?
UNCOV
133
    fn infer_initial_burnchain_block_download(
×
UNCOV
134
        burnchain: &Burnchain,
×
UNCOV
135
        last_processed_height: u64,
×
UNCOV
136
        burnchain_height: u64,
×
UNCOV
137
    ) -> bool {
×
UNCOV
138
        let ibd =
×
UNCOV
139
            last_processed_height + (burnchain.stable_confirmations as u64) < burnchain_height;
×
UNCOV
140
        if ibd {
×
UNCOV
141
            debug!(
×
142
                "PoX watchdog: {last_processed_height} + {} < {burnchain_height}, so initial block download",
143
                burnchain.stable_confirmations
144
            );
145
        } else {
UNCOV
146
            debug!(
×
147
                "PoX watchdog: {last_processed_height} + {} >= {burnchain_height}, so steady-state",
148
                burnchain.stable_confirmations
149
            );
150
        }
UNCOV
151
        ibd
×
UNCOV
152
    }
×
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.
UNCOV
158
    pub fn pox_sync_wait(
×
UNCOV
159
        &mut self,
×
UNCOV
160
        burnchain: &Burnchain,
×
UNCOV
161
        burnchain_tip: &BurnchainTip, // this is the highest burnchain snapshot we've sync'ed to
×
UNCOV
162
        burnchain_height: u64,        // this is the absolute burnchain block height
×
UNCOV
163
    ) -> Result<(bool, u64), burnchain_error> {
×
UNCOV
164
        let burnchain_rc = burnchain
×
UNCOV
165
            .block_height_to_reward_cycle(burnchain_height)
×
UNCOV
166
            .expect("FATAL: burnchain height is before system start");
×
167

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

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

UNCOV
178
        let max_sync_height = if sortition_rc < burnchain_rc {
×
UNCOV
179
            burnchain
×
UNCOV
180
                .reward_cycle_to_block_height(sortition_rc + 1)
×
UNCOV
181
                .min(burnchain_height)
×
182
        } else {
UNCOV
183
            burnchain_tip
×
UNCOV
184
                .block_snapshot
×
UNCOV
185
                .block_height
×
UNCOV
186
                .max(burnchain_height)
×
187
        };
188

UNCOV
189
        self.relayer_comms.set_ibd(ibbd);
×
UNCOV
190
        if !self.unconditionally_download {
×
UNCOV
191
            self.relayer_comms
×
UNCOV
192
                .interruptable_sleep(self.steady_state_burnchain_sync_interval)?;
×
UNCOV
193
        }
×
194

UNCOV
195
        Ok((ibbd, max_sync_height))
×
UNCOV
196
    }
×
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