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

stacks-network / stacks-core / 26126558212-1

19 May 2026 09:31PM UTC coverage: 85.549% (-0.2%) from 85.712%
26126558212-1

Pull #7216

github

33e18e
web-flow
Merge 8ba69fcd0 into 545d2549c
Pull Request #7216: feat: add support for testnet4

7 of 39 new or added lines in 6 files covered. (17.95%)

5684 existing lines in 107 files now uncovered.

188276 of 220081 relevant lines covered (85.55%)

18112859.23 hits per line

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

87.33
/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::{BurnchainController, 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(
438✔
78
        first_burnchain_block_hash: &BurnchainHeaderHash,
438✔
79
        first_burnchain_block_height: u64,
438✔
80
        first_burnchain_block_timestamp: u64,
438✔
81
    ) -> ChainTip {
438✔
82
        ChainTip {
438✔
83
            metadata: StacksHeaderInfo::genesis(
438✔
84
                TrieHash([0u8; 32]),
438✔
85
                first_burnchain_block_hash,
438✔
86
                first_burnchain_block_height as u32,
438✔
87
                first_burnchain_block_timestamp,
438✔
88
            ),
438✔
89
            block: StacksBlock::genesis_block(),
438✔
90
            receipts: vec![],
438✔
91
        }
438✔
92
    }
438✔
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(
291✔
112
    use_test_chainstate_data: bool,
291✔
113
) -> Box<dyn Iterator<Item = ChainstateAccountLockup>> {
291✔
114
    Box::new(
291✔
115
        stx_genesis::GenesisData::new(use_test_chainstate_data)
291✔
116
            .read_lockups()
291✔
117
            .map(|item| ChainstateAccountLockup {
291✔
118
                address: item.address,
5,820✔
119
                amount: item.amount,
5,820✔
120
                block_height: item.block_height,
5,820✔
121
            }),
5,820✔
122
    )
123
}
291✔
124

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

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

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

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

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

206
        loop {
207
            let sortdb = match SortitionDB::open(
252✔
208
                &burn_db_path,
252✔
209
                false,
252✔
210
                pox_consts.clone(),
252✔
211
                Some(config.node.get_marf_opts()),
252✔
212
            ) {
252✔
213
                Ok(x) => x,
252✔
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
            };
220
            let (mut chainstate, _) = match StacksChainState::open(
252✔
221
                is_mainnet,
252✔
222
                chain_id,
252✔
223
                &stacks_chainstate_path,
252✔
224
                Some(config.node.get_marf_opts()),
252✔
225
            ) {
252✔
226
                Ok(x) => x,
252✔
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

234
            let estimator = Box::new(UnitEstimator);
252✔
235
            let metric = Box::new(UnitMetric);
252✔
236

237
            let mut mem_pool = match MemPoolDB::open(
252✔
238
                is_mainnet,
252✔
239
                chain_id,
252✔
240
                &stacks_chainstate_path,
252✔
241
                estimator,
252✔
242
                metric,
252✔
243
            ) {
252✔
244
                Ok(x) => x,
252✔
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

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

254
            let net_result = this
252✔
255
                .run(
252✔
256
                    &indexer,
252✔
257
                    &sortdb,
252✔
258
                    &mut chainstate,
252✔
259
                    &mut mem_pool,
252✔
260
                    None,
252✔
261
                    false,
262
                    false,
263
                    poll_timeout,
252✔
264
                    &handler_args,
252✔
265
                    config.node.txindex,
252✔
266
                )
267
                .unwrap();
252✔
268
            if net_result.has_transactions() {
252✔
269
                event_dispatcher.process_new_mempool_txs(net_result.transactions())
1✔
270
            }
241✔
271
            // Dispatch retrieved attachments, if any.
272
            if net_result.has_attachments() {
242✔
UNCOV
273
                event_dispatcher.process_new_attachments(&net_result.attachments);
×
274
            }
242✔
275
        }
276
    });
277
    server_thread
10✔
278
}
10✔
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,055✔
283
    if env::var("BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE") == Ok("1".to_string()) {
1,055✔
UNCOV
284
        true
×
285
    } else if let Some(use_test_genesis_chainstate) = config.node.use_test_genesis_chainstate {
1,055✔
UNCOV
286
        use_test_genesis_chainstate
×
287
    } else {
288
        USE_TEST_GENESIS_CHAINSTATE
1,055✔
289
    }
