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

stacks-network / stacks-core / 30073655644-1

24 Jul 2026 06:53AM UTC coverage: 86.568% (+0.9%) from 85.712%
30073655644-1

Pull #7401

github

e550e3
web-flow
Merge 355f5f257 into e44da350f
Pull Request #7401: feat: Clarity eval hook call-tracing improvements

666 of 702 new or added lines in 8 files covered. (94.87%)

12118 existing lines in 171 files now uncovered.

200778 of 231930 relevant lines covered (86.57%)

19385538.93 hits per line

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

87.85
/stacks-node/src/run_loop/neon.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2026 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
#[cfg(test)]
18
use std::sync::atomic::AtomicU64;
19
use std::sync::atomic::{AtomicBool, Ordering};
20
use std::sync::mpsc::sync_channel;
21
use std::sync::{Arc, Mutex};
22
use std::thread;
23
use std::thread::JoinHandle;
24

25
use libc;
26
use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType};
27
use stacks::burnchains::{Burnchain, Error as burnchain_error};
28
use stacks::chainstate::burn::db::sortdb::SortitionDB;
29
use stacks::chainstate::burn::{BlockSnapshot, ConsensusHash};
30
use stacks::chainstate::coordinator::comm::{CoordinatorChannels, CoordinatorReceivers};
31
use stacks::chainstate::coordinator::{
32
    migrate_chainstate_dbs, ChainsCoordinator, ChainsCoordinatorConfig, CoordinatorCommunication,
33
    Error as coord_error,
34
};
35
use stacks::chainstate::stacks::db::{ChainStateBootData, StacksChainState};
36
use stacks::chainstate::stacks::miner::{signal_mining_blocked, signal_mining_ready, MinerStatus};
37
use stacks::core::StacksEpochId;
38
use stacks::net::atlas::{AtlasConfig, AtlasDB, Attachment};
39
#[cfg(test)]
40
use stacks::util::tests::TestFlag;
41
use stacks::util_lib::db::Error as db_error;
42
use stacks_common::deps_common::ctrlc as termination;
43
use stacks_common::deps_common::ctrlc::SignalId;
44
use stacks_common::types::PublicKey;
45
use stacks_common::util::hash::Hash160;
46
use stx_genesis::GenesisData;
47

48
use super::RunLoopCallbacks;
49
use crate::burnchains::{make_bitcoin_indexer, Error};
50
use crate::globals::NeonGlobals as Globals;
51
use crate::monitoring::{start_serving_monitoring_metrics, MonitoringError};
52
use crate::neon_node::{
53
    LeaderKeyRegistrationState, StacksNode, BLOCK_PROCESSOR_STACK_SIZE, RELAYER_MAX_BUFFER,
54
};
55
use crate::node::{
56
    get_account_balances, get_account_lockups, get_names, get_namespaces,
57
    use_test_genesis_chainstate,
58
};
59
use crate::run_loop::boot_nakamoto::Neon2NakaData;
60
use crate::syncctl::{PoxSyncWatchdog, PoxSyncWatchdogComms};
61
use crate::{
62
    run_loop, BitcoinRegtestController, BurnchainController, Config, EventDispatcher, Keychain,
63
};
64

65
pub const STDERR: i32 = 2;
66

67
#[cfg(test)]
68
#[derive(Clone, Default)]
69
pub struct RunLoopField<T>(pub Arc<Mutex<T>>);
70

71
#[cfg(not(test))]
72
#[derive(Clone, Default)]
73
pub struct RunLoopField<T>(pub std::marker::PhantomData<T>);
74

75
#[cfg(test)]
76
#[derive(Clone)]
77
pub struct RunLoopCounter(pub Arc<AtomicU64>);
78

79
#[cfg(not(test))]
80
#[derive(Clone)]
81
pub struct RunLoopCounter();
82

83
impl Default for RunLoopCounter {
84
    #[cfg(test)]
85
    fn default() -> Self {
6,552✔
86
        RunLoopCounter(Arc::new(AtomicU64::new(0)))
6,552✔
87
    }
6,552✔
88
    #[cfg(not(test))]
89
    fn default() -> Self {
90
        Self()
91
    }
92
}
93

94
impl<T: Clone> RunLoopField<Option<T>> {
95
    #[cfg(test)]
96
    pub fn get(&self) -> T {
113✔
97
        self.0.lock().unwrap().clone().unwrap()
113✔
98
    }
113✔
99
}
100

101
impl RunLoopCounter {
102
    #[cfg(test)]
103
    pub fn get(&self) -> u64 {
4,122✔
104
        self.0.load(Ordering::SeqCst)
4,122✔
105
    }
4,122✔
106

107
    #[cfg(test)]
108
    pub fn load(&self, ordering: Ordering) -> u64 {
76,652✔
109
        self.0.load(ordering)
76,652✔
110
    }
76,652✔
111
}
112

113
#[cfg(test)]
114
impl std::ops::Deref for RunLoopCounter {
115
    type Target = Arc<AtomicU64>;
116

117
    fn deref(&self) -> &Self::Target {
2,091✔
118
        &self.0
2,091✔
119
    }
2,091✔
120
}
121

122
#[derive(Clone, Default)]
123
pub struct Counters {
124
    pub blocks_processed: RunLoopCounter,
125
    pub microblocks_processed: RunLoopCounter,
126
    pub missed_tenures: RunLoopCounter,
127
    pub missed_microblock_tenures: RunLoopCounter,
128
    pub cancelled_commits: RunLoopCounter,
129

130
    pub sortitions_processed: RunLoopCounter,
131

132
    pub naka_submitted_vrfs: RunLoopCounter,
133
    /// the number of submitted commits
134
    pub neon_submitted_commits: RunLoopCounter,
135
    /// the burn block height when the last commit was submitted
136
    pub neon_submitted_commit_last_burn_height: RunLoopCounter,
137
    pub naka_submitted_commits: RunLoopCounter,
138
    /// the burn block height when the last commit was submitted
139
    pub naka_submitted_commit_last_burn_height: RunLoopCounter,
140
    pub naka_mined_blocks: RunLoopCounter,
141
    pub naka_rejected_blocks: RunLoopCounter,
142
    pub naka_proposed_blocks: RunLoopCounter,
143
    pub naka_mined_tenures: RunLoopCounter,
144
    pub naka_signer_pushed_blocks: RunLoopCounter,
145
    pub naka_miner_directives: RunLoopCounter,
146
    pub naka_submitted_commit_last_stacks_tip: RunLoopCounter,
147
    pub naka_submitted_commit_last_commit_amount: RunLoopCounter,
148
    pub naka_submitted_commit_last_parent_tenure_id: RunLoopField<Option<ConsensusHash>>,
149

150
    pub naka_miner_current_rejections: RunLoopCounter,
151
    pub naka_miner_current_rejections_timeout_secs: RunLoopCounter,
152

153
    #[cfg(test)]
154
    pub skip_commit_op: TestFlag<bool>,
155
}
156

