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

stacks-network / stacks-core / 28582929957-1

02 Jul 2026 10:22AM UTC coverage: 85.69% (-0.02%) from 85.712%
28582929957-1

Pull #7155

github

480cac
web-flow
Merge 8f7d691f7 into e6bed49aa
Pull Request #7155: chore: remove mocknet network mode

421 of 440 new or added lines in 8 files covered. (95.68%)

10192 existing lines in 154 files now uncovered.

194381 of 226843 relevant lines covered (85.69%)

19529078.16 hits per line

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

9.7
/stacks-node/src/node.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
use std::collections::{HashMap, HashSet};
18
use std::net::SocketAddr;
19
use std::thread::JoinHandle;
20
use std::{env, thread, time};
21

22
use rand::RngCore;
23
use stacks::burnchains::bitcoin::BitcoinNetworkType;
24
use stacks::burnchains::db::BurnchainDB;
25
use stacks::burnchains::{PoxConstants, Txid};
26
use stacks::chainstate::burn::db::sortdb::SortitionDB;
27
use stacks::chainstate::burn::operations::leader_block_commit::{
28
    RewardSetInfo, BURN_BLOCK_MINED_AT_MODULUS,
29
};
30
use stacks::chainstate::burn::operations::{
31
    BlockstackOperationType, LeaderBlockCommitOp, LeaderKeyRegisterOp,
32
};
33
use stacks::chainstate::burn::ConsensusHash;
34
use stacks::chainstate::stacks::address::PoxAddress;
35
use stacks::chainstate::stacks::db::{
36
    ChainStateBootData, ChainstateAccountBalance, ChainstateAccountLockup, ChainstateBNSName,
37
    ChainstateBNSNamespace, ClarityTx, StacksChainState, StacksEpochReceipt, StacksHeaderInfo,
38
};
39
use stacks::chainstate::stacks::events::{
40
    StacksTransactionEvent, StacksTransactionReceipt, TransactionOrigin,
41
};
42
use stacks::chainstate::stacks::{
43
    CoinbasePayload, StacksBlock, StacksMicroblock, StacksTransaction, StacksTransactionSigner,
44
    TransactionAnchorMode, TransactionPayload, TransactionVersion,
45
};
46
use stacks::core::mempool::MemPoolDB;
47
use stacks::core::{EpochList, STACKS_EPOCH_2_1_MARKER};
48
use stacks::cost_estimates::metrics::UnitMetric;
49
use stacks::cost_estimates::UnitEstimator;
50
use stacks::net::atlas::{AtlasConfig, AtlasDB, AttachmentInstance};
51
use stacks::net::db::PeerDB;
52
use stacks::net::p2p::PeerNetwork;
53
use stacks::net::stackerdb::StackerDBs;
54
use stacks::net::RPCHandlerArgs;
55
use stacks::util_lib::strings::UrlString;
56
use stacks_common::types::chainstate::{BlockHeaderHash, BurnchainHeaderHash, TrieHash, VRFSeed};
57
use stacks_common::types::net::PeerAddress;
58
use stacks_common::util::get_epoch_time_secs;
59
use stacks_common::util::hash::Sha256Sum;
60
use stacks_common::util::secp256k1::Secp256k1PrivateKey;
61
use stacks_common::util::vrf::VRFPublicKey;
62

63
use super::{BitcoinRegtestController, BurnchainTip, Config, EventDispatcher, Keychain, Tenure};
64
use crate::burnchains::make_bitcoin_indexer;
65
use crate::genesis_data::USE_TEST_GENESIS_CHAINSTATE;
66
use crate::run_loop;
67
use crate::run_loop::RegisteredKey;
68

69
#[derive(Debug, Clone)]
70
pub struct ChainTip {
71
    pub metadata: StacksHeaderInfo,
72
    pub block: StacksBlock,
73
    pub receipts: Vec<StacksTransactionReceipt>,
74
}
75

76
impl ChainTip {
77
    pub fn genesis(
435✔
78
        first_burnchain_block_hash: &BurnchainHeaderHash,
435✔
79
        first_burnchain_block_height: u64,
435✔
80
        first_burnchain_block_timestamp: u64,
435✔
81
    ) -> ChainTip {
435✔
82
        ChainTip {
435✔
83
            metadata: StacksHeaderInfo::genesis(
435✔
84
                TrieHash([0u8; 32]),
435✔
85
                first_burnchain_block_hash,
435✔
86
                first_burnchain_block_height as u32,
435✔
87
                first_burnchain_block_timestamp,
435✔
88
            ),
435✔
89
            block: StacksBlock::genesis_block(),
435✔
90
            receipts: vec![],
435✔
91
        }
435✔
92
    }
435✔
93
}
94

95
/// Node is a structure modelising an active node working on the stacks chain.
96
pub struct Node {
97
    pub chain_state: StacksChainState,
98
    pub config: Config,
99
    active_registered_key: Option<RegisteredKey>,
100
    bootstraping_chain: bool,
101
    pub burnchain_tip: Option<BurnchainTip>,
102
    pub chain_tip: Option<ChainTip>,
103
    keychain: Keychain,
104
    last_sortitioned_block: Option<BurnchainTip>,
105
    event_dispatcher: EventDispatcher,
106
    nonce: u64,
107
    leader_key_registers: HashSet<Txid>,
108
    block_commits: HashSet<Txid>,
109
}
110

111
pub fn get_account_lockups(
289✔
112
    use_test_chainstate_data: bool,
289✔
113
) -> Box<dyn Iterator<Item = ChainstateAccountLockup>> {
289✔
114
    Box::new(
289✔
115
        stx_genesis::GenesisData::new(use_test_chainstate_data)
289✔
116
            .read_lockups()
289✔
117
            .map(|item| ChainstateAccountLockup {
289✔
118
                address: item.address,
5,780✔
119
                amount: item.amount,
5,780✔
120
                block_height: item.block_height,
5,780✔
121
            }),
5,780✔
122
    )
123
}
289✔
124

125
pub fn get_account_balances(
289✔
126
    use_test_chainstate_data: bool,
289✔
127
) -> Box<dyn Iterator<Item = ChainstateAccountBalance>> {
289✔
128
    Box::new(
289✔
129
        stx_genesis::GenesisData::new(use_test_chainstate_data)
289✔
130
            .read_balances()
289✔
131
            .map(|item| ChainstateAccountBalance {
289✔
132
                address: item.address,
6,936✔
133
                amount: item.amount,
6,936✔
134
            }),
6,936✔
135
    )
136
}
289✔
137

138
pub fn get_namespaces(
289✔
139
    use_test_chainstate_data: bool,
289✔
140
) -> Box<dyn Iterator<Item = ChainstateBNSNamespace>> {
289✔
141
    Box::new(
289✔
142
        stx_genesis::GenesisData::new(use_test_chainstate_data)
289✔
143
            .read_namespaces()
289✔
144
            .map(|item| ChainstateBNSNamespace {
289✔
145
                namespace_id: item.namespace_id,
1,445✔
146
                importer: item.importer,
1,445✔
147
                buckets: item.buckets,
1,445✔
148
                base: item.base as u64,
1,445✔
149
                coeff: item.coeff as u64,
1,445✔
150
                nonalpha_discount: item.nonalpha_discount as u64,
1,445✔
151
                no_vowel_discount: item.no_vowel_discount as u64,
1,445✔
152
                lifetime: item.lifetime as u64,
1,445✔
153
            }),
1,445✔
154
    )
155
}
289✔
156