290
}
1,055✔
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 {
10✔
295
        let use_test_genesis_data = if config.burnchain.mode == "mocknet" {
10✔
296
            use_test_genesis_chainstate(&config)
10✔
297
        } else {
UNCOV
298
            USE_TEST_GENESIS_CHAINSTATE
×
299
        };
300

301
        let keychain = Keychain::default(config.node.seed.clone());
10✔
302

303
        let initial_balances = config
10✔
304
            .initial_balances
10✔
305
            .iter()
10✔
306
            .map(|e| (e.address.clone(), e.amount))
529✔
307
            .collect();
10✔
308
        let pox_constants = match config.burnchain.get_bitcoin_network() {
10✔
UNCOV
309
            (_, BitcoinNetworkType::Mainnet) => PoxConstants::mainnet_default(),
×
310
            (_, BitcoinNetworkType::Testnet) | (_, BitcoinNetworkType::Testnet4) => {
NEW
311
                PoxConstants::testnet_default()
×
312
            }
313
            (_, BitcoinNetworkType::Regtest) => PoxConstants::regtest_default(),
10✔
314
        };
315

316
        let mut boot_data = ChainStateBootData {
10✔
317
            initial_balances,
10✔
318
            first_burnchain_block_hash: BurnchainHeaderHash::zero(),
10✔
319
            first_burnchain_block_height: 0,
320
            first_burnchain_block_timestamp: 0,
321
            pox_constants,
10✔
322
            post_flight_callback: Some(boot_block_exec),
10✔
323
            get_bulk_initial_lockups: Some(Box::new(move || {
10✔
324
                get_account_lockups(use_test_genesis_data)
10✔
325
            })),
10✔
326
            get_bulk_initial_balances: Some(Box::new(move || {
10✔
327
                get_account_balances(use_test_genesis_data)
10✔
328
            })),
10✔
329
            get_bulk_initial_namespaces: Some(Box::new(move || {
10✔
330
                get_namespaces(use_test_genesis_data)
10✔
331
            })),
10✔
332
            get_bulk_initial_names: Some(Box::new(move || get_names(use_test_genesis_data))),
10✔
333
        };
334

335
        let chain_state_result = StacksChainState::open_and_exec(
10✔
336
            config.is_mainnet(),
10✔
337
            config.burnchain.chain_id,
10✔
338
            &config.get_chainstate_path_str(),
10✔
339
            Some(&mut boot_data),
10✔
340
            Some(config.node.get_marf_opts()),
10✔
341
        );
342

343
        let (chain_state, receipts) = match chain_state_result {
10✔
344
            Ok(res) => res,
10✔
UNCOV
345
            Err(err) => panic!(
×
346
                "Error while opening chain state at path {}: {err:?}",
UNCOV
347
                config.get_chainstate_path_str()
×
348
            ),
349
        };
350

351
        let estimator = Box::new(UnitEstimator);
10✔
352
        let metric = Box::new(UnitMetric);
10✔
353

354
        // avoid race to create condition on mempool db
355
        let _mem_pool = MemPoolDB::open(
10✔
356
            config.is_mainnet(),
10✔
357
            config.burnchain.chain_id,
10✔
358
            &chain_state.root_path,
10✔
359
            estimator,
10✔
360
            metric,
10✔
361
        )
362
        .expect("FATAL: failed to initiate mempool");
10✔
363

364
        let mut event_dispatcher = EventDispatcher::new_with_custom_queue_size(
10✔
365
            config.get_working_dir(),
10✔
366
            config.node.effective_event_dispatcher_queue_size(),
10✔
367
        );
368

369
        for observer in &config.events_observers {
10✔
UNCOV
370
            event_dispatcher.register_observer(observer);
×
UNCOV
371
        }
×
372

373
        let burnchain_config = config.get_burnchain();
10✔
374

375
        // instantiate DBs
376
        let _burnchain_db = BurnchainDB::connect(
10✔
377
            &burnchain_config.get_burnchaindb_path(),
10✔
378
            &burnchain_config,
10✔
379
            true,
380
        )
381
        .expect("FATAL: failed to connect to burnchain DB");
10✔
382

383
        run_loop::announce_boot_receipts(
10✔
384
            &mut event_dispatcher,
10✔
385
            &chain_state,
10✔
386
            &burnchain_config.pox_constants,
10✔
387
            &receipts,
10✔
388
        );
389

390
        Self {
10✔
391
            active_registered_key: None,
10✔
392
            bootstraping_chain: false,
10✔
393
            chain_state,
10✔
394
            chain_tip: None,
10✔
395
            keychain,
10✔
396
            last_sortitioned_block: None,
10✔
397
            config,
10✔
398
            burnchain_tip: None,
10✔
399
            nonce: 0,
10✔
400
            event_dispatcher,
10✔
401
            leader_key_registers: HashSet::new(),
10✔
402
            block_commits: HashSet::new(),
10✔
403
        }
10✔
404
    }