157
impl Counters {
158
    pub fn new() -> Self {
1✔
159
        Self::default()
1✔
160
    }
1✔
161

162
    #[cfg(test)]
163
    fn inc(ctr: &RunLoopCounter) {
40,371✔
164
        ctr.0.fetch_add(1, Ordering::SeqCst);
40,371✔
165
    }
40,371✔
166

167
    #[cfg(not(test))]
168
    fn inc(_ctr: &RunLoopCounter) {}
169

170
    #[cfg(test)]
171
    fn set(ctr: &RunLoopCounter, value: u64) {
15,198✔
172
        ctr.0.store(value, Ordering::SeqCst);
15,198✔
173
    }
15,198✔
174

175
    #[cfg(not(test))]
176
    fn set(_ctr: &RunLoopCounter, _value: u64) {}
177

178
    #[cfg(test)]
179
    fn update<T: Clone>(ctr: &RunLoopField<Option<T>>, value: &T) {
2,462✔
180
        let mut mutex = ctr.0.lock().expect("FATAL: test counter mutext poisoned");
2,462✔
181
        let _ = mutex.replace(value.clone());
2,462✔
182
    }
2,462✔
183

184
    #[cfg(not(test))]
185
    fn update<T: Clone>(_ctr: &RunLoopField<Option<T>>, _value: &T) {}
186

187
    pub fn bump_blocks_processed(&self) {
13,527✔
188
        Counters::inc(&self.blocks_processed);
13,527✔
189
    }
13,527✔
190

191
    pub fn bump_sortitions_processed(&self) {
5,315✔
192
        Counters::inc(&self.sortitions_processed);
5,315✔
193
    }
5,315✔
194

UNCOV
195
    pub fn bump_microblocks_processed(&self) {
×
UNCOV
196
        Counters::inc(&self.microblocks_processed);
×
UNCOV
197
    }
×
198

199
    pub fn bump_missed_tenures(&self) {
1,623✔
200
        Counters::inc(&self.missed_tenures);
1,623✔
201
    }
1,623✔
202

UNCOV
203
    pub fn bump_missed_microblock_tenures(&self) {
×
UNCOV
204
        Counters::inc(&self.missed_microblock_tenures);
×
UNCOV
205
    }
×
206

UNCOV
207
    pub fn bump_cancelled_commits(&self) {
×
UNCOV
208
        Counters::inc(&self.cancelled_commits);
×
UNCOV
209
    }
×
210

211
    pub fn bump_neon_submitted_commits(&self, committed_burn_height: u64) {
7,299✔
212
        Counters::inc(&self.neon_submitted_commits);
7,299✔
213
        Counters::set(
7,299✔
214
            &self.neon_submitted_commit_last_burn_height,
7,299✔
215
            committed_burn_height,
7,299✔
216
        );
217
    }
7,299✔
218

219
    pub fn bump_naka_submitted_vrfs(&self) {
42✔
220
        Counters::inc(&self.naka_submitted_vrfs);
42✔
221
    }
42✔
222

223
    pub fn bump_naka_submitted_commits(
2,462✔
224
        &self,
2,462✔
225
        committed_burn_height: u64,
2,462✔
226
        committed_stacks_height: u64,
2,462✔
227
        committed_sats_amount: u64,
2,462✔
228
        committed_parent_tenure_id: &ConsensusHash,
2,462✔
229
    ) {
2,462✔
230
        Counters::inc(&self.naka_submitted_commits);
2,462✔
231
        Counters::set(
2,462✔
232
            &self.naka_submitted_commit_last_burn_height,
2,462✔
233
            committed_burn_height,
2,462✔
234
        );
235
        Counters::set(
2,462✔
236
            &self.naka_submitted_commit_last_stacks_tip,
2,462✔
237
            committed_stacks_height,
2,462✔
238
        );
239
        Counters::set(
2,462✔
240
            &self.naka_submitted_commit_last_commit_amount,
2,462✔
241
            committed_sats_amount,
2,462✔
242
        );
243
        Counters::update(
2,462✔
244
            &self.naka_submitted_commit_last_parent_tenure_id,
2,462✔
245
            committed_parent_tenure_id,
2,462✔
246
        );
247
    }
2,462✔
248

249
    pub fn bump_naka_mined_blocks(&self) {
2,887✔
250
        Counters::inc(&self.naka_mined_blocks);
2,887✔
251
    }
2,887✔
252

253
    pub fn bump_naka_proposed_blocks(&self) {
3,049✔
254
        Counters::inc(&self.naka_proposed_blocks);
3,049✔
255
    }
3,049✔
256

257
    pub fn bump_naka_rejected_blocks(&self) {
157✔
258
        Counters::inc(&self.naka_rejected_blocks);
157✔
259
    }
157✔
260

261
    pub fn bump_naka_signer_pushed_blocks(&self) {
18✔
262
        Counters::inc(&self.naka_signer_pushed_blocks);
18✔
263
    }
18✔
264

265
    pub fn bump_naka_mined_tenures(&self) {
1,838✔
266
        Counters::inc(&self.naka_mined_tenures);
1,838✔
267
    }
1,838✔
268

269
    pub fn bump_naka_miner_directives(&self) {
2,154✔
270
        Counters::inc(&self.naka_miner_directives);
2,154✔
271
    }
2,154✔
272

273
    pub fn set_microblocks_processed(&self, value: u64) {
101✔
274
        Counters::set(&self.microblocks_processed, value)
101✔
275
    }
101✔
276

277
    pub fn set_miner_current_rejections_timeout_secs(&self, value: u64) {
206✔
278
        Counters::set(&self.naka_miner_current_rejections_timeout_secs, value)
206✔
279
    }
206✔
280

281
    pub fn set_miner_current_rejections(&self, value: u32) {
206✔
282
        Counters::set(&self.naka_miner_current_rejections, u64::from(value))
206✔
283
    }
206✔
284
}
285

286
/// Coordinating a node running in neon mode.
287
pub struct RunLoop {
288
    config: Config,
289
    pub callbacks: RunLoopCallbacks,
290
    globals: Option<Globals>,
291
    counters: Counters,
292
    coordinator_channels: Option<(CoordinatorReceivers, CoordinatorChannels)>,
293
    should_keep_running: Arc<AtomicBool>,
294
    event_dispatcher: EventDispatcher,
295
    pox_watchdog: Option<PoxSyncWatchdog>, // can't be instantiated until .start() is called
296
    is_miner: Option<bool>,                // not known until .start() is called
297
    burnchain: Option<Burnchain>,          // not known until .start() is called
298
    pox_watchdog_comms: PoxSyncWatchdogComms,
299
    /// NOTE: this is duplicated in self.globals, but it needs to be accessible before globals is
300
    /// instantiated (namely, so the test framework can access it).
301
    miner_status: Arc<Mutex<MinerStatus>>,
302
    monitoring_thread: Option<JoinHandle<Result<(), MonitoringError>>>,
303
}
304

