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

stacks-network / stacks-core / 23913007532

02 Apr 2026 05:21PM UTC coverage: 77.577% (-8.1%) from 85.712%
23913007532

Pull #7075

github

965cc1
web-flow
Merge 64c7ce33b into 461c7b5d9
Pull Request #7075: feat: block preservation for epoch2, microblocks, and Nakamoto

3839 of 4417 new or added lines in 19 files covered. (86.91%)

19316 existing lines in 181 files now uncovered.

172175 of 221942 relevant lines covered (77.58%)

7645264.6 hits per line

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

24.22
/stacks-node/src/burnchains/bitcoin_regtest_controller.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2024 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::cmp;
18
use std::collections::HashSet;
19
use std::sync::atomic::{AtomicBool, Ordering};
20
use std::sync::Arc;
21
use std::time::Instant;
22

23
use stacks::burnchains::bitcoin::address::{
24
    BitcoinAddress, LegacyBitcoinAddress, LegacyBitcoinAddressType, SegwitBitcoinAddress,
25
};
26
use stacks::burnchains::bitcoin::indexer::{
27
    BitcoinIndexer, BitcoinIndexerConfig, BitcoinIndexerRuntime,
28
};
29
use stacks::burnchains::bitcoin::spv::SpvClient;
30
use stacks::burnchains::bitcoin::{BitcoinNetworkType, Error as btc_error};
31
use stacks::burnchains::db::BurnchainDB;
32
use stacks::burnchains::indexer::BurnchainIndexer;
33
use stacks::burnchains::{
34
    Burnchain, BurnchainParameters, BurnchainStateTransitionOps, Error as burnchain_error,
35
    PoxConstants, PublicKey, Txid,
36
};
37
use stacks::chainstate::burn::db::sortdb::SortitionDB;
38
use stacks::chainstate::burn::operations::{
39
    BlockstackOperationType, DelegateStxOp, LeaderBlockCommitOp, LeaderKeyRegisterOp, PreStxOp,
40
    StackStxOp, TransferStxOp, VoteForAggregateKeyOp,
41
};
42
#[cfg(test)]
43
use stacks::chainstate::burn::Opcodes;
44
use stacks::chainstate::coordinator::comm::CoordinatorChannels;
45
#[cfg(test)]
46
use stacks::chainstate::stacks::address::PoxAddress;
47
use stacks::config::BurnchainConfig;
48
#[cfg(test)]
49
use stacks::config::{
50
    OP_TX_ANY_ESTIM_SIZE, OP_TX_DELEGATE_STACKS_ESTIM_SIZE, OP_TX_PRE_STACKS_ESTIM_SIZE,
51
    OP_TX_STACK_STX_ESTIM_SIZE, OP_TX_TRANSFER_STACKS_ESTIM_SIZE, OP_TX_VOTE_AGG_ESTIM_SIZE,
52
};
53
use stacks::core::{EpochList, StacksEpochId};
54
use stacks::monitoring::{increment_btc_blocks_received_counter, increment_btc_ops_sent_counter};
55
use stacks_common::codec::StacksMessageCodec;
56
use stacks_common::deps_common::bitcoin::blockdata::opcodes;
57
use stacks_common::deps_common::bitcoin::blockdata::script::{Builder, Script};
58
use stacks_common::deps_common::bitcoin::blockdata::transaction::{
59
    OutPoint, Transaction, TxIn, TxOut,
60
};
61
use stacks_common::deps_common::bitcoin::network::serialize::{serialize, serialize_hex};
62
use stacks_common::deps_common::bitcoin::util::hash::Sha256dHash;
63
use stacks_common::types::chainstate::BurnchainHeaderHash;
64
use stacks_common::util::hash::{hex_bytes, Hash160};
65
use stacks_common::util::secp256k1::Secp256k1PublicKey;
66
use stacks_common::util::sleep_ms;
67

68
use super::super::operations::BurnchainOpSigner;
69
use super::super::Config;
70
use super::{BurnchainController, BurnchainTip, Error as BurnchainControllerError};
71
use crate::burnchains::rpc::bitcoin_rpc_client::{
72
    BitcoinRpcClient, BitcoinRpcClientError, BitcoinRpcClientResult, ImportDescriptorsRequest,
73
    Timestamp,
74
};
75

76
/// The number of bitcoin blocks that can have
77
///  passed since the UTXO cache was last refreshed before
78
///  the cache is force-reset.
79
const UTXO_CACHE_STALENESS_LIMIT: u64 = 6;
80
const DUST_UTXO_LIMIT: u64 = 5500;
81

82
#[cfg(test)]
83
// Used to inject invalid block commits during testing.
84
pub static TEST_MAGIC_BYTES: std::sync::Mutex<Option<[u8; 2]>> = std::sync::Mutex::new(None);
85

86
pub struct BitcoinRegtestController {
87
    config: Config,
88
    indexer: BitcoinIndexer,
89
    db: Option<SortitionDB>,
90
    burnchain_db: Option<BurnchainDB>,
91
    chain_tip: Option<BurnchainTip>,
92
    use_coordinator: Option<CoordinatorChannels>,
93
    burnchain_config: Option<Burnchain>,
94
    ongoing_block_commit: Option<OngoingBlockCommit>,
95
    should_keep_running: Option<Arc<AtomicBool>>,
96
    /// Optional Bitcoin RPC client used to interact with a `bitcoind` node.
97
    /// - For **miner** node this field must be always `Some`.
98
    /// - For **other** node (e.g. follower node), this field is `None`.
99
    rpc_client: Option<BitcoinRpcClient>,
100
}
101

102
#[derive(Clone)]
103
pub struct OngoingBlockCommit {
104
    pub payload: LeaderBlockCommitOp,
105
    utxos: UTXOSet,
106
    fees: LeaderBlockCommitFees,
107
    txids: Vec<Txid>,
108
}
109

110
#[derive(Clone)]
111
struct LeaderBlockCommitFees {
112
    sunset_fee: u64,
113
    fee_rate: u64,
114
    sortition_fee: u64,
115
    outputs_len: u64,
116
    default_tx_size: u64,
117
    spent_in_attempts: u64,
118
    is_rbf_enabled: bool,
119
    final_size: u64,
120
}
121

122
// TODO: add tests from mutation testing results #4862
123
#[cfg_attr(test, mutants::skip)]
124
pub fn burnchain_params_from_config(config: &BurnchainConfig) -> BurnchainParameters {
177✔
125
    let (network, _) = config.get_bitcoin_network();
177✔
126
    let mut params = BurnchainParameters::from_params(&config.chain, &network)
177✔
127
        .expect("Bitcoin network unsupported");
177✔
128
    if let Some(first_burn_block_height) = config.first_burn_block_height {
177✔
129
        params.first_block_height = first_burn_block_height;
×
130
    }
177✔
131
    params
177✔
132
}
177✔
133

134
// TODO: add tests from mutation testing results #4863
135
#[cfg_attr(test, mutants::skip)]
136
/// Helper method to create a BitcoinIndexer
137
pub fn make_bitcoin_indexer(
166✔
138
    config: &Config,
166✔
139
    should_keep_running: Option<Arc<AtomicBool>>,
166✔
140
) -> BitcoinIndexer {
166✔
141
    let burnchain_params = burnchain_params_from_config(&config.burnchain);
166✔
142
    let indexer_config = {
166✔
143
        let burnchain_config = config.burnchain.clone();
166✔
144
        BitcoinIndexerConfig {
166✔
145
            peer_host: burnchain_config.peer_host,
166✔
146
            peer_port: burnchain_config.peer_port,
166✔
147
            rpc_port: burnchain_config.rpc_port,
166✔
148
            rpc_ssl: burnchain_config.rpc_ssl,
166✔
149
            username: burnchain_config.username,
166✔
150
            password: burnchain_config.password,
166✔
151
            timeout: burnchain_config.timeout,
166✔
152
            socket_timeout: burnchain_config.socket_timeout,
166✔
153
            spv_headers_path: config.get_spv_headers_file_path(),
166✔
154
            first_block: burnchain_params.first_block_height,
166✔
155
            magic_bytes: burnchain_config.magic_bytes,
166✔
156
            epochs: burnchain_config.epochs,
166✔
157
        }
166✔
158
    };
159

160
    let (_, network_type) = config.burnchain.get_bitcoin_network();
166✔
161
    let indexer_runtime = BitcoinIndexerRuntime::new(network_type, indexer_config.timeout);
166✔
162
    BitcoinIndexer {
166✔
163
        config: indexer_config,
166✔
164
        runtime: indexer_runtime,
166✔
165
        should_keep_running,
166✔
166
    }
166✔
167
}
166✔
168

169
pub fn get_satoshis_per_byte(config: &Config) -> u64 {
2✔
170
    config.get_burnchain_config().satoshis_per_byte
2✔
171
}
2✔
172

UNCOV
173
pub fn get_rbf_fee_increment(config: &Config) -> u64 {
×
UNCOV
174
    config.get_burnchain_config().rbf_fee_increment
×
UNCOV
175
}
×
176

UNCOV
177
pub fn get_max_rbf(config: &Config) -> u64 {
×
UNCOV
178
    config.get_burnchain_config().max_rbf
×
UNCOV
179
}
×
180

181
impl LeaderBlockCommitFees {
UNCOV
182
    pub fn fees_from_previous_tx(
×
UNCOV
183
        &self,
×
UNCOV
184
        payload: &LeaderBlockCommitOp,
×
UNCOV
185
        config: &Config,
×
UNCOV
186
    ) -> LeaderBlockCommitFees {
×
UNCOV
187
        let mut fees = LeaderBlockCommitFees::estimated_fees_from_payload(payload, config);
×
UNCOV
188
        fees.spent_in_attempts = cmp::max(1, self.spent_in_attempts);
×
UNCOV
189
        fees.final_size = self.final_size;
×
UNCOV
190
        fees.fee_rate = self.fee_rate + get_rbf_fee_increment(config);
×
UNCOV
191
        fees.is_rbf_enabled = true;
×
UNCOV
192
        fees
×
UNCOV
193
    }
×
194

UNCOV
195
    pub fn estimated_fees_from_payload(
×
UNCOV
196
        payload: &LeaderBlockCommitOp,
×
UNCOV
197
        config: &Config,
×
UNCOV
198
    ) -> LeaderBlockCommitFees {
×
UNCOV
199
        let sunset_fee = if payload.sunset_burn > 0 {
×
UNCOV
200
            cmp::max(payload.sunset_burn, DUST_UTXO_LIMIT)
×
201
        } else {
UNCOV
202
            0
×
203
        };
204

UNCOV
205
        let number_of_transfers = payload.commit_outs.len() as u64;
×
UNCOV
206
        let value_per_transfer = payload.burn_fee / number_of_transfers;
×
UNCOV
207
        let sortition_fee = value_per_transfer * number_of_transfers;
×
UNCOV
208
        let spent_in_attempts = 0;
×
UNCOV
209
        let fee_rate = get_satoshis_per_byte(config);
×
UNCOV
210
        let default_tx_size = config.burnchain.block_commit_tx_estimated_size;
×
211

UNCOV
212
        LeaderBlockCommitFees {
×
UNCOV
213
            sunset_fee,
×
UNCOV
214
            fee_rate,
×
UNCOV
215
            sortition_fee,
×
UNCOV
216
            outputs_len: number_of_transfers,
×
UNCOV
217
            default_tx_size,
×
UNCOV
218
            spent_in_attempts,
×
UNCOV
219
            is_rbf_enabled: false,
×
UNCOV
220
            final_size: 0,
×
UNCOV
221
        }
×
UNCOV
222
    }
×
223

224
    pub fn estimated_miner_fee(&self) -> u64 {
1✔
225
        self.fee_rate * self.default_tx_size
1✔
226
    }
1✔
227

228
    pub fn rbf_fee(&self) -> u64 {
1✔
229
        if self.is_rbf_enabled {
1✔
UNCOV
230
            self.spent_in_attempts + self.default_tx_size
×
231
        } else {
232
            0
1✔
233
        }
234
    }
1✔
235

236
    pub fn estimated_amount_required(&self) -> u64 {
1✔
237
        self.estimated_miner_fee() + self.rbf_fee() + self.sunset_fee + self.sortition_fee
1✔
238
    }
1✔
239

240
    pub fn total_spent(&self) -> u64 {
2✔
241
        self.fee_rate * self.final_size
2✔
242
            + self.spent_in_attempts
2✔
243
            + self.sunset_fee
2✔
244
            + self.sortition_fee
2✔
245
    }
2✔
246

247
    pub fn amount_per_output(&self) -> u64 {
3✔
248
        self.sortition_fee / self.outputs_len
3✔
249
    }
3✔
250

251
    pub fn total_spent_in_outputs(&self) -> u64 {
1✔
252
        self.sunset_fee + self.sortition_fee
1✔
253
    }
1✔
254

255
    pub fn min_tx_size(&self) -> u64 {
1✔
256
        cmp::max(self.final_size, self.default_tx_size)
1✔
257
    }
1✔
258

259
    pub fn register_replacement(&mut self, tx_size: u64) {
1✔
260
        let new_size = cmp::max(tx_size, self.final_size);
1✔
261
        if self.is_rbf_enabled {
1✔
UNCOV
262
            self.spent_in_attempts += new_size;
×
263
        }
1✔
264
        self.final_size = new_size;
1✔
265
    }
1✔
266
}
267

268
/// Extension methods for working with [`BitcoinRpcClient`] result
269
/// that log failures and panic.
270
#[cfg(test)]
271
trait BitcoinRpcClientResultExt<T> {
272
    /// Unwraps the result, returning the value if `Ok`.
273
    ///
274
    /// If the result is an `Err`, it logs the error with the given context
275
    /// using the [`error!`] macro and then panics.
276
    fn unwrap_or_log_panic(self, context: &str) -> T;
277
    /// Ensure the result is `Ok`, ignoring its value.
278
    ///
279
    /// If the result is an `Err`, it logs the error with the given context
280
    /// using the [`error!`] macro and then panics.
281
    fn ok_or_log_panic(self, context: &str);
282
}
283

284
#[cfg(test)]
285
impl<T> BitcoinRpcClientResultExt<T> for Result<T, BitcoinRpcClientError> {
UNCOV
286
    fn unwrap_or_log_panic(self, context: &str) -> T {
×
UNCOV
287
        match self {
×
UNCOV
288
            Ok(val) => val,
×
289
            Err(e) => {
×
290
                error!("Bitcoin RPC failure: {context} {e:?}");
×
291
                panic!();
×
292
            }
293
        }
UNCOV
294
    }
×
295

UNCOV
296
    fn ok_or_log_panic(self, context: &str) {
×
UNCOV
297
        _ = self.unwrap_or_log_panic(context);
×
UNCOV
298
    }
×
299
}
300