10✔
405

406
    fn make_atlas_config() -> AtlasConfig {
98✔
407
        AtlasConfig::new(false)
98✔
408
    }
98✔
409

410
    pub fn make_atlas_db(&self) -> AtlasDB {
54✔
411
        AtlasDB::connect(
54✔
412
            Self::make_atlas_config(),
54✔
413
            &self.config.get_atlas_db_file_path(),
54✔
414
            true,
415
        )
416
        .unwrap()
54✔
417
    }
54✔
418

419
    // This function is used for helium and mocknet.
420
    pub fn spawn_peer_server(&mut self) {
10✔
421
        // we can call _open_ here rather than _connect_, since connect is first called in
422
        //   make_genesis_block
423
        let burnchain = self.config.get_burnchain();
10✔
424
        let sortdb = SortitionDB::open(
10✔
425
            &self.config.get_burn_db_file_path(),
10✔
426
            true,
427
            burnchain.pox_constants.clone(),
10✔
428
            Some(self.config.node.get_marf_opts()),
10✔
429
        )
430
        .expect("Error while instantiating burnchain db");
10✔
431

432
        let epochs_vec = SortitionDB::get_stacks_epochs(sortdb.conn())
10✔
433
            .expect("Error while loading stacks epochs");
10✔
434
        let epochs = EpochList::new(&epochs_vec);
10✔
435

436
        Config::assert_valid_epoch_settings(&burnchain, &epochs);
10✔
437

438
        let view = {
10✔
439
            let sortition_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
10✔
440
                .expect("Failed to get sortition tip");
10✔
441
            SortitionDB::get_burnchain_view(&sortdb.index_conn(), &burnchain, &sortition_tip)
10✔
442
                .unwrap()
10✔
443
        };
444

445
        // create a new peerdb
446
        let data_url = UrlString::try_from(self.config.node.data_url.to_string()).unwrap();
10✔
447

448
        let initial_neighbors = self.config.node.bootstrap_node.clone();
10✔
449

450
        println!("BOOTSTRAP WITH {initial_neighbors:?}");
10✔
451

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

478
        let mut peerdb = PeerDB::connect(
10✔
479
            &self.config.get_peer_db_file_path(),
10✔
480
            true,
481
            self.config.burnchain.chain_id,
10✔
482
            burnchain.network_id,
10✔
483
            Some(node_privkey),
10✔
484
            self.config.connection_options.private_key_lifetime,
10✔
485
            PeerAddress::from_socketaddr(&p2p_addr),
10✔
486
            p2p_sock.port(),
10✔
487
            data_url,
10✔
488
            &[],
10✔
489
            Some(&initial_neighbors),
10✔
490
            &[],
10✔
491
        )
492
        .unwrap();
10✔
493

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

511
        let stackerdbs =
10✔
512
            StackerDBs::connect(&self.config.get_stacker_db_file_path(), true).unwrap();
10✔
513

514
        let local_peer = match PeerDB::get_local_peer(peerdb.conn()) {
10✔
515
            Ok(local_peer) => local_peer,
10✔
UNCOV
516
            _ => panic!("Unable to retrieve local peer"),
×
517
        };
518

519
        let event_dispatcher = self.event_dispatcher.clone();
10✔
520
        let exit_at_block_height = self.config.burnchain.process_exit_at_block_height;
10✔
521
        let burnchain_db = burnchain
10✔
522
            .open_burnchain_db(false)
10✔
523
            .expect("Failed to open burnchain DB");
10✔
524

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

554
        info!("Start HTTP server on: {}", &self.config.node.rpc_bind);
10✔
555
        info!("Start P2P server on: {}", &self.config.node.p2p_bind);
10✔
556
    }