157
pub fn get_names(use_test_chainstate_data: bool) -> Box<dyn Iterator<Item = ChainstateBNSName>> {
289✔
158
    Box::new(
289✔
159
        stx_genesis::GenesisData::new(use_test_chainstate_data)
289✔
160
            .read_names()
289✔
161
            .map(|item| ChainstateBNSName {
289✔
162
                fully_qualified_name: item.fully_qualified_name,
3,468✔
163
                owner: item.owner,
3,468✔
164
                zonefile_hash: item.zonefile_hash,
3,468✔
165
            }),
3,468✔
166
    )
167
}
289✔
168

169
// This function is called for helium.
170
#[allow(clippy::too_many_arguments)]
UNCOV
171
fn spawn_peer(
×
UNCOV
172
    is_mainnet: bool,
×
UNCOV
173
    chain_id: u32,
×
UNCOV
174
    mut this: PeerNetwork,
×
UNCOV
175
    p2p_sock: &SocketAddr,
×
UNCOV
176
    rpc_sock: &SocketAddr,
×
UNCOV
177
    burn_db_path: String,
×
UNCOV
178
    stacks_chainstate_path: String,
×
UNCOV
179
    pox_consts: PoxConstants,
×
UNCOV
180
    event_dispatcher: EventDispatcher,
×
UNCOV
181
    exit_at_block_height: Option<u64>,
×
UNCOV
182
    genesis_chainstate_hash: Sha256Sum,
×
UNCOV
183
    poll_timeout: u64,
×
UNCOV
184
    config: Config,
×
UNCOV
185
) -> JoinHandle<()> {
×
UNCOV
186
    this.bind(p2p_sock, rpc_sock).unwrap();
×
UNCOV
187
    let server_thread = thread::spawn(move || {
×
188
        // create estimators, metric instances for RPC handler
UNCOV
189
        let cost_estimator = config
×
UNCOV
190
            .make_cost_estimator()
×
UNCOV
191
            .unwrap_or_else(|| Box::new(UnitEstimator));
×
UNCOV
192
        let metric = config
×
UNCOV
193
            .make_cost_metric()
×
UNCOV
194
            .unwrap_or_else(|| Box::new(UnitMetric));
×
UNCOV
195
        let fee_estimator = config.make_fee_estimator();
×
196

UNCOV
197
        let handler_args = RPCHandlerArgs {
×
198
            exit_at_block_height,
×
199
            cost_estimator: Some(cost_estimator.as_ref()),
×
200
            cost_metric: Some(metric.as_ref()),
×
201
            fee_estimator: fee_estimator.as_ref().map(|x| x.as_ref()),
×
UNCOV
202
            genesis_chainstate_hash,
×
UNCOV
203
            ..RPCHandlerArgs::default()
×
204
        };
205

206
        loop {
UNCOV
207
            let sortdb = match SortitionDB::open(
×
UNCOV
208
                &burn_db_path,
×
UNCOV
209
                false,
×
UNCOV
210
                pox_consts.clone(),
×
211
                Some(config.node.get_marf_opts()),
×
212
            ) {
×
213
                Ok(x) => x,
×
214
                Err(e) => {
×
UNCOV
215
                    warn!("Error while connecting burnchain db in peer loop: {e}");
×
UNCOV
216
                    thread::sleep(time::Duration::from_secs(1));
×
UNCOV
217
                    continue;
×
218
                }
219
            };
UNCOV
220
            let (mut chainstate, _) = match StacksChainState::open(
×
UNCOV
221
                is_mainnet,
×
UNCOV
222
                chain_id,
×
UNCOV
223
                &stacks_chainstate_path,
×
UNCOV
224
                Some(config.node.get_marf_opts()),
×
UNCOV
225
            ) {
×
UNCOV
226
                Ok(x) => x,
×
UNCOV
227
                Err(e) => {
×
UNCOV
228
                    warn!("Error while connecting chainstate db in peer loop: {e}");
×
229
                    thread::sleep(time::Duration::from_secs(1));
×
230
                    continue;
×
231
                }
232
            };
233

UNCOV
234
            let estimator = Box::new(UnitEstimator);
×
UNCOV
235
            let metric = Box::new(UnitMetric);
×
236

UNCOV
237
            let mut mem_pool = match MemPoolDB::open(
×
UNCOV
238
                is_mainnet,
×
UNCOV
239
                chain_id,
×
UNCOV
240
                &stacks_chainstate_path,
×
UNCOV
241
                estimator,
×
UNCOV
242
                metric,
×
UNCOV
243
            ) {
×
UNCOV
244
                Ok(x) => x,
×
UNCOV
245
                Err(e) => {
×
UNCOV
246
                    warn!("Error while connecting to mempool db in peer loop: {e}");
×
UNCOV
247
                    thread::sleep(time::Duration::from_secs(1));
×
UNCOV
248
                    continue;
×
249
                }
250
            };
251

UNCOV
252
            let indexer = make_bitcoin_indexer(&config, None);
×
253

UNCOV
254
            let net_result = this
×
UNCOV
255
                .run(
×
UNCOV
256
                    &indexer,
×
257
                    &sortdb,
×
UNCOV
258
                    &mut chainstate,
×
UNCOV
259
                    &mut mem_pool,
×
UNCOV
260
                    None,
×
261
                    false,
262
                    false,
UNCOV
263
                    poll_timeout,
×
UNCOV
264
                    &handler_args,
×
UNCOV
265
                    config.node.txindex,
×
266
                )
UNCOV
267
                .unwrap();
×
268
            if net_result.has_transactions() {
×
UNCOV
269
                event_dispatcher.process_new_mempool_txs(net_result.transactions())
×
270
            }
×
271
            // Dispatch retrieved attachments, if any.
UNCOV
272
            if net_result.has_attachments() {
×
UNCOV
273
                event_dispatcher.process_new_attachments(&net_result.attachments);
×
UNCOV
274
            }
×
275
        }
276
    });
UNCOV
277
    server_thread
×
UNCOV
278
}
×
279

280
// Check if the small test genesis chainstate data should be used.
281
// First check env var, then config file, then use default.
282
pub fn use_test_genesis_chainstate(config: &Config) -> bool {
1,073✔
283
    if env::var("BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE") == Ok("1".to_string()) {
1,073✔
UNCOV
284
        true
×
285
    } else if let Some(use_test_genesis_chainstate) = config.node.use_test_genesis_chainstate {
1,073✔
UNCOV
286
        use_test_genesis_chainstate
×
287
    } else {
288
        USE_TEST_GENESIS_CHAINSTATE
1,073✔
289
    }
290
}
1,073✔
291

292
impl Node {
293
    /// Instantiate and initialize a new node, given a config
294
    pub fn new(config: Config, boot_block_exec: Box<dyn FnOnce(&mut ClarityTx)>) -> Self {
×
NEW
295
        let use_test_genesis_data = use_test_genesis_chainstate(&config);
×
296

UNCOV
297
        let keychain = Keychain::default(config.node.seed.clone());
×
298

UNCOV
299
        let initial_balances = config
×
UNCOV
300
            .initial_balances
×
UNCOV
301
            .iter()
×
UNCOV
302
            .map(|e| (e.address.clone(), e.amount))
×
UNCOV
303
            .collect();
×
UNCOV
304
        let pox_constants = match config.burnchain.get_bitcoin_network() {
×
UNCOV
305
            (_, BitcoinNetworkType::Mainnet) => PoxConstants::mainnet_default(),
×
UNCOV
306
            (_, BitcoinNetworkType::Testnet) => PoxConstants::testnet_default(),
×
UNCOV
307
            (_, BitcoinNetworkType::Regtest) => PoxConstants::regtest_default(),
×
308
        };
309

UNCOV
310
        let mut boot_data = ChainStateBootData {
×
UNCOV
311
            initial_balances,
×
UNCOV
312
            first_burnchain_block_hash: BurnchainHeaderHash::zero(),
×
313
            first_burnchain_block_height: 0,
314
            first_burnchain_block_timestamp: 0,
UNCOV
315
            pox_constants,
×
UNCOV
316
            post_flight_callback: Some(boot_block_exec),
×
UNCOV
317
            get_bulk_initial_lockups: Some(Box::new(move || {
×
UNCOV
318
                get_account_lockups(use_test_genesis_data)
×
UNCOV
319
            })),
×
UNCOV
320
            get_bulk_initial_balances: Some(Box::new(move || {
×
UNCOV
321
                get_account_balances(use_test_genesis_data)
×
UNCOV
322
            })),
×
323
            get_bulk_initial_namespaces: Some(Box::new(move || {
×
UNCOV
324
                get_namespaces(use_test_genesis_data)
×
325
            })),
×
UNCOV
326
            get_bulk_initial_names: Some(Box::new(move || get_names(use_test_genesis_data))),
×
327
        };
328

UNCOV
329
        let chain_state_result = StacksChainState::open_and_exec(
×
UNCOV
330
            config.is_mainnet(),
×
UNCOV
331
            config.burnchain.chain_id,
×
UNCOV
332
            &config.get_chainstate_path_str(),
×
UNCOV
333
            Some(&mut boot_data),
×
UNCOV
334
            Some(config.node.get_marf_opts()),
×
335
        );
336

UNCOV
337
        let (chain_state, receipts) = match chain_state_result {
×
UNCOV
338
            Ok(res) => res,
×
UNCOV
339
            Err(err) => panic!(
×
340
                "Error while opening chain state at path {}: {err:?}",
UNCOV
341
                config.get_chainstate_path_str()
×
342
            ),
343
        };
344

345
        let estimator = Box::new(UnitEstimator);
×
346
        let metric = Box::new(UnitMetric);
×
347

348
        // avoid race to create condition on mempool db
UNCOV
349
        let _mem_pool = MemPoolDB::open(
×
UNCOV
350
            config.is_mainnet(),
×
UNCOV
351
            config.burnchain.chain_id,
×
UNCOV
352
            &chain_state.root_path,
×
UNCOV
353
            estimator,
×
UNCOV
354
            metric,
×
355
        )
UNCOV
356
        .expect("FATAL: failed to initiate mempool");
×
357

UNCOV
358
        let mut event_dispatcher = EventDispatcher::new_with_custom_queue_size(
×
UNCOV
359
            config.get_working_dir(),
×
UNCOV
360
            config.node.effective_event_dispatcher_queue_size(),
×
361
        );
362

UNCOV
363
        for observer in &config.events_observers {
×
UNCOV
364
            event_dispatcher.register_observer(observer);
×
UNCOV
365
        }
×
366

UNCOV
367
        let burnchain_config = config.get_burnchain();
×
368

369
        // instantiate DBs
UNCOV
370
        let _burnchain_db = BurnchainDB::connect(
×
UNCOV
371
            &burnchain_config.get_burnchaindb_path(),
×
UNCOV
372
            &burnchain_config,
×
373
            true,
374
        )
UNCOV
375
        .expect("FATAL: failed to connect to burnchain DB");
×
376

UNCOV
377
        run_loop::announce_boot_receipts(
×
UNCOV
378
            &mut event_dispatcher,
×
UNCOV
379
            &chain_state,
×
UNCOV
380
            &burnchain_config.pox_constants,
×
UNCOV
381
            &receipts,
×
382
        );
383

UNCOV
384
        Self {
×
UNCOV
385
            active_registered_key: None,
×
UNCOV
386
            bootstraping_chain: false,
×
UNCOV
387
            chain_state,
×
UNCOV
388
            chain_tip: None,
×
UNCOV
389
            keychain,
×
UNCOV
390
            last_sortitioned_block: None,
×
UNCOV
391
            config,
×
UNCOV
392
            burnchain_tip: None,
×
UNCOV
393
            nonce: 0,
×
UNCOV
394
            event_dispatcher,
×
UNCOV
395
            leader_key_registers: HashSet::new(),
×
UNCOV
396
            block_commits: HashSet::new(),
×
UNCOV
397
        }
×
UNCOV
398
    }
×
399

UNCOV
400
    fn make_atlas_config() -> AtlasConfig {
×
UNCOV
401
        AtlasConfig::new(false)
×
UNCOV
402
    }
×
403

UNCOV
404
    pub fn make_atlas_db(&self) -> AtlasDB {
×
UNCOV
405
        AtlasDB::connect(
×
UNCOV
406
            Self::make_atlas_config(),
×
UNCOV
407
            &self.config.get_atlas_db_file_path(),
×
408
            true,
409
        )
UNCOV
410
        .unwrap()
×
UNCOV
411
    }
×
412

413
    // This function is used for helium.
UNCOV
414
    pub fn spawn_peer_server(&mut self) {
×
415
        // we can call _open_ here rather than _connect_, since connect is first called in
416
        //   make_genesis_block
UNCOV
417
        let burnchain = self.config.get_burnchain();
×
UNCOV
418
        let sortdb = SortitionDB::open(
×
UNCOV
419
            &self.config.get_burn_db_file_path(),
×
420
            true,
UNCOV
421
            burnchain.pox_constants.clone(),
×
UNCOV
422
            Some(self.config.node.get_marf_opts()),
×
423
        )
UNCOV
424
        .expect("Error while instantiating burnchain db");
×
425

UNCOV
426
        let epochs_vec = SortitionDB::get_stacks_epochs(sortdb.conn())
×
UNCOV
427
            .expect("Error while loading stacks epochs");
×
UNCOV
428
        let epochs = EpochList::new(&epochs_vec);
×
429

430
        Config::assert_valid_epoch_settings(&burnchain, &epochs);
×
431

UNCOV
432
        let view = {
×
UNCOV
433
            let sortition_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
×
434
                .expect("Failed to get sortition tip");
×
UNCOV
435
            SortitionDB::get_burnchain_view(&sortdb.index_conn(), &burnchain, &sortition_tip)
×
UNCOV
436
                .unwrap()
×
437
        };
438

439
        // create a new peerdb
UNCOV
440
        let data_url = UrlString::try_from(self.config.node.data_url.to_string()).unwrap();
×
441

UNCOV
442
        let initial_neighbors = self.config.node.bootstrap_node.clone();
×
443

UNCOV
444
        println!("BOOTSTRAP WITH {initial_neighbors:?}");
×
445

446
        let rpc_sock: SocketAddr =
×
447
            self.config.node.rpc_bind.parse().unwrap_or_else(|_| {
×
UNCOV
448
                panic!("Failed to parse socket: {}", &self.config.node.rpc_bind)
×
449
            });
UNCOV
450
        let p2p_sock: SocketAddr =
×
UNCOV
451
            self.config.node.p2p_bind.parse().unwrap_or_else(|_| {
×
UNCOV
452
                panic!("Failed to parse socket: {}", &self.config.node.p2p_bind)
×
453
            });
UNCOV
454
        let p2p_addr: SocketAddr = self.config.node.p2p_address.parse().unwrap_or_else(|_| {
×
UNCOV
455
            panic!("Failed to parse socket: {}", &self.config.node.p2p_address)
×
456
        });
UNCOV
457
        let node_privkey = {
×
UNCOV
458
            let mut re_hashed_seed = self.config.node.local_peer_seed.clone();
×
UNCOV
459
            let my_private_key = loop {
×
UNCOV
460
                match Secp256k1PrivateKey::from_slice(&re_hashed_seed[..]) {
×
UNCOV
461
                    Ok(sk) => break sk,
×
462
                    Err(_) => {
UNCOV
463
                        re_hashed_seed = Sha256Sum::from_data(&re_hashed_seed[..])
×
UNCOV
464
                            .as_bytes()
×
UNCOV
465
                            .to_vec()
×
466
                    }
467
                }
468
            };
UNCOV
469
            my_private_key
×
470
        };
471

UNCOV
472
        let mut peerdb = PeerDB::connect(
×
UNCOV
473
            &self.config.get_peer_db_file_path(),
×
474
            true,
475
            self.config.burnchain.chain_id,
×
476
            burnchain.network_id,
×
477
            Some(node_privkey),
×
478
            self.config.connection_options.private_key_lifetime,
×
479
            PeerAddress::from_socketaddr(&p2p_addr),
×
480
            p2p_sock.port(),
×
481
            data_url,
×
482
            &[],
×
UNCOV
483
            Some(&initial_neighbors),
×
UNCOV
484
            &[],
×
485
        )
UNCOV
486
        .unwrap();
×
487

UNCOV
488
        println!("DENY NEIGHBORS {:?}", &self.config.node.deny_nodes);
×
489
        {
UNCOV
490
            let tx = peerdb.tx_begin().unwrap();
×
UNCOV
491
            for denied in self.config.node.deny_nodes.iter() {
×
492
                PeerDB::set_deny_peer(
×
UNCOV
493
                    &tx,
×
UNCOV
494
                    denied.addr.network_id,
×
UNCOV
495
                    &denied.addr.addrbytes,
×
UNCOV
496
                    denied.addr.port,
×
UNCOV
497
                    get_epoch_time_secs() + 24 * 365 * 3600,
×
UNCOV
498
                )
×
UNCOV
499
                .unwrap();
×
UNCOV
500
            }
×
UNCOV
501
            tx.commit().unwrap();
×
502
        }
UNCOV
503
        let atlasdb = self.make_atlas_db();
×
504

UNCOV
505
        let stackerdbs =
×
UNCOV
506
            StackerDBs::connect(&self.config.get_stacker_db_file_path(), true).unwrap();
×
507

UNCOV
508
        let local_peer = match PeerDB::get_local_peer(peerdb.conn()) {
×
UNCOV
509
            Ok(local_peer) => local_peer,
×
UNCOV
510
            _ => panic!("Unable to retrieve local peer"),
×
511
        };
512

UNCOV
513
        let event_dispatcher = self.event_dispatcher.clone();
×
UNCOV
514
        let exit_at_block_height = self.config.burnchain.process_exit_at_block_height;
×
UNCOV
515
        let burnchain_db = burnchain
×
UNCOV
516
            .open_burnchain_db(false)
×
UNCOV
517
            .expect("Failed to open burnchain DB");
×
518

UNCOV
519
        let p2p_net = PeerNetwork::new(
×
UNCOV
520
            peerdb,
×
UNCOV
521
            atlasdb,
×
UNCOV
522
            stackerdbs,
×
UNCOV
523
            burnchain_db,
×
UNCOV
524
            local_peer,
×
UNCOV
525
            self.config.burnchain.peer_version,
×
UNCOV
526
            burnchain.clone(),
×
UNCOV
527
            view,
×
UNCOV
528
            self.config.connection_options.clone(),
×
UNCOV
529
            HashMap::new(),
×
UNCOV
530
            epochs,
×
531
        );
UNCOV
532
        let _join_handle = spawn_peer(
×
UNCOV
533
            self.config.is_mainnet(),
×
UNCOV
534
            self.config.burnchain.chain_id,
×
UNCOV
535
            p2p_net,
×
UNCOV
536
            &p2p_sock,
×
UNCOV
537
            &rpc_sock,
×
UNCOV
538
            self.config.get_burn_db_file_path(),
×
UNCOV
539
            self.config.get_chainstate_path_str(),
×
UNCOV
540
            burnchain.pox_constants,
×
UNCOV
541
            event_dispatcher,
×
UNCOV
542
            exit_at_block_height,
×
UNCOV
543
            Sha256Sum::from_hex(stx_genesis::GENESIS_CHAINSTATE_HASH).unwrap(),
×
544
            1000,
UNCOV
545
            self.config.clone(),
×
546
        );
547

UNCOV
548
        info!("Start HTTP server on: {}", &self.config.node.rpc_bind);
×
UNCOV
549
        info!("Start P2P server on: {}", &self.config.node.p2p_bind);
×
UNCOV
550
    }
×
551

NEW
552
    pub fn setup(&mut self, burnchain_controller: &mut BitcoinRegtestController) {
×
553
        // Register a new key
UNCOV
554
        let burnchain_tip = burnchain_controller.get_chain_tip();
×
UNCOV
555
        let (vrf_pk, _) = self
×
UNCOV
556
            .keychain
×
UNCOV
557
            .make_vrf_keypair(burnchain_tip.block_snapshot.block_height);
×
UNCOV
558
        let consensus_hash = burnchain_tip.block_snapshot.consensus_hash;
×
559

UNCOV
560
        let burnchain = self.config.get_burnchain();
×
561

UNCOV
562
        let sortdb = SortitionDB::open(
×
UNCOV
563
            &self.config.get_burn_db_file_path(),
×
564
            true,
UNCOV
565
            burnchain.pox_constants.clone(),
×
UNCOV
566
            Some(self.config.node.get_marf_opts()),
×
567
        )
UNCOV
568
        .expect("Error while opening sortition db");
×
569

UNCOV
570
        let epochs = SortitionDB::get_stacks_epochs(sortdb.conn())
×
UNCOV
571
            .expect("FATAL: failed to read sortition DB");
×
572

UNCOV
573
        Config::assert_valid_epoch_settings(&burnchain, &epochs);
×
574

UNCOV
575
        let cur_epoch =
×
UNCOV
576
            SortitionDB::get_stacks_epoch(sortdb.conn(), burnchain_tip.block_snapshot.block_height)
×
UNCOV
577
                .expect("FATAL: failed to read sortition DB")
×
UNCOV
578
                .expect("FATAL: no epoch defined");
×
579

UNCOV
580
        let key_reg_op = self.generate_leader_key_register_op(vrf_pk, consensus_hash);
×
UNCOV
581
        let mut op_signer = self.keychain.generate_op_signer();
×
UNCOV
582
        let key_txid = burnchain_controller
×
UNCOV
583
            .submit_operation(cur_epoch.epoch_id, key_reg_op, &mut op_signer)
×
UNCOV
584
            .expect("FATAL: failed to submit leader key register operation");
×
585

UNCOV
586
        self.leader_key_registers.insert(key_txid);
×
UNCOV
587
    }
×
588

589
    /// Process an state coming from the burnchain, by extracting the validated KeyRegisterOp
590
    /// and inspecting if a sortition was won.
UNCOV
591
    pub fn process_burnchain_state(
×
UNCOV
592
        &mut self,
×
UNCOV
593
        burnchain_tip: &BurnchainTip,
×
UNCOV
594
    ) -> (Option<BurnchainTip>, bool) {
×
UNCOV
595
        let mut new_key = None;
×
UNCOV
596
        let mut last_sortitioned_block = None;
×
UNCOV
597
        let mut won_sortition = false;
×
UNCOV
598
        let ops = &burnchain_tip.state_transition.accepted_ops;
×
599

UNCOV
600
        for op in ops.iter() {
×
UNCOV
601
            match op {
×
602
                BlockstackOperationType::LeaderKeyRegister(ref op) => {
×
UNCOV
603
                    if self.leader_key_registers.contains(&op.txid) {
×
604
                        // Registered key has been mined
×
605
                        new_key = Some(RegisteredKey {
×
606
                            vrf_public_key: op.public_key.clone(),
×
UNCOV
607
                            block_height: op.block_height,
×
UNCOV
608
                            op_vtxindex: op.vtxindex,
×
UNCOV
609
                            target_block_height: op.block_height - 1,
×
UNCOV
610
                            memo: op.memo.clone(),
×
UNCOV
611
                        });
×
UNCOV
612
                    }
×
613
                }
UNCOV
614
                BlockstackOperationType::LeaderBlockCommit(ref op) => {
×
UNCOV
615
                    if op.txid == burnchain_tip.block_snapshot.winning_block_txid {
×
UNCOV
616
                        last_sortitioned_block = Some(burnchain_tip.clone());
×
UNCOV
617
                        if self.block_commits.contains(&op.txid) {
×
UNCOV
618
                            won_sortition = true;
×
UNCOV
619
                        }
×
UNCOV
620
                    }
×
621
                }
UNCOV
622
                _ => {
×
UNCOV
623
                    // no-op, ops are not supported / produced at this point.
×
UNCOV
624
                }
×
625
            }
626
        }
627

628
        // Update the active key so we use the latest registered key.
UNCOV
629
        if new_key.is_some() {
×
UNCOV
630
            self.active_registered_key = new_key;
×
UNCOV
631
        }
×
632

633
        // Update last_sortitioned_block so we keep a reference to the latest
634
        // block including a sortition.
UNCOV
635
        if last_sortitioned_block.is_some() {
×
UNCOV
636
            self.last_sortitioned_block = last_sortitioned_block;
×
UNCOV
637
        }
×
638

639
        // Keep a pointer of the burnchain's chain tip.
UNCOV
640
        self.burnchain_tip = Some(burnchain_tip.clone());
×
641

UNCOV
642
        (self.last_sortitioned_block.clone(), won_sortition)
×
UNCOV
643
    }
×
644

645
    /// Prepares the node to run a tenure consisting in bootstraping the chain.
646
    ///
647
    /// Will internally call initiate_new_tenure().
UNCOV
648
    pub fn initiate_genesis_tenure(&mut self, burnchain_tip: &BurnchainTip) -> Option<Tenure> {
×
649
        // Set the `bootstraping_chain` flag, that will be unset once the
650
        // bootstraping tenure ran successfully (process_tenure).
UNCOV
651
        self.bootstraping_chain = true;
×
652

UNCOV
653
        self.last_sortitioned_block = Some(burnchain_tip.clone());
×
654

UNCOV
655
        self.initiate_new_tenure()
×
UNCOV
656
    }
×
657

658
    /// Constructs and returns an instance of Tenure, that can be run
659
    /// on an isolated thread and discarded or canceled without corrupting the
660
    /// chain state of the node.
UNCOV
661
    pub fn initiate_new_tenure(&mut self) -> Option<Tenure> {
×
662
        // Get the latest registered key
UNCOV
663
        let registered_key = match &self.active_registered_key {
×
664
            None => {
665
                // We're continuously registering new keys, as such, this branch
666
                // should be unreachable.
UNCOV
667
                unreachable!()
×
668
            }
UNCOV
669
            Some(ref key) => key,
×
670
        };
671

UNCOV
672
        let burnchain = self.config.get_burnchain();
×
UNCOV
673
        let sortdb = SortitionDB::open(
×
UNCOV
674
            &self.config.get_burn_db_file_path(),
×
675
            true,
UNCOV
676
            burnchain.pox_constants,
×
UNCOV
677
            Some(self.config.node.get_marf_opts()),
×
678
        )
UNCOV
679
        .expect("Error while opening sortition db");
×
UNCOV
680
        let tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
×
UNCOV
681
            .expect("FATAL: failed to query canonical burn chain tip");
×
682

683
        // Generates a proof out of the sortition hash provided in the params.
UNCOV
684
        let Some(vrf_proof) = self.keychain.generate_proof(
×
UNCOV
685
            registered_key.target_block_height,
×
UNCOV
686
            tip.sortition_hash.as_bytes(),
×
UNCOV
687
        ) else {
×
UNCOV
688
            warn!("Failed to generate VRF proof, will be unable to initiate new tenure");
×
UNCOV
689
            return None;
×
690
        };
691

692
        // Generates a new secret key for signing the trail of microblocks
693
        // of the upcoming tenure.
UNCOV
694
        let microblock_secret_key = self.keychain.get_microblock_key(tip.block_height);
×
695

696
        // Get the stack's chain tip
UNCOV
697
        let chain_tip = match self.bootstraping_chain {
×
UNCOV
698
            true => ChainTip::genesis(&BurnchainHeaderHash::zero(), 0, 0),
×
UNCOV
699
            false => match &self.chain_tip {
×
UNCOV
700
                Some(chain_tip) => chain_tip.clone(),
×
UNCOV
701
                None => unreachable!(),
×
702
            },
703
        };
704

UNCOV
705
        let estimator = self
×
UNCOV
706
            .config
×
UNCOV
707
            .make_cost_estimator()
×
UNCOV
708
            .unwrap_or_else(|| Box::new(UnitEstimator));
×
UNCOV
709
        let metric = self
×
UNCOV
710
            .config
×
UNCOV
711
            .make_cost_metric()
×
712
            .unwrap_or_else(|| Box::new(UnitMetric));
×
713

UNCOV
714
        let mem_pool = MemPoolDB::open(
×
UNCOV
715
            self.config.is_mainnet(),
×
UNCOV
716
            self.config.burnchain.chain_id,
×
UNCOV
717
            &self.chain_state.root_path,
×
UNCOV
718
            estimator,
×
UNCOV
719
            metric,
×
720
        )
UNCOV
721
        .expect("FATAL: failed to open mempool");
×
722

723
        // Construct the coinbase transaction - 1st txn that should be handled and included in
724
        // the upcoming tenure.
UNCOV
725
        let coinbase_tx = self.generate_coinbase_tx(self.config.is_mainnet());
×
726

UNCOV
727
        let burn_fee_cap = self.config.burnchain.burn_fee_cap;
×
728

UNCOV
729
        let block_to_build_upon = match &self.last_sortitioned_block {
×
UNCOV
730
            None => unreachable!(),
×
UNCOV
731
            Some(block) => block.clone(),
×
732
        };
733

734
        // Construct the upcoming tenure
UNCOV
735
        let tenure = Tenure::new(
×
UNCOV
736
            chain_tip,
×
UNCOV
737
            coinbase_tx,
×
UNCOV
738
            self.config.clone(),
×
UNCOV
739
            mem_pool,
×
UNCOV
740
            microblock_secret_key,
×
UNCOV
741
            block_to_build_upon,
×
UNCOV
742
            vrf_proof,
×
UNCOV
743
            burn_fee_cap,
×
744
        );
745

746
        Some(tenure)
×
UNCOV
747
    }
×
748

UNCOV
749
    pub fn commit_artifacts(
×
UNCOV
750
        &mut self,
×
UNCOV
751
        anchored_block_from_ongoing_tenure: &StacksBlock,
×
UNCOV
752
        burnchain_tip: &BurnchainTip,
×
NEW
753
        burnchain_controller: &mut BitcoinRegtestController,
×
UNCOV
754
        burn_fee: u64,
×
UNCOV
755
    ) {
×
UNCOV
756
        if self.active_registered_key.is_some() {
×
UNCOV
757
            let registered_key = self.active_registered_key.clone().unwrap();
×
758

UNCOV
759
            let Some(vrf_proof) = self.keychain.generate_proof(
×
UNCOV
760
                registered_key.target_block_height,
×
UNCOV
761
                burnchain_tip.block_snapshot.sortition_hash.as_bytes(),
×
UNCOV
762
            ) else {
×
UNCOV
763
                warn!("Failed to generate VRF proof, will be unable to mine commits");
×
UNCOV
764
                return;
×
765
            };
766

UNCOV
767
            let op = self.generate_block_commit_op(
×
UNCOV
768
                anchored_block_from_ongoing_tenure.header.block_hash(),
×
UNCOV
769
                burn_fee,
×
UNCOV
770
                &registered_key,
×
UNCOV
771
                burnchain_tip,
×
UNCOV
772
                VRFSeed::from_proof(&vrf_proof),
×
773
            );
774

UNCOV
775
            let burnchain = self.config.get_burnchain();
×
UNCOV
776
            let sortdb = SortitionDB::open(
×
UNCOV
777
                &self.config.get_burn_db_file_path(),
×
778
                true,
UNCOV
779
                burnchain.pox_constants,
×
780
                Some(self.config.node.get_marf_opts()),
×
781
            )
UNCOV
782
            .expect("Error while opening sortition db");
×
783

UNCOV
784
            let cur_epoch = SortitionDB::get_stacks_epoch(
×
UNCOV
785
                sortdb.conn(),
×
UNCOV
786
                burnchain_tip.block_snapshot.block_height,
×
787
            )
UNCOV
788
            .expect("FATAL: failed to read sortition DB")
×
UNCOV
789
            .expect("FATAL: no epoch defined");
×
790

UNCOV
791
            let mut op_signer = self.keychain.generate_op_signer();
×
UNCOV
792
            let txid = burnchain_controller
×
UNCOV
793
                .submit_operation(cur_epoch.epoch_id, op, &mut op_signer)
×
UNCOV
794
                .expect("FATAL: failed to submit block-commit");
×
795

UNCOV
796
            self.block_commits.insert(txid);
×
797
        } else {
UNCOV
798
            warn!("No leader key active!");
×
799
        }
UNCOV
800
    }
×
801

802
    /// Process artifacts from the tenure.
803
    /// At this point, we're modifying the chainstate, and merging the artifacts from the previous tenure.
UNCOV
804
    pub fn process_tenure(
×
805
        &mut self,
×
UNCOV
806
        anchored_block: &StacksBlock,
×
UNCOV
807
        consensus_hash: &ConsensusHash,
×
UNCOV
808
        microblocks: Vec<StacksMicroblock>,
×
809
        db: &mut SortitionDB,
×
UNCOV
810
        atlas_db: &mut AtlasDB,
×
811
    ) -> ChainTip {
×
UNCOV
812
        let _parent_consensus_hash = {
×
813
            // look up parent consensus hash
UNCOV
814
            let ic = db.index_conn();
×
UNCOV
815
            let parent_consensus_hash = StacksChainState::get_parent_consensus_hash(
×
UNCOV
816
                &ic,
×
UNCOV
817
                &anchored_block.header.parent_block,
×
UNCOV
818
                consensus_hash,
×
819
            )
UNCOV
820
            .unwrap_or_else(|_| {
×
UNCOV
821
                panic!(
×
822
                    "BUG: could not query chainstate to find parent consensus hash of {consensus_hash}/{}",
UNCOV
823
                    &anchored_block.block_hash()
×
824
                )
825
            })
UNCOV
826
            .unwrap_or_else(|| {
×
UNCOV
827
                panic!(
×
828
                    "BUG: no such parent of block {consensus_hash}/{}",
829
                    &anchored_block.block_hash()
×
830
                )
831
            });
832

833
            // Preprocess the anchored block
UNCOV
834
            self.chain_state
×
835
                .preprocess_anchored_block(
×
836
                    &ic,
×
837
                    consensus_hash,
×
UNCOV
838
                    anchored_block,
×
839
                    &parent_consensus_hash,
×
840
                    0,
841
                )
UNCOV
842
                .unwrap();
×
843

844
            // Preprocess the microblocks
UNCOV
845
            for microblock in microblocks.iter() {
×
UNCOV
846
                let res = self
×
UNCOV
847
                    .chain_state
×
UNCOV
848
                    .preprocess_streamed_microblock(
×
UNCOV
849
                        consensus_hash,
×
UNCOV
850
                        &anchored_block.block_hash(),
×
UNCOV
851
                        microblock,
×
852
                    )
UNCOV
853
                    .unwrap();
×
UNCOV
854
                if !res {
×
UNCOV
855
                    warn!(
×
856
                        "Unhandled error while pre-processing microblock {}",
UNCOV
857
                        microblock.header.block_hash()
×
858
                    );
UNCOV
859
                }
×
860
            }
861

UNCOV
862
            parent_consensus_hash
×
863
        };
864

UNCOV
865
        let atlas_config = Self::make_atlas_config();
×
866
        let mut processed_blocks = vec![];
×
867
        loop {
868
            let mut process_blocks_at_tip = {
×
UNCOV
869
                let tx = db.tx_begin_at_tip();
×
870
                self.chain_state
×
UNCOV
871
                    .process_blocks(tx, 1, Some(&self.event_dispatcher))
×
872
            };
UNCOV
873
            match process_blocks_at_tip {
×
UNCOV
874
                Err(e) => panic!("Error while processing block - {e:?}"),
×
UNCOV
875
                Ok(ref mut blocks) => {
×
UNCOV
876
                    if blocks.is_empty() {
×
877
                        break;
×
878
                    } else {
UNCOV
879
                        for block in blocks.iter() {
×
880
                            if let (Some(epoch_receipt), _) = block {
×
UNCOV
881
                                let attachments_instances =
×
UNCOV
882
                                    self.get_attachment_instances(epoch_receipt, &atlas_config);
×
UNCOV
883
                                if !attachments_instances.is_empty() {
×
UNCOV
884
                                    for new_attachment in attachments_instances.into_iter() {
×
UNCOV
885
                                        if let Err(e) =
×
UNCOV
886
                                            atlas_db.queue_attachment_instance(&new_attachment)
×
887
                                        {
UNCOV
888
                                            warn!(
×
889
                                                "Atlas: Error writing attachment instance to DB";
890
                                                "err" => ?e,
891
                                                "index_block_hash" => %new_attachment.index_block_hash,
892
                                                "contract_id" => %new_attachment.contract_id,
893
                                                "attachment_index" => %new_attachment.attachment_index,
894
                                            );
UNCOV
895
                                        }
×
896
                                    }
UNCOV
897
                                }
×
UNCOV
898
                            }
×
899
                        }
900

UNCOV
901
                        processed_blocks.append(blocks);
×
902
                    }
903
                }
904
            }
905
        }
906

907
        // todo(ludo): yikes but good enough in the context of helium:
908
        // we only expect 1 block.
UNCOV
909
        let processed_block = processed_blocks[0].clone().0.unwrap();
×
910

911
        let mut cost_estimator = self.config.make_cost_estimator();
×
UNCOV
912
        let mut fee_estimator = self.config.make_fee_estimator();
×
913

UNCOV
914
        let stacks_epoch =
×
915
            SortitionDB::get_stacks_epoch_by_epoch_id(db.conn(), &processed_block.evaluated_epoch)
×
UNCOV
916
                .expect("FATAL: could not query sortition DB for epochs")
×
UNCOV
917
                .expect("Could not find a stacks epoch.");
×
UNCOV
918
        if let Some(estimator) = cost_estimator.as_mut() {
×
UNCOV
919
            estimator.notify_block(
×
UNCOV
920
                &processed_block.tx_receipts,
×
UNCOV
921
                &stacks_epoch.block_limit,
×
UNCOV
922
                &stacks_epoch.epoch_id,
×
UNCOV
923
            );
×
UNCOV
924
        }
×
925

UNCOV
926
        if let Some(estimator) = fee_estimator.as_mut() {
×
UNCOV
927
            if let Err(e) = estimator.notify_block(&processed_block, &stacks_epoch.block_limit) {
×
UNCOV
928
                warn!("FeeEstimator failed to process block receipt";
×
UNCOV
929
                      "stacks_block_hash" => %processed_block.header.anchored_header.block_hash(),
×
930
                      "stacks_height" => %processed_block.header.stacks_block_height,
931
                      "error" => %e);
UNCOV
932
            }
×
UNCOV
933
        }
×
934

935
        // Handle events
UNCOV
936
        let receipts = processed_block.tx_receipts;
×
UNCOV
937
        let metadata = processed_block.header;
×
UNCOV
938
        let block: StacksBlock = {
×
UNCOV
939
            let block_path = StacksChainState::get_block_path(
×
UNCOV
940
                &self.chain_state.blocks_path,
×
UNCOV
941
                &metadata.consensus_hash,
×
UNCOV
942
                &metadata.anchored_header.block_hash(),
×
943
            )
UNCOV
944
            .unwrap();
×
UNCOV
945
            StacksChainState::consensus_load(&block_path).unwrap()
×
946
        };
947

UNCOV
948
        let chain_tip = ChainTip {
×
UNCOV
949
            metadata,
×
UNCOV
950
            block,
×
UNCOV
951
            receipts,
×
UNCOV
952
        };
×
UNCOV
953
        self.chain_tip = Some(chain_tip.clone());
×
954

955
        // Unset the `bootstraping_chain` flag.
956
        if self.bootstraping_chain {
×
957
            self.bootstraping_chain = false;
×
958
        }
×
959

960
        chain_tip
×
961
    }
×
962

963
    pub fn get_attachment_instances(
×
964
        &self,
×
965
        epoch_receipt: &StacksEpochReceipt,
×
966
        atlas_config: &AtlasConfig,
×
967
    ) -> HashSet<AttachmentInstance> {
×
968
        let mut attachments_instances = HashSet::new();
×
UNCOV
969
        for receipt in epoch_receipt.tx_receipts.iter() {
×
970
            if let TransactionOrigin::Stacks(ref transaction) = receipt.transaction {
×
971
                if let TransactionPayload::ContractCall(ref contract_call) = transaction.payload {
×
972
                    let contract_id = contract_call.to_clarity_contract_id();
×
973
                    if atlas_config.contracts.contains(&contract_id) {
×
UNCOV
974
                        for event in receipt.events.iter() {
×
UNCOV
975
                            if let StacksTransactionEvent::SmartContractEvent(ref event_data) =
×
UNCOV
976
                                event
×
977
                            {
UNCOV
978
                                let res = AttachmentInstance::try_new_from_value(
×
UNCOV
979
                                    &event_data.value,
×
UNCOV
980
                                    &contract_id,
×
UNCOV
981
                                    epoch_receipt.header.index_block_hash(),
×
UNCOV
982
                                    epoch_receipt.header.stacks_block_height,
×
UNCOV
983
                                    receipt.transaction.txid(),
×
UNCOV
984
                                    self.chain_tip
×
UNCOV
985
                                        .as_ref()
×
UNCOV
986
                                        .map(|t| t.metadata.stacks_block_height),
×
987
                                );
UNCOV
988
                                if let Some(attachment_instance) = res {
×
UNCOV
989
                                    attachments_instances.insert(attachment_instance);
×
UNCOV
990
                                }
×
UNCOV
991
                            }
×
992
                        }
UNCOV
993
                    }
×
UNCOV
994
                }
×
UNCOV
995
            }
×
996
        }
UNCOV
997
        attachments_instances
×
UNCOV
998
    }
×
999

1000
    /// Constructs and returns a LeaderKeyRegisterOp out of the provided params
UNCOV
1001
    fn generate_leader_key_register_op(
×
UNCOV
1002
        &mut self,
×
UNCOV
1003
        vrf_public_key: VRFPublicKey,
×
UNCOV
1004
        consensus_hash: ConsensusHash,
×
UNCOV
1005
    ) -> BlockstackOperationType {
×
UNCOV
1006
        let mut txid_bytes = [0u8; 32];
×
UNCOV
1007
        let mut rng = rand::thread_rng();
×
UNCOV
1008
        rng.fill_bytes(&mut txid_bytes);
×
UNCOV
1009
        let txid = Txid(txid_bytes);
×
1010

UNCOV
1011
        BlockstackOperationType::LeaderKeyRegister(LeaderKeyRegisterOp {
×
UNCOV
1012
            public_key: vrf_public_key,
×
UNCOV
1013
            memo: vec![],
×
UNCOV
1014
            consensus_hash,
×
UNCOV
1015
            vtxindex: 1,
×
UNCOV
1016
            txid,
×
UNCOV
1017
            block_height: 0,
×
UNCOV
1018
            burn_header_hash: BurnchainHeaderHash::zero(),
×
UNCOV
1019
        })
×
UNCOV
1020
    }
×
1021

1022
    /// Constructs and returns a LeaderBlockCommitOp out of the provided params
UNCOV
1023
    fn generate_block_commit_op(
×
UNCOV
1024
        &mut self,
×
UNCOV
1025
        block_header_hash: BlockHeaderHash,
×
UNCOV
1026
        burn_fee: u64,
×
UNCOV
1027
        key: &RegisteredKey,
×
UNCOV
1028
        burnchain_tip: &BurnchainTip,
×
UNCOV
1029
        vrf_seed: VRFSeed,
×
UNCOV
1030
    ) -> BlockstackOperationType {
×
UNCOV
1031
        let winning_tx_vtindex = burnchain_tip.get_winning_tx_index().unwrap_or(0);
×
1032

UNCOV
1033
        let (parent_block_ptr, parent_vtxindex) = match self.bootstraping_chain {
×
UNCOV
1034
            true => (0, 0), // parent_block_ptr and parent_vtxindex should both be 0 on block #1
×
UNCOV
1035
            false => (
×
UNCOV
1036
                burnchain_tip.block_snapshot.block_height as u32,
×
UNCOV
1037
                winning_tx_vtindex as u16,
×
UNCOV
1038
            ),
×
1039
        };
1040

UNCOV
1041
        let burnchain = self.config.get_burnchain();
×
UNCOV
1042
        let commit_outs = if burnchain_tip.block_snapshot.block_height + 1
×
UNCOV
1043
            < burnchain.pox_constants.sunset_end
×
UNCOV
1044
            && !burnchain.is_in_prepare_phase(burnchain_tip.block_snapshot.block_height + 1)
×
1045
        {
UNCOV
1046
            RewardSetInfo::into_commit_outs(None, self.config.is_mainnet())
×
1047
        } else {
UNCOV
1048
            vec![PoxAddress::standard_burn_address(self.config.is_mainnet())]
×
1049
        };
1050

UNCOV
1051
        let burn_parent_modulus =
×
UNCOV
1052
            (burnchain_tip.block_snapshot.block_height % BURN_BLOCK_MINED_AT_MODULUS) as u8;
×
1053

UNCOV
1054
        let mut txid_bytes = [0u8; 32];
×
UNCOV
1055
        let mut rng = rand::thread_rng();
×
UNCOV
1056
        rng.fill_bytes(&mut txid_bytes);
×
UNCOV
1057
        let txid = Txid(txid_bytes);
×
1058

UNCOV
1059
        BlockstackOperationType::LeaderBlockCommit(LeaderBlockCommitOp {
×
UNCOV
1060
            treatment: vec![],
×
UNCOV
1061
            sunset_burn: 0,
×
UNCOV
1062
            block_header_hash,
×
UNCOV
1063
            burn_fee,
×
UNCOV
1064
            input: (Txid([0; 32]), 0),
×
UNCOV
1065
            apparent_sender: self.keychain.get_burnchain_signer(),
×
UNCOV
1066
            key_block_ptr: key.block_height as u32,
×
UNCOV
1067
            key_vtxindex: key.op_vtxindex as u16,
×
UNCOV
1068
            memo: vec![STACKS_EPOCH_2_1_MARKER],
×
1069
            new_seed: vrf_seed,
×
UNCOV
1070
            parent_block_ptr,
×
UNCOV
1071
            parent_vtxindex,
×
UNCOV
1072
            vtxindex: 2,
×
UNCOV
1073
            txid,
×
UNCOV
1074
            commit_outs,
×
UNCOV
1075
            block_height: 0,
×
UNCOV
1076
            burn_header_hash: BurnchainHeaderHash::zero(),
×
UNCOV
1077
            burn_parent_modulus,
×
UNCOV
1078
        })
×
UNCOV
1079
    }
×
1080

1081
    // Constructs a coinbase transaction
UNCOV
1082
    fn generate_coinbase_tx(&mut self, is_mainnet: bool) -> StacksTransaction {
×
UNCOV
1083
        let mut tx_auth = self.keychain.get_transaction_auth().unwrap();
×
UNCOV
1084
        tx_auth.set_origin_nonce(self.nonce);
×
1085

UNCOV
1086
        let version = if is_mainnet {
×
UNCOV
1087
            TransactionVersion::Mainnet
×
1088
        } else {
UNCOV
1089
            TransactionVersion::Testnet
×
1090
        };
UNCOV
1091
        let mut tx = StacksTransaction::new(
×
UNCOV
1092
            version,
×
UNCOV
1093
            tx_auth,
×
UNCOV
1094
            TransactionPayload::Coinbase(CoinbasePayload([0u8; 32]), None, None),
×
1095
        );
UNCOV
1096
        tx.chain_id = self.config.burnchain.chain_id;
×
UNCOV
1097
        tx.anchor_mode = TransactionAnchorMode::OnChainOnly;
×
UNCOV
1098
        let mut tx_signer = StacksTransactionSigner::new(&tx);
×
UNCOV
1099
        self.keychain.sign_as_origin(&mut tx_signer);
×
1100

1101
        // Increment nonce
UNCOV
1102
        self.nonce += 1;
×
1103

UNCOV
1104
        tx_signer.get_tx().unwrap()
×
UNCOV
1105
    }
×
1106
}
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