301
/// Represents errors that can occur when using [`BitcoinRegtestController`].
302
#[derive(Debug, thiserror::Error)]
303
pub enum BitcoinRegtestControllerError {
304
    /// Error related to Bitcoin RPC failures.
305
    #[error("Bitcoin RPC error: {0}")]
306
    Rpc(#[from] BitcoinRpcClientError),
307
    /// Error related to invalid or malformed [`Secp256k1PublicKey`].
308
    #[error("Invalid public key: {0}")]
309
    InvalidPublicKey(btc_error),
310
}
311

312
/// Alias for results returned from [`BitcoinRegtestController`] operations.
313
pub type BitcoinRegtestControllerResult<T> = Result<T, BitcoinRegtestControllerError>;
314

315
impl BitcoinRegtestController {
316
    pub fn new(config: Config, coordinator_channel: Option<CoordinatorChannels>) -> Self {
5✔
317
        BitcoinRegtestController::with_burnchain(config, coordinator_channel, None, None)
5✔
318
    }
5✔
319

320
    // TODO: add tests from mutation testing results #4864
321
    #[cfg_attr(test, mutants::skip)]
322
    pub fn with_burnchain(
8✔
323
        config: Config,
8✔
324
        coordinator_channel: Option<CoordinatorChannels>,
8✔
325
        burnchain: Option<Burnchain>,
8✔
326
        should_keep_running: Option<Arc<AtomicBool>>,
8✔
327
    ) -> Self {
8✔
328
        std::fs::create_dir_all(config.get_burnchain_path_str()).expect("Unable to create workdir");
8✔
329
        let (_, network_id) = config.burnchain.get_bitcoin_network();
8✔
330

331
        let res = SpvClient::new(
8✔
332
            &config.get_spv_headers_file_path(),
8✔
333
            0,
334
            None,
8✔
335
            network_id,
8✔
336
            true,
337
            false,
338
        );
339
        if let Err(err) = res {
8✔
340
            error!("Unable to init block headers: {err}");
×
341
            panic!()
×
342
        }
8✔
343

344
        let burnchain_params = burnchain_params_from_config(&config.burnchain);
8✔
345

346
        if network_id == BitcoinNetworkType::Mainnet && config.burnchain.epochs.is_some() {
8✔
347
            panic!("It is an error to set custom epochs while running on Mainnet: network_id {network_id:?} config.burnchain {:#?}",
×
348
                   &config.burnchain);
×
349
        }
8✔
350

351
        let indexer_config = {
8✔
352
            let burnchain_config = config.burnchain.clone();
8✔
353
            BitcoinIndexerConfig {
8✔
354
                peer_host: burnchain_config.peer_host,
8✔
355
                peer_port: burnchain_config.peer_port,
8✔
356
                rpc_port: burnchain_config.rpc_port,
8✔
357
                rpc_ssl: burnchain_config.rpc_ssl,
8✔
358
                username: burnchain_config.username,
8✔
359
                password: burnchain_config.password,
8✔
360
                timeout: burnchain_config.timeout,
8✔
361
                socket_timeout: burnchain_config.socket_timeout,
8✔
362
                spv_headers_path: config.get_spv_headers_file_path(),
8✔
363
                first_block: burnchain_params.first_block_height,
8✔
364
                magic_bytes: burnchain_config.magic_bytes,
8✔
365
                epochs: burnchain_config.epochs,
8✔
366
            }
8✔
367
        };
368

369
        let (_, network_type) = config.burnchain.get_bitcoin_network();
8✔
370
        let indexer_runtime = BitcoinIndexerRuntime::new(network_type, config.burnchain.timeout);
8✔
371
        let burnchain_indexer = BitcoinIndexer {
8✔
372
            config: indexer_config,
8✔
373
            runtime: indexer_runtime,
8✔
374
            should_keep_running: should_keep_running.clone(),
8✔
375
        };
8✔
376

377
        let rpc_client = Self::create_rpc_client_unchecked(&config);
8✔
378

379
        Self {
8✔
380
            use_coordinator: coordinator_channel,
8✔
381
            config,
8✔
382
            indexer: burnchain_indexer,
8✔
383
            db: None,
8✔
384
            burnchain_db: None,
8✔
385
            chain_tip: None,
8✔
386
            burnchain_config: burnchain,
8✔
387
            ongoing_block_commit: None,
8✔
388
            should_keep_running,
8✔
389
            rpc_client,
8✔
390
        }
8✔
391
    }
8✔
392

393
    // TODO: add tests from mutation testing results #4864
394
    #[cfg_attr(test, mutants::skip)]
395
    /// create a dummy bitcoin regtest controller.
396
    ///   used just for submitting bitcoin ops.
397
    pub fn new_dummy(config: Config) -> Self {
3✔
398
        let burnchain_params = burnchain_params_from_config(&config.burnchain);
3✔
399

400
        let indexer_config = {
3✔
401
            let burnchain_config = config.burnchain.clone();
3✔
402
            BitcoinIndexerConfig {
3✔
403
                peer_host: burnchain_config.peer_host,
3✔
404
                peer_port: burnchain_config.peer_port,
3✔
405
                rpc_port: burnchain_config.rpc_port,
3✔
406
                rpc_ssl: burnchain_config.rpc_ssl,
3✔
407
                username: burnchain_config.username,
3✔
408
                password: burnchain_config.password,
3✔
409
                timeout: burnchain_config.timeout,
3✔
410
                socket_timeout: burnchain_config.socket_timeout,
3✔
411
                spv_headers_path: config.get_spv_headers_file_path(),
3✔
412
                first_block: burnchain_params.first_block_height,
3✔
413
                magic_bytes: burnchain_config.magic_bytes,
3✔
414
                epochs: burnchain_config.epochs,
3✔
415
            }
3✔
416
        };
417

418
        let (_, network_type) = config.burnchain.get_bitcoin_network();
3✔
419
        let indexer_runtime = BitcoinIndexerRuntime::new(network_type, config.burnchain.timeout);
3✔
420
        let burnchain_indexer = BitcoinIndexer {
3✔
421
            config: indexer_config,
3✔
422
            runtime: indexer_runtime,
3✔
423
            should_keep_running: None,
3✔
424
        };
3✔
425

426
        let rpc_client = Self::create_rpc_client_unchecked(&config);
3✔
427

428
        Self {
3✔
429
            use_coordinator: None,
3✔
430
            config,
3✔
431
            indexer: burnchain_indexer,
3✔
432
            db: None,
3✔
433
            burnchain_db: None,
3✔
434
            chain_tip: None,
3✔
435
            burnchain_config: None,
3✔
436
            ongoing_block_commit: None,
3✔
437
            should_keep_running: None,
3✔
438
            rpc_client,
3✔
439
        }
3✔
440
    }
3✔
441

442
    /// Creates a dummy bitcoin regtest controller, with the given ongoing block-commits
UNCOV
443
    pub fn new_ongoing_dummy(config: Config, ongoing: Option<OngoingBlockCommit>) -> Self {
×
UNCOV
444
        let mut ret = Self::new_dummy(config);
×
UNCOV
445
        ret.ongoing_block_commit = ongoing;
×
UNCOV
446
        ret
×
UNCOV
447
    }
×
448

449
    /// Get an owned copy of the ongoing block commit state
UNCOV
450
    pub fn get_ongoing_commit(&self) -> Option<OngoingBlockCommit> {
×
UNCOV
451
        self.ongoing_block_commit.clone()
×
UNCOV
452
    }
×
453

454
    /// Set the ongoing block commit state
UNCOV
455
    pub fn set_ongoing_commit(&mut self, ongoing: Option<OngoingBlockCommit>) {
×
UNCOV
456
        self.ongoing_block_commit = ongoing;
×
UNCOV
457
    }
×
458

459
    /// Get the default Burnchain instance from our config
UNCOV
460
    fn default_burnchain(&self) -> Burnchain {
×
UNCOV
461
        match &self.burnchain_config {
×
462
            Some(burnchain) => burnchain.clone(),
×
UNCOV
463
            None => self.config.get_burnchain(),
×
464
        }
UNCOV
465
    }
×
466

467
    /// Get the PoX constants in use
468
    pub fn get_pox_constants(&self) -> PoxConstants {
×
469
        let burnchain = self.get_burnchain();
×
470
        burnchain.pox_constants
×
471
    }
×
472

473
    /// Get the Burnchain in use
UNCOV
474
    pub fn get_burnchain(&self) -> Burnchain {
×
UNCOV
475
        match self.burnchain_config {
×
UNCOV
476
            Some(ref burnchain) => burnchain.clone(),
×
UNCOV
477
            None => self.default_burnchain(),
×
478
        }
UNCOV
479
    }
×
480

481
    /// Attempt to create a new [`BitcoinRpcClient`] from the given [`Config`].
482
    ///
483
    /// If the provided config indicates that the node is a **miner**,
484
    /// tries to instantiate it or **panics** otherwise.
485
    /// If the node is **not** a miner, returns None (e.g. follower node).
486
    fn create_rpc_client_unchecked(config: &Config) -> Option<BitcoinRpcClient> {
11✔
487
        config.node.miner.then(|| {
11✔
488
            BitcoinRpcClient::from_stx_config(&config)
9✔
489
                .expect("unable to instantiate the RPC client for miner node!")
9✔
490
        })
9✔
491
    }
11✔
492

493
    /// Attempt to get a reference to the underlying [`BitcoinRpcClient`].
494
    ///
495
    /// This function will panic if the RPC client has not been configured
496
    /// (i.e. [`Self::create_rpc_client_unchecked`] returned `None` during initialization),
497
    /// but an attempt is made to use it anyway.
498
    ///
499
    /// In practice, this means the node is expected to act as a miner,
500
    /// yet no [`BitcoinRpcClient`] was created or properly configured.
501
    fn get_rpc_client(&self) -> &BitcoinRpcClient {
4✔
502
        self.rpc_client
4✔
503
            .as_ref()
4✔
504
            .expect("BUG: BitcoinRpcClient is required, but it has not been configured properly!")
4✔
505
    }
4✔
506

507
    /// Helium (devnet) blocks receiver.  Returns the new burnchain tip.
508
    fn receive_blocks_helium(&mut self) -> BurnchainTip {
×
509
        let mut burnchain = self.get_burnchain();
×
510
        let (block_snapshot, state_transition) = loop {
×
511
            match burnchain.sync_with_indexer_deprecated(&mut self.indexer) {
×
512
                Ok(x) => {
×
513
                    break x;
×
514
                }
515
                Err(e) => {
×
516
                    // keep trying
517
                    error!("Unable to sync with burnchain: {e}");
×
518
                    match e {
×
519
                        burnchain_error::TrySyncAgain => {
520
                            // try again immediately
521
                            continue;
×
522
                        }
523
                        burnchain_error::BurnchainPeerBroken => {
524
                            // remote burnchain peer broke, and produced a shorter blockchain fork.
525
                            // just keep trying
526
                            sleep_ms(5000);
×
527
                            continue;
×
528
                        }
529
                        _ => {
530
                            // delay and try again
531
                            sleep_ms(5000);
×
532
                            continue;
×
533
                        }
534
                    }
535
                }
536
            }
537
        };
538

539
        let rest = match (state_transition, &self.chain_tip) {
×
540
            (None, Some(chain_tip)) => chain_tip.clone(),
×
541
            (Some(state_transition), _) => {
×
542
                let burnchain_tip = BurnchainTip {
×
543
                    block_snapshot,
×
544
                    state_transition: BurnchainStateTransitionOps::from(state_transition),
×
545
                    received_at: Instant::now(),
×
546
                };
×
547
                self.chain_tip = Some(burnchain_tip.clone());
×
548
                burnchain_tip
×
549
            }
550
            (None, None) => {
551
                // can happen at genesis
552
                let burnchain_tip = BurnchainTip {
×
553
                    block_snapshot,
×
554
                    state_transition: BurnchainStateTransitionOps::noop(),
×
555
                    received_at: Instant::now(),
×
556
                };
×
557
                self.chain_tip = Some(burnchain_tip.clone());
×
558
                burnchain_tip
×
559
            }
560
        };
561

562
        debug!("Done receiving blocks");
×
563
        rest
×
564
    }
×
565

UNCOV
566
    fn receive_blocks(
×
UNCOV
567
        &mut self,
×
UNCOV
568
        block_for_sortitions: bool,
×
UNCOV
569
        target_block_height_opt: Option<u64>,
×
UNCOV
570
    ) -> Result<(BurnchainTip, u64), BurnchainControllerError> {
×
UNCOV
571
        let coordinator_comms = match self.use_coordinator.as_ref() {
×
UNCOV
572
            Some(x) => x.clone(),
×
573
            None => {
574
                // pre-PoX helium node
575
                let tip = self.receive_blocks_helium();
×
576
                let height = tip.block_snapshot.block_height;
×
577
                return Ok((tip, height));
×
578
            }
579
        };
580

UNCOV
581
        let mut burnchain = self.get_burnchain();
×
UNCOV
582
        let (block_snapshot, burnchain_height, state_transition) = loop {
×
UNCOV
583
            if !self.should_keep_running() {
×
UNCOV
584
                return Err(BurnchainControllerError::CoordinatorClosed);
×
UNCOV
585
            }
×
586

UNCOV
587
            match burnchain.sync_with_indexer(
×
UNCOV
588
                &mut self.indexer,
×
UNCOV
589
                coordinator_comms.clone(),
×
UNCOV
590
                target_block_height_opt,
×
UNCOV
591
                Some(burnchain.pox_constants.reward_cycle_length as u64),
×
UNCOV
592
                self.should_keep_running.clone(),
×
UNCOV
593
            ) {
×
UNCOV
594
                Ok(x) => {
×
UNCOV
595
                    increment_btc_blocks_received_counter();
×
596

597
                    // initialize the dbs...
UNCOV
598
                    self.sortdb_mut();
×
599

600
                    // wait for the chains coordinator to catch up with us.
601
                    // don't wait for heights beyond the burnchain tip.
UNCOV
602
                    if block_for_sortitions {
×
UNCOV
603
                        self.wait_for_sortitions(
×
UNCOV
604
                            coordinator_comms,
×
UNCOV
605
                            target_block_height_opt.unwrap_or(x.block_height),
×
UNCOV
606
                        )?;
×
UNCOV
607
                    }
×
608

609
                    // NOTE: This is the latest _sortition_ on the canonical sortition history, not the latest burnchain block!
UNCOV
610
                    let sort_tip =
×
UNCOV
611
                        SortitionDB::get_canonical_burn_chain_tip(self.sortdb_ref().conn())
×
UNCOV
612
                            .expect("Sortition DB error.");
×
613

UNCOV
614
                    let (snapshot, state_transition) = self
×
UNCOV
615
                        .sortdb_ref()
×
UNCOV
616
                        .get_sortition_result(&sort_tip.sortition_id)
×
UNCOV
617
                        .expect("Sortition DB error.")
×
UNCOV
618
                        .expect("BUG: no data for the canonical chain tip");
×
619

UNCOV
620
                    let burnchain_height = self
×
UNCOV
621
                        .indexer
×
UNCOV
622
                        .get_highest_header_height()
×
UNCOV
623
                        .map_err(BurnchainControllerError::IndexerError)?;
×
UNCOV
624
                    break (snapshot, burnchain_height, state_transition);
×
625
                }
UNCOV
626
                Err(e) => {
×
627
                    // keep trying
UNCOV
628
                    error!("Unable to sync with burnchain: {e}");
×
UNCOV
629
                    match e {
×
630
                        burnchain_error::CoordinatorClosed => {
UNCOV
631
                            return Err(BurnchainControllerError::CoordinatorClosed)
×
632
                        }
633
                        burnchain_error::TrySyncAgain => {
634
                            // try again immediately
UNCOV
635
                            continue;
×
636
                        }
637
                        burnchain_error::BurnchainPeerBroken => {
638
                            // remote burnchain peer broke, and produced a shorter blockchain fork.
639
                            // just keep trying
UNCOV
640
                            sleep_ms(5000);
×
UNCOV
641
                            continue;
×
642
                        }
643
                        _ => {
644
                            // delay and try again
UNCOV
645
                            sleep_ms(5000);
×
UNCOV
646
                            continue;
×
647
                        }
648
                    }
649
                }
650
            }
651
        };
652

UNCOV
653
        let burnchain_tip = BurnchainTip {
×
UNCOV
654
            block_snapshot,
×
UNCOV
655
            state_transition,
×
UNCOV
656
            received_at: Instant::now(),
×
UNCOV
657
        };
×
658

UNCOV
659
        let received = self
×
UNCOV
660
            .chain_tip
×
UNCOV
661
            .as_ref()
×
UNCOV
662
            .map(|tip| tip.block_snapshot.block_height)
×
UNCOV
663
            .unwrap_or(0)
×
UNCOV
664
            == burnchain_tip.block_snapshot.block_height;
×
UNCOV
665
        self.chain_tip = Some(burnchain_tip.clone());
×
UNCOV
666
        debug!("Done receiving blocks");
×
667

UNCOV
668
        if self.config.burnchain.fault_injection_burnchain_block_delay > 0 && received {
×
669
            info!(
×
670
                "Fault injection: delaying burnchain blocks by {} milliseconds",
671
                self.config.burnchain.fault_injection_burnchain_block_delay
672
            );
673
            sleep_ms(self.config.burnchain.fault_injection_burnchain_block_delay);
×
UNCOV
674
        }
×
675

UNCOV
676
        Ok((burnchain_tip, burnchain_height))
×
UNCOV
677
    }
×
678

UNCOV
679
    fn should_keep_running(&self) -> bool {
×
UNCOV
680
        match self.should_keep_running {
×
UNCOV
681
            Some(ref should_keep_running) => should_keep_running.load(Ordering::SeqCst),
×
682
            _ => true,
×
683
        }
UNCOV
684
    }
×
685

686
    /// Retrieves all UTXOs associated with the given public key.
687
    ///
688
    /// The address to query is computed from the public key,
689
    /// disregard the epoch we're in and currently set to [`StacksEpochId::Epoch21`].
690
    ///
691
    /// Automatically imports descriptors into the wallet for the public_key
692
    #[cfg(test)]
UNCOV
693
    pub fn get_all_utxos(&self, public_key: &Secp256k1PublicKey) -> Vec<UTXO> {
×
694
        const EPOCH: StacksEpochId = StacksEpochId::Epoch21;
UNCOV
695
        let address = self.get_miner_address(EPOCH, public_key);
×
UNCOV
696
        let pub_key_rev = self.to_epoch_aware_pubkey(EPOCH, public_key);
×
697

UNCOV
698
        test_debug!("Import public key '{}'", &pub_key_rev.to_hex());
×
UNCOV
699
        self.import_public_key(&pub_key_rev)
×
UNCOV
700
            .unwrap_or_else(|error| {
×
701
                panic!(
×
702
                    "Import public key '{}' failed: {error:?}",
703
                    pub_key_rev.to_hex()
×
704
                )
705
            });
706

UNCOV
707
        sleep_ms(1000);
×
708

UNCOV
709
        self.retrieve_utxo_set(&address, true, 1, &None, 0)
×
UNCOV
710
            .unwrap_or_log_panic("retrieve all utxos")
×
UNCOV
711
            .utxos
×
UNCOV
712
    }
×
713

714
    /// Retrieve all loaded wallets.
UNCOV
715
    pub fn list_wallets(&self) -> BitcoinRegtestControllerResult<Vec<String>> {
×
UNCOV
716
        Ok(self.get_rpc_client().list_wallets()?)
×
UNCOV
717
    }
×
718

719
    /// Checks if the config-supplied wallet exists.
720
    /// If it does not exist, this function creates it.
UNCOV
721
    pub fn create_wallet_if_dne(&self) -> BitcoinRegtestControllerResult<()> {
×
UNCOV
722
        let wallets = self.list_wallets()?;
×
UNCOV
723
        let wallet = self.get_wallet_name();
×
UNCOV
724
        if !wallets.contains(wallet) {
×
UNCOV
725
            self.get_rpc_client().create_wallet(wallet, Some(true))?
×
UNCOV
726
        }
×
UNCOV
727
        Ok(())
×
UNCOV
728
    }
×
729

UNCOV
730
    pub fn get_utxos(
×
UNCOV
731
        &self,
×
UNCOV
732
        epoch_id: StacksEpochId,
×
UNCOV
733
        public_key: &Secp256k1PublicKey,
×
UNCOV
734
        total_required: u64,
×
UNCOV
735
        utxos_to_exclude: Option<UTXOSet>,
×
UNCOV
736
        block_height: u64,
×
UNCOV
737
    ) -> Option<UTXOSet> {
×
UNCOV
738
        let pub_key_rev = self.to_epoch_aware_pubkey(epoch_id, public_key);
×
739

740
        // Configure UTXO filter
UNCOV
741
        let address = self.get_miner_address(epoch_id, &pub_key_rev);
×
UNCOV
742
        test_debug!("Get UTXOs for {} ({address})", pub_key_rev.to_hex());
×
743

UNCOV
744
        let mut utxos = loop {
×
UNCOV
745
            let result = self.retrieve_utxo_set(
×
UNCOV
746
                &address,
×
747
                false,
UNCOV
748
                total_required,
×
UNCOV
749
                &utxos_to_exclude,
×
UNCOV
750
                block_height,
×
751
            );
752

753
            // Perform request
UNCOV
754
            match result {
×
UNCOV
755
                Ok(utxos) => {
×
UNCOV
756
                    break utxos;
×
757
                }
758
                Err(e) => {
×
759
                    error!("Bitcoin RPC failure: error listing utxos {e:?}");
×
760
                    sleep_ms(5000);
×
761
                    continue;
×
762
                }
763
            };
764
        };
765

UNCOV
766
        let utxos = if utxos.is_empty() {
×
UNCOV
767
            let (_, network) = self.config.burnchain.get_bitcoin_network();
×
768
            loop {
UNCOV
769
                if let BitcoinNetworkType::Regtest = network {
×
770
                    // Performing this operation on Mainnet / Testnet is very expensive, and can be longer than bitcoin block time.
771
                    // Assuming that miners are in charge of correctly operating their bitcoind nodes sounds
772
                    // reasonable to me.
773
                    // $ bitcoin-cli importaddress mxVFsFW5N4mu1HPkxPttorvocvzeZ7KZyk
UNCOV
774
                    let result = self.import_public_key(&pub_key_rev);
×
UNCOV
775
                    if let Err(error) = result {
×
776
                        warn!(
×
777
                            "Import public key '{}' failed: {error:?}",
778
                            &pub_key_rev.to_hex()
×
779
                        );
UNCOV
780
                    }
×
UNCOV
781
                    sleep_ms(1000);
×
782
                }
×
783

UNCOV
784
                let result = self.retrieve_utxo_set(
×
UNCOV
785
                    &address,
×
786
                    false,
UNCOV
787
                    total_required,
×
UNCOV
788
                    &utxos_to_exclude,
×
UNCOV
789
                    block_height,
×
790
                );
791

UNCOV
792
                utxos = match result {
×
UNCOV
793
                    Ok(utxos) => utxos,
×
794
                    Err(e) => {
×
795
                        error!("Bitcoin RPC failure: error listing utxos {e:?}");
×
796
                        sleep_ms(5000);
×
797
                        continue;
×
798
                    }
799
                };
800

UNCOV
801
                test_debug!("Unspent for {address:?}: {utxos:?}");
×
802

UNCOV
803
                if utxos.is_empty() {
×
UNCOV
804
                    return None;
×
805
                } else {
UNCOV
806
                    break utxos;
×
807
                }
808
            }
809
        } else {
UNCOV
810
            debug!("Got {} UTXOs for {address:?}", utxos.utxos.len(),);
×
UNCOV
811
            utxos
×
812
        };
813

UNCOV
814
        let total_unspent = utxos.total_available();
×
UNCOV
815
        if total_unspent < total_required {
×
816
            warn!(
×
817
                "Total unspent {total_unspent} < {total_required} for {:?}",
818
                &pub_key_rev.to_hex()
×
819
            );
820
            return None;
×
UNCOV
821
        }
×
822

UNCOV
823
        Some(utxos)
×
UNCOV
824
    }
×
825

UNCOV
826
    fn build_leader_key_register_tx(
×
UNCOV
827
        &mut self,
×
UNCOV
828
        epoch_id: StacksEpochId,
×
UNCOV
829
        payload: LeaderKeyRegisterOp,
×
UNCOV
830
        signer: &mut BurnchainOpSigner,
×
UNCOV
831
    ) -> Result<Transaction, BurnchainControllerError> {
×
UNCOV
832
        let public_key = signer.get_public_key();
×
833

834
        // reload the config to find satoshis_per_byte changes
UNCOV
835
        let btc_miner_fee = self.config.burnchain.leader_key_tx_estimated_size
×
UNCOV
836
            * get_satoshis_per_byte(&self.config);
×
UNCOV
837
        let budget_for_outputs = DUST_UTXO_LIMIT;
×
UNCOV
838
        let total_required = btc_miner_fee + budget_for_outputs;
×
839

UNCOV
840
        let (mut tx, mut utxos) =
×
UNCOV
841
            self.prepare_tx(epoch_id, &public_key, total_required, None, None, 0)?;
×
842

843
        // Serialize the payload
UNCOV
844
        let op_bytes = {
×
UNCOV
845
            let mut buffer = vec![];
×
UNCOV
846
            let mut magic_bytes = self.config.burnchain.magic_bytes.as_bytes().to_vec();
×
UNCOV
847
            buffer.append(&mut magic_bytes);
×
UNCOV
848
            payload
×
UNCOV
849
                .consensus_serialize(&mut buffer)
×
UNCOV
850
                .expect("FATAL: invalid operation");
×
UNCOV
851
            buffer
×
852
        };
853

UNCOV
854
        let consensus_output = TxOut {
×
UNCOV
855
            value: 0,
×
UNCOV
856
            script_pubkey: Builder::new()
×
UNCOV
857
                .push_opcode(opcodes::All::OP_RETURN)
×
UNCOV
858
                .push_slice(&op_bytes)
×
UNCOV
859
                .into_script(),
×
UNCOV
860
        };
×
861

UNCOV
862
        tx.output = vec![consensus_output];
×
863

UNCOV
864
        let fee_rate = get_satoshis_per_byte(&self.config);
×
865

UNCOV
866
        self.finalize_tx(
×
UNCOV
867
            epoch_id,
×
UNCOV
868
            &mut tx,
×
UNCOV
869
            budget_for_outputs,
×
870
            0,
UNCOV
871
            self.config.burnchain.leader_key_tx_estimated_size,
×
UNCOV
872
            fee_rate,
×
UNCOV
873
            &mut utxos,
×
UNCOV
874
            signer,
×
875
            true, // key register op requires change output to exist
876
        );
877

UNCOV
878
        increment_btc_ops_sent_counter();
×
879

UNCOV
880
        info!(
×
881
            "Miner node: submitting leader_key_register op - {}, waiting for its inclusion in the next Bitcoin block",
UNCOV
882
            public_key.to_hex()
×
883
        );
884

UNCOV
885
        Ok(tx)
×
UNCOV
886
    }
×
887

888
    #[cfg(not(test))]
889
    fn build_transfer_stacks_tx(
890
        &mut self,
891
        _epoch_id: StacksEpochId,
892
        _payload: TransferStxOp,
893
        _signer: &mut BurnchainOpSigner,
894
        _utxo: Option<UTXO>,
895
    ) -> Result<Transaction, BurnchainControllerError> {
896
        unimplemented!()
897
    }
898

899
    #[cfg(not(test))]
900
    fn build_delegate_stacks_tx(
901
        &mut self,
902
        _epoch_id: StacksEpochId,
903
        _payload: DelegateStxOp,
904
        _signer: &mut BurnchainOpSigner,
905
        _utxo: Option<UTXO>,
906
    ) -> Result<Transaction, BurnchainControllerError> {
907
        unimplemented!()
908
    }
909

910
    #[cfg(test)]
UNCOV
911
    pub fn submit_manual(
×
UNCOV
912
        &mut self,
×
UNCOV
913
        epoch_id: StacksEpochId,
×
UNCOV
914
        operation: BlockstackOperationType,
×
UNCOV
915
        op_signer: &mut BurnchainOpSigner,
×
UNCOV
916
        utxo: Option<UTXO>,
×
UNCOV
917
    ) -> Result<Transaction, BurnchainControllerError> {
×
UNCOV
918
        let transaction = match operation {
×
919
            BlockstackOperationType::LeaderBlockCommit(_)
920
            | BlockstackOperationType::LeaderKeyRegister(_)
921
            | BlockstackOperationType::StackStx(_)
922
            | BlockstackOperationType::DelegateStx(_)
923
            | BlockstackOperationType::VoteForAggregateKey(_) => {
924
                unimplemented!();
×
925
            }
UNCOV
926
            BlockstackOperationType::PreStx(payload) => {
×
UNCOV
927
                self.build_pre_stacks_tx(epoch_id, payload, op_signer)
×
928
            }
UNCOV
929
            BlockstackOperationType::TransferStx(payload) => {
×
UNCOV
930
                self.build_transfer_stacks_tx(epoch_id, payload, op_signer, utxo)
×
931
            }
932
        }?;
×
UNCOV
933
        self.send_transaction(&transaction).map(|_| transaction)
×
UNCOV
934
    }
×
935

936
    #[cfg(test)]
937
    /// Build a transfer stacks tx.
938
    ///   this *only* works if the only existant UTXO is from a PreStx Op
939
    ///   this is okay for testing, but obviously not okay for actual use.
940
    ///   The reason for this constraint is that the bitcoin_regtest_controller's UTXO
941
    ///     and signing logic are fairly intertwined, and untangling the two seems excessive
942
    ///     for a functionality that won't be implemented for production via this controller.
UNCOV
943
    fn build_transfer_stacks_tx(
×
UNCOV
944
        &mut self,
×
UNCOV
945
        epoch_id: StacksEpochId,
×
UNCOV
946
        payload: TransferStxOp,
×
UNCOV
947
        signer: &mut BurnchainOpSigner,
×
UNCOV
948
        utxo_to_use: Option<UTXO>,
×
UNCOV
949
    ) -> Result<Transaction, BurnchainControllerError> {
×
UNCOV
950
        let public_key = signer.get_public_key();
×
UNCOV
951
        let max_tx_size = OP_TX_TRANSFER_STACKS_ESTIM_SIZE;
×
UNCOV
952
        let (mut tx, mut utxos) = if let Some(utxo) = utxo_to_use {
×
UNCOV
953
            (
×
UNCOV
954
                Transaction {
×
UNCOV
955
                    input: vec![],
×
UNCOV
956
                    output: vec![],
×
UNCOV
957
                    version: 1,
×
UNCOV
958
                    lock_time: 0,
×
UNCOV
959
                },
×
UNCOV
960
                UTXOSet {
×
UNCOV
961
                    bhh: BurnchainHeaderHash::zero(),
×
UNCOV
962
                    utxos: vec![utxo],
×
UNCOV
963
                },
×
UNCOV
964
            )
×
965
        } else {
UNCOV
966
            self.prepare_tx(
×
UNCOV
967
                epoch_id,
×
UNCOV
968
                &public_key,
×
UNCOV
969
                DUST_UTXO_LIMIT + max_tx_size * get_satoshis_per_byte(&self.config),
×
UNCOV
970
                None,
×
UNCOV
971
                None,
×
972
                0,
973
            )?
×
974
        };
975

976
        // Serialize the payload
UNCOV
977
        let op_bytes = {
×
UNCOV
978
            let mut bytes = self.config.burnchain.magic_bytes.as_bytes().to_vec();
×
UNCOV
979
            payload
×
UNCOV
980
                .consensus_serialize(&mut bytes)
×
UNCOV
981
                .map_err(BurnchainControllerError::SerializerError)?;
×
UNCOV
982
            bytes
×
983
        };
984

UNCOV
985
        let consensus_output = TxOut {
×
UNCOV
986
            value: 0,
×
UNCOV
987
            script_pubkey: Builder::new()
×
UNCOV
988
                .push_opcode(opcodes::All::OP_RETURN)
×
UNCOV
989
                .push_slice(&op_bytes)
×
UNCOV
990
                .into_script(),
×
UNCOV
991
        };
×
992

UNCOV
993
        tx.output = vec![consensus_output];
×
UNCOV
994
        tx.output
×
UNCOV
995
            .push(PoxAddress::Standard(payload.recipient, None).to_bitcoin_tx_out(DUST_UTXO_LIMIT));
×
996

UNCOV
997
        self.finalize_tx(
×
UNCOV
998
            epoch_id,
×
UNCOV
999
            &mut tx,
×
1000
            DUST_UTXO_LIMIT,
1001
            0,
UNCOV
1002
            max_tx_size,
×
UNCOV
1003
            get_satoshis_per_byte(&self.config),
×
UNCOV
1004
            &mut utxos,
×
UNCOV
1005
            signer,
×
1006
            false,
1007
        );
1008

UNCOV
1009
        increment_btc_ops_sent_counter();
×
1010

UNCOV
1011
        info!(
×
1012
            "Miner node: submitting stacks transfer op - {}",
UNCOV
1013
            public_key.to_hex()
×
1014
        );
1015

UNCOV
1016
        Ok(tx)
×
UNCOV
1017
    }
×
1018

1019
    #[cfg(test)]
1020
    /// Build a delegate stacks tx.
1021
    ///   this *only* works if the only existant UTXO is from a PreStx Op
1022
    ///   this is okay for testing, but obviously not okay for actual use.
1023
    ///   The reason for this constraint is that the bitcoin_regtest_controller's UTXO
1024
    ///     and signing logic are fairly intertwined, and untangling the two seems excessive
1025
    ///     for a functionality that won't be implemented for production via this controller.
UNCOV
1026
    fn build_delegate_stacks_tx(
×
UNCOV
1027
        &mut self,
×
UNCOV
1028
        epoch_id: StacksEpochId,
×
UNCOV
1029
        payload: DelegateStxOp,
×
UNCOV
1030
        signer: &mut BurnchainOpSigner,
×
UNCOV
1031
        utxo_to_use: Option<UTXO>,
×
UNCOV
1032
    ) -> Result<Transaction, BurnchainControllerError> {
×
UNCOV
1033
        let public_key = signer.get_public_key();
×
UNCOV
1034
        let max_tx_size = OP_TX_DELEGATE_STACKS_ESTIM_SIZE;
×
1035

UNCOV
1036
        let (mut tx, mut utxos) = if let Some(utxo) = utxo_to_use {
×
1037
            (
×
1038
                Transaction {
×
1039
                    input: vec![],
×
1040
                    output: vec![],
×
1041
                    version: 1,
×
1042
                    lock_time: 0,
×
1043
                },
×
1044
                UTXOSet {
×
1045
                    bhh: BurnchainHeaderHash::zero(),
×
1046
                    utxos: vec![utxo],
×
1047
                },
×
1048
            )
×
1049
        } else {
UNCOV
1050
            self.prepare_tx(
×
UNCOV
1051
                epoch_id,
×
UNCOV
1052
                &public_key,
×
UNCOV
1053
                DUST_UTXO_LIMIT + max_tx_size * get_satoshis_per_byte(&self.config),
×
UNCOV
1054
                None,
×
UNCOV
1055
                None,
×
1056
                0,
1057
            )?
×
1058
        };
1059

1060
        // Serialize the payload
UNCOV
1061
        let op_bytes = {
×
UNCOV
1062
            let mut bytes = self.config.burnchain.magic_bytes.as_bytes().to_vec();
×
UNCOV
1063
            payload
×
UNCOV
1064
                .consensus_serialize(&mut bytes)
×
UNCOV
1065
                .map_err(BurnchainControllerError::SerializerError)?;
×
UNCOV
1066
            bytes
×
1067
        };
1068

UNCOV
1069
        let consensus_output = TxOut {
×
UNCOV
1070
            value: 0,
×
UNCOV
1071
            script_pubkey: Builder::new()
×
UNCOV
1072
                .push_opcode(opcodes::All::OP_RETURN)
×
UNCOV
1073
                .push_slice(&op_bytes)
×
UNCOV
1074
                .into_script(),
×
UNCOV
1075
        };
×
1076

UNCOV
1077
        tx.output = vec![consensus_output];
×
UNCOV
1078
        tx.output.push(
×
UNCOV
1079
            PoxAddress::Standard(payload.delegate_to, None).to_bitcoin_tx_out(DUST_UTXO_LIMIT),
×
1080
        );
1081

UNCOV
1082
        self.finalize_tx(
×
UNCOV
1083
            epoch_id,
×
UNCOV
1084
            &mut tx,
×
1085
            DUST_UTXO_LIMIT,
1086
            0,
UNCOV
1087
            max_tx_size,
×
UNCOV
1088
            get_satoshis_per_byte(&self.config),
×
UNCOV
1089
            &mut utxos,
×
UNCOV
1090
            signer,
×
1091
            false,
1092
        );
1093

UNCOV
1094
        increment_btc_ops_sent_counter();
×
1095

UNCOV
1096
        info!(
×
1097
            "Miner node: submitting stacks delegate op - {}",
UNCOV
1098
            public_key.to_hex()
×
1099
        );
1100

UNCOV
1101
        Ok(tx)
×
UNCOV
1102
    }
×
1103

1104
    #[cfg(test)]
1105
    /// Build a vote-for-aggregate-key burn op tx
UNCOV
1106
    fn build_vote_for_aggregate_key_tx(
×
UNCOV
1107
        &mut self,
×
UNCOV
1108
        epoch_id: StacksEpochId,
×
UNCOV
1109
        payload: VoteForAggregateKeyOp,
×
UNCOV
1110
        signer: &mut BurnchainOpSigner,
×
UNCOV
1111
        utxo_to_use: Option<UTXO>,
×
UNCOV
1112
    ) -> Result<Transaction, BurnchainControllerError> {
×
UNCOV
1113
        let public_key = signer.get_public_key();
×
UNCOV
1114
        let max_tx_size = OP_TX_VOTE_AGG_ESTIM_SIZE;
×
1115

UNCOV
1116
        let (mut tx, mut utxos) = if let Some(utxo) = utxo_to_use {
×
1117
            (
×
1118
                Transaction {
×
1119
                    input: vec![],
×
1120
                    output: vec![],
×
1121
                    version: 1,
×
1122
                    lock_time: 0,
×
1123
                },
×
1124
                UTXOSet {
×
1125
                    bhh: BurnchainHeaderHash::zero(),
×
1126
                    utxos: vec![utxo],
×
1127
                },
×
1128
            )
×
1129
        } else {
UNCOV
1130
            self.prepare_tx(
×
UNCOV
1131
                epoch_id,
×
UNCOV
1132
                &public_key,
×
UNCOV
1133
                DUST_UTXO_LIMIT + max_tx_size * get_satoshis_per_byte(&self.config),
×
UNCOV
1134
                None,
×
UNCOV
1135
                None,
×
1136
                0,
1137
            )?
×
1138
        };
1139

1140
        // Serialize the payload
UNCOV
1141
        let op_bytes = {
×
UNCOV
1142
            let mut bytes = self.config.burnchain.magic_bytes.as_bytes().to_vec();
×
UNCOV
1143
            payload
×
UNCOV
1144
                .consensus_serialize(&mut bytes)
×
UNCOV
1145
                .map_err(BurnchainControllerError::SerializerError)?;
×
UNCOV
1146
            bytes
×
1147
        };
1148

UNCOV
1149
        let consensus_output = TxOut {
×
UNCOV
1150
            value: 0,
×
UNCOV
1151
            script_pubkey: Builder::new()
×
UNCOV
1152
                .push_opcode(opcodes::All::OP_RETURN)
×
UNCOV
1153
                .push_slice(&op_bytes)
×
UNCOV
1154
                .into_script(),
×
UNCOV
1155
        };
×
1156

UNCOV
1157
        tx.output = vec![consensus_output];
×
1158

UNCOV
1159
        self.finalize_tx(
×
UNCOV
1160
            epoch_id,
×
UNCOV
1161
            &mut tx,
×
1162
            DUST_UTXO_LIMIT,
1163
            0,
UNCOV
1164
            max_tx_size,
×
UNCOV
1165
            get_satoshis_per_byte(&self.config),
×
UNCOV
1166
            &mut utxos,
×
UNCOV
1167
            signer,
×
1168
            false,
1169
        );
1170

UNCOV
1171
        increment_btc_ops_sent_counter();
×
1172

UNCOV
1173
        info!(
×
1174
            "Miner node: submitting vote for aggregate key op - {}",
UNCOV
1175
            public_key.to_hex()
×
1176
        );
1177

UNCOV
1178
        Ok(tx)
×
UNCOV
1179
    }
×
1180

1181
    #[cfg(not(test))]
1182
    /// Build a vote-for-aggregate-key burn op tx
1183
    fn build_vote_for_aggregate_key_tx(
1184
        &mut self,
1185
        _epoch_id: StacksEpochId,
1186
        _payload: VoteForAggregateKeyOp,
1187
        _signer: &mut BurnchainOpSigner,
1188
        _utxo_to_use: Option<UTXO>,
1189
    ) -> Result<Transaction, BurnchainControllerError> {
1190
        unimplemented!()
1191
    }
1192

1193
    #[cfg(not(test))]
1194
    fn build_pre_stacks_tx(
1195
        &mut self,
1196
        _epoch_id: StacksEpochId,
1197
        _payload: PreStxOp,
1198
        _signer: &mut BurnchainOpSigner,
1199
    ) -> Result<Transaction, BurnchainControllerError> {
1200
        unimplemented!()
1201
    }
1202

1203
    #[cfg(test)]
UNCOV
1204
    fn build_pre_stacks_tx(
×
UNCOV
1205
        &mut self,
×
UNCOV
1206
        epoch_id: StacksEpochId,
×
UNCOV
1207
        payload: PreStxOp,
×
UNCOV
1208
        signer: &mut BurnchainOpSigner,
×
UNCOV
1209
    ) -> Result<Transaction, BurnchainControllerError> {
×
UNCOV
1210
        let public_key = signer.get_public_key();
×
UNCOV
1211
        let max_tx_size = OP_TX_PRE_STACKS_ESTIM_SIZE;
×
1212

UNCOV
1213
        let max_tx_size_any_op = OP_TX_ANY_ESTIM_SIZE;
×
UNCOV
1214
        let output_amt = DUST_UTXO_LIMIT + max_tx_size_any_op * get_satoshis_per_byte(&self.config);
×
1215

UNCOV
1216
        let (mut tx, mut utxos) =
×
UNCOV
1217
            self.prepare_tx(epoch_id, &public_key, output_amt, None, None, 0)?;
×
1218

1219
        // Serialize the payload
UNCOV
1220
        let op_bytes = {
×
UNCOV
1221
            let mut bytes = self.config.burnchain.magic_bytes.as_bytes().to_vec();
×
UNCOV
1222
            bytes.push(Opcodes::PreStx as u8);
×
UNCOV
1223
            bytes
×
1224
        };
1225

UNCOV
1226
        let consensus_output = TxOut {
×
UNCOV
1227
            value: 0,
×
UNCOV
1228
            script_pubkey: Builder::new()
×
UNCOV
1229
                .push_opcode(opcodes::All::OP_RETURN)
×
UNCOV
1230
                .push_slice(&op_bytes)
×
UNCOV
1231
                .into_script(),
×
UNCOV
1232
        };
×
1233

UNCOV
1234
        tx.output = vec![consensus_output];
×
UNCOV
1235
        tx.output
×
UNCOV
1236
            .push(PoxAddress::Standard(payload.output, None).to_bitcoin_tx_out(output_amt));
×
1237

UNCOV
1238
        self.finalize_tx(
×
UNCOV
1239
            epoch_id,
×
UNCOV
1240
            &mut tx,
×
UNCOV
1241
            output_amt,
×
1242
            0,
UNCOV
1243
            max_tx_size,
×
UNCOV
1244
            get_satoshis_per_byte(&self.config),
×
UNCOV
1245
            &mut utxos,
×
UNCOV
1246
            signer,
×
1247
            false,
1248
        );
1249

UNCOV
1250
        increment_btc_ops_sent_counter();
×
1251

UNCOV
1252
        info!(
×
1253
            "Miner node: submitting pre_stacks op - {}",
UNCOV
1254
            public_key.to_hex()
×
1255
        );
1256

UNCOV
1257
        Ok(tx)
×
UNCOV
1258
    }
×
1259

1260
    #[cfg_attr(test, mutants::skip)]
1261
    #[cfg(not(test))]
1262
    fn build_stack_stx_tx(
1263
        &mut self,
1264
        _epoch_id: StacksEpochId,
1265
        _payload: StackStxOp,
1266
        _signer: &mut BurnchainOpSigner,
1267
        _utxo_to_use: Option<UTXO>,
1268
    ) -> Result<Transaction, BurnchainControllerError> {
1269
        unimplemented!()
1270
    }
1271

1272
    #[cfg(test)]
UNCOV
1273
    fn build_stack_stx_tx(
×
UNCOV
1274
        &mut self,
×
UNCOV
1275
        epoch_id: StacksEpochId,
×
UNCOV
1276
        payload: StackStxOp,
×
UNCOV
1277
        signer: &mut BurnchainOpSigner,
×
UNCOV
1278
        utxo_to_use: Option<UTXO>,
×
UNCOV
1279
    ) -> Result<Transaction, BurnchainControllerError> {
×
UNCOV
1280
        let public_key = signer.get_public_key();
×
UNCOV
1281
        let max_tx_size = OP_TX_STACK_STX_ESTIM_SIZE;
×
1282

UNCOV
1283
        let (mut tx, mut utxos) = if let Some(utxo) = utxo_to_use {
×
1284
            (
×
1285
                Transaction {
×
1286
                    input: vec![],
×
1287
                    output: vec![],
×
1288
                    version: 1,
×
1289
                    lock_time: 0,
×
1290
                },
×
1291
                UTXOSet {
×
1292
                    bhh: BurnchainHeaderHash::zero(),
×
1293
                    utxos: vec![utxo],
×
1294
                },
×
1295
            )
×
1296
        } else {
UNCOV
1297
            self.prepare_tx(
×
UNCOV
1298
                epoch_id,
×
UNCOV
1299
                &public_key,
×
UNCOV
1300
                DUST_UTXO_LIMIT + max_tx_size * get_satoshis_per_byte(&self.config),
×
UNCOV
1301
                None,
×
UNCOV
1302
                None,
×
1303
                0,
1304
            )?
×
1305
        };
1306

1307
        // Serialize the payload
UNCOV
1308
        let op_bytes = {
×
UNCOV
1309
            let mut bytes = self.config.burnchain.magic_bytes.as_bytes().to_vec();
×
UNCOV
1310
            payload
×
UNCOV
1311
                .consensus_serialize(&mut bytes)
×
UNCOV
1312
                .map_err(BurnchainControllerError::SerializerError)?;
×
UNCOV
1313
            bytes
×
1314
        };
1315

UNCOV
1316
        let consensus_output = TxOut {
×
UNCOV
1317
            value: 0,
×
UNCOV
1318
            script_pubkey: Builder::new()
×
UNCOV
1319
                .push_opcode(opcodes::All::OP_RETURN)
×
UNCOV
1320
                .push_slice(&op_bytes)
×
UNCOV
1321
                .into_script(),
×
UNCOV
1322
        };
×
1323

UNCOV
1324
        tx.output = vec![consensus_output];
×
UNCOV
1325
        tx.output
×
UNCOV
1326
            .push(payload.reward_addr.to_bitcoin_tx_out(DUST_UTXO_LIMIT));
×
1327

UNCOV
1328
        self.finalize_tx(
×
UNCOV
1329
            epoch_id,
×
UNCOV
1330
            &mut tx,
×
1331
            DUST_UTXO_LIMIT,
1332
            0,
UNCOV
1333
            max_tx_size,
×
UNCOV
1334
            get_satoshis_per_byte(&self.config),
×
UNCOV
1335
            &mut utxos,
×
UNCOV
1336
            signer,
×
1337
            false,
1338
        );
1339

UNCOV
1340
        increment_btc_ops_sent_counter();
×
1341

UNCOV
1342
        info!(
×
1343
            "Miner node: submitting stack-stx op - {}",
UNCOV
1344
            public_key.to_hex()
×
1345
        );
1346

UNCOV
1347
        Ok(tx)
×
UNCOV
1348
    }
×
1349

1350
    fn magic_bytes(&self) -> Vec<u8> {
1✔
1351
        #[cfg(test)]
1352
        {
1353
            if let Some(set_bytes) = *TEST_MAGIC_BYTES
1✔
1354
                .lock()
1✔
1355
                .expect("FATAL: test magic bytes mutex poisoned")
1✔
1356
            {
UNCOV
1357
                return set_bytes.to_vec();
×
1358
            }
1✔
1359
        }
1360
        self.config.burnchain.magic_bytes.as_bytes().to_vec()
1✔
1361
    }
1✔
1362

1363
    #[allow(clippy::too_many_arguments)]
UNCOV
1364
    fn send_block_commit_operation(
×
UNCOV
1365
        &mut self,
×
UNCOV
1366
        epoch_id: StacksEpochId,
×
UNCOV
1367
        payload: LeaderBlockCommitOp,
×
UNCOV
1368
        signer: &mut BurnchainOpSigner,
×
UNCOV
1369
        utxos_to_include: Option<UTXOSet>,
×
UNCOV
1370
        utxos_to_exclude: Option<UTXOSet>,
×
UNCOV
1371
        previous_fees: Option<LeaderBlockCommitFees>,
×
UNCOV
1372
        previous_txids: &[Txid],
×
UNCOV
1373
    ) -> Result<Transaction, BurnchainControllerError> {
×
UNCOV
1374
        let _ = self.sortdb_mut();
×
UNCOV
1375
        let burn_chain_tip = self
×
UNCOV
1376
            .burnchain_db
×
UNCOV
1377
            .as_ref()
×
UNCOV
1378
            .ok_or(BurnchainControllerError::BurnchainError)?
×
UNCOV
1379
            .get_canonical_chain_tip()
×
UNCOV
1380
            .map_err(|_| BurnchainControllerError::BurnchainError)?;
×
UNCOV
1381
        let estimated_fees = match previous_fees {
×
UNCOV
1382
            Some(fees) => fees.fees_from_previous_tx(&payload, &self.config),
×
UNCOV
1383
            None => LeaderBlockCommitFees::estimated_fees_from_payload(&payload, &self.config),
×
1384
        };
1385

UNCOV
1386
        self.send_block_commit_operation_at_burnchain_height(
×
UNCOV
1387
            epoch_id,
×
UNCOV
1388
            payload,
×
UNCOV
1389
            signer,
×
UNCOV
1390
            utxos_to_include,
×
UNCOV
1391
            utxos_to_exclude,
×
UNCOV
1392
            estimated_fees,
×
UNCOV
1393
            previous_txids,
×
UNCOV
1394
            burn_chain_tip.block_height,
×
1395
        )
UNCOV
1396
    }
×
1397

1398
    #[allow(clippy::too_many_arguments)]
1399
    fn send_block_commit_operation_at_burnchain_height(
1✔
1400
        &mut self,
1✔
1401
        epoch_id: StacksEpochId,
1✔
1402
        payload: LeaderBlockCommitOp,
1✔
1403
        signer: &mut BurnchainOpSigner,
1✔
1404
        utxos_to_include: Option<UTXOSet>,
1✔
1405
        utxos_to_exclude: Option<UTXOSet>,
1✔
1406
        mut estimated_fees: LeaderBlockCommitFees,
1✔
1407
        previous_txids: &[Txid],
1✔
1408
        burnchain_block_height: u64,
1✔
1409
    ) -> Result<Transaction, BurnchainControllerError> {
1✔
1410
        let public_key = signer.get_public_key();
1✔
1411
        let (mut tx, mut utxos) = self.prepare_tx(
1✔
1412
            epoch_id,
1✔
1413
            &public_key,
1✔
1414
            estimated_fees.estimated_amount_required(),
1✔
1415
            utxos_to_include,
1✔
1416
            utxos_to_exclude,
1✔
1417
            burnchain_block_height,
1✔
UNCOV
1418
        )?;
×
1419

1420
        // Serialize the payload
1421
        let op_bytes = {
1✔
1422
            let mut buffer = vec![];
1✔
1423
            let mut magic_bytes = self.magic_bytes();
1✔
1424
            buffer.append(&mut magic_bytes);
1✔
1425
            payload
1✔
1426
                .consensus_serialize(&mut buffer)
1✔
1427
                .expect("FATAL: invalid operation");
1✔
1428
            buffer
1✔
1429
        };
1430

1431
        let consensus_output = TxOut {
1✔
1432
            value: estimated_fees.sunset_fee,
1✔
1433
            script_pubkey: Builder::new()
1✔
1434
                .push_opcode(opcodes::All::OP_RETURN)
1✔
1435
                .push_slice(&op_bytes)
1✔
1436
                .into_script(),
1✔
1437
        };
1✔
1438

1439
        tx.output = vec![consensus_output];
1✔
1440

1441
        for commit_to in payload.commit_outs.iter() {
2✔
1442
            tx.output
2✔
1443
                .push(commit_to.to_bitcoin_tx_out(estimated_fees.amount_per_output()));
2✔
1444
        }
2✔
1445

1446
        let fee_rate = estimated_fees.fee_rate;
1✔
1447
        self.finalize_tx(
1✔
1448
            epoch_id,
1✔
1449
            &mut tx,
1✔
1450
            estimated_fees.total_spent_in_outputs(),
1✔
1451
            estimated_fees.spent_in_attempts,
1✔
1452
            estimated_fees.min_tx_size(),
1✔
1453
            fee_rate,
1✔
1454
            &mut utxos,
1✔
1455
            signer,
1✔
1456
            true, // block commit op requires change output to exist
1457
        );
1458
        debug!("Transaction relying on UTXOs: {utxos:?}");
1✔
1459

1460
        let serialized_tx = serialize(&tx).expect("BUG: failed to serialize to a vec");
1✔
1461
        let tx_size = serialized_tx.len() as u64;
1✔
1462
        estimated_fees.register_replacement(tx_size);
1✔
1463

1464
        let txid = Txid::from_bitcoin_tx_hash(&tx.txid());
1✔
1465
        let mut txids = previous_txids.to_vec();
1✔
1466
        txids.push(txid.clone());
1✔
1467
        let ongoing_block_commit = OngoingBlockCommit {
1✔
1468
            payload,
1✔
1469
            utxos,
1✔
1470
            fees: estimated_fees,
1✔
1471
            txids,
1✔
1472
        };
1✔
1473

1474
        info!(
1✔
1475
            "Miner node: submitting leader_block_commit (txid: {}, rbf: {}, total spent: {}, size: {}, fee_rate: {fee_rate})",
1476
            txid.to_hex(),
1✔
1477
            ongoing_block_commit.fees.is_rbf_enabled,
1478
            ongoing_block_commit.fees.total_spent(),
1✔
1479
            ongoing_block_commit.fees.final_size
1480
        );
1481

1482
        self.ongoing_block_commit = Some(ongoing_block_commit);
1✔
1483

1484
        increment_btc_ops_sent_counter();
1✔
1485

1486
        Ok(tx)
1✔
1487
    }
1✔
1488

UNCOV
1489
    fn build_leader_block_commit_tx(
×
UNCOV
1490
        &mut self,
×
UNCOV
1491
        epoch_id: StacksEpochId,
×
UNCOV
1492
        payload: LeaderBlockCommitOp,
×
UNCOV
1493
        signer: &mut BurnchainOpSigner,
×
UNCOV
1494
    ) -> Result<Transaction, BurnchainControllerError> {
×
1495
        // Are we currently tracking an operation?
UNCOV
1496
        if self.ongoing_block_commit.is_none() {
×
1497
            // Good to go, let's build the transaction and send it.
UNCOV
1498
            let res =
×
UNCOV
1499
                self.send_block_commit_operation(epoch_id, payload, signer, None, None, None, &[]);
×
UNCOV
1500
            return res;
×
UNCOV
1501
        }
×
1502

UNCOV
1503
        let ongoing_op = self.ongoing_block_commit.take().unwrap();
×
1504

UNCOV
1505
        let _ = self.sortdb_mut();
×
UNCOV
1506
        let burnchain_db = self.burnchain_db.as_ref().expect("BurnchainDB not opened");
×
1507

UNCOV
1508
        for txid in ongoing_op.txids.iter() {
×
1509
            // check if ongoing_op is in the burnchain_db *or* has been confirmed via the bitcoin RPC
UNCOV
1510
            let mined_op = burnchain_db.find_burnchain_op(&self.indexer, txid);
×
UNCOV
1511
            let ongoing_tx_confirmed = mined_op.is_some() || self.is_transaction_confirmed(txid);
×
1512

UNCOV
1513
            test_debug!("Ongoing Tx confirmed: {ongoing_tx_confirmed} - TXID: {txid}");
×
UNCOV
1514
            if ongoing_tx_confirmed {
×
UNCOV
1515
                if ongoing_op.payload == payload {
×
UNCOV
1516
                    info!("Abort attempt to re-submit confirmed LeaderBlockCommit");
×
UNCOV
1517
                    self.ongoing_block_commit = Some(ongoing_op);
×
UNCOV
1518
                    return Err(BurnchainControllerError::IdenticalOperation);
×
UNCOV
1519
                }
×
1520

UNCOV
1521
                debug!("Was able to retrieve confirmation of ongoing burnchain TXID - {txid}");
×
UNCOV
1522
                let res = self.send_block_commit_operation(
×
UNCOV
1523
                    epoch_id,
×
UNCOV
1524
                    payload,
×
UNCOV
1525
                    signer,
×
UNCOV
1526
                    None,
×
UNCOV
1527
                    None,
×
UNCOV
1528
                    None,
×
UNCOV
1529
                    &[],
×
1530
                );
UNCOV
1531
                return res;
×
1532
            } else {
UNCOV
1533
                debug!("Was unable to retrieve ongoing TXID - {txid}");
×
1534
            };
1535
        }
1536

1537
        // Did a re-org occur since we fetched our UTXOs, or are the UTXOs so stale that they should be abandoned?
UNCOV
1538
        let mut traversal_depth = 0;
×
UNCOV
1539
        let mut burn_chain_tip = burnchain_db
×
UNCOV
1540
            .get_canonical_chain_tip()
×
UNCOV
1541
            .map_err(|_| BurnchainControllerError::BurnchainError)?;
×
UNCOV
1542
        let mut found_last_mined_at = false;
×
UNCOV
1543
        while traversal_depth < UTXO_CACHE_STALENESS_LIMIT {
×
UNCOV
1544
            if burn_chain_tip.block_hash == ongoing_op.utxos.bhh {
×
UNCOV
1545
                found_last_mined_at = true;
×
UNCOV
1546
                break;
×
UNCOV
1547
            }
×
1548

UNCOV
1549
            let parent = BurnchainDB::get_burnchain_block(
×
UNCOV
1550
                burnchain_db.conn(),
×
UNCOV
1551
                &burn_chain_tip.parent_block_hash,
×
1552
            )
UNCOV
1553
            .map_err(|_| BurnchainControllerError::BurnchainError)?;
×
1554

UNCOV
1555
            burn_chain_tip = parent.header;
×
UNCOV
1556
            traversal_depth += 1;
×
1557
        }
1558

UNCOV
1559
        if !found_last_mined_at {
×
UNCOV
1560
            info!(
×
1561
                "Possible presence of fork or stale UTXO cache, invalidating cached set of UTXOs.";
1562
                "cached_burn_block_hash" => %ongoing_op.utxos.bhh,
1563
            );
UNCOV
1564
            let res =
×
UNCOV
1565
                self.send_block_commit_operation(epoch_id, payload, signer, None, None, None, &[]);
×
UNCOV
1566
            return res;
×
UNCOV
1567
        }
×
1568

1569
        // Stop as soon as the fee_rate is ${self.config.burnchain.max_rbf} percent higher, stop RBF
UNCOV
1570
        if ongoing_op.fees.fee_rate
×
UNCOV
1571
            > (get_satoshis_per_byte(&self.config) * get_max_rbf(&self.config) / 100)
×
1572
        {
1573
            warn!(
×
1574
                "RBF'd block commits reached {}% satoshi per byte fee rate, not resubmitting",
1575
                get_max_rbf(&self.config)
×
1576
            );
1577
            self.ongoing_block_commit = Some(ongoing_op);
×
1578
            return Err(BurnchainControllerError::MaxFeeRateExceeded);
×
UNCOV
1579
        }
×
1580

1581
        // An ongoing operation is in the mempool and we received a new block. The desired behaviour is the following:
1582
        // (1) If the ongoing and the incoming operation are **strictly** identical, we will be idempotent and discard the incoming.
1583
        // (2) If the 2 operations are different, attempt to RBF the outgoing transaction:
1584

1585
        // Let's start by early returning (1)
UNCOV
1586
        if payload == ongoing_op.payload {
×
UNCOV
1587
            info!("Abort attempt to re-submit identical LeaderBlockCommit");
×
UNCOV
1588
            self.ongoing_block_commit = Some(ongoing_op);
×
UNCOV
1589
            return Err(BurnchainControllerError::IdenticalOperation);
×
UNCOV
1590
        }
×
1591

1592
        // If we reach this point, we are attempting to RBF the ongoing operation (2)
UNCOV
1593
        info!(
×
1594
            "Attempt to replace by fee an outdated leader block commit";
1595
            "ongoing_txids" => ?ongoing_op.txids
1596
        );
UNCOV
1597
        let res = self.send_block_commit_operation(
×
UNCOV
1598
            epoch_id,
×
UNCOV
1599
            payload,
×
UNCOV
1600
            signer,
×
UNCOV
1601
            Some(ongoing_op.utxos.clone()),
×
UNCOV
1602
            None,
×
UNCOV
1603
            Some(ongoing_op.fees.clone()),
×
UNCOV
1604
            &ongoing_op.txids,
×
1605
        );
1606

UNCOV
1607
        if res.is_err() {
×
1608
            self.ongoing_block_commit = Some(ongoing_op);
×
UNCOV
1609
        }
×
1610

UNCOV
1611
        res
×
UNCOV
1612
    }
×
1613

1614
    pub(crate) fn get_miner_address(
4✔
1615
        &self,
4✔
1616
        epoch_id: StacksEpochId,
4✔
1617
        public_key: &Secp256k1PublicKey,
4✔
1618
    ) -> BitcoinAddress {
4✔
1619
        let (_, network_id) = self.config.burnchain.get_bitcoin_network();
4✔
1620

1621
        if self.config.miner.segwit && epoch_id >= StacksEpochId::Epoch21 {
4✔
1622
            let hash160 = Hash160::from_data(&public_key.to_bytes_compressed());
1✔
1623
            BitcoinAddress::from_bytes_segwit_p2wpkh(network_id, &hash160.0)
1✔
1624
                .expect("Public key incorrect")
1✔
1625
        } else {
1626
            let hash160 = Hash160::from_data(&public_key.to_bytes());
3✔
1627
            BitcoinAddress::from_bytes_legacy(
3✔
1628
                network_id,
3✔
1629
                LegacyBitcoinAddressType::PublicKeyHash,
3✔
1630
                &hash160.0,
3✔
1631
            )
1632
            .expect("Public key incorrect")
3✔
1633
        }
1634
    }
4✔
1635

1636
    // TODO: add tests from mutation testing results #4865
1637
    #[cfg_attr(test, mutants::skip)]
1638
    fn prepare_tx(
1✔
1639
        &mut self,
1✔
1640
        epoch_id: StacksEpochId,
1✔
1641
        public_key: &Secp256k1PublicKey,
1✔
1642
        total_required: u64,
1✔
1643
        utxos_to_include: Option<UTXOSet>,
1✔
1644
        utxos_to_exclude: Option<UTXOSet>,
1✔
1645
        block_height: u64,
1✔
1646
    ) -> Result<(Transaction, UTXOSet), BurnchainControllerError> {
1✔
1647
        let utxos = if let Some(utxos) = utxos_to_include {
1✔
1648
            // in RBF, you have to consume the same UTXOs
1649
            utxos
1✔
1650
        } else {
1651
            // if mock mining, do not even bother requesting UTXOs
UNCOV
1652
            if self.config.node.mock_mining {
×
UNCOV
1653
                return Err(BurnchainControllerError::NoUTXOs);
×
UNCOV
1654
            }
×
1655

1656
            // Fetch some UTXOs
UNCOV
1657
            let addr = self.get_miner_address(epoch_id, public_key);
×
UNCOV
1658
            match self.get_utxos(
×
UNCOV
1659
                epoch_id,
×
UNCOV
1660
                public_key,
×
UNCOV
1661
                total_required,
×
UNCOV
1662
                utxos_to_exclude,
×
UNCOV
1663
                block_height,
×
UNCOV
1664
            ) {
×
UNCOV
1665
                Some(utxos) => utxos,
×
1666
                None => {
UNCOV
1667
                    warn!(
×
1668
                        "No UTXOs for {} ({addr}) in epoch {epoch_id}",
UNCOV
1669
                        &public_key.to_hex(),
×
1670
                    );
UNCOV
1671
                    return Err(BurnchainControllerError::NoUTXOs);
×
1672
                }
1673
            }
1674
        };
1675

1676
        // Prepare a backbone for the tx
1677
        let transaction = Transaction {
1✔
1678
            input: vec![],
1✔
1679
            output: vec![],
1✔
1680
            version: 1,
1✔
1681
            lock_time: 0,
1✔
1682
        };
1✔
1683

1684
        Ok((transaction, utxos))
1✔
1685
    }
1✔
1686

1687
    #[allow(clippy::too_many_arguments)]
1688
    fn finalize_tx(
1✔
1689
        &mut self,
1✔
1690
        epoch_id: StacksEpochId,
1✔
1691
        tx: &mut Transaction,
1✔
1692
        spent_in_outputs: u64,
1✔
1693
        spent_in_rbf: u64,
1✔
1694
        min_tx_size: u64,
1✔
1695
        fee_rate: u64,
1✔
1696
        utxos_set: &mut UTXOSet,
1✔
1697
        signer: &mut BurnchainOpSigner,
1✔
1698
        force_change_output: bool,
1✔
1699
    ) {
1✔
1700
        // spend UTXOs in order by confirmations.  Spend the least-confirmed UTXO first, and in the
1701
        // event of a tie, spend the smallest-value UTXO first.
1702
        utxos_set.utxos.sort_by(|u1, u2| {
1✔
1703
            if u1.confirmations != u2.confirmations {
1✔
UNCOV
1704
                u1.confirmations.cmp(&u2.confirmations)
×
1705
            } else {
1706
                // for block-commits, the smaller value is likely the UTXO-chained value, so
1707
                // continue to prioritize it as the first spend in order to avoid breaking the
1708
                // miner commit chain.
1709
                u1.amount.cmp(&u2.amount)
1✔
1710
            }
1711
        });
1✔
1712

1713
        let tx_size = {
1✔
1714
            // We will be calling 2 times serialize_tx, the first time with an estimated size,
1715
            // Second time with the actual size, computed thanks to the 1st attempt.
1716
            let estimated_rbf = if spent_in_rbf == 0 {
1✔
1717
                0
1✔
1718
            } else {
UNCOV
1719
                spent_in_rbf + min_tx_size // we're spending 1 sat / byte in RBF
×
1720
            };
1721
            let mut tx_cloned = tx.clone();
1✔
1722
            let mut utxos_cloned = utxos_set.clone();
1✔
1723
            self.serialize_tx(
1✔
1724
                epoch_id,
1✔
1725
                &mut tx_cloned,
1✔
1726
                spent_in_outputs + min_tx_size * fee_rate + estimated_rbf,
1✔
1727
                &mut utxos_cloned,
1✔
1728
                signer,
1✔
1729
                force_change_output,
1✔
1730
            );
1731
            let serialized_tx = serialize(&tx_cloned).expect("BUG: failed to serialize to a vec");
1✔
1732
            cmp::max(min_tx_size, serialized_tx.len() as u64)
1✔
1733
        };
1734

1735
        let rbf_fee = if spent_in_rbf == 0 {
1✔
1736
            0
1✔
1737
        } else {
UNCOV
1738
            spent_in_rbf + tx_size // we're spending 1 sat / byte in RBF
×
1739
        };
1740
        self.serialize_tx(
1✔
1741
            epoch_id,
1✔
1742
            tx,
1✔
1743
            spent_in_outputs + tx_size * fee_rate + rbf_fee,
1✔
1744
            utxos_set,
1✔
1745
            signer,
1✔
1746
            force_change_output,
1✔
1747
        );
1748
        signer.dispose();
1✔
1749
    }
1✔
1750

1751
    /// Sign and serialize a tx, consuming the UTXOs in utxo_set and spending total_to_spend
1752
    /// satoshis.  Uses the key in signer.
1753
    /// If self.config.miner.segwit is true, the transaction's change address will be a p2wpkh
1754
    /// output. Otherwise, it will be a p2pkh output.
1755
    fn serialize_tx(
3✔
1756
        &mut self,
3✔
1757
        epoch_id: StacksEpochId,
3✔
1758
        tx: &mut Transaction,
3✔
1759
        tx_cost: u64,
3✔
1760
        utxos_set: &mut UTXOSet,
3✔
1761
        signer: &mut BurnchainOpSigner,
3✔
1762
        force_change_output: bool,
3✔
1763
    ) -> bool {
3✔
1764
        let mut public_key = signer.get_public_key();
3✔
1765

1766
        let total_target = if force_change_output {
3✔
1767
            tx_cost + DUST_UTXO_LIMIT
3✔
1768
        } else {
UNCOV
1769
            tx_cost
×
1770
        };
1771

1772
        // select UTXOs until we have enough to cover the cost
1773
        let mut total_consumed = 0;
3✔
1774
        let mut available_utxos = vec![];
3✔
1775
        available_utxos.append(&mut utxos_set.utxos);
3✔
1776
        for utxo in available_utxos.into_iter() {
6✔
1777
            total_consumed += utxo.amount;
6✔
1778
            utxos_set.utxos.push(utxo);
6✔
1779

1780
            if total_consumed >= total_target {
6✔
1781
                break;
3✔
1782
            }
3✔
1783
        }
1784

1785
        if total_consumed < total_target {
3✔
1786
            warn!("Consumed total {total_consumed} is less than intended spend: {total_target}");
×
1787
            return false;
×
1788
        }
3✔
1789

1790
        // Append the change output
1791
        let value = total_consumed - tx_cost;
3✔
1792
        debug!(
3✔
1793
            "Payments value: {value:?}, total_consumed: {total_consumed:?}, total_spent: {total_target:?}"
1794
        );
1795
        if value >= DUST_UTXO_LIMIT {
3✔
1796
            let change_output = if self.config.miner.segwit && epoch_id >= StacksEpochId::Epoch21 {
3✔
1797
                // p2wpkh
1798
                public_key.set_compressed(true);
×
1799
                let change_address_hash = Hash160::from_data(&public_key.to_bytes());
×
1800
                SegwitBitcoinAddress::to_p2wpkh_tx_out(&change_address_hash.0, value)
×
1801
            } else {
1802
                // p2pkh
1803
                let change_address_hash = Hash160::from_data(&public_key.to_bytes());
3✔
1804
                LegacyBitcoinAddress::to_p2pkh_tx_out(&change_address_hash, value)
3✔
1805
            };
1806
            tx.output.push(change_output);
3✔
1807
        } else {
1808
            // Instead of leaving that change to the BTC miner, we could / should bump the sortition fee
UNCOV
1809
            debug!("Not enough change to clear dust limit. Not adding change address.");
×
1810
        }
1811

1812
        for utxo in utxos_set.utxos.iter() {
6✔
1813
            let input = TxIn {
6✔
1814
                previous_output: OutPoint {
6✔
1815
                    txid: utxo.txid.clone(),
6✔
1816
                    vout: utxo.vout,
6✔
1817
                },
6✔
1818
                script_sig: Script::new(),
6✔
1819
                sequence: 0xFFFFFFFD, // allow RBF
6✔
1820
                witness: vec![],
6✔
1821
            };
6✔
1822
            tx.input.push(input);
6✔
1823
        }
6✔
1824
        for (i, utxo) in utxos_set.utxos.iter().enumerate() {
6✔
1825
            let script_pub_key = utxo.script_pub_key.clone();
6✔
1826
            let sig_hash_all = 0x01;
6✔
1827

1828
            let (sig_hash, is_segwit) = if script_pub_key.as_bytes().len() == 22
6✔
1829
                && script_pub_key.as_bytes()[0..2] == [0x00, 0x14]
×
1830
            {
1831
                // p2wpkh
1832
                (
×
1833
                    tx.segwit_signature_hash(i, &script_pub_key, utxo.amount, sig_hash_all),
×
1834
                    true,
×
1835
                )
×
1836
            } else {
1837
                // p2pkh
1838
                (tx.signature_hash(i, &script_pub_key, sig_hash_all), false)
6✔
1839
            };
1840

1841
            let sig1_der = {
6✔
1842
                let message = signer
6✔
1843
                    .sign_message(sig_hash.as_bytes())
6✔
1844
                    .expect("Unable to sign message");
6✔
1845
                message
6✔
1846
                    .to_secp256k1_recoverable()
6✔
1847
                    .expect("Unable to get recoverable signature")
6✔
1848
                    .to_standard()
6✔
1849
                    .serialize_der()
6✔
1850
            };
1851

1852
            if is_segwit {
6✔
1853
                // segwit
×
1854
                public_key.set_compressed(true);
×
1855
                tx.input[i].script_sig = Script::from(vec![]);
×
1856
                tx.input[i].witness = vec![
×
1857
                    [&*sig1_der, &[sig_hash_all as u8][..]].concat().to_vec(),
×
1858
                    public_key.to_bytes(),
×
1859
                ];
×
1860
            } else {
6✔
1861
                // legacy scriptSig
6✔
1862
                tx.input[i].script_sig = Builder::new()
6✔
1863
                    .push_slice(&[&*sig1_der, &[sig_hash_all as u8][..]].concat())
6✔
1864
                    .push_slice(&public_key.to_bytes())
6✔
1865
                    .into_script();
6✔
1866
                tx.input[i].witness.clear();
6✔
1867
            }
6✔
1868
        }
1869
        true
3✔
1870
    }
3✔
1871

1872
    /// Broadcast a signed raw [`Transaction`] to the underlying Bitcoin node.
1873
    ///
1874
    /// The transaction is submitted with following parameters:
1875
    /// - `max_fee_rate = 0.0` (uncapped, accept any fee rate),
1876
    /// - `max_burn_amount = 1_000_000` (in sats).
1877
    ///
1878
    /// # Arguments
1879
    /// * `transaction` - A fully signed raw [`Transaction`] to broadcast.
1880
    ///
1881
    /// # Returns
1882
    /// On success, returns the [`Txid`] of the broadcasted transaction.
UNCOV
1883
    pub fn send_transaction(&self, tx: &Transaction) -> Result<Txid, BurnchainControllerError> {
×
UNCOV
1884
        debug!(
×
1885
            "Sending raw transaction: {}",
1886
            serialize_hex(tx).unwrap_or("SERIALIZATION FAILED".to_string())
×
1887
        );
1888

1889
        const UNCAPPED_FEE: f64 = 0.0;
1890
        const MAX_BURN_AMOUNT: u64 = 1_000_000;
UNCOV
1891
        self.get_rpc_client()
×
UNCOV
1892
            .send_raw_transaction(tx, Some(UNCAPPED_FEE), Some(MAX_BURN_AMOUNT))
×
UNCOV
1893
            .map(|txid| {
×
UNCOV
1894
                debug!("Transaction {txid} sent successfully");
×
UNCOV
1895
                txid
×
UNCOV
1896
            })
×
UNCOV
1897
            .map_err(|e| {
×
1898
                error!("Bitcoin RPC error: transaction submission failed - {e:?}");
×
1899
                BurnchainControllerError::TransactionSubmissionFailed(format!("{e:?}"))
×
1900
            })
×
UNCOV
1901
    }
×
1902

1903
    /// wait until the ChainsCoordinator has processed sortitions up to
1904
    /// height_to_wait
UNCOV
1905
    pub fn wait_for_sortitions(
×
UNCOV
1906
        &self,
×
UNCOV
1907
        coord_comms: CoordinatorChannels,
×
UNCOV
1908
        height_to_wait: u64,
×
UNCOV
1909
    ) -> Result<BurnchainTip, BurnchainControllerError> {
×
UNCOV
1910
        let mut debug_ctr = 0;
×
1911
        loop {
UNCOV
1912
            let canonical_sortition_tip =
×
UNCOV
1913
                SortitionDB::get_canonical_burn_chain_tip(self.sortdb_ref().conn()).unwrap();
×
1914

UNCOV
1915
            if debug_ctr % 10 == 0 {
×
UNCOV
1916
                debug!(
×
1917
                    "Waiting until canonical sortition height reaches {height_to_wait} (currently {})",
1918
                    canonical_sortition_tip.block_height
1919
                );
UNCOV
1920
            }
×
UNCOV
1921
            debug_ctr += 1;
×
1922

UNCOV
1923
            if canonical_sortition_tip.block_height >= height_to_wait {
×
UNCOV
1924
                let (_, state_transition) = self
×
UNCOV
1925
                    .sortdb_ref()
×
UNCOV
1926
                    .get_sortition_result(&canonical_sortition_tip.sortition_id)
×
UNCOV
1927
                    .expect("Sortition DB error.")
×
UNCOV
1928
                    .expect("BUG: no data for the canonical chain tip");
×
1929

UNCOV
1930
                return Ok(BurnchainTip {
×
UNCOV
1931
                    block_snapshot: canonical_sortition_tip,
×
UNCOV
1932
                    received_at: Instant::now(),
×
UNCOV
1933
                    state_transition,
×
UNCOV
1934
                });
×
UNCOV
1935
            }
×
1936

UNCOV
1937
            if !self.should_keep_running() {
×
UNCOV
1938
                return Err(BurnchainControllerError::CoordinatorClosed);
×
UNCOV
1939
            }
×
1940

1941
            // help the chains coordinator along
UNCOV
1942
            coord_comms.announce_new_burn_block();
×
UNCOV
1943
            coord_comms.announce_new_stacks_block();
×
1944

1945
            // yield some time
UNCOV
1946
            sleep_ms(1000);
×
1947
        }
UNCOV
1948
    }
×
1949

1950
    /// Instruct a regtest Bitcoin node to build the next block.
UNCOV
1951
    pub fn build_next_block(&self, num_blocks: u64) {
×
UNCOV
1952
        debug!("Generate {num_blocks} block(s)");
×
UNCOV
1953
        let public_key_bytes = match &self.config.burnchain.local_mining_public_key {
×
UNCOV
1954
            Some(public_key) => hex_bytes(public_key).expect("Invalid byte sequence"),
×
1955
            None => panic!("Unable to make new block, mining public key"),
×
1956
        };
1957

1958
        // NOTE: miner address is whatever the configured segwit setting is
UNCOV
1959
        let public_key = Secp256k1PublicKey::from_slice(&public_key_bytes)
×
UNCOV
1960
            .expect("FATAL: invalid public key bytes");
×
UNCOV
1961
        let address = self.get_miner_address(StacksEpochId::Epoch21, &public_key);
×
1962

UNCOV
1963
        let result = self
×
UNCOV
1964
            .get_rpc_client()
×
UNCOV
1965
            .generate_to_address(num_blocks, &address);
×
1966
        /*
1967
            Temporary: not using `BitcoinRpcClientResultExt::ok_or_log_panic` (test code related),
1968
            because we need this logic available outside `#[cfg(test)]` due to Helium network.
1969

1970
            After the Helium cleanup (https://github.com/stacks-network/stacks-core/issues/6408),
1971
            we can:
1972
              - move `build_next_block` behind `#[cfg(test)]`
1973
              - simplify this match by using `ok_or_log_panic`.
1974
        */
UNCOV
1975
        match result {
×
UNCOV
1976
            Ok(_) => {}
×
1977
            Err(e) => {
×
1978
                error!("Bitcoin RPC failure: error generating block {e:?}");
×
1979
                panic!();
×
1980
            }
1981
        }
UNCOV
1982
    }
×
1983

1984
    /// Instruct a regtest Bitcoin node to build an empty block.
1985
    #[cfg(test)]
UNCOV
1986
    pub fn build_empty_block(&self) {
×
UNCOV
1987
        info!("Generate empty block");
×
UNCOV
1988
        let public_key_bytes = match &self.config.burnchain.local_mining_public_key {
×
UNCOV
1989
            Some(public_key) => hex_bytes(public_key).expect("Invalid byte sequence"),
×
1990
            None => panic!("Unable to make new block, mining public key"),
×
1991
        };
1992

1993
        // NOTE: miner address is whatever the configured segwit setting is
UNCOV
1994
        let public_key = Secp256k1PublicKey::from_slice(&public_key_bytes)
×
UNCOV
1995
            .expect("FATAL: invalid public key bytes");
×
UNCOV
1996
        let address = self.get_miner_address(StacksEpochId::Epoch21, &public_key);
×
1997

UNCOV
1998
        self.get_rpc_client()
×
UNCOV
1999
            .generate_block(&address, &[])
×
UNCOV
2000
            .ok_or_log_panic("generating block")
×
UNCOV
2001
    }
×
2002

2003
    /// Invalidate a block given its hash as a [`BurnchainHeaderHash`].
2004
    #[cfg(test)]
UNCOV
2005
    pub fn invalidate_block(&self, block: &BurnchainHeaderHash) {
×
UNCOV
2006
        info!("Invalidating block {block}");
×
UNCOV
2007
        self.get_rpc_client()
×
UNCOV
2008
            .invalidate_block(block)
×
UNCOV
2009
            .ok_or_log_panic("invalidate block")
×
UNCOV
2010
    }
×
2011

2012
    /// Retrieve the hash (as a [`BurnchainHeaderHash`]) of the block at the given height.
2013
    #[cfg(test)]
UNCOV
2014
    pub fn get_block_hash(&self, height: u64) -> BurnchainHeaderHash {
×
UNCOV
2015
        self.get_rpc_client()
×
UNCOV
2016
            .get_block_hash(height)
×
UNCOV
2017
            .unwrap_or_log_panic("retrieve block")
×
UNCOV
2018
    }
×
2019

2020
    #[cfg(test)]
UNCOV
2021
    pub fn get_mining_pubkey(&self) -> Option<String> {
×
UNCOV
2022
        self.config.burnchain.local_mining_public_key.clone()
×
UNCOV
2023
    }
×
2024

2025
    #[cfg(test)]
2026
    pub fn set_mining_pubkey(&mut self, pubkey: String) -> Option<String> {
×
2027
        let old_key = self.config.burnchain.local_mining_public_key.take();
×
2028
        self.config.burnchain.local_mining_public_key = Some(pubkey);
×
2029
        old_key
×
2030
    }
×
2031

2032
    #[cfg(test)]
2033
    pub fn set_use_segwit(&mut self, segwit: bool) {
×
2034
        self.config.miner.segwit = segwit;
×
2035
    }
×
2036

2037
    // TODO: add tests from mutation testing results #4866
2038
    #[cfg_attr(test, mutants::skip)]
UNCOV
2039
    fn make_operation_tx(
×
UNCOV
2040
        &mut self,
×
UNCOV
2041
        epoch_id: StacksEpochId,
×
UNCOV
2042
        operation: BlockstackOperationType,
×
UNCOV
2043
        op_signer: &mut BurnchainOpSigner,
×
UNCOV
2044
    ) -> Result<Transaction, BurnchainControllerError> {
×
UNCOV
2045
        match operation {
×
UNCOV
2046
            BlockstackOperationType::LeaderBlockCommit(payload) => {
×
UNCOV
2047
                self.build_leader_block_commit_tx(epoch_id, payload, op_signer)
×
2048
            }
UNCOV
2049
            BlockstackOperationType::LeaderKeyRegister(payload) => {
×
UNCOV
2050
                self.build_leader_key_register_tx(epoch_id, payload, op_signer)
×
2051
            }
UNCOV
2052
            BlockstackOperationType::PreStx(payload) => {
×
UNCOV
2053
                self.build_pre_stacks_tx(epoch_id, payload, op_signer)
×
2054
            }
UNCOV
2055
            BlockstackOperationType::TransferStx(payload) => {
×
UNCOV
2056
                self.build_transfer_stacks_tx(epoch_id, payload, op_signer, None)
×
2057
            }
UNCOV
2058
            BlockstackOperationType::StackStx(_payload) => {
×
UNCOV
2059
                self.build_stack_stx_tx(epoch_id, _payload, op_signer, None)
×
2060
            }
UNCOV
2061
            BlockstackOperationType::DelegateStx(payload) => {
×
UNCOV
2062
                self.build_delegate_stacks_tx(epoch_id, payload, op_signer, None)
×
2063
            }
UNCOV
2064
            BlockstackOperationType::VoteForAggregateKey(payload) => {
×
UNCOV
2065
                self.build_vote_for_aggregate_key_tx(epoch_id, payload, op_signer, None)
×
2066
            }
2067
        }
UNCOV
2068
    }
×
2069

2070
    /// Retrieves a raw [`Transaction`] by its [`Txid`]
2071
    #[cfg(test)]
UNCOV
2072
    pub fn get_raw_transaction(&self, txid: &Txid) -> Transaction {
×
UNCOV
2073
        self.get_rpc_client()
×
UNCOV
2074
            .get_raw_transaction(txid)
×
UNCOV
2075
            .unwrap_or_log_panic("retrieve raw tx")
×
UNCOV
2076
    }
×
2077

2078
    /// Produce `num_blocks` regtest bitcoin blocks, sending the bitcoin coinbase rewards
2079
    ///  to the bitcoin single sig addresses corresponding to `pks` in a round robin fashion.
2080
    #[cfg(test)]
UNCOV
2081
    pub fn bootstrap_chain_to_pks(&self, num_blocks: u64, pks: &[Secp256k1PublicKey]) {
×
UNCOV
2082
        info!("Creating wallet if it does not exist");
×
UNCOV
2083
        if let Err(e) = self.create_wallet_if_dne() {
×
2084
            error!("Error when creating wallet: {e:?}");
×
UNCOV
2085
        }
×
2086

UNCOV
2087
        for pk in pks {
×
UNCOV
2088
            debug!("Import public key '{}'", &pk.to_hex());
×
UNCOV
2089
            if let Err(e) = self.import_public_key(pk) {
×
2090
                warn!("Error when importing pubkey: {e:?}");
×
UNCOV
2091
            }
×
2092
        }
2093

UNCOV
2094
        if pks.len() == 1 {
×
2095
            // if we only have one pubkey, just generate all the blocks at once
UNCOV
2096
            let address = self.get_miner_address(StacksEpochId::Epoch21, &pks[0]);
×
UNCOV
2097
            debug!(
×
2098
                "Generate to address '{address}' for public key '{}'",
2099
                &pks[0].to_hex()
×
2100
            );
UNCOV
2101
            self.get_rpc_client()
×
UNCOV
2102
                .generate_to_address(num_blocks, &address)
×
UNCOV
2103
                .ok_or_log_panic("generating block");
×
UNCOV
2104
            return;
×
UNCOV
2105
        }
×
2106

2107
        // otherwise, round robin generate blocks
UNCOV
2108
        let num_blocks = num_blocks as usize;
×
UNCOV
2109
        for i in 0..num_blocks {
×
UNCOV
2110
            let pk = &pks[i % pks.len()];
×
UNCOV
2111
            let address = self.get_miner_address(StacksEpochId::Epoch21, pk);
×
UNCOV
2112
            if i < pks.len() {
×
UNCOV
2113
                debug!(
×
2114
                    "Generate to address '{}' for public key '{}'",
2115
                    address.to_string(),
×
2116
                    &pk.to_hex(),
×
2117
                );
UNCOV
2118
            }
×
UNCOV
2119
            self.get_rpc_client()
×
UNCOV
2120
                .generate_to_address(1, &address)
×
UNCOV
2121
                .ok_or_log_panic("generating block");
×
2122
        }
UNCOV
2123
    }
×
2124

2125
    /// Checks whether a transaction has been confirmed by the burnchain
2126
    ///
2127
    /// # Arguments
2128
    ///
2129
    /// * `txid` - The transaction ID to check (in big-endian order)
2130
    ///
2131
    /// # Returns
2132
    ///
2133
    /// * `true` if the transaction is confirmed (has at least one confirmation).
2134
    /// * `false` if the transaction is unconfirmed or could not be found.
UNCOV
2135
    pub fn is_transaction_confirmed(&self, txid: &Txid) -> bool {
×
UNCOV
2136
        match self
×
UNCOV
2137
            .get_rpc_client()
×
UNCOV
2138
            .get_transaction(self.get_wallet_name(), txid)
×
2139
        {
UNCOV
2140
            Ok(info) => info.confirmations > 0,
×
UNCOV
2141
            Err(e) => {
×
UNCOV
2142
                error!("Bitcoin RPC failure: checking tx confirmation {e:?}");
×
UNCOV
2143
                false
×
2144
            }
2145
        }
UNCOV
2146
    }
×
2147

2148
    /// Returns the configured wallet name from [`Config`].
UNCOV
2149
    fn get_wallet_name(&self) -> &String {
×
UNCOV
2150
        &self.config.burnchain.wallet_name
×
UNCOV
2151
    }
×
2152

2153
    /// Imports a public key into configured wallet by registering its
2154
    /// corresponding addresses as descriptors.
2155
    ///
2156
    /// This computes both **legacy (P2PKH)** and, if the miner is configured
2157
    /// with `segwit` enabled, also **SegWit (P2WPKH)** addresses, then imports
2158
    /// the related descriptors into the wallet.
UNCOV
2159
    pub fn import_public_key(
×
UNCOV
2160
        &self,
×
UNCOV
2161
        public_key: &Secp256k1PublicKey,
×
UNCOV
2162
    ) -> BitcoinRegtestControllerResult<()> {
×
UNCOV
2163
        let pkh = Hash160::from_data(&public_key.to_bytes())
×
UNCOV
2164
            .to_bytes()
×
UNCOV
2165
            .to_vec();
×
UNCOV
2166
        let (_, network_id) = self.config.burnchain.get_bitcoin_network();
×
2167

2168
        // import both the legacy and segwit variants of this public key
UNCOV
2169
        let mut addresses = vec![BitcoinAddress::from_bytes_legacy(
×
UNCOV
2170
            network_id,
×
UNCOV
2171
            LegacyBitcoinAddressType::PublicKeyHash,
×
UNCOV
2172
            &pkh,
×
2173
        )
UNCOV
2174
        .map_err(BitcoinRegtestControllerError::InvalidPublicKey)?];
×
2175

UNCOV
2176
        if self.config.miner.segwit {
×
UNCOV
2177
            addresses.push(
×
UNCOV
2178
                BitcoinAddress::from_bytes_segwit_p2wpkh(network_id, &pkh)
×
UNCOV
2179
                    .map_err(BitcoinRegtestControllerError::InvalidPublicKey)?,
×
2180
            );
UNCOV
2181
        }
×
2182

UNCOV
2183
        for address in addresses.into_iter() {
×
UNCOV
2184
            debug!(
×
2185
                "Import address {address} for public key {}",
2186
                public_key.to_hex()
×
2187
            );
2188

UNCOV
2189
            let descriptor = format!("addr({address})");
×
UNCOV
2190
            let info = self.get_rpc_client().get_descriptor_info(&descriptor)?;
×
2191

UNCOV
2192
            let descr_req = ImportDescriptorsRequest {
×
UNCOV
2193
                descriptor: format!("addr({address})#{}", info.checksum),
×
UNCOV
2194
                timestamp: Timestamp::Time(0),
×
UNCOV
2195
                internal: Some(true),
×
UNCOV
2196
            };
×
2197

UNCOV
2198
            self.get_rpc_client()
×
UNCOV
2199
                .import_descriptors(self.get_wallet_name(), &[&descr_req])?;
×
2200
        }
UNCOV
2201
        Ok(())
×
UNCOV
2202
    }
×
2203

2204
    /// Returns a copy of the given public key adjusted to the current epoch rules.
2205
    ///
2206
    /// In particular:
2207
    /// - For epochs **before** [`StacksEpochId::Epoch21`], the public key is returned
2208
    ///   unchanged.
2209
    /// - Starting with [`StacksEpochId::Epoch21`], if **SegWit** is enabled in the miner
2210
    ///   configuration, the key is forced into compressed form.
2211
    ///
2212
    /// # Arguments
2213
    /// * `epoch_id` — The epoch identifier to check against protocol upgrade rules.
2214
    /// * `public_key` — The original public key to adjust.
2215
    ///
2216
    /// # Returns
2217
    /// A [`Secp256k1PublicKey`] that is either the same as the input or compressed,
2218
    /// depending on the epoch and miner configuration.
2219
    fn to_epoch_aware_pubkey(
4✔
2220
        &self,
4✔
2221
        epoch_id: StacksEpochId,
4✔
2222
        public_key: &Secp256k1PublicKey,
4✔
2223
    ) -> Secp256k1PublicKey {
4✔
2224
        let mut reviewed = public_key.clone();
4✔
2225
        if self.config.miner.segwit && epoch_id >= StacksEpochId::Epoch21 {
4✔
2226
            reviewed.set_compressed(true);
1✔
2227
        }
3✔
2228
        return reviewed;
4✔
2229
    }
4✔
2230

2231
    /// Retrieves the set of UTXOs for a given address at a specific block height.
2232
    ///
2233
    /// This method queries all unspent outputs belonging to the provided address:
2234
    /// 1. Using a confirmation window of `0..=9_999_999` for the RPC call.
2235
    /// 2. Filtering out UTXOs that:
2236
    ///    - Are present in the optional exclusion set (matched by transaction ID).
2237
    ///    - Have an amount below the specified `minimum_sum_amount`.
2238
    ///
2239
    /// Note: The `block_height` is only used to retrieve the corresponding block hash
2240
    /// and does not affect which UTXOs are included in the result.
2241
    ///
2242
    /// # Arguments
2243
    /// - `address`: The Bitcoin address whose UTXOs should be retrieved.
2244
    /// - `include_unsafe`: Whether to include unsafe UTXOs.
2245
    /// - `minimum_sum_amount`: Minimum amount (in satoshis) that a UTXO must have to be included in the final set.
2246
    /// - `utxos_to_exclude`: Optional set of UTXOs to exclude from the final result.
2247
    /// - `block_height`: The block height at which to resolve the block hash used in the result.
2248
    ///
2249
    /// # Returns
2250
    /// A [`UTXOSet`] containing the filtered UTXOs and the block hash corresponding to `block_height`.
UNCOV
2251
    fn retrieve_utxo_set(
×
UNCOV
2252
        &self,
×
UNCOV
2253
        address: &BitcoinAddress,
×
UNCOV
2254
        include_unsafe: bool,
×
UNCOV
2255
        minimum_sum_amount: u64,
×
UNCOV
2256
        utxos_to_exclude: &Option<UTXOSet>,
×
UNCOV
2257
        block_height: u64,
×
UNCOV
2258
    ) -> BitcoinRpcClientResult<UTXOSet> {
×
UNCOV
2259
        let bhh = self.get_rpc_client().get_block_hash(block_height)?;
×
2260

2261
        const MIN_CONFIRMATIONS: u64 = 0;
2262
        const MAX_CONFIRMATIONS: u64 = 9_999_999;
UNCOV
2263
        let unspents = self.get_rpc_client().list_unspent(
×
UNCOV
2264
            &self.get_wallet_name(),
×
UNCOV
2265
            Some(MIN_CONFIRMATIONS),
×
UNCOV
2266
            Some(MAX_CONFIRMATIONS),
×
UNCOV
2267
            Some(&[address]),
×
UNCOV
2268
            Some(include_unsafe),
×
UNCOV
2269
            Some(minimum_sum_amount),
×
UNCOV
2270
            self.config.burnchain.max_unspent_utxos.clone(),
×
2271
        )?;
×
2272

UNCOV
2273
        let txids_to_exclude = utxos_to_exclude.as_ref().map_or_else(HashSet::new, |set| {
×
UNCOV
2274
            set.utxos
×
UNCOV
2275
                .iter()
×
UNCOV
2276
                .map(|utxo| Txid::from_bitcoin_tx_hash(&utxo.txid))
×
UNCOV
2277
                .collect()
×
UNCOV
2278
        });
×
2279

UNCOV
2280
        let utxos = unspents
×
UNCOV
2281
            .into_iter()
×
UNCOV
2282
            .filter(|each| !txids_to_exclude.contains(&each.txid))
×
UNCOV
2283
            .filter(|each| each.amount >= minimum_sum_amount)
×
UNCOV
2284
            .map(|each| UTXO {
×
UNCOV
2285
                txid: Txid::to_bitcoin_tx_hash(&each.txid),
×
UNCOV
2286
                vout: each.vout,
×
UNCOV
2287
                script_pub_key: each.script_pub_key,
×
UNCOV
2288
                amount: each.amount,
×
UNCOV
2289
                confirmations: each.confirmations,
×
UNCOV
2290
            })
×
UNCOV
2291
            .collect::<Vec<_>>();
×
UNCOV
2292
        Ok(UTXOSet { bhh, utxos })
×
UNCOV
2293
    }
×
2294
}
2295

2296
impl BurnchainController for BitcoinRegtestController {
UNCOV
2297
    fn sortdb_ref(&self) -> &SortitionDB {
×
UNCOV
2298
        self.db
×
UNCOV
2299
            .as_ref()
×
UNCOV
2300
            .expect("BUG: did not instantiate the burn DB")
×
UNCOV
2301
    }
×
2302

UNCOV
2303
    fn sortdb_mut(&mut self) -> &mut SortitionDB {
×
UNCOV
2304
        let burnchain = self.get_burnchain();
×
2305

UNCOV
2306
        let (db, burnchain_db) = burnchain.open_db(true).unwrap();
×
UNCOV
2307
        self.db = Some(db);
×
UNCOV
2308
        self.burnchain_db = Some(burnchain_db);
×
2309

UNCOV
2310
        match self.db {
×
UNCOV
2311
            Some(ref mut sortdb) => sortdb,
×
2312
            None => unreachable!(),
×
2313
        }
UNCOV
2314
    }
×
2315

2316
    fn get_chain_tip(&self) -> BurnchainTip {
×
2317
        match &self.chain_tip {
×
2318
            Some(chain_tip) => chain_tip.clone(),
×
2319
            None => {
2320
                unreachable!();
×
2321
            }
2322
        }
2323
    }
×
2324

UNCOV
2325
    fn get_headers_height(&self) -> u64 {
×
UNCOV
2326
        let (_, network_id) = self.config.burnchain.get_bitcoin_network();
×
UNCOV
2327
        let spv_client = SpvClient::new(
×
UNCOV
2328
            &self.config.get_spv_headers_file_path(),
×
2329
            0,
UNCOV
2330
            None,
×
UNCOV
2331
            network_id,
×
2332
            false,
2333
            false,
2334
        )
UNCOV
2335
        .expect("Unable to open burnchain headers DB");
×
UNCOV
2336
        spv_client
×
UNCOV
2337
            .get_headers_height()
×
UNCOV
2338
            .expect("Unable to query number of burnchain headers")
×
UNCOV
2339
    }
×
2340

UNCOV
2341
    fn connect_dbs(&mut self) -> Result<(), BurnchainControllerError> {
×
UNCOV
2342
        let burnchain = self.get_burnchain();
×
UNCOV
2343
        burnchain.connect_db(
×
2344
            true,
UNCOV
2345
            &self.indexer.get_first_block_header_hash()?,
×
UNCOV
2346
            self.indexer.get_first_block_header_timestamp()?,
×
UNCOV
2347
            self.indexer.get_stacks_epochs(),
×
2348
        )?;
×
UNCOV
2349
        Ok(())
×
UNCOV
2350
    }
×
2351

UNCOV
2352
    fn get_stacks_epochs(&self) -> EpochList {
×
UNCOV
2353
        self.indexer.get_stacks_epochs()
×
UNCOV
2354
    }
×
2355

UNCOV
2356
    fn start(
×
UNCOV
2357
        &mut self,
×
UNCOV
2358
        target_block_height_opt: Option<u64>,
×
UNCOV
2359
    ) -> Result<(BurnchainTip, u64), BurnchainControllerError> {
×
2360
        // if no target block height is given, just fetch the first burnchain block.
UNCOV
2361
        self.receive_blocks(false, target_block_height_opt.map_or_else(|| Some(1), Some))
×
UNCOV
2362
    }
×
2363

UNCOV
2364
    fn sync(
×
UNCOV
2365
        &mut self,
×
UNCOV
2366
        target_block_height_opt: Option<u64>,
×
UNCOV
2367
    ) -> Result<(BurnchainTip, u64), BurnchainControllerError> {
×
UNCOV
2368
        let (burnchain_tip, burnchain_height) = if self.config.burnchain.mode == "helium" {
×
2369
            // Helium: this node is responsible for mining new burnchain blocks
2370
            self.build_next_block(1);
×
2371
            self.receive_blocks(true, None)?
×
2372
        } else {
2373
            // Neon: this node is waiting on a block to be produced
UNCOV
2374
            self.receive_blocks(true, target_block_height_opt)?
×
2375
        };
2376

2377
        // Evaluate process_exit_at_block_height setting
UNCOV
2378
        if let Some(cap) = self.config.burnchain.process_exit_at_block_height {
×
UNCOV
2379
            if burnchain_tip.block_snapshot.block_height >= cap {
×
2380
                info!("Node succesfully reached the end of the ongoing {cap} blocks epoch!");
×
2381
                info!("This process will automatically terminate in 30s, restart your node for participating in the next epoch.");
×
2382
                sleep_ms(30000);
×
2383
                std::process::exit(0);
×
UNCOV
2384
            }
×
UNCOV
2385
        }
×
UNCOV
2386
        Ok((burnchain_tip, burnchain_height))
×
UNCOV
2387
    }
×
2388

2389
    /// Build and send a burnchain operation transaction.
2390
    /// Returns the [`Txid`] on success, [`BurnchainControllerError`] otherwise.
2391
    /// On [`BitcoinRegtestController::send_transaction`] failure for block commits,
2392
    /// clears `ongoing_block_commit` so the commit can be resubmitted.
UNCOV
2393
    fn submit_operation(
×
UNCOV
2394
        &mut self,
×
UNCOV
2395
        epoch_id: StacksEpochId,
×
UNCOV
2396
        operation: BlockstackOperationType,
×
UNCOV
2397
        op_signer: &mut BurnchainOpSigner,
×
UNCOV
2398
    ) -> Result<Txid, BurnchainControllerError> {
×
UNCOV
2399
        let is_block_commit = matches!(operation, BlockstackOperationType::LeaderBlockCommit(_));
×
UNCOV
2400
        let transaction = self.make_operation_tx(epoch_id, operation, op_signer)?;
×
UNCOV
2401
        self.send_transaction(&transaction).inspect_err(|_| {
×
UNCOV
2402
            if is_block_commit {
×
UNCOV
2403
                self.ongoing_block_commit = None;
×
UNCOV
2404
            }
×
UNCOV
2405
        })
×
UNCOV
2406
    }
×
2407

2408
    #[cfg(test)]
UNCOV
2409
    fn bootstrap_chain(&self, num_blocks: u64) {
×
UNCOV
2410
        let Some(ref local_mining_pubkey) = &self.config.burnchain.local_mining_public_key else {
×
2411
            warn!("No local mining pubkey while bootstrapping bitcoin regtest, will not generate bitcoin blocks");
×
UNCOV
2412
            return;
×
2413
        };
2414

2415
        // NOTE: miner address is whatever the miner's segwit setting says it is here
UNCOV
2416
        let mut local_mining_pubkey = Secp256k1PublicKey::from_hex(local_mining_pubkey).unwrap();
×
2417

UNCOV
2418
        if self.config.miner.segwit {
×
UNCOV
2419
            local_mining_pubkey.set_compressed(true);
×
UNCOV
2420
        }
×
2421

UNCOV
2422
        self.bootstrap_chain_to_pks(num_blocks, &[local_mining_pubkey])
×
UNCOV
2423
    }
×
2424
}
2425

2426
#[derive(Debug, Clone)]
2427
pub struct UTXOSet {
2428
    bhh: BurnchainHeaderHash,
2429
    utxos: Vec<UTXO>,
2430
}
2431

2432
impl UTXOSet {
UNCOV
2433
    pub fn is_empty(&self) -> bool {
×
UNCOV
2434
        self.utxos.len() == 0
×
UNCOV
2435
    }
×
2436

UNCOV
2437
    pub fn total_available(&self) -> u64 {
×
UNCOV
2438
        self.utxos.iter().map(|o| o.amount).sum()
×
UNCOV
2439
    }
×
2440

UNCOV
2441
    pub fn num_utxos(&self) -> usize {
×
UNCOV
2442
        self.utxos.len()
×
UNCOV
2443
    }
×
2444
}
2445

2446
#[derive(Clone, Debug, PartialEq)]
2447
pub struct UTXO {
2448
    pub txid: Sha256dHash,
2449
    pub vout: u32,
2450
    pub script_pub_key: Script,
2451
    pub amount: u64,
2452
    pub confirmations: u32,
2453
}
2454

2455
#[cfg(test)]
2456
mod tests {
2457
    use std::env::{self, temp_dir};
2458
    use std::fs::File;
2459
    use std::io::Write;
2460
    use std::panic::{self, AssertUnwindSafe};
2461

2462
    use stacks::burnchains::BurnchainSigner;
2463
    use stacks::config::DEFAULT_SATS_PER_VB;
2464
    use stacks_common::deps_common::bitcoin::blockdata::script::Builder;
2465
    use stacks_common::types::chainstate::{BlockHeaderHash, StacksAddress, VRFSeed};
2466
    use stacks_common::util::hash::to_hex;
2467
    use stacks_common::util::secp256k1::Secp256k1PrivateKey;
2468

2469
    use super::*;
2470
    use crate::burnchains::bitcoin::core_controller::BitcoinCoreController;
2471
    use crate::burnchains::bitcoin_regtest_controller::tests::utils::{
2472
        create_follower_config, create_miner_config, to_address_legacy,
2473
    };
2474
    use crate::Keychain;
2475

2476
    mod utils {
2477
        use std::net::TcpListener;
2478

2479
        use stacks::burnchains::MagicBytes;
2480
        use stacks::chainstate::burn::ConsensusHash;
2481
        use stacks::util::vrf::{VRFPrivateKey, VRFPublicKey};
2482

2483
        use super::*;
2484
        use crate::burnchains::bitcoin::core_controller::BURNCHAIN_CONFIG_PEER_PORT_DISABLED;
2485
        use crate::util::get_epoch_time_nanos;
2486

2487
        pub fn create_miner_config() -> Config {
7✔
2488
            let mut config = Config::default();
7✔
2489
            config.node.miner = true;
7✔
2490
            config.burnchain.magic_bytes = "T3".as_bytes().into();
7✔
2491
            config.burnchain.username = Some(String::from("user"));
7✔
2492
            config.burnchain.password = Some(String::from("12345"));
7✔
2493
            // overriding default "0.0.0.0" because doesn't play nicely on Windows.
2494
            config.burnchain.peer_host = String::from("127.0.0.1");
7✔
2495
            // avoiding peer port biding to reduce the number of ports to bind to.
2496
            config.burnchain.peer_port = BURNCHAIN_CONFIG_PEER_PORT_DISABLED;
7✔
2497

2498
            //Ask the OS for a free port. Not guaranteed to stay free,
2499
            //after TcpListner is dropped, but good enough for testing
2500
            //and starting bitcoind right after config is created
2501
            let tmp_listener =
7✔
2502
                TcpListener::bind("127.0.0.1:0").expect("Failed to bind to get a free port");
7✔
2503
            let port = tmp_listener.local_addr().unwrap().port();
7✔
2504

2505
            config.burnchain.rpc_port = port;
7✔
2506

2507
            let now = get_epoch_time_nanos();
7✔
2508
            let dir = format!("/tmp/regtest-ctrl-{port}-{now}");
7✔
2509
            config.node.working_dir = dir;
7✔
2510

2511
            config
7✔
2512
        }
7✔
2513

UNCOV
2514
        pub fn create_keychain() -> Keychain {
×
UNCOV
2515
            create_keychain_with_seed(1)
×
UNCOV
2516
        }
×
2517

2518
        pub fn create_keychain_with_seed(value: u8) -> Keychain {
2✔
2519
            let seed = vec![value; 4];
2✔
2520
            let keychain = Keychain::default(seed);
2✔
2521
            keychain
2✔
2522
        }
2✔
2523

2524
        pub fn create_miner1_pubkey() -> Secp256k1PublicKey {
2✔
2525
            create_keychain_with_seed(1).get_pub_key()
2✔
2526
        }
2✔
2527

UNCOV
2528
        pub fn create_miner2_pubkey() -> Secp256k1PublicKey {
×
UNCOV
2529
            create_keychain_with_seed(2).get_pub_key()
×
UNCOV
2530
        }
×
2531

2532
        pub fn to_address_legacy(pub_key: &Secp256k1PublicKey) -> BitcoinAddress {
3✔
2533
            let hash160 = Hash160::from_data(&pub_key.to_bytes());
3✔
2534
            BitcoinAddress::from_bytes_legacy(
3✔
2535
                BitcoinNetworkType::Regtest,
3✔
2536
                LegacyBitcoinAddressType::PublicKeyHash,
3✔
2537
                &hash160.0,
3✔
2538
            )
2539
            .expect("Public key incorrect")
3✔
2540
        }
3✔
2541

2542
        pub fn to_address_segwit_p2wpkh(pub_key: &Secp256k1PublicKey) -> BitcoinAddress {
1✔
2543
            // pub_key.to_byte_compressed() equivalent to pub_key.set_compressed(true) + pub_key.to_bytes()
2544
            let hash160 = Hash160::from_data(&pub_key.to_bytes_compressed());
1✔
2545
            BitcoinAddress::from_bytes_segwit_p2wpkh(BitcoinNetworkType::Regtest, &hash160.0)
1✔
2546
                .expect("Public key incorrect")
1✔
2547
        }
1✔
2548

UNCOV
2549
        pub fn mine_tx(btc_controller: &BitcoinRegtestController, tx: &Transaction) {
×
UNCOV
2550
            btc_controller
×
UNCOV
2551
                .send_transaction(tx)
×
UNCOV
2552
                .expect("Tx should be sent to the burnchain!");
×
UNCOV
2553
            btc_controller.build_next_block(1); // Now tx is confirmed
×
UNCOV
2554
        }
×
2555

UNCOV
2556
        pub fn create_templated_commit_op() -> LeaderBlockCommitOp {
×
UNCOV
2557
            LeaderBlockCommitOp {
×
UNCOV
2558
                block_header_hash: BlockHeaderHash::from_hex(
×
UNCOV
2559
                    "e88c3d30cb59a142f83de3b27f897a43bbb0f13316911bb98a3229973dae32af",
×
UNCOV
2560
                )
×
UNCOV
2561
                .unwrap(),
×
UNCOV
2562
                new_seed: VRFSeed::from_hex(
×
UNCOV
2563
                    "d5b9f21bc1f40f24e2c101ecd13c55b8619e5e03dad81de2c62a1cc1d8c1b375",
×
UNCOV
2564
                )
×
UNCOV
2565
                .unwrap(),
×
UNCOV
2566
                parent_block_ptr: 2211, // 0x000008a3
×
UNCOV
2567
                parent_vtxindex: 1,     // 0x0001
×
UNCOV
2568
                key_block_ptr: 1432,    // 0x00000598
×
UNCOV
2569
                key_vtxindex: 1,        // 0x0001
×
UNCOV
2570
                memo: vec![11],         // 0x5a >> 3
×
UNCOV
2571

×
UNCOV
2572
                burn_fee: 110_000, //relevant for fee calculation when sending the tx
×
UNCOV
2573
                input: (Txid([0x00; 32]), 0),
×
UNCOV
2574
                burn_parent_modulus: 2, // 0x5a & 0b111
×
UNCOV
2575

×
UNCOV
2576
                apparent_sender: BurnchainSigner("mgbpit8FvkVJ9kuXY8QSM5P7eibnhcEMBk".to_string()),
×
UNCOV
2577
                commit_outs: vec![
×
UNCOV
2578
                    PoxAddress::Standard(StacksAddress::burn_address(false), None),
×
UNCOV
2579
                    PoxAddress::Standard(StacksAddress::burn_address(false), None),
×
UNCOV
2580
                ],
×
UNCOV
2581

×
UNCOV
2582
                treatment: vec![],
×
UNCOV
2583
                sunset_burn: 5_500, //relevant for fee calculation when sending the tx
×
UNCOV
2584

×
UNCOV
2585
                txid: Txid([0x00; 32]),
×
UNCOV
2586
                vtxindex: 0,
×
UNCOV
2587
                block_height: 2212,
×
UNCOV
2588
                burn_header_hash: BurnchainHeaderHash([0x01; 32]),
×
UNCOV
2589
            }
×
UNCOV
2590
        }
×
2591

UNCOV
2592
        pub fn txout_opreturn<T: StacksMessageCodec>(
×
UNCOV
2593
            op: &T,
×
UNCOV
2594
            magic: &MagicBytes,
×
UNCOV
2595
            value: u64,
×
UNCOV
2596
        ) -> TxOut {
×
UNCOV
2597
            let op_bytes = {
×
UNCOV
2598
                let mut buffer = vec![];
×
UNCOV
2599
                let mut magic_bytes = magic.as_bytes().to_vec();
×
UNCOV
2600
                buffer.append(&mut magic_bytes);
×
UNCOV
2601
                op.consensus_serialize(&mut buffer)
×
UNCOV
2602
                    .expect("FATAL: invalid operation");
×
UNCOV
2603
                buffer
×
2604
            };
2605

UNCOV
2606
            TxOut {
×
UNCOV
2607
                value,
×
UNCOV
2608
                script_pubkey: Builder::new()
×
UNCOV
2609
                    .push_opcode(opcodes::All::OP_RETURN)
×
UNCOV
2610
                    .push_slice(&op_bytes)
×
UNCOV
2611
                    .into_script(),
×
UNCOV
2612
            }
×
UNCOV
2613
        }
×
2614

UNCOV
2615
        pub fn txout_opdup_commit_to(addr: &PoxAddress, amount: u64) -> TxOut {
×
UNCOV
2616
            addr.to_bitcoin_tx_out(amount)
×
UNCOV
2617
        }
×
2618

UNCOV
2619
        pub fn txout_opdup_change_legacy(signer: &mut BurnchainOpSigner, amount: u64) -> TxOut {
×
UNCOV
2620
            let public_key = signer.get_public_key();
×
UNCOV
2621
            let change_address_hash = Hash160::from_data(&public_key.to_bytes());
×
UNCOV
2622
            LegacyBitcoinAddress::to_p2pkh_tx_out(&change_address_hash, amount)
×
UNCOV
2623
        }
×
2624

UNCOV
2625
        pub fn txin_at_index(
×
UNCOV
2626
            complete_tx: &Transaction,
×
UNCOV
2627
            signer: &BurnchainOpSigner,
×
UNCOV
2628
            utxos: &[UTXO],
×
UNCOV
2629
            index: usize,
×
UNCOV
2630
        ) -> TxIn {
×
2631
            //Refresh op signer
UNCOV
2632
            let mut signer = signer.undisposed();
×
UNCOV
2633
            let mut public_key = signer.get_public_key();
×
2634

UNCOV
2635
            let mut tx = Transaction {
×
UNCOV
2636
                version: complete_tx.version,
×
UNCOV
2637
                lock_time: complete_tx.lock_time,
×
UNCOV
2638
                input: vec![],
×
UNCOV
2639
                output: complete_tx.output.clone(),
×
UNCOV
2640
            };
×
2641

UNCOV
2642
            for utxo in utxos.iter() {
×
UNCOV
2643
                let input = TxIn {
×
UNCOV
2644
                    previous_output: OutPoint {
×
UNCOV
2645
                        txid: utxo.txid.clone(),
×
UNCOV
2646
                        vout: utxo.vout,
×
UNCOV
2647
                    },
×
UNCOV
2648
                    script_sig: Script::new(),
×
UNCOV
2649
                    sequence: 0xFFFFFFFD, // allow RBF
×
UNCOV
2650
                    witness: vec![],
×
UNCOV
2651
                };
×
2652
                tx.input.push(input);
×
UNCOV
2653
            }
×
2654

2655
            for (i, utxo) in utxos.iter().enumerate() {
×
2656
                let script_pub_key = utxo.script_pub_key.clone();
×
2657
                let sig_hash_all = 0x01;
×
2658

UNCOV
2659
                let (sig_hash, is_segwit) = if script_pub_key.as_bytes().len() == 22
×
UNCOV
2660
                    && script_pub_key.as_bytes()[0..2] == [0x00, 0x14]
×
2661
                {
2662
                    // p2wpkh
UNCOV
2663
                    (
×
UNCOV
2664
                        tx.segwit_signature_hash(i, &script_pub_key, utxo.amount, sig_hash_all),
×
UNCOV
2665
                        true,
×
UNCOV
2666
                    )
×
2667
                } else {
2668
                    // p2pkh
UNCOV
2669
                    (tx.signature_hash(i, &script_pub_key, sig_hash_all), false)
×
2670
                };
2671

UNCOV
2672
                let sig1_der = {
×
UNCOV
2673
                    let message = signer
×
UNCOV
2674
                        .sign_message(sig_hash.as_bytes())
×
UNCOV
2675
                        .expect("Unable to sign message");
×
2676
                    message
×
2677
                        .to_secp256k1_recoverable()
×
2678
                        .expect("Unable to get recoverable signature")
×
2679
                        .to_standard()
×
2680
                        .serialize_der()
×
2681
                };
2682

UNCOV
2683
                if is_segwit {
×
UNCOV
2684
                    // segwit
×
UNCOV
2685
                    public_key.set_compressed(true);
×
UNCOV
2686
                    tx.input[i].script_sig = Script::from(vec![]);
×
UNCOV
2687
                    tx.input[i].witness = vec![
×
UNCOV
2688
                        [&*sig1_der, &[sig_hash_all as u8][..]].concat().to_vec(),
×
UNCOV
2689
                        public_key.to_bytes(),
×
UNCOV
2690
                    ];
×
UNCOV
2691
                } else {
×
UNCOV
2692
                    // legacy scriptSig
×
UNCOV
2693
                    tx.input[i].script_sig = Builder::new()
×
UNCOV
2694
                        .push_slice(&[&*sig1_der, &[sig_hash_all as u8][..]].concat())
×
UNCOV
2695
                        .push_slice(&public_key.to_bytes())
×
UNCOV
2696
                        .into_script();
×
UNCOV
2697
                    tx.input[i].witness.clear();
×
UNCOV
2698
                }
×
2699
            }
2700

UNCOV
2701
            tx.input[index].clone()
×
UNCOV
2702
        }
×
2703

UNCOV
2704
        pub fn create_templated_leader_key_op() -> LeaderKeyRegisterOp {
×
UNCOV
2705
            LeaderKeyRegisterOp {
×
UNCOV
2706
                consensus_hash: ConsensusHash([0u8; 20]),
×
UNCOV
2707
                public_key: VRFPublicKey::from_private(
×
UNCOV
2708
                    &VRFPrivateKey::from_bytes(&[0u8; 32]).unwrap(),
×
UNCOV
2709
                ),
×
UNCOV
2710
                memo: vec![],
×
UNCOV
2711
                txid: Txid([3u8; 32]),
×
UNCOV
2712
                vtxindex: 0,
×
UNCOV
2713
                block_height: 1,
×
UNCOV
2714
                burn_header_hash: BurnchainHeaderHash([9u8; 32]),
×
UNCOV
2715
            }
×
UNCOV
2716
        }
×
2717

UNCOV
2718
        pub fn create_templated_pre_stx_op() -> PreStxOp {
×
UNCOV
2719
            PreStxOp {
×
UNCOV
2720
                output: StacksAddress::p2pkh_from_hash(false, Hash160::from_data(&[2u8; 20])),
×
UNCOV
2721
                txid: Txid([0u8; 32]),
×
UNCOV
2722
                vtxindex: 0,
×
UNCOV
2723
                block_height: 0,
×
UNCOV
2724
                burn_header_hash: BurnchainHeaderHash([0u8; 32]),
×
UNCOV
2725
            }
×
UNCOV
2726
        }
×
2727

2728
        pub fn create_follower_config() -> Config {
2✔
2729
            let mut config = Config::default();
2✔
2730
            config.node.miner = false;
2✔
2731
            config.burnchain.magic_bytes = "T3".as_bytes().into();
2✔
2732
            config.burnchain.username = None;
2✔
2733
            config.burnchain.password = None;
2✔
2734
            config.burnchain.peer_host = String::from("127.0.0.1");
2✔
2735
            config.burnchain.peer_port = 8333;
2✔
2736
            config.node.working_dir = format!("/tmp/follower");
2✔
2737
            config
2✔
2738
        }
2✔
2739
    }
2740

2741
    #[test]
2742
    fn test_get_satoshis_per_byte() {
1✔
2743
        let dir = temp_dir();
1✔
2744
        let file_path = dir.as_path().join("config.toml");
1✔
2745

2746
        let mut config = Config::default();
1✔
2747

2748
        let satoshis_per_byte = get_satoshis_per_byte(&config);
1✔
2749
        assert_eq!(satoshis_per_byte, DEFAULT_SATS_PER_VB);
1✔
2750

2751
        let mut file = File::create(&file_path).unwrap();
1✔
2752
        writeln!(file, "[burnchain]").unwrap();
1✔
2753
        writeln!(file, "satoshis_per_byte = 51").unwrap();
1✔
2754
        config.config_path = Some(file_path.to_str().unwrap().to_string());
1✔
2755

2756
        assert_eq!(get_satoshis_per_byte(&config), 51);
1✔
2757
    }
1✔
2758

2759
    /// Verify that we can build a valid Bitcoin transaction with multiple UTXOs.
2760
    /// Taken from production data.
2761
    /// Tests `serialize_tx()` and `send_block_commit_operation_at_burnchain_height()`
2762
    #[test]
2763
    fn test_multiple_inputs() {
1✔
2764
        let spend_utxos = vec![
1✔
2765
            UTXO {
1✔
2766
                txid: Sha256dHash::from_hex(
1✔
2767
                    "d3eafb3aba3cec925473550ed2e4d00bcb0d00744bb3212e4a8e72878909daee",
1✔
2768
                )
1✔
2769
                .unwrap(),
1✔
2770
                vout: 3,
1✔
2771
                script_pub_key: Builder::from(
1✔
2772
                    hex_bytes("76a9141dc27eba0247f8cc9575e7d45e50a0bc7e72427d88ac").unwrap(),
1✔
2773
                )
1✔
2774
                .into_script(),
1✔
2775
                amount: 42051,
1✔
2776
                confirmations: 1421,
1✔
2777
            },
1✔
2778
            UTXO {
1✔
2779
                txid: Sha256dHash::from_hex(
1✔
2780
                    "01132f2d4a98cc715624e033214c8d841098a1ee15b30188ab89589a320b3b24",
1✔
2781
                )
1✔
2782
                .unwrap(),
1✔
2783
                vout: 0,
1✔
2784
                script_pub_key: Builder::from(
1✔
2785
                    hex_bytes("76a9141dc27eba0247f8cc9575e7d45e50a0bc7e72427d88ac").unwrap(),
1✔
2786
                )
1✔
2787
                .into_script(),
1✔
2788
                amount: 326456,
1✔
2789
                confirmations: 1421,
1✔
2790
            },
1✔
2791
        ];
2792

2793
        // test serialize_tx()
2794
        let config = utils::create_miner_config();
1✔
2795

2796
        let mut btc_controller = BitcoinRegtestController::new(config, None);
1✔
2797
        let mut utxo_set = UTXOSet {
1✔
2798
            bhh: BurnchainHeaderHash([0x01; 32]),
1✔
2799
            utxos: spend_utxos.clone(),
1✔
2800
        };
1✔
2801
        let mut transaction = Transaction {
1✔
2802
            input: vec![],
1✔
2803
            output: vec![
1✔
2804
                TxOut {
1✔
2805
                    value: 0,
1✔
2806
                    script_pubkey: Builder::from(hex_bytes("6a4c5054335be88c3d30cb59a142f83de3b27f897a43bbb0f13316911bb98a3229973dae32afd5b9f21bc1f40f24e2c101ecd13c55b8619e5e03dad81de2c62a1cc1d8c1b375000008a300010000059800015a").unwrap()).into_script(),
1✔
2807
                },
1✔
2808
                TxOut {
1✔
2809
                    value: 10000,
1✔
2810
                    script_pubkey: Builder::from(hex_bytes("76a914000000000000000000000000000000000000000088ac").unwrap()).into_script(),
1✔
2811
                },
1✔
2812
                TxOut {
1✔
2813
                    value: 10000,
1✔
2814
                    script_pubkey: Builder::from(hex_bytes("76a914000000000000000000000000000000000000000088ac").unwrap()).into_script(),
1✔
2815
                },
1✔
2816
            ],
1✔
2817
            version: 1,
1✔
2818
            lock_time: 0,
1✔
2819
        };
1✔
2820

2821
        let mut signer = BurnchainOpSigner::new(
1✔
2822
            Secp256k1PrivateKey::from_hex(
1✔
2823
                "9e446f6b0c6a96cf2190e54bcd5a8569c3e386f091605499464389b8d4e0bfc201",
1✔
2824
            )
2825
            .unwrap(),
1✔
2826
        );
2827
        assert!(btc_controller.serialize_tx(
1✔
2828
            StacksEpochId::Epoch25,
1✔
2829
            &mut transaction,
1✔
2830
            44950,
2831
            &mut utxo_set,
1✔
2832
            &mut signer,
1✔
2833
            true
2834
        ));
2835
        assert_eq!(transaction.output[3].value, 323557);
1✔
2836

2837
        // test send_block_commit_operation_at_burn_height()
2838
        let utxo_set = UTXOSet {
1✔
2839
            bhh: BurnchainHeaderHash([0x01; 32]),
1✔
2840
            utxos: spend_utxos,
1✔
2841
        };
1✔
2842

2843
        let commit_op = LeaderBlockCommitOp {
1✔
2844
            block_header_hash: BlockHeaderHash::from_hex(
1✔
2845
                "e88c3d30cb59a142f83de3b27f897a43bbb0f13316911bb98a3229973dae32af",
1✔
2846
            )
1✔
2847
            .unwrap(),
1✔
2848
            new_seed: VRFSeed::from_hex(
1✔
2849
                "d5b9f21bc1f40f24e2c101ecd13c55b8619e5e03dad81de2c62a1cc1d8c1b375",
1✔
2850
            )
1✔
2851
            .unwrap(),
1✔
2852
            parent_block_ptr: 2211, // 0x000008a3
1✔
2853
            parent_vtxindex: 1,     // 0x0001
1✔
2854
            key_block_ptr: 1432,    // 0x00000598
1✔
2855
            key_vtxindex: 1,        // 0x0001
1✔
2856
            memo: vec![11],         // 0x5a >> 3
1✔
2857

1✔
2858
            burn_fee: 0,
1✔
2859
            input: (Txid([0x00; 32]), 0),
1✔
2860
            burn_parent_modulus: 2, // 0x5a & 0b111
1✔
2861

1✔
2862
            apparent_sender: BurnchainSigner("mgbpit8FvkVJ9kuXY8QSM5P7eibnhcEMBk".to_string()),
1✔
2863
            commit_outs: vec![
1✔
2864
                PoxAddress::Standard(StacksAddress::burn_address(false), None),
1✔
2865
                PoxAddress::Standard(StacksAddress::burn_address(false), None),
1✔
2866
            ],
1✔
2867

1✔
2868
            treatment: vec![],
1✔
2869
            sunset_burn: 0,
1✔
2870

1✔
2871
            txid: Txid([0x00; 32]),
1✔
2872
            vtxindex: 0,
1✔
2873
            block_height: 2212,
1✔
2874
            burn_header_hash: BurnchainHeaderHash([0x01; 32]),
1✔
2875
        };
1✔
2876

2877
        assert_eq!(to_hex(&commit_op.serialize_to_vec()), "5be88c3d30cb59a142f83de3b27f897a43bbb0f13316911bb98a3229973dae32afd5b9f21bc1f40f24e2c101ecd13c55b8619e5e03dad81de2c62a1cc1d8c1b375000008a300010000059800015a".to_string());
1✔
2878

2879
        let leader_fees = LeaderBlockCommitFees {
1✔
2880
            sunset_fee: 0,
1✔
2881
            fee_rate: 50,
1✔
2882
            sortition_fee: 20000,
1✔
2883
            outputs_len: 2,
1✔
2884
            default_tx_size: 380,
1✔
2885
            spent_in_attempts: 0,
1✔
2886
            is_rbf_enabled: false,
1✔
2887
            final_size: 498,
1✔
2888
        };
1✔
2889

2890
        assert_eq!(leader_fees.amount_per_output(), 10000);
1✔
2891
        assert_eq!(leader_fees.total_spent(), 44900);
1✔
2892

2893
        let block_commit = btc_controller
1✔
2894
            .send_block_commit_operation_at_burnchain_height(
1✔
2895
                StacksEpochId::Epoch30,
1✔
2896
                commit_op,
1✔
2897
                &mut signer,
1✔
2898
                Some(utxo_set),
1✔
2899
                None,
1✔
2900
                leader_fees,
1✔
2901
                &[],
1✔
2902
                2212,
2903
            )
2904
            .unwrap();
1✔
2905

2906
        debug!("send_block_commit_operation:\n{block_commit:#?}");
1✔
2907
        assert_eq!(block_commit.output[3].value, 323507);
1✔
2908
        assert_eq!(serialize_hex(&block_commit).unwrap(), "0100000002eeda098987728e4a2e21b34b74000dcb0bd0e4d20e55735492ec3cba3afbead3030000006a4730440220558286e20e10ce31537f0625dae5cc62fac7961b9d2cf272c990de96323d7e2502202255adbea3d2e0509b80c5d8a3a4fe6397a87bcf18da1852740d5267d89a0cb20121035379aa40c02890d253cfa577964116eb5295570ae9f7287cbae5f2585f5b2c7cfdffffff243b0b329a5889ab8801b315eea19810848d4c2133e0245671cc984a2d2f1301000000006a47304402206d9f8de107f9e1eb15aafac66c2bb34331a7523260b30e18779257e367048d34022013c7dabb32a5c281aa00d405e2ccbd00f34f03a65b2336553a4acd6c52c251ef0121035379aa40c02890d253cfa577964116eb5295570ae9f7287cbae5f2585f5b2c7cfdffffff040000000000000000536a4c5054335be88c3d30cb59a142f83de3b27f897a43bbb0f13316911bb98a3229973dae32afd5b9f21bc1f40f24e2c101ecd13c55b8619e5e03dad81de2c62a1cc1d8c1b375000008a300010000059800015a10270000000000001976a914000000000000000000000000000000000000000088ac10270000000000001976a914000000000000000000000000000000000000000088acb3ef0400000000001976a9141dc27eba0247f8cc9575e7d45e50a0bc7e72427d88ac00000000");
1✔
2909
    }
1✔
2910

2911
    #[test]
2912
    fn test_to_epoch_aware_pubkey() {
1✔
2913
        let mut config = utils::create_miner_config();
1✔
2914
        let pubkey = utils::create_miner1_pubkey();
1✔
2915

2916
        config.miner.segwit = false;
1✔
2917
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
1✔
2918

2919
        let reviewed = btc_controller.to_epoch_aware_pubkey(StacksEpochId::Epoch20, &pubkey);
1✔
2920
        assert_eq!(
1✔
2921
            false,
2922
            reviewed.compressed(),
1✔
2923
            "Segwit disabled with Epoch < 2.1: not compressed"
2924
        );
2925
        let reviewed = btc_controller.to_epoch_aware_pubkey(StacksEpochId::Epoch21, &pubkey);
1✔
2926
        assert_eq!(
1✔
2927
            false,
2928
            reviewed.compressed(),
1✔
2929
            "Segwit disabled with Epoch >= 2.1: not compressed"
2930
        );
2931

2932
        config.miner.segwit = true;
1✔
2933
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
1✔
2934

2935
        let reviewed = btc_controller.to_epoch_aware_pubkey(StacksEpochId::Epoch20, &pubkey);
1✔
2936
        assert_eq!(
1✔
2937
            false,
2938
            reviewed.compressed(),
1✔
2939
            "Segwit enabled with Epoch < 2.1: not compressed"
2940
        );
2941
        let reviewed = btc_controller.to_epoch_aware_pubkey(StacksEpochId::Epoch21, &pubkey);
1✔
2942
        assert_eq!(
1✔
2943
            true,
2944
            reviewed.compressed(),
1✔
2945
            "Segwit enabled with Epoch >= 2.1: compressed"
2946
        );
2947
    }
1✔
2948

2949
    #[test]
2950
    fn test_get_miner_address() {
1✔
2951
        let mut config = utils::create_miner_config();
1✔
2952
        let pub_key = utils::create_miner1_pubkey();
1✔
2953

2954
        config.miner.segwit = false;
1✔
2955
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
1✔
2956

2957
        let expected = utils::to_address_legacy(&pub_key);
1✔
2958
        let address = btc_controller.get_miner_address(StacksEpochId::Epoch20, &pub_key);
1✔
2959
        assert_eq!(
1✔
2960
            expected, address,
2961
            "Segwit disabled with Epoch < 2.1: legacy addr"
2962
        );
2963

2964
        let expected = utils::to_address_legacy(&pub_key);
1✔
2965
        let address = btc_controller.get_miner_address(StacksEpochId::Epoch21, &pub_key);
1✔
2966
        assert_eq!(
1✔
2967
            expected, address,
2968
            "Segwit disabled with Epoch >= 2.1: legacy addr"
2969
        );
2970

2971
        config.miner.segwit = true;
1✔
2972
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
1✔
2973

2974
        let expected = utils::to_address_legacy(&pub_key);
1✔
2975
        let address = btc_controller.get_miner_address(StacksEpochId::Epoch20, &pub_key);
1✔
2976
        assert_eq!(
1✔
2977
            expected, address,
2978
            "Segwit enabled with Epoch < 2.1: legacy addr"
2979
        );
2980

2981
        let expected = utils::to_address_segwit_p2wpkh(&pub_key);
1✔
2982
        let address = btc_controller.get_miner_address(StacksEpochId::Epoch21, &pub_key);
1✔
2983
        assert_eq!(
1✔
2984
            expected, address,
2985
            "Segwit enabled with Epoch >= 2.1: segwit addr"
2986
        );
2987
    }
1✔
2988

2989
    #[test]
2990
    fn test_instantiate_with_burnchain_on_follower_node_ok() {
1✔
2991
        let config = create_follower_config();
1✔
2992

2993
        let btc_controller = BitcoinRegtestController::with_burnchain(config, None, None, None);
1✔
2994

2995
        let result = panic::catch_unwind(AssertUnwindSafe(|| {
1✔
2996
            _ = btc_controller.get_rpc_client();
1✔
2997
        }));
1✔
2998
        assert!(
1✔
2999
            result.is_err(),
1✔
3000
            "Invoking any Bitcoin RPC related method should panic."
3001
        );
3002
    }
1✔
3003

3004
    #[test]
3005
    fn test_instantiate_with_burnchain_on_miner_node_ok() {
1✔
3006
        let config = create_miner_config();
1✔
3007

3008
        let btc_controller = BitcoinRegtestController::with_burnchain(config, None, None, None);
1✔
3009

3010
        let _ = btc_controller.get_rpc_client();
1✔
3011
        assert!(true, "Invoking any Bitcoin RPC related method should work.");
1✔
3012
    }
1✔
3013

3014
    #[test]
3015
    fn test_instantiate_with_burnchain_on_miner_node_failure() {
1✔
3016
        let mut config = create_miner_config();
1✔
3017
        config.burnchain.username = None;
1✔
3018
        config.burnchain.password = None;
1✔
3019

3020
        let result = panic::catch_unwind(|| {
1✔
3021
            _ = BitcoinRegtestController::with_burnchain(config, None, None, None);
1✔
3022
        });
1✔
3023
        assert!(
1✔
3024
            result.is_err(),
1✔
3025
            "Bitcoin RPC credentials are mandatory for miner node."
3026
        );
3027
    }
1✔
3028

3029
    #[test]
3030
    fn test_instantiate_new_dummy_on_follower_node_ok() {
1✔
3031
        let config = create_follower_config();
1✔
3032

3033
        let btc_controller = BitcoinRegtestController::new_dummy(config);
1✔
3034

3035
        let result = panic::catch_unwind(AssertUnwindSafe(|| {
1✔
3036
            _ = btc_controller.get_rpc_client();
1✔
3037
        }));
1✔
3038
        assert!(
1✔
3039
            result.is_err(),
1✔
3040
            "Invoking any Bitcoin RPC related method should panic."
3041
        );
3042
    }
1✔
3043

3044
    #[test]
3045
    fn test_instantiate_new_dummy_on_miner_node_ok() {
1✔
3046
        let config = create_miner_config();
1✔
3047

3048
        let btc_controller = BitcoinRegtestController::new_dummy(config);
1✔
3049

3050
        let _ = btc_controller.get_rpc_client();
1✔
3051
        assert!(true, "Invoking any Bitcoin RPC related method should work.");
1✔
3052
    }
1✔
3053

3054
    #[test]
3055
    fn test_instantiate_new_dummy_on_miner_node_failure() {
1✔
3056
        let mut config = create_miner_config();
1✔
3057
        config.burnchain.username = None;
1✔
3058
        config.burnchain.password = None;
1✔
3059

3060
        let result = panic::catch_unwind(|| {
1✔
3061
            _ = BitcoinRegtestController::new_dummy(config);
1✔
3062
        });
1✔
3063
        assert!(
1✔
3064
            result.is_err(),
1✔
3065
            "Bitcoin RPC credentials are mandatory for miner node."
3066
        );
3067
    }
1✔
3068

3069
    #[test]
3070
    #[ignore]
UNCOV
3071
    fn test_create_wallet_from_default_empty_name() {
×
UNCOV
3072
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3073
            return;
×
UNCOV
3074
        }
×
3075

UNCOV
3076
        let config = utils::create_miner_config();
×
3077

UNCOV
3078
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3079
        btcd_controller
×
UNCOV
3080
            .start_bitcoind()
×
UNCOV
3081
            .expect("bitcoind should be started!");
×
3082

UNCOV
3083
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
3084

UNCOV
3085
        let wallets = btc_controller.list_wallets().unwrap();
×
UNCOV
3086
        assert_eq!(0, wallets.len());
×
3087

UNCOV
3088
        btc_controller
×
UNCOV
3089
            .create_wallet_if_dne()
×
UNCOV
3090
            .expect("Wallet should now exists!");
×
3091

UNCOV
3092
        let wallets = btc_controller.list_wallets().unwrap();
×
3093
        assert_eq!(1, wallets.len());
×
UNCOV
3094
        assert_eq!("".to_owned(), wallets[0]);
×
UNCOV
3095
    }
×
3096

3097
    #[test]
3098
    #[ignore]
UNCOV
3099
    fn test_create_wallet_from_custom_name() {
×
UNCOV
3100
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3101
            return;
×
UNCOV
3102
        }
×
3103

UNCOV
3104
        let mut config = utils::create_miner_config();
×
UNCOV
3105
        config.burnchain.wallet_name = String::from("mywallet");
×
3106

UNCOV
3107
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3108
        btcd_controller
×
UNCOV
3109
            .start_bitcoind()
×
UNCOV
3110
            .expect("bitcoind should be started!");
×
3111

UNCOV
3112
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
3113

UNCOV
3114
        btc_controller
×
UNCOV
3115
            .create_wallet_if_dne()
×
UNCOV
3116
            .expect("Wallet should now exists!");
×
3117

UNCOV
3118
        let wallets = btc_controller.list_wallets().unwrap();
×
3119
        assert_eq!(1, wallets.len());
×
UNCOV
3120
        assert_eq!("mywallet".to_owned(), wallets[0]);
×
UNCOV
3121
    }