10✔
557

558
    pub fn setup(&mut self, burnchain_controller: &mut Box<dyn BurnchainController>) {
10✔
559
        // Register a new key
560
        let burnchain_tip = burnchain_controller.get_chain_tip();
10✔
561
        let (vrf_pk, _) = self
10✔
562
            .keychain
10✔
563
            .make_vrf_keypair(burnchain_tip.block_snapshot.block_height);
10✔
564
        let consensus_hash = burnchain_tip.block_snapshot.consensus_hash;
10✔
565

566
        let burnchain = self.config.get_burnchain();
10✔
567

568
        let sortdb = SortitionDB::open(
10✔
569
            &self.config.get_burn_db_file_path(),
10✔
570
            true,
571
            burnchain.pox_constants.clone(),
10✔
572
            Some(self.config.node.get_marf_opts()),
10✔
573
        )
574
        .expect("Error while opening sortition db");
10✔
575

576
        let epochs = SortitionDB::get_stacks_epochs(sortdb.conn())
10✔
577
            .expect("FATAL: failed to read sortition DB");
10✔
578

579
        Config::assert_valid_epoch_settings(&burnchain, &epochs);
10✔
580

581
        let cur_epoch =
10✔
582
            SortitionDB::get_stacks_epoch(sortdb.conn(), burnchain_tip.block_snapshot.block_height)
10✔
583
                .expect("FATAL: failed to read sortition DB")
10✔
584
                .expect("FATAL: no epoch defined");
10✔
585

586
        let key_reg_op = self.generate_leader_key_register_op(vrf_pk, consensus_hash);
10✔
587
        let mut op_signer = self.keychain.generate_op_signer();
10✔
588
        let key_txid = burnchain_controller
10✔
589
            .submit_operation(cur_epoch.epoch_id, key_reg_op, &mut op_signer)
10✔
590
            .expect("FATAL: failed to submit leader key register operation");
10✔
591

592
        self.leader_key_registers.insert(key_txid);
10✔
593
    }
10✔
594

595
    /// Process an state coming from the burnchain, by extracting the validated KeyRegisterOp
596
    /// and inspecting if a sortition was won.
597
    pub fn process_burnchain_state(
64✔
598
        &mut self,
64✔
599
        burnchain_tip: &BurnchainTip,
64✔
600
    ) -> (Option<BurnchainTip>, bool) {
64✔
601
        let mut new_key = None;
64✔
602
        let mut last_sortitioned_block = None;
64✔
603
        let mut won_sortition = false;
64✔
604
        let ops = &burnchain_tip.state_transition.accepted_ops;
64✔
605

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

634
        // Update the active key so we use the latest registered key.
635
        if new_key.is_some() {
64✔
636
            self.active_registered_key = new_key;
10✔
637
        }
54✔
638

639
        // Update last_sortitioned_block so we keep a reference to the latest
640
        // block including a sortition.
641
        if last_sortitioned_block.is_some() {
64✔
642
            self.last_sortitioned_block = last_sortitioned_block;
44✔
643
        }
44✔
644

645
        // Keep a pointer of the burnchain's chain tip.
646
        self.burnchain_tip = Some(burnchain_tip.clone());
64✔
647

648
        (self.last_sortitioned_block.clone(), won_sortition)
64✔
649
    }
64✔
650

651
    /// Prepares the node to run a tenure consisting in bootstraping the chain.
652
    ///
653
    /// Will internally call initiate_new_tenure().
654
    pub fn initiate_genesis_tenure(&mut self, burnchain_tip: &BurnchainTip) -> Option<Tenure> {
10✔
655
        // Set the `bootstraping_chain` flag, that will be unset once the
656
        // bootstraping tenure ran successfully (process_tenure).
657
        self.bootstraping_chain = true;
10✔
658

659
        self.last_sortitioned_block = Some(burnchain_tip.clone());
10✔
660

661
        self.initiate_new_tenure()
10✔
662
    }
10✔
663

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

678
        let burnchain = self.config.get_burnchain();
54✔
679
        let sortdb = SortitionDB::open(
54✔
680
            &self.config.get_burn_db_file_path(),
54✔
681
            true,
682
            burnchain.pox_constants,
54✔
683
            Some(self.config.node.get_marf_opts()),
54✔
684
        )
685
        .expect("Error while opening sortition db");
54✔
686
        let tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
54✔
687
            .expect("FATAL: failed to query canonical burn chain tip");
54✔
688

689
        // Generates a proof out of the sortition hash provided in the params.
690
        let Some(vrf_proof) = self.keychain.generate_proof(
54✔
691
            registered_key.target_block_height,
54✔
692
            tip.sortition_hash.as_bytes(),
54✔
693
        ) else {
54✔
UNCOV
694
            warn!("Failed to generate VRF proof, will be unable to initiate new tenure");
×
UNCOV
695
            return None;
×
696
        };
697

698
        // Generates a new secret key for signing the trail of microblocks
699
        // of the upcoming tenure.
700
        let microblock_secret_key = self.keychain.get_microblock_key(tip.block_height);
54✔
701

702
        // Get the stack's chain tip
703
        let chain_tip = match self.bootstraping_chain {
54✔
704
            true => ChainTip::genesis(&BurnchainHeaderHash::zero(), 0, 0),
10✔
705
            false => match &self.chain_tip {
44✔
706
                Some(chain_tip) => chain_tip.clone(),
44✔
UNCOV
707
                None => unreachable!(),
×
708
            },
709
        };
710

711
        let estimator = self
54✔
712
            .config
54✔
713
            .make_cost_estimator()
54✔
714
            .unwrap_or_else(|| Box::new(UnitEstimator));
54✔
715
        let metric = self
54✔
716
            .config
54✔
717
            .make_cost_metric()
54✔
718
            .unwrap_or_else(|| Box::new(UnitMetric));
54✔
719

720
        let mem_pool = MemPoolDB::open(
54✔
721
            self.config.is_mainnet(),
54✔
722
            self.config.burnchain.chain_id,
54✔
723
            &self.chain_state.root_path,
54✔
724
            estimator,
54✔
725
            metric,
54✔
726
        )
727
        .expect("FATAL: failed to open mempool");
54✔
728

729
        // Construct the coinbase transaction - 1st txn that should be handled and included in
730
        // the upcoming tenure.
731
        let coinbase_tx = self.generate_coinbase_tx(self.config.is_mainnet());
54✔
732

733
        let burn_fee_cap = self.config.burnchain.burn_fee_cap;
54✔
734

735
        let block_to_build_upon = match &self.last_sortitioned_block {
54✔
UNCOV
736
            None => unreachable!(),
×
737
            Some(block) => block.clone(),
54✔
738
        };
739

740
        // Construct the upcoming tenure
741
        let tenure = Tenure::new(
54✔
742
            chain_tip,
54✔
743
            coinbase_tx,
54✔
744
            self.config.clone(),
54✔
745
            mem_pool,
54✔
746
            microblock_secret_key,
54✔
747
            block_to_build_upon,
54✔
748
            vrf_proof,
54✔
749
            burn_fee_cap,
54✔
750
        );
751

752
        Some(tenure)
54✔
753
    }
54✔
754

755
    pub fn commit_artifacts(
44✔
756
        &mut self,
44✔
757
        anchored_block_from_ongoing_tenure: &StacksBlock,
44✔
758
        burnchain_tip: &BurnchainTip,
44✔
759
        burnchain_controller: &mut Box<dyn BurnchainController>,
44✔
760
        burn_fee: u64,
44✔
761
    ) {
44✔
762
        if self.active_registered_key.is_some() {
44✔
763
            let registered_key = self.active_registered_key.clone().unwrap();
44✔
764

765
            let Some(vrf_proof) = self.keychain.generate_proof(
44✔
766
                registered_key.target_block_height,
44✔
767
                burnchain_tip.block_snapshot.sortition_hash.as_bytes(),
44✔
768
            ) else {
44✔
UNCOV
769
                warn!("Failed to generate VRF proof, will be unable to mine commits");
×
UNCOV
770
                return;
×
771
            };
772

773
            let op = self.generate_block_commit_op(
44✔
774
                anchored_block_from_ongoing_tenure.header.block_hash(),
44✔
775
                burn_fee,
44✔
776
                &registered_key,
44✔
777
                burnchain_tip,
44✔
778
                VRFSeed::from_proof(&vrf_proof),
44✔
779
            );
780

781
            let burnchain = self.config.get_burnchain();
44✔
782
            let sortdb = SortitionDB::open(
44✔
783
                &self.config.get_burn_db_file_path(),
44✔
784
                true,
785
                burnchain.pox_constants,
44✔
786
                Some(self.config.node.get_marf_opts()),
44✔
787
            )
788
            .expect("Error while opening sortition db");
44✔
789

790
            let cur_epoch = SortitionDB::get_stacks_epoch(
44✔
791
                sortdb.conn(),
44✔
792
                burnchain_tip.block_snapshot.block_height,
44✔
793
            )
794
            .expect("FATAL: failed to read sortition DB")
44✔
795
            .expect("FATAL: no epoch defined");
44✔
796

797
            let mut op_signer = self.keychain.generate_op_signer();
44✔
798
            let txid = burnchain_controller
44✔
799
                .submit_operation(cur_epoch.epoch_id, op, &mut op_signer)
44✔
800
                .expect("FATAL: failed to submit block-commit");
44✔
801

802
            self.block_commits.insert(txid);
44✔
803
        } else {
UNCOV
804
            warn!("No leader key active!");
×
805
        }
806
    }
44✔
807

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

839
            // Preprocess the anchored block
840
            self.chain_state
44✔
841
                .preprocess_anchored_block(
44✔
842
                    &ic,
44✔
843
                    consensus_hash,
44✔
844
                    anchored_block,
44✔
845
                    &parent_consensus_hash,
44✔
846
                    0,
847
                )
848
                .unwrap();
44✔
849

850
            // Preprocess the microblocks
851
            for microblock in microblocks.iter() {
44✔
UNCOV
852
                let res = self
×
UNCOV
853
                    .chain_state
×
UNCOV
854
                    .preprocess_streamed_microblock(
×
UNCOV
855
                        consensus_hash,
×
UNCOV
856
                        &anchored_block.block_hash(),
×
UNCOV
857
                        microblock,
×
858
                    )
UNCOV
859
                    .unwrap();
×
UNCOV
860
                if !res {
×
UNCOV
861
                    warn!(
×
862
                        "Unhandled error while pre-processing microblock {}",
UNCOV
863
                        microblock.header.block_hash()
×
864
                    );
UNCOV
865
                }
×
866
            }
867

868
            parent_consensus_hash
44✔
869
        };
870

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

907
                        processed_blocks.append(blocks);
44✔
908
                    }
909
                }
910
            }
911
        }
912

913
        // todo(ludo): yikes but good enough in the context of helium:
914
        // we only expect 1 block.
915
        let processed_block = processed_blocks[0].clone().0.unwrap();
44✔
916

917
        let mut cost_estimator = self.config.make_cost_estimator();
44✔
918
        let mut fee_estimator = self.config.make_fee_estimator();
44✔
919

920
        let stacks_epoch =
44✔
921
            SortitionDB::get_stacks_epoch_by_epoch_id(db.conn(), &processed_block.evaluated_epoch)
44✔
922
                .expect("FATAL: could not query sortition DB for epochs")
44✔
923
                .expect("Could not find a stacks epoch.");
44✔
924
        if let Some(estimator) = cost_estimator.as_mut() {
44✔
925
            estimator.notify_block(
44✔
926
                &processed_block.tx_receipts,
44✔
927
                &stacks_epoch.block_limit,
44✔
928
                &stacks_epoch.epoch_id,
44✔
929
            );
44✔
930
        }
44✔
931

932
        if let Some(estimator) = fee_estimator.as_mut() {
44✔
933
            if let Err(e) = estimator.notify_block(&processed_block, &stacks_epoch.block_limit) {
44✔
UNCOV
934
                warn!("FeeEstimator failed to process block receipt";
×
UNCOV
935
                      "stacks_block_hash" => %processed_block.header.anchored_header.block_hash(),
×
936
                      "stacks_height" => %processed_block.header.stacks_block_height,
937
                      "error" => %e);
938
            }
44✔
UNCOV
939
        }
×
940

941
        // Handle events
942
        let receipts = processed_block.tx_receipts;
44✔
943
        let metadata = processed_block.header;
44✔
944
        let block: StacksBlock = {
44✔
945
            let block_path = StacksChainState::get_block_path(
44✔
946
                &self.chain_state.blocks_path,
44✔
947
                &metadata.consensus_hash,
44✔
948
                &metadata.anchored_header.block_hash(),
44✔
949
            )
950
            .unwrap();
44✔
951
            StacksChainState::consensus_load(&block_path).unwrap()
44✔
952
        };
953

954
        let chain_tip = ChainTip {
44✔
955
            metadata,
44✔
956
            block,
44✔
957
            receipts,
44✔
958
        };
44✔
959
        self.chain_tip = Some(chain_tip.clone());
44✔
960

961
        // Unset the `bootstraping_chain` flag.
962
        if self.bootstraping_chain {
44✔
963
            self.bootstraping_chain = false;
10✔
964
        }
34✔
965

966
        chain_tip
44✔
967
    }
44✔
968

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

1006
    /// Constructs and returns a LeaderKeyRegisterOp out of the provided params
1007
    fn generate_leader_key_register_op(
10✔
1008
        &mut self,
10✔
1009
        vrf_public_key: VRFPublicKey,
10✔
1010
        consensus_hash: ConsensusHash,
10✔
1011
    ) -> BlockstackOperationType {
10✔
1012
        let mut txid_bytes = [0u8; 32];
10✔
1013
        let mut rng = rand::thread_rng();
10✔
1014
        rng.fill_bytes(&mut txid_bytes);
10✔
1015
        let txid = Txid(txid_bytes);
10✔
1016

1017
        BlockstackOperationType::LeaderKeyRegister(LeaderKeyRegisterOp {
10✔
1018
            public_key: vrf_public_key,
10✔
1019
            memo: vec![],
10✔
1020
            consensus_hash,
10✔
1021
            vtxindex: 1,
10✔
1022
            txid,
10✔
1023
            block_height: 0,
10✔
1024
            burn_header_hash: BurnchainHeaderHash::zero(),
10✔
1025
        })
10✔
1026
    }
