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

stacks-network / stacks-core / 26829844658-1

02 Jun 2026 03:24PM UTC coverage: 86.038% (+0.08%) from 85.959%
26829844658-1

Pull #7249

github

06b7ec
web-flow
Merge e017b11f4 into b945664c8
Pull Request #7249: feat: seamless rollovers between bond and stx-only staking positions

382 of 407 new or added lines in 3 files covered. (93.86%)

6474 existing lines in 95 files now uncovered.

194813 of 226426 relevant lines covered (86.04%)

18725269.97 hits per line

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

94.93
/stacks-node/src/run_loop/helium.rs
1
use stacks::chainstate::stacks::db::ClarityTx;
2
use stacks_common::types::chainstate::BurnchainHeaderHash;
3

4
use super::RunLoopCallbacks;
5
use crate::burnchains::Error as BurnchainControllerError;
6
use crate::{
7
    BitcoinRegtestController, BurnchainController, ChainTip, Config, MocknetController, Node,
8
};
9

10
/// RunLoop is coordinating a simulated burnchain and some simulated nodes
11
/// taking turns in producing blocks.
12
pub struct RunLoop {
13
    config: Config,
14
    pub node: Node,
15
    pub callbacks: RunLoopCallbacks,
16
}
17

18
impl RunLoop {
19
    pub fn new(config: Config) -> Self {
10✔
20
        RunLoop::new_with_boot_exec(config, Box::new(|_| {}))
10✔
21
    }
10✔
22

23
    /// Sets up a runloop and node, given a config.
24
    pub fn new_with_boot_exec(config: Config, boot_exec: Box<dyn FnOnce(&mut ClarityTx)>) -> Self {
10✔
25
        // Apply config-driven process-wide state before any chainstate is opened.
26
        // Helium opens chainstate inside `Node::new`, so this must run first.
27
        config.apply_runtime_state();
10✔
28

29
        // Build node based on config
30
        let node = Node::new(config.clone(), boot_exec);
10✔
31

32
        Self {
10✔
33
            config,
10✔
34
            node,
10✔
35
            callbacks: RunLoopCallbacks::new(),
10✔
36
        }
10✔
37
    }
10✔
38

39
    /// Starts the testnet runloop.
40
    ///
41
    /// This function will block by looping infinitely.
42
    /// It will start the burnchain (separate thread), set-up a channel in
43
    /// charge of coordinating the new blocks coming from the burnchain and
44
    /// the nodes, taking turns on tenures.  
45
    pub fn start(&mut self, expected_num_rounds: u64) -> Result<(), BurnchainControllerError> {
10✔
46
        // Initialize and start the burnchain.
47
        let mut burnchain: Box<dyn BurnchainController> = match &self.config.burnchain.mode[..] {
10✔
48
            "helium" => Box::new(BitcoinRegtestController::new(self.config.clone(), None)),
10✔
49
            "mocknet" => MocknetController::generic(self.config.clone()),
10✔
UNCOV
50
            _ => unreachable!(),
×
51
        };
52

53
        self.callbacks.invoke_burn_chain_initialized(&mut burnchain);
10✔
54

55
        let (initial_state, _) = burnchain.start(None)?;
10✔
56

57
        // Update each node with the genesis block.
58
        self.node.process_burnchain_state(&initial_state);
10✔
59

60
        // make first non-genesis block, with initial VRF keys
61
        self.node.setup(&mut burnchain);
10✔
62

63
        // Waiting on the 1st block (post-genesis) from the burnchain, containing the first key registrations
64
        // that will be used for bootstraping the chain.
65
        let mut round_index: u64 = 0;
10✔
66

67
        // Sync and update node with this new block.
68
        let (burnchain_tip, _) = burnchain.sync(None)?;
10✔
69
        self.node.process_burnchain_state(&burnchain_tip); // todo(ludo): should return genesis?
10✔
70
        let mut chain_tip = ChainTip::genesis(&BurnchainHeaderHash::zero(), 0, 0);
10✔
71

72
        self.node.spawn_peer_server();
10✔
73

74
        // Bootstrap the chain: node will start a new tenure,
75
        // using the sortition hash from block #1 for generating a VRF.
76
        let leader = &mut self.node;
10✔
77
        let mut first_tenure = match leader.initiate_genesis_tenure(&burnchain_tip) {
10✔
78
            Some(res) => res,
10✔
UNCOV
79
            None => panic!("Error while initiating genesis tenure"),
×
80
        };
81

82
        self.callbacks.invoke_new_tenure(
10✔
83
            round_index,
10✔
84
            &burnchain_tip,
10✔
85
            &chain_tip,
10✔
86
            &mut first_tenure,
10✔
87
        );
88

89
        // TODO (hack) instantiate db
90
        let _ = burnchain.sortdb_mut();
10✔
91

92
        // Run the tenure, keep the artifacts
93
        let artifacts_from_1st_tenure = match first_tenure.run(
10✔
94
            &burnchain
10✔
95
                .sortdb_ref()
10✔
96
                .index_handle(&burnchain_tip.block_snapshot.sortition_id),
10✔
97
        ) {
10✔
98
            Some(res) => res,
10✔
UNCOV
99
            None => panic!("Error while running 1st tenure"),
×
100
        };
101

102
        // Tenures are instantiating their own chainstate, so that nodes can keep a clean chainstate,
103
        // while having the option of running multiple tenures concurrently and try different strategies.
104
        // As a result, once the tenure ran and we have the artifacts (anchored_blocks, microblocks),
105
        // we have the 1st node (leading) updating its chainstate with the artifacts from its own tenure.
106
        leader.commit_artifacts(
10✔
107
            &artifacts_from_1st_tenure.anchored_block,
10✔
108
            &artifacts_from_1st_tenure.parent_block,
10✔
109
            &mut burnchain,
10✔
110
            artifacts_from_1st_tenure.burn_fee,
10✔
111
        );
112

113
        let (mut burnchain_tip, _) = burnchain.sync(None)?;
10✔
114

115
        self.callbacks
10✔
116
            .invoke_new_burn_chain_state(round_index, &burnchain_tip, &chain_tip);
10✔
117

118
        let mut leader_tenure = None;
10✔
119

120
        let (last_sortitioned_block, won_sortition) =
10✔
121
            match self.node.process_burnchain_state(&burnchain_tip) {
10✔
122
                (Some(sortitioned_block), won_sortition) => (sortitioned_block, won_sortition),
10✔
UNCOV
123
                (None, _) => panic!("Node should have a sortitioned block"),
×
124
            };
125

126
        // Have the node process its own tenure.
127
        // We should have some additional checks here, and ensure that the previous artifacts are legit.
128
        let mut atlas_db = self.node.make_atlas_db();
10✔
129

130
        chain_tip = self.node.process_tenure(
10✔
131
            &artifacts_from_1st_tenure.anchored_block,
10✔
132
            &last_sortitioned_block.block_snapshot.consensus_hash,
10✔
133
            artifacts_from_1st_tenure.microblocks.clone(),
10✔
134
            burnchain.sortdb_mut(),
10✔
135
            &mut atlas_db,
10✔
136
        );
137

138
        self.callbacks.invoke_new_stacks_chain_state(
10✔
139
            round_index,
10✔
140
            &burnchain_tip,
10✔
141
            &chain_tip,
10✔
142
            &mut self.node.chain_state,
10✔
143
            &burnchain
10✔
144
                .sortdb_ref()
10✔
145
                .index_handle(&burnchain_tip.block_snapshot.sortition_id),
10✔
146
        );
147

148
        // If the node we're looping on won the sortition, initialize and configure the next tenure
149
        if won_sortition {
10✔
150
            leader_tenure = self.node.initiate_new_tenure();
10✔
151
        }
10✔
152

153
        // Start the runloop
154
        round_index = 1;
10✔
155
        loop {
156
            if expected_num_rounds == round_index {
44✔
157
                return Ok(());
10✔
158
            }
34✔
159

160
            // Run the last initialized tenure
161
            let artifacts_from_tenure = match leader_tenure {
34✔
162
                Some(mut tenure) => {
34✔
163
                    self.callbacks.invoke_new_tenure(
34✔
164
                        round_index,
34✔
165
                        &burnchain_tip,
34✔
166
                        &chain_tip,
34✔
167
                        &mut tenure,
34✔
168
                    );
169
                    tenure.run(
34✔
170
                        &burnchain
34✔
171
                            .sortdb_ref()
34✔
172
                            .index_handle(&burnchain_tip.block_snapshot.sortition_id),
34✔
173
                    )
174
                }
UNCOV
175
                None => None,
×
176
            };
177

178
            if let Some(artifacts) = &artifacts_from_tenure {
34✔
179
                // Have each node receive artifacts from the current tenure
34✔
180
                self.node.commit_artifacts(
34✔
181
                    &artifacts.anchored_block,
34✔
182
                    &artifacts.parent_block,
34✔
183
                    &mut burnchain,
34✔
184
                    artifacts.burn_fee,
34✔
185
                );
34✔
186
            }
34✔
187

188
            let (new_burnchain_tip, _) = burnchain.sync(None)?;
34✔
189
            burnchain_tip = new_burnchain_tip;
34✔
190

191
            self.callbacks
34✔
192
                .invoke_new_burn_chain_state(round_index, &burnchain_tip, &chain_tip);
34✔
193

194
            leader_tenure = None;
34✔
195

196
            // Have each node process the new block, that can include, or not, a sortition.
197
            let (last_sortitioned_block, won_sortition) =
34✔
198
                match self.node.process_burnchain_state(&burnchain_tip) {
34✔
199
                    (Some(sortitioned_block), won_sortition) => (sortitioned_block, won_sortition),
34✔
UNCOV
200
                    (None, _) => panic!("Node should have a sortitioned block"),
×
201
                };
202

203
            match artifacts_from_tenure {
34✔
204
                // Pass if we're missing the artifacts from the current tenure.
UNCOV
205
                None => continue,
×
206
                Some(ref artifacts) => {
34✔
207
                    // Have the node process its tenure.
34✔
208
                    // We should have some additional checks here, and ensure that the previous artifacts are legit.
34✔
209
                    let mut atlas_db = self.node.make_atlas_db();
34✔
210

34✔
211
                    chain_tip = self.node.process_tenure(
34✔
212
                        &artifacts.anchored_block,
34✔
213
                        &last_sortitioned_block.block_snapshot.consensus_hash,
34✔
214
                        artifacts.microblocks.clone(),
34✔
215
                        burnchain.sortdb_mut(),
34✔
216
                        &mut atlas_db,
34✔
217
                    );
34✔
218

34✔
219
                    self.callbacks.invoke_new_stacks_chain_state(
34✔
220
                        round_index,
34✔
221
                        &burnchain_tip,
34✔
222
                        &chain_tip,
34✔
223
                        &mut self.node.chain_state,
34✔
224
                        &burnchain
34✔
225
                            .sortdb_ref()
34✔
226
                            .index_handle(&burnchain_tip.block_snapshot.sortition_id),
34✔
227
                    );
34✔
228
                }
34✔
229
            };
230

231
            // If won sortition, initialize and configure the next tenure
232
            if won_sortition {
34✔
233
                leader_tenure = self.node.initiate_new_tenure();
34✔
234
            }
34✔
235

236
            round_index += 1;
34✔
237
        }
238
    }
10✔
239
}
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