×
3122

3123
    #[test]
3124
    #[ignore]
UNCOV
3125
    fn test_retrieve_utxo_set_with_all_utxos() {
×
UNCOV
3126
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3127
            return;
×
UNCOV
3128
        }
×
3129

UNCOV
3130
        let miner_pubkey = utils::create_miner1_pubkey();
×
3131

UNCOV
3132
        let mut config = utils::create_miner_config();
×
UNCOV
3133
        config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
3134

UNCOV
3135
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3136
        btcd_controller
×
UNCOV
3137
            .start_bitcoind()
×
UNCOV
3138
            .expect("Failed starting bitcoind");
×
3139

UNCOV
3140
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3141
        btc_controller.bootstrap_chain(150); //produces 50 spendable utxos
×
3142

UNCOV
3143
        let address = to_address_legacy(&miner_pubkey);
×
UNCOV
3144
        let utxo_set = btc_controller
×
UNCOV
3145
            .retrieve_utxo_set(&address, false, 0, &None, 0)
×
UNCOV
3146
            .expect("Failed to get utxos");
×
3147
        assert_eq!(btc_controller.get_block_hash(0), utxo_set.bhh);
×
UNCOV
3148
        assert_eq!(50, utxo_set.num_utxos());
×
UNCOV
3149
    }