10✔
1027

1028
    /// Constructs and returns a LeaderBlockCommitOp out of the provided params
1029
    fn generate_block_commit_op(
44✔
1030
        &mut self,
44✔
1031
        block_header_hash: BlockHeaderHash,
44✔
1032
        burn_fee: u64,
44✔
1033
        key: &RegisteredKey,
44✔
1034
        burnchain_tip: &BurnchainTip,
44✔
1035
        vrf_seed: VRFSeed,
44✔
1036
    ) -> BlockstackOperationType {
44✔
1037
        let winning_tx_vtindex = burnchain_tip.get_winning_tx_index().unwrap_or(0);
44✔
1038

1039
        let (parent_block_ptr, parent_vtxindex) = match self.bootstraping_chain {
44✔
1040
            true => (0, 0), // parent_block_ptr and parent_vtxindex should both be 0 on block #1
10✔
1041
            false => (
34✔
1042
                burnchain_tip.block_snapshot.block_height as u32,
34✔
1043
                winning_tx_vtindex as u16,
34✔
1044
            ),
34✔
1045
        };
1046

1047
        let burnchain = self.config.get_burnchain();
44✔
1048
        let commit_outs = if burnchain_tip.block_snapshot.block_height + 1
44✔
1049
            < burnchain.pox_constants.sunset_end
44✔
1050
            && !burnchain.is_in_prepare_phase(burnchain_tip.block_snapshot.block_height + 1)
44✔
1051
        {
1052
            RewardSetInfo::into_commit_outs(None, self.config.is_mainnet())
17✔
1053
        } else {
1054
            vec![PoxAddress::standard_burn_address(self.config.is_mainnet())]
27✔
1055
        };
1056

1057
        let burn_parent_modulus =
44✔
1058
            (burnchain_tip.block_snapshot.block_height % BURN_BLOCK_MINED_AT_MODULUS) as u8;
44✔
1059

1060
        let mut txid_bytes = [0u8; 32];
44✔
1061
        let mut rng = rand::thread_rng();
44✔
1062
        rng.fill_bytes(&mut txid_bytes);
44✔
1063
        let txid = Txid(txid_bytes);
44✔
1064

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

1087
    // Constructs a coinbase transaction
1088
    fn generate_coinbase_tx(&mut self, is_mainnet: bool) -> StacksTransaction {
54✔
1089
        let mut tx_auth = self.keychain.get_transaction_auth().unwrap();
54✔
1090
        tx_auth.set_origin_nonce(self.nonce);
54✔
1091

1092
        let version = if is_mainnet {
54✔
UNCOV
1093
            TransactionVersion::Mainnet
×
1094
        } else {
1095
            TransactionVersion::Testnet
54✔
1096
        };
1097
        let mut tx = StacksTransaction::new(
54✔
1098
            version,
54✔
1099
            tx_auth,
54✔
1100
            TransactionPayload::Coinbase(CoinbasePayload([0u8; 32]), None, None),
54✔
1101
        );
1102
        tx.chain_id = self.config.burnchain.chain_id;
54✔
1103
        tx.anchor_mode = TransactionAnchorMode::OnChainOnly;
54✔
1104
        let mut tx_signer = StacksTransactionSigner::new(&tx);
54✔
1105
        self.keychain.sign_as_origin(&mut tx_signer);
54✔
1106

1107
        // Increment nonce
1108
        self.nonce += 1;
54✔
1109

1110
        tx_signer.get_tx().unwrap()
54✔
1111
    }
54✔
1112
}
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