305
/// Write to stderr in an async-safe manner.
306
/// See signal-safety(7)
UNCOV
307
fn async_safe_write_stderr(msg: &str) {
×
308
    #[cfg(windows)]
309
    unsafe {
310
        // write(2) inexplicably has a different ABI only on Windows.
311
        libc::write(
312
            STDERR,
313
            msg.as_ptr() as *const libc::c_void,
314
            msg.len() as u32,
315
        );
316
    }
317
    #[cfg(not(windows))]
UNCOV
318
    unsafe {
×
UNCOV
319
        libc::write(STDERR, msg.as_ptr() as *const libc::c_void, msg.len());
×
UNCOV
320
    }
×
UNCOV
321
}
×
322

323
impl RunLoop {
324
    /// Sets up a runloop and node, given a config.
325
    pub fn new(config: Config) -> Self {
309✔
326
        let channels = CoordinatorCommunication::instantiate();
309✔
327
        let should_keep_running = Arc::new(AtomicBool::new(true));
309✔
328
        let pox_watchdog_comms = PoxSyncWatchdogComms::new(should_keep_running.clone());
309✔
329
        let miner_status = Arc::new(Mutex::new(MinerStatus::make_ready(
309✔
330
            config.burnchain.burn_fee_cap,
309✔
331
        )));
332

333
        let mut event_dispatcher = EventDispatcher::new_with_custom_queue_size(
309✔
334
            config.get_working_dir(),
309✔
335
            config.node.effective_event_dispatcher_queue_size(),
309✔
336
        );
337
        for observer in config.events_observers.iter() {
1,069✔
338
            event_dispatcher.register_observer(observer);
1,044✔
339
        }
1,044✔
340

341
        Self {
309✔
342
            config,
309✔
343
            globals: None,
309✔
344
            coordinator_channels: Some(channels),
309✔
345
            callbacks: RunLoopCallbacks::new(),
309✔
346
            counters: Counters::default(),
309✔
347
            should_keep_running,
309✔
348
            event_dispatcher,
309✔
349
            pox_watchdog: None,
309✔
350
            is_miner: None,
309✔
351
            burnchain: None,
309✔
352
            pox_watchdog_comms,
309✔
353
            miner_status,
309✔
354
            monitoring_thread: None,
309✔
355
        }
309✔
356
    }
309✔
357

358
    pub fn get_globals(&self) -> Globals {
612✔
359
        self.globals
612✔
360
            .clone()
612✔
361
            .expect("FATAL: globals not instantiated")
612✔
362
    }
612✔
363

364
    fn set_globals(&mut self, globals: Globals) {
306✔
365
        self.globals = Some(globals);
306✔
366
    }
306✔
367

368
    pub fn get_coordinator_channel(&self) -> Option<CoordinatorChannels> {
305✔
369
        self.coordinator_channels.as_ref().map(|x| x.1.clone())
305✔
370
    }
305✔
371

372
    pub fn get_blocks_processed_arc(&self) -> RunLoopCounter {
39✔
373
        self.counters.blocks_processed.clone()
39✔
374
    }
39✔
375

UNCOV
376
    pub fn get_microblocks_processed_arc(&self) -> RunLoopCounter {
×
UNCOV
377
        self.counters.microblocks_processed.clone()
×
UNCOV
378
    }
×
379

380
    pub fn get_missed_tenures_arc(&self) -> RunLoopCounter {
1✔
381
        self.counters.missed_tenures.clone()
1✔
382
    }
1✔
383

UNCOV
384
    pub fn get_missed_microblock_tenures_arc(&self) -> RunLoopCounter {
×
UNCOV
385
        self.counters.missed_microblock_tenures.clone()
×
UNCOV
386
    }
×
387

UNCOV
388
    pub fn get_cancelled_commits_arc(&self) -> RunLoopCounter {
×
UNCOV
389
        self.counters.cancelled_commits.clone()
×
UNCOV
390
    }
×
391

392
    pub fn get_counters(&self) -> Counters {
598✔
393
        self.counters.clone()
598✔
394
    }
598✔
395

396
    pub fn config(&self) -> &Config {
69,488✔
397
        &self.config
69,488✔
398
    }
69,488✔
399

400
    pub fn get_event_dispatcher(&self) -> EventDispatcher {
876✔
401
        self.event_dispatcher.clone()
876✔
402
    }
876✔
403

404
    pub fn is_miner(&self) -> bool {
306✔
405
        self.is_miner.unwrap_or(false)
306✔
406
    }
306✔
407

408
    pub fn get_pox_sync_comms(&self) -> PoxSyncWatchdogComms {
1✔
409
        self.pox_watchdog_comms.clone()
1✔
410
    }
1✔
411

412
    pub fn get_termination_switch(&self) -> Arc<AtomicBool> {
809✔
413
        self.should_keep_running.clone()
809✔
414
    }
809✔
415

416
    pub fn get_burnchain(&self) -> Burnchain {
1,224✔
417
        self.burnchain
1,224✔
418
            .clone()
1,224✔
419
            .expect("FATAL: tried to get runloop burnchain before calling .start()")
1,224✔
420
    }
1,224✔
421

422
    pub fn get_pox_watchdog(&mut self) -> &mut PoxSyncWatchdog {
464,443✔
423
        self.pox_watchdog
464,443✔
424
            .as_mut()
464,443✔
425
            .expect("FATAL: tried to get PoX watchdog before calling .start()")
464,443✔
426
    }
464,443✔
427

428
    pub fn get_miner_status(&self) -> Arc<Mutex<MinerStatus>> {
306✔
429
        self.miner_status.clone()
306✔
430
    }
306✔
431

432
    /// Set up termination handler.  Have a signal set the `should_keep_running` atomic bool to
433
    /// false.  Panics of called more than once.
434
    pub fn setup_termination_handler(keep_running_writer: Arc<AtomicBool>, allow_err: bool) {
574✔
435
        let install = termination::set_handler(move |sig_id| match sig_id {
574✔
436
            SignalId::Bus => {
437
                let msg = "Caught SIGBUS; crashing immediately and dumping core\n";
×
UNCOV
438
                async_safe_write_stderr(msg);
×
439
                unsafe {
UNCOV
440
                    libc::abort();
×
441
                }
442
            }
UNCOV
443
            _ => {
×
UNCOV
444
                let msg = format!("Graceful termination request received (signal `{sig_id}`), will complete the ongoing runloop cycles and terminate\n");
×
UNCOV
445
                async_safe_write_stderr(&msg);
×
UNCOV
446
                keep_running_writer.store(false, Ordering::SeqCst);
×
UNCOV
447
            }
×
UNCOV
448
        });
×
449

450
        if let Err(e) = install {
574✔
451
            // integration tests can do this
452
            if cfg!(test) || allow_err {
322✔
453
                info!("Error setting up signal handler, may have already been set");
322✔
454
            } else {
UNCOV
455
                panic!("FATAL: error setting termination handler - {e}");
×
456
            }
457
        }
252✔
458
    }
574✔
459

460
    /// Seconds to wait before retrying UTXO check during startup
461
    const UTXO_RETRY_INTERVAL: u64 = 10;
462
    /// Number of times to retry UTXO check during startup
463
    const UTXO_RETRY_COUNT: u64 = 6;
464

465
    /// Determine if we're the miner.
466
    /// If there's a network error, then assume that we're not a miner.
467
    fn check_is_miner(&mut self, burnchain: &mut BitcoinRegtestController) -> bool {
307✔
468
        if self.config.node.miner {
307✔
469
            // If we are mock mining, then we don't need to check for UTXOs and
470
            // we can just return true.
471
            if self.config.get_node_config(false).mock_mining {
302✔
472
                return true;
5✔
473
            }
297✔
474
            let keychain = Keychain::default(self.config.node.seed.clone());
297✔
475
            let mut op_signer = keychain.generate_op_signer();
297✔
476
            if let Err(e) = burnchain.create_wallet_if_dne() {
297✔
477
                warn!("Error when creating wallet: {e:?}");
×
478
            }
297✔
479
            let mut btc_addrs = vec![(
297✔
480
                StacksEpochId::Epoch2_05,
297✔
481
                // legacy
297✔
482
                BitcoinAddress::from_bytes_legacy(
297✔
483
                    self.config.burnchain.get_bitcoin_network().1,
297✔
484
                    LegacyBitcoinAddressType::PublicKeyHash,
297✔
485
                    &Hash160::from_data(&op_signer.get_public_key().to_bytes()).0,
297✔
486
                )
297✔
487
                .expect("FATAL: failed to construct legacy bitcoin address"),
297✔
488
            )];
297✔
489
            if self.config.miner.segwit {
297✔
UNCOV
490
                btc_addrs.push((
×
UNCOV
491
                    StacksEpochId::Epoch21,
×
UNCOV
492
                    // segwit p2wpkh
×
UNCOV
493
                    BitcoinAddress::from_bytes_segwit_p2wpkh(
×
UNCOV
494
                        self.config.burnchain.get_bitcoin_network().1,
×
UNCOV
495
                        &Hash160::from_data(&op_signer.get_public_key().to_bytes_compressed()).0,
×
UNCOV
496
                    )
×
UNCOV
497
                    .expect("FATAL: failed to construct segwit p2wpkh address"),
×
UNCOV
498
                ));
×
499
            }
297✔
500

501
            // retry UTXO check a few times, in case bitcoind is still starting up
502
            for _ in 0..Self::UTXO_RETRY_COUNT {
297✔
503
                for (epoch_id, btc_addr) in &btc_addrs {
305✔
504
                    info!("Miner node: checking UTXOs at address: {btc_addr}");
305✔
505
                    let utxos =
305✔
506
                        burnchain.get_utxos(*epoch_id, &op_signer.get_public_key(), 1, None, 0);
305✔
507
                    if utxos.is_none() {
305✔
508
                        warn!("UTXOs not found for {btc_addr}. If this is unexpected, please ensure that your bitcoind instance is indexing transactions for the address {btc_addr} (importaddress)");
9✔
509
                    } else {
510
                        info!("UTXOs found - will run as a Miner node");
296✔
511
                        return true;
296✔
512
                    }
513
                }
514
                thread::sleep(std::time::Duration::from_secs(Self::UTXO_RETRY_INTERVAL));
9✔
515
            }
516
            panic!("No UTXOs found, exiting");
1✔
517
        } else {
518
            info!("Will run as a Follower node");
5✔
519
            false
5✔
520
        }
521
    }
306✔
522

523
    /// Instantiate the burnchain client and databases.
524
    /// Fetches headers and instantiates the burnchain.
525
    /// Panics on failure.
526
    pub fn instantiate_burnchain_state(
574✔
527
        config: &Config,
574✔
528
        should_keep_running: Arc<AtomicBool>,
574✔
529
        burnchain_opt: Option<Burnchain>,
574✔
530
        coordinator_senders: CoordinatorChannels,
574✔
531
    ) -> Result<BitcoinRegtestController, burnchain_error> {
574✔
532
        // Initialize and start the burnchain.
533
        let mut burnchain_controller = BitcoinRegtestController::with_burnchain(
574✔
534
            config.clone(),
574✔
535
            Some(coordinator_senders),
574✔
536
            burnchain_opt,
574✔
537
            Some(should_keep_running.clone()),
574✔
538
        );
539

540
        let burnchain = burnchain_controller.get_burnchain();
574✔
541
        let epochs = burnchain_controller.get_stacks_epochs();
574✔
542

543
        // sanity check -- epoch data must be valid
544
        Config::assert_valid_epoch_settings(&burnchain, &epochs);
574✔
545

546
        // Upgrade chainstate databases if they exist already
547
        // NOTE: this has to be done before the subsequent call to
548
        // `burnchain_controller.connect_dbs()` below!
549
        match migrate_chainstate_dbs(
574✔
550
            &epochs,
574✔
551
            &burnchain,
574✔
552
            &config.get_burn_db_file_path(),
574✔
553
            &config.get_chainstate_path_str(),
574✔
554
            Some(config.node.get_marf_opts()),
574✔
555
        ) {
556
            Ok(_) => {}
574✔
557
            Err(coord_error::DBError(db_error::TooOldForEpoch)) => {
UNCOV
558
                error!(
×
559
                    "FATAL: chainstate database(s) are not compatible with the current system epoch"
560
                );
UNCOV
561
                panic!();
×
562
            }
UNCOV
563
            Err(e) => {
×
UNCOV
564
                panic!("FATAL: unable to query filesystem or databases: {e:?}");
×
565
            }
566
        }
567

568
        info!("Start syncing Bitcoin headers, feel free to grab a cup of coffee, this can take a while");
574✔
569

570
        let burnchain_config = burnchain_controller.get_burnchain();
574✔
571
        let target_burnchain_block_height = match burnchain_config
574✔
572
            .get_highest_burnchain_block()
574✔
573
            .expect("FATAL: failed to access burnchain database")
574✔
574
        {
575
            Some(burnchain_tip) => {
267✔
576
                // database exists already, and has blocks -- just sync to its tip.
577
                let target_height = burnchain_tip.block_height + 1;
267✔
578
                debug!("Burnchain DB exists and has blocks up to {}; synchronizing from where it left off up to {target_height}", burnchain_tip.block_height);
267✔
579
                target_height
267✔
580
            }
581
            None => {
582
                // database does not exist yet
583
                let target_height = 1.max(burnchain_config.first_block_height + 1);
307✔
584
                debug!("Burnchain DB does not exist or does not have blocks; synchronizing to first burnchain block height {target_height}");
307✔
585
                target_height
307✔
586
            }
587
        };
588

589
        burnchain_controller
574✔
590
            .start(Some(target_burnchain_block_height))
574✔
591
            .map_err(|e| {
574✔
592
                if matches!(e, Error::CoordinatorClosed)
1✔
593
                    && !should_keep_running.load(Ordering::SeqCst)
1✔
594
                {
595
                    info!("Shutdown initiated during burnchain initialization: {e}");
1✔
596
                    return burnchain_error::ShutdownInitiated;
1✔
UNCOV
597
                }
×
UNCOV
598
                error!("Burnchain controller stopped: {e}");
×
UNCOV
599
                panic!();
×
600
            })?;
1✔
601

602
        // if the chainstate DBs don't exist, this will instantiate them
603
        if let Err(e) = burnchain_controller.connect_dbs() {
573✔
UNCOV
604
            error!("Failed to connect to burnchain databases: {e}");
×
UNCOV
605
            panic!();
×
606
        };
573✔
607

608
        // TODO (hack) instantiate the sortdb in the burnchain
609
        let _ = burnchain_controller.sortdb_mut();
573✔
610
        Ok(burnchain_controller)
573✔
611
    }
574✔
612

613
    /// Boot up the stacks chainstate.
614
    /// Instantiate the chainstate and push out the boot receipts to observers
615
    /// This is only public so we can test it.
616
    pub fn boot_chainstate(&mut self, burnchain_config: &Burnchain) -> StacksChainState {
307✔
617
        let use_test_genesis_data = use_test_genesis_chainstate(&self.config);
307✔
618

619
        // load up genesis balances
620
        let initial_balances = self
307✔
621
            .config
307✔
622
            .initial_balances
307✔
623
            .iter()
307✔
624
            .map(|e| (e.address.clone(), e.amount))
2,331✔
625
            .collect();
307✔
626

627
        // instantiate chainstate
628
        let mut boot_data = ChainStateBootData {
307✔
629
            initial_balances,
307✔
630
            post_flight_callback: None,
307✔
631
            first_burnchain_block_hash: burnchain_config.first_block_hash.clone(),
307✔
632
            first_burnchain_block_height: burnchain_config.first_block_height as u32,
307✔
633
            first_burnchain_block_timestamp: burnchain_config.first_block_timestamp,
307✔
634
            pox_constants: burnchain_config.pox_constants.clone(),
307✔
635
            get_bulk_initial_lockups: Some(Box::new(move || {
307✔
636
                get_account_lockups(use_test_genesis_data)
307✔
637
            })),
307✔
638
            get_bulk_initial_balances: Some(Box::new(move || {
307✔
639
                get_account_balances(use_test_genesis_data)
307✔
640
            })),
307✔
641
            get_bulk_initial_namespaces: Some(Box::new(move || {
307✔
642
                get_namespaces(use_test_genesis_data)
307✔
643
            })),
307✔
644
            get_bulk_initial_names: Some(Box::new(move || get_names(use_test_genesis_data))),
307✔
645
        };
646

647
        info!("About to call open_and_exec");
307✔
648
        let (chain_state_db, receipts) = StacksChainState::open_and_exec(
307✔
649
            self.config.is_mainnet(),
307✔
650
            self.config.burnchain.chain_id,
307✔
651
            &self.config.get_chainstate_path_str(),
307✔
652
            Some(&mut boot_data),
307✔
653
            Some(self.config.node.get_marf_opts()),
307✔
654
        )
307✔
655
        .unwrap();
307✔
656
        run_loop::announce_boot_receipts(
307✔
657
            &mut self.event_dispatcher,
307✔
658
            &chain_state_db,
307✔
659
            &burnchain_config.pox_constants,
307✔
660
            &receipts,
307✔
661
        );
662
        chain_state_db
307✔
663
    }
307✔
664

665
    /// Instantiate the Stacks chain state and start the chains coordinator thread.
666
    /// Returns the coordinator thread handle, and the receiving end of the coordinator's atlas
667
    /// attachment channel.
668
    fn spawn_chains_coordinator(
306✔
669
        &mut self,
306✔
670
        burnchain_config: &Burnchain,
306✔
671
        coordinator_receivers: CoordinatorReceivers,
306✔
672
        miner_status: Arc<Mutex<MinerStatus>>,
306✔
673
    ) -> JoinHandle<()> {
306✔
674
        let use_test_genesis_data = use_test_genesis_chainstate(&self.config);
306✔
675

676
        // load up genesis Atlas attachments
677
        let mut atlas_config = AtlasConfig::new(self.config.is_mainnet());
306✔
678
        let genesis_attachments = GenesisData::new(use_test_genesis_data)
306✔
679
            .read_name_zonefiles()
306✔
680
            .map(|z| Attachment::new(z.zonefile_content.as_bytes().to_vec()))
3,672✔
681
            .collect();
306✔
682
        atlas_config.genesis_attachments = Some(genesis_attachments);
306✔
683

684
        let chain_state_db = self.boot_chainstate(burnchain_config);
306✔
685

686
        // NOTE: re-instantiate AtlasConfig so we don't have to keep the genesis attachments around
687
        let moved_atlas_config = self.config.atlas.clone();
306✔
688
        let moved_config = self.config.clone();
306✔
689
        let moved_burnchain_config = burnchain_config.clone();
306✔
690
        let coordinator_dispatcher = self.event_dispatcher.clone();
306✔
691
        let atlas_db = AtlasDB::connect(
306✔
692
            moved_atlas_config.clone(),
306✔
693
            &self.config.get_atlas_db_file_path(),
306✔
694
            true,
695
        )
696
        .expect("Failed to connect Atlas DB during startup");
306✔
697
        let coordinator_indexer =
306✔
698
            make_bitcoin_indexer(&self.config, Some(self.should_keep_running.clone()));
306✔
699

700
        let coordinator_thread_handle = thread::Builder::new()
306✔
701
            .name(format!(
306✔
702
                "chains-coordinator-{}",
703
                &moved_config.node.rpc_bind
306✔
704
            ))
705
            .stack_size(BLOCK_PROCESSOR_STACK_SIZE)
306✔
706
            .spawn(move || {
306✔
707
                debug!(
306✔
708
                    "chains-coordinator thread ID is {:?}",
UNCOV
709
                    thread::current().id()
×
710
                );
711
                let mut cost_estimator = moved_config.make_cost_estimator();
306✔
712
                let mut fee_estimator = moved_config.make_fee_estimator();
306✔
713

714
                let coord_config = ChainsCoordinatorConfig {
306✔
715
                    txindex: moved_config.node.txindex,
306✔
716
                };
306✔
717
                ChainsCoordinator::run(
306✔
718
                    coord_config,
306✔
719
                    chain_state_db,
306✔
720
                    moved_burnchain_config,
306✔
721
                    &coordinator_dispatcher,
306✔
722
                    coordinator_receivers,
306✔
723
                    moved_atlas_config,
306✔
724
                    cost_estimator.as_deref_mut(),
306✔
725
                    fee_estimator.as_deref_mut(),
306✔
726
                    miner_status,
306✔
727
                    coordinator_indexer,
306✔
728
                    atlas_db,
306✔
729
                );
730
            })
306✔
731
            .expect("FATAL: failed to start chains coordinator thread");
306✔
732

733
        coordinator_thread_handle
306✔
734
    }
306✔
735

736
    /// Instantiate the PoX watchdog
737
    fn instantiate_pox_watchdog(&mut self) {
306✔
738
        let pox_watchdog = PoxSyncWatchdog::new(&self.config, self.pox_watchdog_comms.clone())
306✔
739
            .expect("FATAL: failed to instantiate PoX sync watchdog");
306✔
740
        self.pox_watchdog = Some(pox_watchdog);
306✔
741
    }
306✔
742

743
    /// Start Prometheus logging
744
    fn start_prometheus(&mut self) {
306✔
745
        let Some(prometheus_bind) = self.config.node.prometheus_bind.clone() else {
306✔
746
            return;
297✔
747
        };
748
        let monitoring_thread = thread::Builder::new()
9✔
749
            .name("prometheus".to_string())
9✔
750
            .spawn(move || {
9✔
751
                debug!("prometheus thread ID is {:?}", thread::current().id());
9✔
752
                start_serving_monitoring_metrics(prometheus_bind)
9✔
753
            })
9✔
754
            .expect("FATAL: failed to start monitoring thread");
9✔
755

756
        self.monitoring_thread.replace(monitoring_thread);
9✔
757
    }
306✔
758

759
    pub fn take_monitoring_thread(&mut self) -> Option<JoinHandle<Result<(), MonitoringError>>> {
265✔
760
        self.monitoring_thread.take()
265✔
761
    }
265✔
762

763
    /// Get the sortition DB's highest block height, aligned to a reward cycle boundary, and the
764
    /// highest sortition.
765
    /// Returns (height at rc start, sortition)
766
    fn get_reward_cycle_sortition_db_height(
306✔
767
        sortdb: &SortitionDB,
306✔
768
        burnchain_config: &Burnchain,
306✔
769
    ) -> (u64, BlockSnapshot) {
306✔
770
        let (stacks_ch, _) = SortitionDB::get_canonical_stacks_chain_tip_hash(sortdb.conn())
306✔
771
            .expect("BUG: failed to load canonical stacks chain tip hash");
306✔
772

773
        let sn = match SortitionDB::get_block_snapshot_consensus(sortdb.conn(), &stacks_ch)
306✔
774
            .expect("BUG: failed to query sortition DB")
306✔
775
        {
776
            Some(sn) => sn,
306✔
777
            None => {
UNCOV
778
                debug!("No canonical stacks chain tip hash present");
×
UNCOV
779
                let sn = SortitionDB::get_first_block_snapshot(sortdb.conn())
×
UNCOV
780
                    .expect("BUG: failed to get first-ever block snapshot");
×
UNCOV
781
                sn
×
782
            }
783
        };
784

785
        (
306✔
786
            burnchain_config.reward_cycle_to_block_height(
306✔
787
                burnchain_config
306✔
788
                    .block_height_to_reward_cycle(sn.block_height)
306✔
789
                    .expect("BUG: snapshot preceeds first reward cycle"),
306✔
790
            ),
306✔
791
            sn,
306✔
792
        )
306✔
793
    }
306✔
794

795
    /// Starts the node runloop.
796
    ///
797
    /// This function will block by looping infinitely.
798
    /// It will start the burnchain (separate thread), set-up a channel in
799
    /// charge of coordinating the new blocks coming from the burnchain and
800
    /// the nodes, taking turns on tenures.
801
    ///
802
    /// Returns `Option<NeonGlobals>` so that data can be passed to `NakamotoNode`
803
    pub fn start(
308✔
804
        &mut self,
308✔
805
        burnchain_opt: Option<Burnchain>,
308✔
806
        mut mine_start: u64,
308✔
807
    ) -> Option<Neon2NakaData> {
308✔
808
        let (coordinator_receivers, coordinator_senders) = self
308✔
809
            .coordinator_channels
308✔
810
            .take()
308✔
811
            .expect("Run loop already started, can only start once after initialization.");
308✔
812

813
        // Apply config-driven process-wide state before any chainstate is opened.
814
        self.config.apply_runtime_state();
308✔
815

816
        Self::setup_termination_handler(self.should_keep_running.clone(), false);
308✔
817

818
        let burnchain_result = Self::instantiate_burnchain_state(
308✔
819
            &self.config,
308✔
820
            self.should_keep_running.clone(),
308✔
821
            burnchain_opt,
308✔
822
            coordinator_senders.clone(),
308✔
823
        );
824

825
        let mut burnchain = match burnchain_result {
307✔
826
            Ok(burnchain_controller) => burnchain_controller,
307✔
827
            Err(burnchain_error::ShutdownInitiated) => {
828
                info!("Exiting stacks-node");
1✔
829
                return None;
1✔
830
            }
UNCOV
831
            Err(e) => {
×
UNCOV
832
                error!("Error initializing burnchain: {e}");
×
UNCOV
833
                info!("Exiting stacks-node");
×
UNCOV
834
                return None;
×
835
            }
836
        };
837

838
        let burnchain_config = burnchain.get_burnchain();
307✔
839
        self.burnchain = Some(burnchain_config.clone());
307✔
840

841
        // can we mine?
842
        let is_miner = self.check_is_miner(&mut burnchain);
307✔
843
        self.is_miner = Some(is_miner);
307✔
844

845
        // relayer linkup
846
        let (relay_send, relay_recv) = sync_channel(RELAYER_MAX_BUFFER);
307✔
847

848
        // set up globals so other subsystems can instantiate off of the runloop state.
849
        let globals = Globals::new(
307✔
850
            coordinator_senders,
307✔
851
            self.get_miner_status(),
307✔
852
            relay_send,
307✔
853
            self.counters.clone(),
307✔
854
            self.pox_watchdog_comms.clone(),
307✔
855
            self.should_keep_running.clone(),
307✔
856
            mine_start,
307✔
857
            LeaderKeyRegistrationState::default(),
307✔
858
        );
859
        self.set_globals(globals.clone());
307✔
860

861
        // have headers; boot up the chains coordinator and instantiate the chain state
862
        let coordinator_thread_handle = self.spawn_chains_coordinator(
307✔
863
            &burnchain_config,
307✔
864
            coordinator_receivers,
307✔
865
            globals.get_miner_status(),
307✔
866
        );
867
        self.instantiate_pox_watchdog();
307✔
868
        self.start_prometheus();
307✔
869

870
        // We announce a new burn block so that the chains coordinator
871
        // can resume prior work and handle eventual unprocessed sortitions
872
        // stored during a previous session.
873
        globals.coord().announce_new_burn_block();
307✔
874

875
        // Make sure at least one sortition has happened, and make sure it's globally available
876
        let sortdb = burnchain.sortdb_mut();
307✔
877
        let (rc_aligned_height, sn) =
307✔
878
            RunLoop::get_reward_cycle_sortition_db_height(sortdb, &burnchain_config);
307✔
879

880
        let burnchain_tip_snapshot = if sn.block_height == burnchain_config.first_block_height {
307✔
881
            // need at least one sortition to happen.
882
            burnchain
306✔
883
                .wait_for_sortitions(globals.coord().clone(), sn.block_height + 1)
306✔
884
                .expect("Unable to get burnchain tip")
306✔
885
                .block_snapshot
306✔
886
        } else {
887
            sn
1✔
888
        };
889

890
        globals.set_last_sortition(burnchain_tip_snapshot);
307✔
891

892
        // Boot up the p2p network and relayer, and figure out how many sortitions we have so far
893
        // (it could be non-zero if the node is resuming from chainstate)
894
        let mut node = StacksNode::spawn(self, globals.clone(), relay_recv);
307✔
895

896
        // Wait for all pending sortitions to process
897
        let burnchain_db = burnchain_config
307✔
898
            .open_burnchain_db(true)
307✔
899
            .expect("FATAL: failed to open burnchain DB");
307✔
900
        let burnchain_db_tip = burnchain_db
307✔
901
            .get_canonical_chain_tip()
307✔
902
            .expect("FATAL: failed to query burnchain DB");
307✔
903
        let mut burnchain_tip = burnchain
307✔
904
            .wait_for_sortitions(globals.coord().clone(), burnchain_db_tip.block_height)
307✔
905
            .expect("Unable to get burnchain tip");
307✔
906

907
        // Start the runloop
908
        debug!("Runloop: Begin run loop");
307✔
909
        self.counters.bump_blocks_processed();
307✔
910

911
        let mut sortition_db_height = rc_aligned_height;
307✔
912
        let mut burnchain_height = sortition_db_height;
307✔
913

914
        // prepare to fetch the first reward cycle!
915
        debug!("Runloop: Begin main runloop starting a burnchain block {sortition_db_height}");
307✔
916

917
        let mut last_tenure_sortition_height = 0;
307✔
918

919
        loop {
920
            if !globals.keep_running() {
464,601✔
921
                // The p2p thread relies on the same atomic_bool, it will
922
                // discontinue its execution after completing its ongoing runloop epoch.
923
                info!("Terminating p2p process");
158✔
924
                info!("Terminating relayer");
158✔
925
                info!("Terminating chains-coordinator");
158✔
926

927
                globals.coord().stop_chains_coordinator();
158✔
928
                coordinator_thread_handle.join().unwrap();
158✔
929
                let peer_network = node.join();
158✔
930

931
                // Data that will be passed to Nakamoto run loop
932
                // Only gets transferred on clean shutdown of neon run loop
933
                let data_to_naka = Neon2NakaData::new(globals, peer_network);
158✔
934

935
                info!("Exiting stacks-node");
158✔
936
                break Some(data_to_naka);
158✔
937
            }
464,443✔
938

939
            let remote_chain_height = burnchain.get_headers_height() - 1;
464,443✔
940

941
            // wait until it's okay to process the next reward cycle's sortitions.
942
            let (ibd, target_burnchain_block_height) = match self.get_pox_watchdog().pox_sync_wait(
464,443✔
943
                &burnchain_config,
464,443✔
944
                &burnchain_tip,
464,443✔
945
                remote_chain_height,
464,443✔
946
            ) {
464,443✔
947
                Ok(x) => x,
464,354✔
948
                Err(e) => {
89✔
949
                    debug!("Runloop: PoX sync wait routine aborted: {e:?}");
89✔
950
                    continue;
89✔
951
                }
952
            };
953

954
            // calculate burnchain sync percentage
955
            let percent: f64 = if remote_chain_height > 0 {
464,354✔
956
                burnchain_tip.block_snapshot.block_height as f64 / remote_chain_height as f64
464,352✔
957
            } else {
958
                0.0
2✔
959
            };
960

961
            // Download each burnchain block and process their sortitions.  This, in turn, will
962
            // cause the node's p2p and relayer threads to go fetch and download Stacks blocks and
963
            // process them.  This loop runs for one reward cycle, so that the next pass of the
964
            // runloop will cause the PoX sync watchdog to wait until it believes that the node has
965
            // obtained all the Stacks blocks it can.
966
            debug!(
464,354✔
967
                "Runloop: Download burnchain blocks up to reward cycle #{} (height {target_burnchain_block_height})",
UNCOV
968
                burnchain_config
×
UNCOV
969
                    .block_height_to_reward_cycle(target_burnchain_block_height)
×
UNCOV
970
                    .expect("FATAL: target burnchain block height does not have a reward cycle");
×
971
                "total_burn_sync_percent" => %percent,
UNCOV
972
                "local_burn_height" => burnchain_tip.block_snapshot.block_height,
×
UNCOV
973
                "remote_tip_height" => remote_chain_height
×
974
            );
975

976
            loop {
977
                if !globals.keep_running() {
464,410✔
978
                    break;
58✔
979
                }
464,352✔
980

981
                let (next_burnchain_tip, tip_burnchain_height) =
464,294✔
982
                    match burnchain.sync(Some(target_burnchain_block_height)) {
464,352✔
983
                        Ok(x) => x,
464,294✔
984
                        Err(e) => {
58✔
985
                            warn!("Runloop: Burnchain controller stopped: {e}");
58✔
986
                            continue;
58✔
987
                        }
988
                    };
989

990
                // *now* we know the burnchain height
991
                burnchain_tip = next_burnchain_tip;
464,294✔
992
                burnchain_height = tip_burnchain_height;
464,294✔
993

994
                let sortition_tip = &burnchain_tip.block_snapshot.sortition_id;
464,294✔
995
                let next_sortition_height = burnchain_tip.block_snapshot.block_height;
464,294✔
996

997
                if next_sortition_height != last_tenure_sortition_height {
464,294✔
998
                    info!(
12,615✔
999
                        "Runloop: Downloaded burnchain blocks up to height {burnchain_height}; target height is {target_burnchain_block_height}; remote_chain_height = {remote_chain_height} next_sortition_height = {next_sortition_height}, sortition_db_height = {sortition_db_height}"
1000
                    );
1001
                }
451,679✔
1002

1003
                if next_sortition_height > sortition_db_height {
464,294✔
1004
                    debug!(
12,432✔
1005
                        "Runloop: New burnchain block height {next_sortition_height} > {sortition_db_height}"
1006
                    );
1007

1008
                    debug!("Runloop: block mining until we process all sortitions");
12,432✔
1009
                    signal_mining_blocked(globals.get_miner_status());
12,432✔
1010

1011
                    // first, let's process all blocks in (sortition_db_height, next_sortition_height]
1012
                    for block_to_process in (sortition_db_height + 1)..(next_sortition_height + 1) {
68,570✔
1013
                        // stop mining so we can advance the sortition DB and so our
1014
                        // ProcessTenure() directive (sent by relayer_sortition_notify() below)
1015
                        // will be unblocked.
1016

1017
                        let block = {
68,570✔
1018
                            let ic = burnchain.sortdb_ref().index_conn();
68,570✔
1019
                            SortitionDB::get_ancestor_snapshot(&ic, block_to_process, sortition_tip)
68,570✔
1020
                                .unwrap()
68,570✔
1021
                                .expect(
68,570✔
1022
                                    "Failed to find block in fork processed by burnchain indexer",
68,570✔
1023
                                )
1024
                        };
1025

1026
                        let sortition_id = &block.sortition_id;
68,570✔
1027

1028
                        // Have the node process the new block, that can include, or not, a sortition.
1029
                        node.process_burnchain_state(
68,570✔
1030
                            self.config(),
68,570✔
1031
                            burnchain.sortdb_mut(),
68,570✔
1032
                            sortition_id,
68,570✔
1033
                            ibd,
68,570✔
1034
                        );
1035

1036
                        // Now, tell the relayer to check if it won a sortition during this block,
1037
                        // and, if so, to process and advertize the block.  This is basically a
1038
                        // no-op during boot-up.
1039
                        //
1040
                        // _this will block if the relayer's buffer is full_
1041
                        if !node.relayer_sortition_notify() {
68,570✔
1042
                            // First check if we were supposed to cleanly exit
1043
                            if !globals.keep_running() {
78✔
1044
                                // The p2p thread relies on the same atomic_bool, it will
1045
                                // discontinue its execution after completing its ongoing runloop epoch.
1046
                                info!("Terminating p2p process");
78✔
1047
                                info!("Terminating relayer");
78✔
1048
                                info!("Terminating chains-coordinator");
78✔
1049

1050
                                globals.coord().stop_chains_coordinator();
78✔
1051
                                coordinator_thread_handle.join().unwrap();
78✔
1052
                                let peer_network = node.join();
78✔
1053

1054
                                // Data that will be passed to Nakamoto run loop
1055
                                // Only gets transferred on clean shutdown of neon run loop
1056
                                let data_to_naka = Neon2NakaData::new(globals, peer_network);
78✔
1057

1058
                                info!("Exiting stacks-node");
78✔
1059
                                return Some(data_to_naka);
78✔
UNCOV
1060
                            }
×
1061
                            // relayer hung up, exit.
UNCOV
1062
                            error!("Runloop: Block relayer and miner hung up, exiting.");
×
UNCOV
1063
                            return None;
×
1064
                        }
68,492✔
1065
                    }
1066

1067
                    debug!("Runloop: enable miner after processing sortitions");
12,354✔
1068
                    signal_mining_ready(globals.get_miner_status());
12,354✔
1069

1070
                    debug!(
12,354✔
1071
                        "Runloop: Synchronized sortitions up to block height {next_sortition_height} from {sortition_db_height} (chain tip height is {burnchain_height})"
1072
                    );
1073

1074
                    sortition_db_height = next_sortition_height;
12,354✔
1075
                } else if ibd {
451,862✔
UNCOV
1076
                    // drive block processing after we reach the burnchain tip.
×
UNCOV
1077
                    // we may have downloaded all the blocks already,
×
UNCOV
1078
                    // so we can't rely on the relayer alone to
×
UNCOV
1079
                    // drive it.
×
UNCOV
1080
                    globals.coord().announce_new_stacks_block();
×
1081
                }
451,862✔
1082

1083
                if burnchain_height >= target_burnchain_block_height
464,216✔
1084
                    || burnchain_height >= remote_chain_height
38✔
1085
                {
1086
                    break;
464,218✔
1087
                }
2,147,483,647✔
1088
            }
1089

1090
            if sortition_db_height >= burnchain_height && !ibd {
464,276✔
1091
                let canonical_stacks_tip_height =
451,818✔
1092
                    SortitionDB::get_canonical_burn_chain_tip(burnchain.sortdb_ref().conn())
451,818✔
1093
                        .map(|snapshot| snapshot.canonical_stacks_tip_height)
451,818✔
1094
                        .unwrap_or(0);
451,818✔
1095
                if canonical_stacks_tip_height < mine_start {
451,818✔
UNCOV
1096
                    info!(
×
1097
                        "Runloop: Synchronized full burnchain, but stacks tip height is {canonical_stacks_tip_height}, and we are trying to boot to {mine_start}, not mining until reaching chain tip"
1098
                    );
1099
                } else {
1100
                    // once we've synced to the chain tip once, don't apply this check again.
1101
                    //  this prevents a possible corner case in the event of a PoX fork.
1102
                    mine_start = 0;
451,818✔
1103
                    globals.set_start_mining_height_if_zero(sortition_db_height);
451,818✔
1104

1105
                    // at tip, and not downloading. proceed to mine.
1106
                    if last_tenure_sortition_height != sortition_db_height {
451,818✔
1107
                        if is_miner {
8,707✔
1108
                            info!(
8,705✔
1109
                                "Runloop: Synchronized full burnchain up to height {sortition_db_height}. Proceeding to mine blocks"
1110
                            );
1111
                        } else {
1112
                            info!(
2✔
1113
                                "Runloop: Synchronized full burnchain up to height {sortition_db_height}."
1114
                            );
1115
                        }
1116
                        last_tenure_sortition_height = sortition_db_height;
8,707✔
1117
                    }
443,111✔
1118

1119
                    if !node.relayer_issue_tenure(ibd) {
451,818✔
1120
                        // First check if we were supposed to cleanly exit
1121
                        if !globals.keep_running() {
71✔
1122
                            // The p2p thread relies on the same atomic_bool, it will
1123
                            // discontinue its execution after completing its ongoing runloop epoch.
1124
                            info!("Terminating p2p process");
71✔
1125
                            info!("Terminating relayer");
71✔
1126
                            info!("Terminating chains-coordinator");
71✔
1127

1128
                            globals.coord().stop_chains_coordinator();
71✔
1129
                            coordinator_thread_handle.join().unwrap();
71✔
1130
                            let peer_network = node.join();
71✔
1131

1132
                            // Data that will be passed to Nakamoto run loop
1133
                            // Only gets transferred on clean shutdown of neon run loop
1134
                            let data_to_naka = Neon2NakaData::new(globals, peer_network);
71✔
1135

1136
                            info!("Exiting stacks-node");
71✔
1137
                            return Some(data_to_naka);
71✔
UNCOV
1138
                        }
×
1139
                        // relayer hung up, exit.
UNCOV
1140
                        error!("Runloop: Block relayer and miner hung up, exiting.");
×
UNCOV
1141
                        break None;
×
1142
                    }
305,952✔
1143
                }
1144
            }
12,458✔
1145
        }
1146
    }
308✔
1147
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc