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

stacks-network / stacks-core / 25903914664-1

15 May 2026 06:28AM UTC coverage: 47.122% (-38.8%) from 85.959%
25903914664-1

Pull #7199

github

94e391
web-flow
Merge 109f2828c into 1c7b8e6ac
Pull Request #7199: Feat: L1 and L2 early unlocks, updating signer

103343 of 219309 relevant lines covered (47.12%)

12880462.62 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 {
2✔
20
        RunLoop::new_with_boot_exec(config, Box::new(|_| {}))
2✔
21
    }
2✔
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 {
2✔
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();
2✔
28

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

32
        Self {
2✔
33
            config,
2✔
34
            node,
2✔
35
            callbacks: RunLoopCallbacks::new(),
2✔
36
        }
2✔
37
    }
2✔
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> {
2✔
46
        // Initialize and start the burnchain.
47
        let mut burnchain: Box<dyn BurnchainController> = match &self.config.burnchain.mode[..] {
2✔
48
            "helium" => Box::new(BitcoinRegtestController::new(self.config.clone(), None)),
2✔
49
            "mocknet" => MocknetController::generic(self.config.clone()),
2✔
50
            _ => unreachable!(),
×
51
        };
52

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

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

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

60
        // make first non-genesis block, with initial VRF keys
61
        self.node.setup(&mut burnchain);
2✔
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;
2✔
66

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

72
        self.node.spawn_peer_server();
2✔
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;
2✔
77
        let mut first_tenure = match leader.initiate_genesis_tenure(&burnchain_tip) {
2✔
78
            Some(res) => res,
2✔
79
            None => panic!("Error while initiating genesis tenure"),
×
80
        };
81

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

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

92
        // Run the tenure, keep the artifacts
93
        let artifacts_from_1st_tenure = match first_tenure.run(
2✔
94
            &burnchain
2✔
95
                .sortdb_ref()
2✔
96
                .index_handle(&burnchain_tip.block_snapshot.sortition_id),
2✔
97
        ) {
2✔
98
            Some(res) => res,
2✔
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(
2✔
107
            &artifacts_from_1st_tenure.anchored_block,
2✔
108
            &artifacts_from_1st_tenure.parent_block,
2✔
109
            &mut burnchain,
2✔
110
            artifacts_from_1st_tenure.burn_fee,
2✔
111
        );
112

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

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

118
        let mut leader_tenure = None;
2✔
119

120
        let (last_sortitioned_block, won_sortition) =
2✔
121
            match self.node.process_burnchain_state(&burnchain_tip) {
2✔
122
                (Some(sortitioned_block), won_sortition) => (sortitioned_block, won_sortition),
2✔
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();
2✔
129

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

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

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

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

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

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

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

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

194
            leader_tenure = None;
7✔
195

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

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

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

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

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

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