×
3150

3151
    #[test]
3152
    #[ignore]
UNCOV
3153
    fn test_retrive_utxo_set_excluding_some_utxo() {
×
UNCOV
3154
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3155
            return;
×
UNCOV
3156
        }
×
3157

UNCOV
3158
        let miner_pubkey = utils::create_miner1_pubkey();
×
3159

UNCOV
3160
        let mut config = utils::create_miner_config();
×
UNCOV
3161
        config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
3162

UNCOV
3163
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3164
        btcd_controller
×
UNCOV
3165
            .start_bitcoind()
×
UNCOV
3166
            .expect("Failed starting bitcoind");
×
3167

UNCOV
3168
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3169
        btc_controller.bootstrap_chain(150); //produces 50 spendable utxos
×
3170

UNCOV
3171
        let address = to_address_legacy(&miner_pubkey);
×
UNCOV
3172
        let mut all_utxos = btc_controller
×
UNCOV
3173
            .retrieve_utxo_set(&address, false, 0, &None, 0)
×
UNCOV
3174
            .expect("Failed to get utxos (50)");
×
3175

UNCOV
3176
        let filtered_utxos = btc_controller
×
UNCOV
3177
            .retrieve_utxo_set(&address, false, 0, &Some(all_utxos.clone()), 0)
×
UNCOV
3178
            .expect("Failed to get utxos");
×
UNCOV
3179
        assert_eq!(0, filtered_utxos.num_utxos(), "all utxos filtered out!");
×
3180

UNCOV
3181
        all_utxos.utxos.drain(0..10);
×
UNCOV
3182
        let filtered_utxos = btc_controller
×
UNCOV
3183
            .retrieve_utxo_set(&address, false, 0, &Some(all_utxos), 0)
×
3184
            .expect("Failed to get utxos");
×
UNCOV
3185
        assert_eq!(10, filtered_utxos.num_utxos(), "40 utxos filtered out!");
×
UNCOV
3186
    }
×
3187

3188
    #[test]
3189
    #[ignore]
UNCOV
3190
    fn test_list_unspent_with_max_utxos_config() {
×
UNCOV
3191
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3192
            return;
×
UNCOV
3193
        }
×
3194

UNCOV
3195
        let miner_pubkey = utils::create_miner1_pubkey();
×
3196

UNCOV
3197
        let mut config = utils::create_miner_config();
×
UNCOV
3198
        config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
UNCOV
3199
        config.burnchain.max_unspent_utxos = Some(10);
×
3200

UNCOV
3201
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3202
        btcd_controller
×
UNCOV
3203
            .start_bitcoind()
×
UNCOV
3204
            .expect("Failed starting bitcoind");
×
3205

UNCOV
3206
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3207
        btc_controller.bootstrap_chain(150); //produces 50 spendable utxos
×
3208

UNCOV
3209
        let address = to_address_legacy(&miner_pubkey);
×
UNCOV
3210
        let utxos = btc_controller
×
UNCOV
3211
            .retrieve_utxo_set(&address, false, 1, &None, 0)
×
3212
            .expect("Failed to get utxos");
×
UNCOV
3213
        assert_eq!(10, utxos.num_utxos());
×
UNCOV
3214
    }
×
3215

3216
    #[test]
3217
    #[ignore]
UNCOV
3218
    fn test_get_all_utxos_with_confirmation() {
×
UNCOV
3219
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3220
            return;
×
UNCOV
3221
        }
×
3222

UNCOV
3223
        let miner_pubkey = utils::create_miner1_pubkey();
×
3224

UNCOV
3225
        let mut config = utils::create_miner_config();
×
UNCOV
3226
        config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
3227

UNCOV
3228
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3229
        btcd_controller
×
UNCOV
3230
            .start_bitcoind()
×
UNCOV
3231
            .expect("bitcoind should be started!");
×
3232

UNCOV
3233
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
3234

UNCOV
3235
        btc_controller.bootstrap_chain(100);
×
UNCOV
3236
        let utxos = btc_controller.get_all_utxos(&miner_pubkey);
×
UNCOV
3237
        assert_eq!(0, utxos.len());
×
3238

UNCOV
3239
        btc_controller.build_next_block(1);
×
UNCOV
3240
        let utxos = btc_controller.get_all_utxos(&miner_pubkey);
×
UNCOV
3241
        assert_eq!(1, utxos.len());
×
UNCOV
3242
        assert_eq!(101, utxos[0].confirmations);
×
UNCOV
3243
        assert_eq!(5_000_000_000, utxos[0].amount);
×
3244

UNCOV
3245
        btc_controller.build_next_block(1);
×
UNCOV
3246
        let mut utxos = btc_controller.get_all_utxos(&miner_pubkey);
×
UNCOV
3247
        utxos.sort_by(|a, b| b.confirmations.cmp(&a.confirmations));
×
3248

UNCOV
3249
        assert_eq!(2, utxos.len());
×
UNCOV
3250
        assert_eq!(102, utxos[0].confirmations);
×
UNCOV
3251
        assert_eq!(5_000_000_000, utxos[0].amount);
×
3252
        assert_eq!(101, utxos[1].confirmations);
×
UNCOV
3253
        assert_eq!(5_000_000_000, utxos[1].amount);
×
UNCOV
3254
    }
×
3255

3256
    #[test]
3257
    #[ignore]
UNCOV
3258
    fn test_get_all_utxos_for_other_pubkey() {
×
UNCOV
3259
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3260
            return;
×
UNCOV
3261
        }
×
3262

UNCOV
3263
        let miner1_pubkey = utils::create_miner1_pubkey();
×
UNCOV
3264
        let miner2_pubkey = utils::create_miner2_pubkey();
×
3265

UNCOV
3266
        let mut config = utils::create_miner_config();
×
UNCOV
3267
        config.burnchain.local_mining_public_key = Some(miner1_pubkey.to_hex());
×
3268

UNCOV
3269
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3270
        btcd_controller
×
UNCOV
3271
            .start_bitcoind()
×
UNCOV
3272
            .expect("bitcoind should be started!");
×
3273

UNCOV
3274
        let miner1_btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3275
        miner1_btc_controller.bootstrap_chain(1); // one utxo for miner_pubkey related address
×
3276

UNCOV
3277
        config.burnchain.local_mining_public_key = Some(miner2_pubkey.to_hex());
×
UNCOV
3278
        config.burnchain.wallet_name = "miner2_wallet".to_string();
×
UNCOV
3279
        let miner2_btc_controller = BitcoinRegtestController::new(config, None);
×
UNCOV
3280
        miner2_btc_controller.bootstrap_chain(102); // two utxo for other_pubkeys related address
×
3281

UNCOV
3282
        let utxos = miner1_btc_controller.get_all_utxos(&miner1_pubkey);
×
UNCOV
3283
        assert_eq!(1, utxos.len(), "miner1 see its own utxos");
×
3284

UNCOV
3285
        let utxos = miner1_btc_controller.get_all_utxos(&miner2_pubkey);
×
UNCOV
3286
        assert_eq!(2, utxos.len(), "miner1 see miner2 utxos");
×
3287

UNCOV
3288
        let utxos = miner2_btc_controller.get_all_utxos(&miner2_pubkey);
×
UNCOV
3289
        assert_eq!(2, utxos.len(), "miner2 see its own utxos");
×
3290

3291
        let utxos = miner2_btc_controller.get_all_utxos(&miner1_pubkey);
×
UNCOV
3292
        assert_eq!(1, utxos.len(), "miner2 see miner1 own utxos");
×
UNCOV
3293
    }
×
3294

3295
    #[test]
3296
    #[ignore]
UNCOV
3297
    fn test_get_utxos_ok_with_confirmation() {
×
UNCOV
3298
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3299
            return;
×
UNCOV
3300
        }
×
3301

UNCOV
3302
        let miner_pubkey = utils::create_miner1_pubkey();
×
3303

UNCOV
3304
        let mut config = utils::create_miner_config();
×
UNCOV
3305
        config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
3306

UNCOV
3307
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3308
        btcd_controller
×
UNCOV
3309
            .start_bitcoind()
×
UNCOV
3310
            .expect("bitcoind should be started!");
×
3311

UNCOV
3312
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3313
        btc_controller.bootstrap_chain(101);
×
3314

UNCOV
3315
        let utxos_opt =
×
UNCOV
3316
            btc_controller.get_utxos(StacksEpochId::Epoch31, &miner_pubkey, 1, None, 101);
×
UNCOV
3317
        let uxto_set = utxos_opt.expect("Shouldn't be None at height 101!");
×
3318

UNCOV
3319
        assert_eq!(btc_controller.get_block_hash(101), uxto_set.bhh);
×
UNCOV
3320
        assert_eq!(1, uxto_set.num_utxos());
×
UNCOV
3321
        assert_eq!(5_000_000_000, uxto_set.total_available());
×
UNCOV
3322
        let utxos = uxto_set.utxos;
×
UNCOV
3323
        assert_eq!(101, utxos[0].confirmations);
×
UNCOV
3324
        assert_eq!(5_000_000_000, utxos[0].amount);
×
3325

UNCOV
3326
        btc_controller.build_next_block(1);
×
3327

UNCOV
3328
        let utxos_opt =
×
UNCOV
3329
            btc_controller.get_utxos(StacksEpochId::Epoch31, &miner_pubkey, 1, None, 102);
×
UNCOV
3330
        let uxto_set = utxos_opt.expect("Shouldn't be None at height 102!");
×
3331

UNCOV
3332
        assert_eq!(btc_controller.get_block_hash(102), uxto_set.bhh);
×
UNCOV
3333
        assert_eq!(2, uxto_set.num_utxos());
×
UNCOV
3334
        assert_eq!(10_000_000_000, uxto_set.total_available());
×
UNCOV
3335
        let mut utxos = uxto_set.utxos;
×
UNCOV
3336
        utxos.sort_by(|a, b| b.confirmations.cmp(&a.confirmations));
×
UNCOV
3337
        assert_eq!(102, utxos[0].confirmations);
×
UNCOV
3338
        assert_eq!(5_000_000_000, utxos[0].amount);
×
3339
        assert_eq!(101, utxos[1].confirmations);
×
UNCOV
3340
        assert_eq!(5_000_000_000, utxos[1].amount);
×
UNCOV
3341
    }
×
3342

3343
    #[test]
3344
    #[ignore]
UNCOV
3345
    fn test_get_utxos_none_due_to_filter_total_required() {
×
UNCOV
3346
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3347
            return;
×
UNCOV
3348
        }
×
3349

UNCOV
3350
        let miner_pubkey = utils::create_miner1_pubkey();
×
3351

UNCOV
3352
        let mut config = utils::create_miner_config();
×
UNCOV
3353
        config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
3354

UNCOV
3355
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3356
        btcd_controller
×
UNCOV
3357
            .start_bitcoind()
×
UNCOV
3358
            .expect("bitcoind should be started!");
×
3359

UNCOV
3360
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3361
        btc_controller.bootstrap_chain(101); // one utxo exists
×
3362

UNCOV
3363
        let too_much_required = 10_000_000_000;
×
UNCOV
3364
        let utxos = btc_controller.get_utxos(
×
UNCOV
3365
            StacksEpochId::Epoch31,
×
UNCOV
3366
            &miner_pubkey,
×
UNCOV
3367
            too_much_required,
×
UNCOV
3368
            None,
×
3369
            0,
3370
        );
UNCOV
3371
        assert!(utxos.is_none(), "None because too much required");
×
UNCOV
3372
    }
×
3373

3374
    #[test]
3375
    #[ignore]
UNCOV
3376
    fn test_get_utxos_none_due_to_filter_pubkey() {
×
UNCOV
3377
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3378
            return;
×
UNCOV
3379
        }
×
3380

UNCOV
3381
        let miner_pubkey = utils::create_miner1_pubkey();
×
3382

UNCOV
3383
        let mut config = utils::create_miner_config();
×
UNCOV
3384
        config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
3385

UNCOV
3386
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3387
        btcd_controller
×
UNCOV
3388
            .start_bitcoind()
×
UNCOV
3389
            .expect("bitcoind should be started!");
×
3390

UNCOV
3391
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3392
        btc_controller.bootstrap_chain(101); // one utxo exists
×
3393

UNCOV
3394
        let other_pubkey = utils::create_miner2_pubkey();
×
UNCOV
3395
        let utxos = btc_controller.get_utxos(StacksEpochId::Epoch31, &other_pubkey, 1, None, 0);
×
UNCOV
3396
        assert!(
×
UNCOV
3397
            utxos.is_none(),
×
3398
            "None because utxos for other pubkey don't exist"
3399
        );
UNCOV
3400
    }
×
3401

3402
    #[test]
3403
    #[ignore]
UNCOV
3404
    fn test_get_utxos_none_due_to_filter_utxo_exclusion() {
×
UNCOV
3405
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3406
            return;
×
UNCOV
3407
        }
×
3408

UNCOV
3409
        let miner_pubkey = utils::create_miner1_pubkey();
×
3410

UNCOV
3411
        let mut config = utils::create_miner_config();
×
UNCOV
3412
        config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
3413

UNCOV
3414
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3415
        btcd_controller
×
UNCOV
3416
            .start_bitcoind()
×
UNCOV
3417
            .expect("bitcoind should be started!");
×
3418

UNCOV
3419
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3420
        btc_controller.bootstrap_chain(101); // one utxo exists
×
3421

UNCOV
3422
        let existent_utxo = btc_controller
×
UNCOV
3423
            .get_utxos(StacksEpochId::Epoch31, &miner_pubkey, 0, None, 0)
×
UNCOV
3424
            .expect("utxo set should exist");
×
UNCOV
3425
        let utxos = btc_controller.get_utxos(
×
UNCOV
3426
            StacksEpochId::Epoch31,
×
UNCOV
3427
            &miner_pubkey,
×
3428
            0,
UNCOV
3429
            Some(existent_utxo),
×
3430
            0,
3431
        );
UNCOV
3432
        assert!(
×
UNCOV
3433
            utxos.is_none(),
×
3434
            "None because filtering exclude existent utxo set"
3435
        );
UNCOV
3436
    }
×
3437

3438
    #[test]
3439
    #[ignore]
UNCOV
3440
    fn test_tx_confirmed_from_utxo_ok() {
×
UNCOV
3441
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3442
            return;
×
UNCOV
3443
        }
×
3444

UNCOV
3445
        let miner_pubkey = utils::create_miner1_pubkey();
×
3446

UNCOV
3447
        let mut config = utils::create_miner_config();
×
UNCOV
3448
        config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
3449

UNCOV
3450
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3451
        btcd_controller
×
UNCOV
3452
            .start_bitcoind()
×
UNCOV
3453
            .expect("bitcoind should be started!");
×
3454

UNCOV
3455
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
3456

UNCOV
3457
        btc_controller.bootstrap_chain(101);
×
UNCOV
3458
        let utxos = btc_controller.get_all_utxos(&miner_pubkey);
×
UNCOV
3459
        assert_eq!(1, utxos.len(), "One UTXO should be confirmed!");
×
3460

UNCOV
3461
        let txid = Txid::from_bitcoin_tx_hash(&utxos[0].txid);
×
UNCOV
3462
        assert!(
×
UNCOV
3463
            btc_controller.is_transaction_confirmed(&txid),
×
3464
            "UTXO tx should be confirmed!"
3465
        );
UNCOV
3466
    }
×
3467

3468
    #[test]
3469
    #[ignore]
UNCOV
3470
    fn test_import_public_key_ok() {
×
UNCOV
3471
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3472
            return;
×
UNCOV
3473
        }
×
3474

UNCOV
3475
        let miner_pubkey = utils::create_miner1_pubkey();
×
3476

UNCOV
3477
        let config = utils::create_miner_config();
×
3478

UNCOV
3479
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3480
        btcd_controller
×
UNCOV
3481
            .start_bitcoind()
×
UNCOV
3482
            .expect("bitcoind should be started!");
×
3483

UNCOV
3484
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
3485
        btc_controller
×
UNCOV
3486
            .create_wallet_if_dne()
×
UNCOV
3487
            .expect("Wallet should be created!");
×
3488

UNCOV
3489
        let result = btc_controller.import_public_key(&miner_pubkey);
×
UNCOV
3490
        assert!(
×
UNCOV
3491
            result.is_ok(),
×
3492
            "Should be ok, got err instead: {:?}",
3493
            result.unwrap_err()
×
3494
        );
UNCOV
3495
    }
×
3496

3497
    #[test]
3498
    #[ignore]
UNCOV
3499
    fn test_import_public_key_twice_ok() {
×
UNCOV
3500
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3501
            return;
×
UNCOV
3502
        }
×
3503

UNCOV
3504
        let miner_pubkey = utils::create_miner1_pubkey();
×
3505

UNCOV
3506
        let config = utils::create_miner_config();
×
3507

UNCOV
3508
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3509
        btcd_controller
×
UNCOV
3510
            .start_bitcoind()
×
UNCOV
3511
            .expect("bitcoind should be started!");
×
3512

UNCOV
3513
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3514
        btc_controller
×
UNCOV
3515
            .create_wallet_if_dne()
×
UNCOV
3516
            .expect("Wallet should be created!");
×
3517

UNCOV
3518
        btc_controller
×
3519
            .import_public_key(&miner_pubkey)
×
UNCOV
3520
            .expect("Import should be ok: first time!");
×
3521

3522
        //ok, but it is basically a no-op
UNCOV
3523
        let result = btc_controller.import_public_key(&miner_pubkey);
×
UNCOV
3524
        assert!(
×
UNCOV
3525
            result.is_ok(),
×
3526
            "Should be ok, got err instead: {:?}",
3527
            result.unwrap_err()
×
3528
        );
UNCOV
3529
    }
×
3530

3531
    #[test]
3532
    #[ignore]
UNCOV
3533
    fn test_import_public_key_segwit_ok() {
×
UNCOV
3534
        if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3535
            return;
×
UNCOV
3536
        }
×
3537

UNCOV
3538
        let miner_pubkey = utils::create_miner1_pubkey();
×
3539

UNCOV
3540
        let mut config = utils::create_miner_config();
×
UNCOV
3541
        config.miner.segwit = true;
×
3542

UNCOV
3543
        let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3544
        btcd_controller
×
UNCOV
3545
            .start_bitcoind()
×
UNCOV
3546
            .expect("bitcoind should be started!");
×
3547

UNCOV
3548
        let btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
3549
        btc_controller
×
UNCOV
3550
            .create_wallet_if_dne()
×
UNCOV
3551
            .expect("Wallet should be created!");
×
3552

UNCOV
3553
        let result = btc_controller.import_public_key(&miner_pubkey);
×
UNCOV
3554
        assert!(
×
UNCOV
3555
            result.is_ok(),
×
3556
            "Should be ok, got err instead: {:?}",
UNCOV
3557
            result.unwrap_err()
×
3558
        );
UNCOV
3559
    }
×
3560

3561
    /// Tests related to Leader Block Commit operation
3562
    mod leader_commit_op {
3563
        use super::*;
3564

3565
        #[test]
3566
        #[ignore]
UNCOV
3567
        fn test_build_leader_block_commit_tx_ok_with_new_commit_op() {
×
UNCOV
3568
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3569
                return;
×
UNCOV
3570
            }
×
3571

UNCOV
3572
            let keychain = utils::create_keychain();
×
UNCOV
3573
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
3574
            let mut op_signer = keychain.generate_op_signer();
×
3575

UNCOV
3576
            let mut config = utils::create_miner_config();
×
UNCOV
3577
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
UNCOV
3578
            config.burnchain.pox_reward_length = Some(11);
×
3579

UNCOV
3580
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3581
            btcd_controller
×
UNCOV
3582
                .start_bitcoind()
×
UNCOV
3583
                .expect("bitcoind should be started!");
×
3584

UNCOV
3585
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3586
            btc_controller
×
UNCOV
3587
                .connect_dbs()
×
UNCOV
3588
                .expect("Dbs initialization required!");
×
UNCOV
3589
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
3590

UNCOV
3591
            let mut commit_op = utils::create_templated_commit_op();
×
UNCOV
3592
            commit_op.sunset_burn = 5_500;
×
UNCOV
3593
            commit_op.burn_fee = 110_000;
×
3594

UNCOV
3595
            let tx = btc_controller
×
UNCOV
3596
                .build_leader_block_commit_tx(
×
UNCOV
3597
                    StacksEpochId::Epoch31,
×
UNCOV
3598
                    commit_op.clone(),
×
UNCOV
3599
                    &mut op_signer,
×
3600
                )
UNCOV
3601
                .expect("Build leader block commit should work");
×
3602

UNCOV
3603
            assert!(op_signer.is_disposed());
×
3604

UNCOV
3605
            assert_eq!(1, tx.version);
×
UNCOV
3606
            assert_eq!(0, tx.lock_time);
×
UNCOV
3607
            assert_eq!(1, tx.input.len());
×
UNCOV
3608
            assert_eq!(4, tx.output.len());
×
3609

3610
            // utxos list contains the only existing utxo
UNCOV
3611
            let used_utxos = btc_controller.get_all_utxos(&miner_pubkey);
×
UNCOV
3612
            let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0);
×
UNCOV
3613
            assert_eq!(input_0, tx.input[0]);
×
3614

UNCOV
3615
            let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500);
×
UNCOV
3616
            let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_000);
×
UNCOV
3617
            let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_000);
×
UNCOV
3618
            let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 4_999_865_300);
×
UNCOV
3619
            assert_eq!(op_return, tx.output[0]);
×
UNCOV
3620
            assert_eq!(op_commit_1, tx.output[1]);
×
3621
            assert_eq!(op_commit_2, tx.output[2]);
×
UNCOV
3622
            assert_eq!(op_change, tx.output[3]);
×
UNCOV
3623
        }
×
3624

3625
        #[test]
3626
        #[ignore]
UNCOV
3627
        fn test_build_leader_block_commit_tx_fails_resub_same_commit_op_while_prev_not_confirmed() {
×
UNCOV
3628
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3629
                return;
×
UNCOV
3630
            }
×
3631

UNCOV
3632
            let keychain = utils::create_keychain();
×
UNCOV
3633
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
3634
            let mut op_signer = keychain.generate_op_signer();
×
3635

UNCOV
3636
            let mut config = utils::create_miner_config();
×
UNCOV
3637
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
UNCOV
3638
            config.burnchain.pox_reward_length = Some(11);
×
3639

UNCOV
3640
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3641
            btcd_controller
×
UNCOV
3642
                .start_bitcoind()
×
UNCOV
3643
                .expect("bitcoind should be started!");
×
3644

UNCOV
3645
            let mut btc_controller = BitcoinRegtestController::new(config, None);
×
UNCOV
3646
            btc_controller
×
UNCOV
3647
                .connect_dbs()
×
UNCOV
3648
                .expect("Dbs initialization required!");
×
UNCOV
3649
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
3650

UNCOV
3651
            let commit_op = utils::create_templated_commit_op();
×
3652

UNCOV
3653
            let _first_tx_ok = btc_controller
×
UNCOV
3654
                .build_leader_block_commit_tx(
×
UNCOV
3655
                    StacksEpochId::Epoch31,
×
UNCOV
3656
                    commit_op.clone(),
×
UNCOV
3657
                    &mut op_signer,
×
3658
                )
UNCOV
3659
                .expect("At first, building leader block commit should work");
×
3660

3661
            // re-submitting same commit while previous it is not confirmed by the burnchain
UNCOV
3662
            let resubmit = btc_controller.build_leader_block_commit_tx(
×
UNCOV
3663
                StacksEpochId::Epoch31,
×
UNCOV
3664
                commit_op,
×
UNCOV
3665
                &mut op_signer,
×
3666
            );
3667

UNCOV
3668
            assert!(resubmit.is_err());
×
UNCOV
3669
            assert_eq!(
×
3670
                BurnchainControllerError::IdenticalOperation,
3671
                resubmit.unwrap_err()
×
3672
            );
UNCOV
3673
        }
×
3674

3675
        #[test]
3676
        #[ignore]
UNCOV
3677
        fn test_build_leader_block_commit_tx_fails_resub_same_commit_op_while_prev_is_confirmed() {
×
UNCOV
3678
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3679
                return;
×
UNCOV
3680
            }
×
3681

UNCOV
3682
            let keychain = utils::create_keychain();
×
UNCOV
3683
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
3684
            let mut op_signer = keychain.generate_op_signer();
×
3685

UNCOV
3686
            let mut config = utils::create_miner_config();
×
UNCOV
3687
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
UNCOV
3688
            config.burnchain.pox_reward_length = Some(11);
×
3689

UNCOV
3690
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3691
            btcd_controller
×
UNCOV
3692
                .start_bitcoind()
×
UNCOV
3693
                .expect("bitcoind should be started!");
×
3694

UNCOV
3695
            let mut btc_controller = BitcoinRegtestController::new(config, None);
×
UNCOV
3696
            btc_controller
×
UNCOV
3697
                .connect_dbs()
×
UNCOV
3698
                .expect("Dbs initialization required!");
×
UNCOV
3699
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
3700

UNCOV
3701
            let commit_op = utils::create_templated_commit_op();
×
3702

UNCOV
3703
            let first_tx_ok = btc_controller
×
UNCOV
3704
                .build_leader_block_commit_tx(
×
UNCOV
3705
                    StacksEpochId::Epoch31,
×
UNCOV
3706
                    commit_op.clone(),
×
UNCOV
3707
                    &mut op_signer,
×
3708
                )
UNCOV
3709
                .expect("At first, building leader block commit should work");
×
3710

UNCOV
3711
            utils::mine_tx(&btc_controller, &first_tx_ok); // Now tx is confirmed
×
3712

3713
            // re-submitting same commit while previous it is confirmed by the burnchain
UNCOV
3714
            let resubmit = btc_controller.build_leader_block_commit_tx(
×
UNCOV
3715
                StacksEpochId::Epoch31,
×
UNCOV
3716
                commit_op,
×
UNCOV
3717
                &mut op_signer,
×
3718
            );
3719

UNCOV
3720
            assert!(resubmit.is_err());
×
UNCOV
3721
            assert_eq!(
×
3722
                BurnchainControllerError::IdenticalOperation,
3723
                resubmit.unwrap_err()
×
3724
            );
UNCOV
3725
        }
×
3726

3727
        #[test]
3728
        #[ignore]
UNCOV
3729
        fn test_build_leader_block_commit_tx_ok_while_prev_is_confirmed() {
×
UNCOV
3730
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3731
                return;
×
UNCOV
3732
            }
×
3733

UNCOV
3734
            let keychain = utils::create_keychain();
×
UNCOV
3735
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
3736
            let mut op_signer = keychain.generate_op_signer();
×
3737

UNCOV
3738
            let mut config = utils::create_miner_config();
×
UNCOV
3739
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
UNCOV
3740
            config.burnchain.pox_reward_length = Some(11);
×
3741

UNCOV
3742
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3743
            btcd_controller
×
UNCOV
3744
                .start_bitcoind()
×
UNCOV
3745
                .expect("bitcoind should be started!");
×
3746

UNCOV
3747
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3748
            btc_controller
×
UNCOV
3749
                .connect_dbs()
×
UNCOV
3750
                .expect("Dbs initialization required!");
×
UNCOV
3751
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
3752

UNCOV
3753
            let mut commit_op = utils::create_templated_commit_op();
×
UNCOV
3754
            commit_op.sunset_burn = 5_500;
×
UNCOV
3755
            commit_op.burn_fee = 110_000;
×
3756

UNCOV
3757
            let first_tx_ok = btc_controller
×
UNCOV
3758
                .build_leader_block_commit_tx(
×
UNCOV
3759
                    StacksEpochId::Epoch31,
×
UNCOV
3760
                    commit_op.clone(),
×
UNCOV
3761
                    &mut op_signer,
×
3762
                )
UNCOV
3763
                .expect("At first, building leader block commit should work");
×
3764

UNCOV
3765
            let first_txid = first_tx_ok.txid();
×
3766

3767
            // Now tx is confirmed: prev utxo is updated and one more utxo is generated
UNCOV
3768
            utils::mine_tx(&btc_controller, &first_tx_ok);
×
3769

3770
            // re-gen signer othewise fails because it will be disposed during previous commit tx.
UNCOV
3771
            let mut signer = keychain.generate_op_signer();
×
3772
            // Modify the commit operation payload slightly, so it no longer matches the confirmed version.
UNCOV
3773
            commit_op.burn_fee += 10;
×
3774

UNCOV
3775
            let new_tx = btc_controller
×
UNCOV
3776
                .build_leader_block_commit_tx(
×
UNCOV
3777
                    StacksEpochId::Epoch31,
×
UNCOV
3778
                    commit_op.clone(),
×
UNCOV
3779
                    &mut signer,
×
3780
                )
UNCOV
3781
                .expect("Commit tx should be created!");
×
3782

UNCOV
3783
            assert!(op_signer.is_disposed());
×
3784

UNCOV
3785
            assert_eq!(1, new_tx.version);
×
UNCOV
3786
            assert_eq!(0, new_tx.lock_time);
×
UNCOV
3787
            assert_eq!(1, new_tx.input.len());
×
UNCOV
3788
            assert_eq!(4, new_tx.output.len());
×
3789

3790
            // utxos list contains the sole utxo used by prev commit operation
3791
            // because has enough amount to cover the new commit
UNCOV
3792
            let used_utxos: Vec<UTXO> = btc_controller
×
UNCOV
3793
                .get_all_utxos(&miner_pubkey)
×
UNCOV
3794
                .into_iter()
×
UNCOV
3795
                .filter(|utxo| utxo.txid == first_txid)
×
UNCOV
3796
                .collect();
×
3797

UNCOV
3798
            let input_0 = utils::txin_at_index(&new_tx, &op_signer, &used_utxos, 0);
×
UNCOV
3799
            assert_eq!(input_0, new_tx.input[0]);
×
3800

UNCOV
3801
            let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500);
×
UNCOV
3802
            let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_005);
×
UNCOV
3803
            let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_005);
×
UNCOV
3804
            let op_change = utils::txout_opdup_change_legacy(&mut signer, 4_999_730_590);
×
UNCOV
3805
            assert_eq!(op_return, new_tx.output[0]);
×
UNCOV
3806
            assert_eq!(op_commit_1, new_tx.output[1]);
×
3807
            assert_eq!(op_commit_2, new_tx.output[2]);
×
UNCOV
3808
            assert_eq!(op_change, new_tx.output[3]);
×
UNCOV
3809
        }
×
3810

3811
        #[test]
3812
        #[ignore]
UNCOV
3813
        fn test_build_leader_block_commit_tx_ok_rbf_while_prev_not_confirmed() {
×
UNCOV
3814
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3815
                return;
×
UNCOV
3816
            }
×
3817

UNCOV
3818
            let keychain = utils::create_keychain();
×
UNCOV
3819
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
3820
            let mut op_signer = keychain.generate_op_signer();
×
3821

UNCOV
3822
            let mut config = utils::create_miner_config();
×
UNCOV
3823
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
UNCOV
3824
            config.burnchain.pox_reward_length = Some(11);
×
3825

UNCOV
3826
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3827
            btcd_controller
×
UNCOV
3828
                .start_bitcoind()
×
UNCOV
3829
                .expect("bitcoind should be started!");
×
3830

UNCOV
3831
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3832
            btc_controller
×
UNCOV
3833
                .connect_dbs()
×
UNCOV
3834
                .expect("Dbs initialization required!");
×
UNCOV
3835
            btc_controller.bootstrap_chain(101); // Now, one utxo exists
×
3836

UNCOV
3837
            let mut commit_op = utils::create_templated_commit_op();
×
UNCOV
3838
            commit_op.sunset_burn = 5_500;
×
UNCOV
3839
            commit_op.burn_fee = 110_000;
×
3840

UNCOV
3841
            let _first_tx_ok = btc_controller
×
UNCOV
3842
                .build_leader_block_commit_tx(
×
UNCOV
3843
                    StacksEpochId::Epoch31,
×
UNCOV
3844
                    commit_op.clone(),
×
UNCOV
3845
                    &mut op_signer,
×
3846
                )
UNCOV
3847
                .expect("At first, building leader block commit should work");
×
3848

3849
            //re-gen signer othewise fails because it will be disposed during previous commit tx.
UNCOV
3850
            let mut signer = keychain.generate_op_signer();
×
3851
            //small change to the commit op payload
UNCOV
3852
            commit_op.burn_fee += 10;
×
3853

UNCOV
3854
            let rbf_tx = btc_controller
×
UNCOV
3855
                .build_leader_block_commit_tx(
×
UNCOV
3856
                    StacksEpochId::Epoch31,
×
UNCOV
3857
                    commit_op.clone(),
×
UNCOV
3858
                    &mut signer,
×
3859
                )
UNCOV
3860
                .expect("Commit tx should be rbf-ed");
×
3861

UNCOV
3862
            assert!(op_signer.is_disposed());
×
3863

UNCOV
3864
            assert_eq!(1, rbf_tx.version);
×
UNCOV
3865
            assert_eq!(0, rbf_tx.lock_time);
×
UNCOV
3866
            assert_eq!(1, rbf_tx.input.len());
×
UNCOV
3867
            assert_eq!(4, rbf_tx.output.len());
×
3868

3869
            // utxos list contains the only existing utxo
UNCOV
3870
            let used_utxos = btc_controller.get_all_utxos(&miner_pubkey);
×
3871

UNCOV
3872
            let input_0 = utils::txin_at_index(&rbf_tx, &op_signer, &used_utxos, 0);
×
UNCOV
3873
            assert_eq!(input_0, rbf_tx.input[0]);
×
3874

UNCOV
3875
            let op_return = utils::txout_opreturn(&commit_op, &config.burnchain.magic_bytes, 5_500);
×
UNCOV
3876
            let op_commit_1 = utils::txout_opdup_commit_to(&commit_op.commit_outs[0], 55_005);
×
UNCOV
3877
            let op_commit_2 = utils::txout_opdup_commit_to(&commit_op.commit_outs[1], 55_005);
×
UNCOV
3878
            let op_change = utils::txout_opdup_change_legacy(&mut signer, 4_999_862_985);
×
UNCOV
3879
            assert_eq!(op_return, rbf_tx.output[0]);
×
UNCOV
3880
            assert_eq!(op_commit_1, rbf_tx.output[1]);
×
3881
            assert_eq!(op_commit_2, rbf_tx.output[2]);
×
UNCOV
3882
            assert_eq!(op_change, rbf_tx.output[3]);
×
UNCOV
3883
        }
×
3884

3885
        #[test]
3886
        #[ignore]
UNCOV
3887
        fn test_make_operation_leader_block_commit_tx_ok() {
×
UNCOV
3888
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3889
                return;
×
UNCOV
3890
            }
×
3891

UNCOV
3892
            let keychain = utils::create_keychain();
×
UNCOV
3893
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
3894
            let mut op_signer = keychain.generate_op_signer();
×
3895

UNCOV
3896
            let mut config = utils::create_miner_config();
×
UNCOV
3897
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
UNCOV
3898
            config.burnchain.pox_reward_length = Some(11);
×
3899

UNCOV
3900
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3901
            btcd_controller
×
UNCOV
3902
                .start_bitcoind()
×
UNCOV
3903
                .expect("bitcoind should be started!");
×
3904

UNCOV
3905
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3906
            btc_controller
×
UNCOV
3907
                .connect_dbs()
×
UNCOV
3908
                .expect("Dbs initialization required!");
×
UNCOV
3909
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
3910

UNCOV
3911
            let mut commit_op = utils::create_templated_commit_op();
×
UNCOV
3912
            commit_op.sunset_burn = 5_500;
×
UNCOV
3913
            commit_op.burn_fee = 110_000;
×
3914

UNCOV
3915
            let tx = btc_controller
×
UNCOV
3916
                .make_operation_tx(
×
UNCOV
3917
                    StacksEpochId::Epoch31,
×
UNCOV
3918
                    BlockstackOperationType::LeaderBlockCommit(commit_op),
×
UNCOV
3919
                    &mut op_signer,
×
3920
                )
UNCOV
3921
                .expect("Make op should work");
×
3922

UNCOV
3923
            assert!(op_signer.is_disposed());
×
3924

UNCOV
3925
            assert_eq!(
×
3926
                "1a74106bd760117892fbd90fca11646b4de46f99fd2b065c9e0706cfdcea0336",
3927
                tx.txid().to_string()
×
3928
            );
UNCOV
3929
        }
×
3930

3931
        #[test]
3932
        #[ignore]
UNCOV
3933
        fn test_submit_leader_block_commit_tx_ok() {
×
UNCOV
3934
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3935
                return;
×
UNCOV
3936
            }
×
3937

UNCOV
3938
            let keychain = utils::create_keychain();
×
UNCOV
3939
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
3940
            let mut op_signer = keychain.generate_op_signer();
×
3941

UNCOV
3942
            let mut config = utils::create_miner_config();
×
UNCOV
3943
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
UNCOV
3944
            config.burnchain.pox_reward_length = Some(11);
×
3945

UNCOV
3946
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3947
            btcd_controller
×
UNCOV
3948
                .start_bitcoind()
×
UNCOV
3949
                .expect("bitcoind should be started!");
×
3950

UNCOV
3951
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3952
            btc_controller
×
UNCOV
3953
                .connect_dbs()
×
UNCOV
3954
                .expect("Dbs initialization required!");
×
UNCOV
3955
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
3956

UNCOV
3957
            let mut commit_op = utils::create_templated_commit_op();
×
UNCOV
3958
            commit_op.sunset_burn = 5_500;
×
UNCOV
3959
            commit_op.burn_fee = 110_000;
×
3960

UNCOV
3961
            let tx_id = btc_controller
×
UNCOV
3962
                .submit_operation(
×
UNCOV
3963
                    StacksEpochId::Epoch31,
×
UNCOV
3964
                    BlockstackOperationType::LeaderBlockCommit(commit_op),
×
UNCOV
3965
                    &mut op_signer,
×
3966
                )
UNCOV
3967
                .expect("Submit op should work");
×
3968

UNCOV
3969
            assert!(op_signer.is_disposed());
×
3970

UNCOV
3971
            assert_eq!(
×
3972
                "1a74106bd760117892fbd90fca11646b4de46f99fd2b065c9e0706cfdcea0336",
UNCOV
3973
                tx_id.to_hex()
×
3974
            );
UNCOV
3975
        }
×
3976

3977
        #[test]
3978
        #[ignore]
UNCOV
3979
        fn test_submit_operation_block_commit_clears_ongoing_on_send_failure() {
×
UNCOV
3980
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
3981
                return;
×
UNCOV
3982
            }
×
3983

UNCOV
3984
            let keychain = utils::create_keychain();
×
UNCOV
3985
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
3986
            let mut op_signer = keychain.generate_op_signer();
×
3987

UNCOV
3988
            let mut config = utils::create_miner_config();
×
UNCOV
3989
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
UNCOV
3990
            config.burnchain.pox_reward_length = Some(11);
×
3991

UNCOV
3992
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
3993
            btcd_controller
×
UNCOV
3994
                .start_bitcoind()
×
UNCOV
3995
                .expect("bitcoind should be started!");
×
3996

UNCOV
3997
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
3998
            btc_controller
×
UNCOV
3999
                .connect_dbs()
×
UNCOV
4000
                .expect("Dbs initialization required!");
×
UNCOV
4001
            btc_controller.bootstrap_chain(101);
×
4002

UNCOV
4003
            let mut commit_op = utils::create_templated_commit_op();
×
UNCOV
4004
            commit_op.sunset_burn = 5_500;
×
UNCOV
4005
            commit_op.burn_fee = 110_000;
×
4006

4007
            // First submit succeeds and sets ongoing_block_commit
UNCOV
4008
            btc_controller
×
UNCOV
4009
                .submit_operation(
×
UNCOV
4010
                    StacksEpochId::Epoch31,
×
UNCOV
4011
                    BlockstackOperationType::LeaderBlockCommit(commit_op.clone()),
×
UNCOV
4012
                    &mut op_signer,
×
4013
                )
UNCOV
4014
                .expect("First submit should succeed");
×
4015

UNCOV
4016
            assert!(
×
UNCOV
4017
                btc_controller.get_ongoing_commit().is_some(),
×
4018
                "ongoing_block_commit should be set after successful submit"
4019
            );
4020

4021
            // Corrupt the cached UTXOs so the RBF transaction references
4022
            // non-existent inputs, causing send_transaction to be rejected
4023
            // by bitcoind immediately.
UNCOV
4024
            let mut ongoing = btc_controller.get_ongoing_commit().unwrap();
×
UNCOV
4025
            for utxo in ongoing.utxos.utxos.iter_mut() {
×
UNCOV
4026
                utxo.txid = Sha256dHash::default();
×
UNCOV
4027
            }
×
4028
            btc_controller.set_ongoing_commit(Some(ongoing));
×
4029

4030
            // Second submit with different payload triggers the RBF path.
4031
            // make_operation_tx builds the tx using the corrupted UTXOs,
4032
            // then send_transaction fails because the inputs don't exist.
UNCOV
4033
            let mut op_signer = keychain.generate_op_signer();
×
UNCOV
4034
            commit_op.burn_fee += 10;
×
4035

UNCOV
4036
            let err = btc_controller
×
UNCOV
4037
                .submit_operation(
×
UNCOV
4038
                    StacksEpochId::Epoch31,
×
UNCOV
4039
                    BlockstackOperationType::LeaderBlockCommit(commit_op),
×
UNCOV
4040
                    &mut op_signer,
×
4041
                )
UNCOV
4042
                .unwrap_err();
×
4043

UNCOV
4044
            assert!(
×
UNCOV
4045
                matches!(
×
UNCOV
4046
                    err,
×
4047
                    BurnchainControllerError::TransactionSubmissionFailed(_)
4048
                ),
4049
                "Error should be TransactionSubmissionFailed, but was {err}"
4050
            );
UNCOV
4051
            assert!(
×
UNCOV
4052
                btc_controller.get_ongoing_commit().is_none(),
×
4053
                "ongoing_block_commit should be cleared after send failure"
4054
            );
UNCOV
4055
        }
×
4056
    }
4057

4058
    /// Tests related to Leader Key Register operation
4059
    mod leader_key_op {
4060
        use super::*;
4061

4062
        #[test]
4063
        #[ignore]
4064
        fn test_build_leader_key_tx_ok() {
×
UNCOV
4065
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
4066
                return;
×
UNCOV
4067
            }
×
4068

UNCOV
4069
            let keychain = utils::create_keychain();
×
UNCOV
4070
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
4071
            let mut op_signer = keychain.generate_op_signer();
×
4072

UNCOV
4073
            let mut config = utils::create_miner_config();
×
UNCOV
4074
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
4075

UNCOV
4076
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
4077
            btcd_controller
×
UNCOV
4078
                .start_bitcoind()
×
UNCOV
4079
                .expect("bitcoind should be started!");
×
4080

UNCOV
4081
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
4082
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
4083

UNCOV
4084
            let leader_key_op = utils::create_templated_leader_key_op();
×
4085

UNCOV
4086
            let tx = btc_controller
×
UNCOV
4087
                .build_leader_key_register_tx(
×
UNCOV
4088
                    StacksEpochId::Epoch31,
×
UNCOV
4089
                    leader_key_op.clone(),
×
UNCOV
4090
                    &mut op_signer,
×
4091
                )
UNCOV
4092
                .expect("Build leader key should work");
×
4093

UNCOV
4094
            assert!(op_signer.is_disposed());
×
4095

UNCOV
4096
            assert_eq!(1, tx.version);
×
UNCOV
4097
            assert_eq!(0, tx.lock_time);
×
UNCOV
4098
            assert_eq!(1, tx.input.len());
×
UNCOV
4099
            assert_eq!(2, tx.output.len());
×
4100

4101
            // utxos list contains the only existing utxo
UNCOV
4102
            let used_utxos = btc_controller.get_all_utxos(&miner_pubkey);
×
UNCOV
4103
            let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0);
×
UNCOV
4104
            assert_eq!(input_0, tx.input[0]);
×
4105

UNCOV
4106
            let op_return = utils::txout_opreturn(&leader_key_op, &config.burnchain.magic_bytes, 0);
×
UNCOV
4107
            let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 4_999_980_000);
×
UNCOV
4108
            assert_eq!(op_return, tx.output[0]);
×
UNCOV
4109
            assert_eq!(op_change, tx.output[1]);
×
UNCOV
4110
        }
×
4111

4112
        #[test]
4113
        #[ignore]
UNCOV
4114
        fn test_build_leader_key_tx_fails_due_to_no_utxos() {
×
UNCOV
4115
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
4116
                return;
×
UNCOV
4117
            }
×
4118

UNCOV
4119
            let keychain = utils::create_keychain();
×
UNCOV
4120
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
4121
            let mut op_signer = keychain.generate_op_signer();
×
4122

UNCOV
4123
            let mut config = utils::create_miner_config();
×
UNCOV
4124
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
4125

UNCOV
4126
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
4127
            btcd_controller
×
UNCOV
4128
                .start_bitcoind()
×
UNCOV
4129
                .expect("bitcoind should be started!");
×
4130

UNCOV
4131
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
4132
            btc_controller.bootstrap_chain(100); // no utxos exist
×
4133

UNCOV
4134
            let leader_key_op = utils::create_templated_leader_key_op();
×
4135

UNCOV
4136
            let error = btc_controller
×
UNCOV
4137
                .build_leader_key_register_tx(
×
UNCOV
4138
                    StacksEpochId::Epoch31,
×
UNCOV
4139
                    leader_key_op.clone(),
×
UNCOV
4140
                    &mut op_signer,
×
4141
                )
UNCOV
4142
                .expect_err("Leader key build should fail!");
×
4143

UNCOV
4144
            assert!(!op_signer.is_disposed());
×
UNCOV
4145
            assert_eq!(BurnchainControllerError::NoUTXOs, error);
×
UNCOV
4146
        }
×
4147

4148
        #[test]
4149
        #[ignore]
4150
        fn test_make_operation_leader_key_register_tx_ok() {
×
UNCOV
4151
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
4152
                return;
×
UNCOV
4153
            }
×
4154

UNCOV
4155
            let keychain = utils::create_keychain();
×
UNCOV
4156
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
4157
            let mut op_signer = keychain.generate_op_signer();
×
4158

UNCOV
4159
            let mut config = utils::create_miner_config();
×
UNCOV
4160
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
UNCOV
4161
            config.burnchain.pox_reward_length = Some(11);
×
4162

UNCOV
4163
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
4164
            btcd_controller
×
UNCOV
4165
                .start_bitcoind()
×
UNCOV
4166
                .expect("bitcoind should be started!");
×
4167

UNCOV
4168
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
4169
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
4170

UNCOV
4171
            let leader_key_op = utils::create_templated_leader_key_op();
×
4172

UNCOV
4173
            let tx = btc_controller
×
UNCOV
4174
                .make_operation_tx(
×
UNCOV
4175
                    StacksEpochId::Epoch31,
×
UNCOV
4176
                    BlockstackOperationType::LeaderKeyRegister(leader_key_op),
×
UNCOV
4177
                    &mut op_signer,
×
4178
                )
UNCOV
4179
                .expect("Make op should work");
×
4180

UNCOV
4181
            assert!(op_signer.is_disposed());
×
4182

UNCOV
4183
            assert_eq!(
×
4184
                "4ecd7ba71bebd1aaed49dd63747ee424473f1c571bb9a576361607a669191024",
UNCOV
4185
                tx.txid().to_string()
×
4186
            );
UNCOV
4187
        }
×
4188

4189
        #[test]
4190
        #[ignore]
UNCOV
4191
        fn test_submit_operation_leader_key_register_tx_ok() {
×
UNCOV
4192
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
4193
                return;
×
UNCOV
4194
            }
×
4195

UNCOV
4196
            let keychain = utils::create_keychain();
×
4197
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
4198
            let mut op_signer = keychain.generate_op_signer();
×
4199

UNCOV
4200
            let mut config = utils::create_miner_config();
×
UNCOV
4201
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
4202

UNCOV
4203
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
4204
            btcd_controller
×
UNCOV
4205
                .start_bitcoind()
×
UNCOV
4206
                .expect("bitcoind should be started!");
×
4207

UNCOV
4208
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
4209
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
4210

UNCOV
4211
            let leader_key_op = utils::create_templated_leader_key_op();
×
4212

UNCOV
4213
            let tx_id = btc_controller
×
UNCOV
4214
                .submit_operation(
×
UNCOV
4215
                    StacksEpochId::Epoch31,
×
UNCOV
4216
                    BlockstackOperationType::LeaderKeyRegister(leader_key_op),
×
UNCOV
4217
                    &mut op_signer,
×
4218
                )
UNCOV
4219
                .expect("Submit op should work");
×
4220

UNCOV
4221
            assert!(op_signer.is_disposed());
×
4222

UNCOV
4223
            assert_eq!(
×
4224
                "4ecd7ba71bebd1aaed49dd63747ee424473f1c571bb9a576361607a669191024",
UNCOV
4225
                tx_id.to_hex()
×
4226
            );
UNCOV
4227
        }
×
4228
    }
4229

4230
    /// Tests related to Pre Stacks operation
4231
    mod pre_stx_op {
4232
        use super::*;
4233

4234
        #[test]
4235
        #[ignore]
UNCOV
4236
        fn test_build_pre_stx_tx_ok() {
×
UNCOV
4237
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
4238
                return;
×
UNCOV
4239
            }
×
4240

UNCOV
4241
            let keychain = utils::create_keychain();
×
UNCOV
4242
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
4243
            let mut op_signer = keychain.generate_op_signer();
×
4244

UNCOV
4245
            let mut config = utils::create_miner_config();
×
UNCOV
4246
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
4247

UNCOV
4248
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
4249
            btcd_controller
×
UNCOV
4250
                .start_bitcoind()
×
UNCOV
4251
                .expect("bitcoind should be started!");
×
4252

UNCOV
4253
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
4254
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
4255

UNCOV
4256
            let mut pre_stx_op = utils::create_templated_pre_stx_op();
×
UNCOV
4257
            pre_stx_op.output = keychain.get_address(false);
×
4258

UNCOV
4259
            let tx = btc_controller
×
UNCOV
4260
                .build_pre_stacks_tx(StacksEpochId::Epoch31, pre_stx_op.clone(), &mut op_signer)
×
UNCOV
4261
                .expect("Build leader key should work");
×
4262

UNCOV
4263
            assert!(op_signer.is_disposed());
×
4264

UNCOV
4265
            assert_eq!(1, tx.version);
×
UNCOV
4266
            assert_eq!(0, tx.lock_time);
×
UNCOV
4267
            assert_eq!(1, tx.input.len());
×
UNCOV
4268
            assert_eq!(3, tx.output.len());
×
4269

4270
            // utxos list contains the only existing utxo
4271
            let used_utxos = btc_controller.get_all_utxos(&miner_pubkey);
×
UNCOV
4272
            let input_0 = utils::txin_at_index(&tx, &op_signer, &used_utxos, 0);
×
UNCOV
4273
            assert_eq!(input_0, tx.input[0]);
×
4274

UNCOV
4275
            let op_return = utils::txout_opreturn(&pre_stx_op, &config.burnchain.magic_bytes, 0);
×
UNCOV
4276
            let op_change = utils::txout_opdup_change_legacy(&mut op_signer, 24_500);
×
UNCOV
4277
            assert_eq!(op_return, tx.output[0]);
×
UNCOV
4278
            assert_eq!(op_change, tx.output[1]);
×
UNCOV
4279
        }
×
4280

4281
        #[test]
4282
        #[ignore]
UNCOV
4283
        fn test_build_pre_stx_tx_fails_due_to_no_utxos() {
×
UNCOV
4284
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
4285
                return;
×
UNCOV
4286
            }
×
4287

UNCOV
4288
            let keychain = utils::create_keychain();
×
UNCOV
4289
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
4290
            let mut op_signer = keychain.generate_op_signer();
×
4291

UNCOV
4292
            let mut config = utils::create_miner_config();
×
UNCOV
4293
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
4294

UNCOV
4295
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
4296
            btcd_controller
×
UNCOV
4297
                .start_bitcoind()
×
UNCOV
4298
                .expect("bitcoind should be started!");
×
4299

UNCOV
4300
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
4301
            btc_controller.bootstrap_chain(100); // no utxo exists
×
4302

UNCOV
4303
            let mut pre_stx_op = utils::create_templated_pre_stx_op();
×
UNCOV
4304
            pre_stx_op.output = keychain.get_address(false);
×
4305

UNCOV
4306
            let error = btc_controller
×
UNCOV
4307
                .build_pre_stacks_tx(StacksEpochId::Epoch31, pre_stx_op.clone(), &mut op_signer)
×
UNCOV
4308
                .expect_err("Leader key build should fail!");
×
4309

UNCOV
4310
            assert!(!op_signer.is_disposed());
×
UNCOV
4311
            assert_eq!(BurnchainControllerError::NoUTXOs, error);
×
UNCOV
4312
        }
×
4313

4314
        #[test]
4315
        #[ignore]
UNCOV
4316
        fn test_make_operation_pre_stx_tx_ok() {
×
UNCOV
4317
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
4318
                return;
×
UNCOV
4319
            }
×
4320

UNCOV
4321
            let keychain = utils::create_keychain();
×
UNCOV
4322
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
4323
            let mut op_signer = keychain.generate_op_signer();
×
4324

UNCOV
4325
            let mut config = utils::create_miner_config();
×
UNCOV
4326
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
4327

UNCOV
4328
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
4329
            btcd_controller
×
UNCOV
4330
                .start_bitcoind()
×
UNCOV
4331
                .expect("bitcoind should be started!");
×
4332

UNCOV
4333
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
4334
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
4335

UNCOV
4336
            let mut pre_stx_op = utils::create_templated_pre_stx_op();
×
UNCOV
4337
            pre_stx_op.output = keychain.get_address(false);
×
4338

UNCOV
4339
            let tx = btc_controller
×
UNCOV
4340
                .make_operation_tx(
×
UNCOV
4341
                    StacksEpochId::Epoch31,
×
UNCOV
4342
                    BlockstackOperationType::PreStx(pre_stx_op),
×
UNCOV
4343
                    &mut op_signer,
×
4344
                )
UNCOV
4345
                .expect("Make op should work");
×
4346

UNCOV
4347
            assert!(op_signer.is_disposed());
×
4348

UNCOV
4349
            assert_eq!(
×
4350
                "2d061c42c6f13a62fd9d80dc9fdcd19bdb4f9e4a07f786e42530c64c52ed9d1d",
UNCOV
4351
                tx.txid().to_string()
×
4352
            );
UNCOV
4353
        }
×
4354

4355
        #[test]
4356
        #[ignore]
UNCOV
4357
        fn test_submit_operation_pre_stx_tx_ok() {
×
UNCOV
4358
            if env::var("BITCOIND_TEST") != Ok("1".into()) {
×
UNCOV
4359
                return;
×
UNCOV
4360
            }
×
4361

UNCOV
4362
            let keychain = utils::create_keychain();
×
UNCOV
4363
            let miner_pubkey = keychain.get_pub_key();
×
UNCOV
4364
            let mut op_signer = keychain.generate_op_signer();
×
4365

UNCOV
4366
            let mut config = utils::create_miner_config();
×
UNCOV
4367
            config.burnchain.local_mining_public_key = Some(miner_pubkey.to_hex());
×
4368

UNCOV
4369
            let mut btcd_controller = BitcoinCoreController::from_stx_config(&config);
×
UNCOV
4370
            btcd_controller
×
UNCOV
4371
                .start_bitcoind()
×
UNCOV
4372
                .expect("bitcoind should be started!");
×
4373

UNCOV
4374
            let mut btc_controller = BitcoinRegtestController::new(config.clone(), None);
×
UNCOV
4375
            btc_controller.bootstrap_chain(101); // now, one utxo exists
×
4376

UNCOV
4377
            let mut pre_stx_op = utils::create_templated_pre_stx_op();
×
UNCOV
4378
            pre_stx_op.output = keychain.get_address(false);
×
4379

UNCOV
4380
            let tx_id = btc_controller
×
UNCOV
4381
                .submit_operation(
×
UNCOV
4382
                    StacksEpochId::Epoch31,
×
UNCOV
4383
                    BlockstackOperationType::PreStx(pre_stx_op),
×
UNCOV
4384
                    &mut op_signer,
×
4385
                )
UNCOV
4386
                .expect("submit op should work");
×
4387

UNCOV
4388
            assert!(op_signer.is_disposed());
×
4389

UNCOV
4390
            assert_eq!(
×
4391
                "2d061c42c6f13a62fd9d80dc9fdcd19bdb4f9e4a07f786e42530c64c52ed9d1d",
UNCOV
4392
                tx_id.to_hex()
×
4393
            );
UNCOV
4394
        }
×
4395
    }
4396
}
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