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

stacks-network / stacks-core / 23509724014

24 Mar 2026 08:02PM UTC coverage: 85.676% (-0.04%) from 85.712%
23509724014

Pull #7031

github

2faee1
web-flow
Merge 8b0e95be4 into 1e36cefa9
Pull Request #7031: Updated ci.yml to have a 'v2' of code coverage action, which is more robust, is skipped when prior steps are skipped, and uses env vars instead of hard-coding

186502 of 217682 relevant lines covered (85.68%)

17190821.87 hits per line

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

83.08
/stacks-node/src/neon_node.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
/// Main body of code for the Stacks node and miner.
18
///
19
/// System schematic.
20
/// Legend:
21
///    |------|    Thread
22
///    /------\    Shared memory
23
///    @------@    Database
24
///    .------.    Code module
25
///
26
///
27
///                           |------------------|
28
///                           |  RunLoop thread  |   [1,7]
29
///                           |   .----------.   |--------------------------------------.
30
///                           |   .StacksNode.   |                                      |
31
///                           |---.----------.---|                                      |
32
///                    [1,12]     |     |    |     [1]                                  |
33
///              .----------------*     |    *---------------.                          |
34
///              |                  [3] |                    |                          |
35
///              V                      |                    V                          V
36
///      |----------------|             |    [9,10]   |---------------| [11] |--------------------------|
37
/// .--- | Relayer thread | <-----------|-----------> |   P2P Thread  | <--- | ChainsCoordinator thread | <--.
38
/// |    |----------------|             V             |---------------|      |--------------------------|    |
39
/// |            |     |          /-------------\    [2,3]    |    |              |          |               |
40
/// |        [1] |     *--------> /   Globals   \ <-----------*----|--------------*          | [4]           |
41
/// |            |     [2,3,7]    /-------------\                  |                         |               |
42
/// |            V                                                 V [5]                     V               |
43
/// |    |----------------|                                 @--------------@        @------------------@     |
44
/// |    |  Miner thread  | <------------------------------ @  Mempool DB  @        @  Chainstate DBs  @     |
45
/// |    |----------------|             [6]                 @--------------@        @------------------@     |
46
/// |                                                                                        ^               |
47
/// |                                               [8]                                      |               |
48
/// *----------------------------------------------------------------------------------------*               |
49
/// |                                               [7]                                                      |
50
/// *--------------------------------------------------------------------------------------------------------*
51
///
52
/// [1]  Spawns
53
/// [2]  Synchronize unconfirmed state
54
/// [3]  Enable/disable miner
55
/// [4]  Processes block data
56
/// [5]  Stores unconfirmed transactions
57
/// [6]  Reads unconfirmed transactions
58
/// [7]  Signals block arrival
59
/// [8]  Store blocks and microblocks
60
/// [9]  Pushes retrieved blocks and microblocks
61
/// [10] Broadcasts new blocks, microblocks, and transactions
62
/// [11] Notifies about new transaction attachment events
63
/// [12] Signals VRF key registration
64
///
65
/// When the node is running, there are 4-5 active threads at once. They are:
66
///
67
/// * **RunLoop Thread**:
68
///     This is the main thread, whose code body lives in `src/run_loop/neon.rs`.
69
///     This thread is responsible for:
70
///       * Bootup
71
///       * Running the burnchain indexer
72
///       * Notifying the ChainsCoordinator thread when there are new burnchain blocks to process
73
///
74
/// * **Relayer Thread**:
75
///     This is the thread that stores and relays blocks and microblocks. Both
76
///     it and the ChainsCoordinator thread are very I/O-heavy threads, and care has been taken to
77
///     ensure that neither one attempts to acquire a write-lock in the underlying databases.
78
///     Specifically, this thread directs the ChainsCoordinator thread when to process new Stacks
79
///     blocks, and it directs the miner thread (if running) to stop when either it or the
80
///     ChainsCoordinator thread needs to acquire the write-lock.
81
///     This thread is responsible for:
82
///       * Receiving new blocks and microblocks from the P2P thread via a shared channel
83
///       * (Synchronously) requesting the CoordinatorThread to process newly-stored Stacks blocks
84
///         and microblocks
85
///       * Building up the node's unconfirmed microblock stream state, and sharing it with the P2P
86
///         thread so it can answer queries about the unconfirmed microblock chain
87
///       * Pushing newly-discovered blocks and microblocks to the P2P thread for broadcast
88
///       * Registering the VRF public key for the miner
89
///       * Spawning the block and microblock miner threads, and stopping them if their continued
90
///         execution would inhibit block or microblock storage or processing.
91
///       * Submitting the burnchain operation to commit to a freshly-mined block
92
///
93
/// * **Miner Thread**:
94
///     This is the thread that actually produces new blocks and microblocks. It
95
///     is spawned only by the Relayer thread to carry out mining activity when the underlying
96
///     chainstate is not needed by either the Relayer or ChainsCoordinator threads.
97
///     This thread does the following:
98
///       * Walk the mempool DB to build a new block or microblock
99
///       * Return the block or microblock to the Relayer thread
100
///
101
/// * **P2P Thread**:
102
///     This is the thread that communicates with the rest of the P2P network, and
103
///     handles RPC requests. It is meant to do as little storage-write I/O as possible to avoid lock
104
///     contention with the Miner, Relayer, and ChainsCoordinator threads. In particular, it forwards
105
///     data it receives from the P2P thread to the Relayer thread for I/O-bound processing. At the
106
///     time of this writing, it still requires holding a write-lock to handle some RPC requests, but
107
///     future work will remove this so that this thread's execution will not interfere with the
108
///     others. This is the only thread that does socket I/O.
109
///     This thread runs the PeerNetwork state machines, which include the following:
110
///       * Learning the node's public IP address
111
///       * Discovering neighbor nodes
112
///       * Forwarding newly-discovered blocks, microblocks, and transactions from the Relayer thread
113
///         to other neighbors
114
///       * Synchronizing block and microblock inventory state with other neighbors
115
///       * Downloading blocks and microblocks, and passing them to the Relayer for storage and
116
///         processing
117
///       * Downloading transaction attachments as their hashes are discovered during block processing
118
///       * Synchronizing the local mempool database with other neighbors
119
///         (notifications for new attachments come from a shared channel in the ChainsCoordinator thread)
120
///       * Handling HTTP requests
121
///
122
/// * **ChainsCoordinator Thread**:
123
///     This thread processes sortitions and Stacks blocks and
124
///     microblocks, and handles PoX reorgs should they occur (this mainly happens in boot-up). It,
125
///     like the Relayer thread, is a very I/O-heavy thread, and it will hold a write-lock on the
126
///     chainstate DBs while it works. Its actions are controlled by a CoordinatorComms structure in
127
///     the Globals shared state, which the Relayer thread and RunLoop thread both drive (the former
128
///     drives Stacks blocks processing, the latter sortitions).
129
///     This thread is responsible for:
130
///       * Responding to requests from other threads to process sortitions
131
///       * Responding to requests from other threads to process Stacks blocks and microblocks
132
///       * Processing PoX chain reorgs, should they ever happen
133
///       * Detecting attachment creation events, and informing the P2P thread of them so it can go
134
///         and download them
135
///
136
/// In addition to the mempool and chainstate databases, these threads share access to a Globals
137
/// singleton that contains soft state shared between threads. Mainly, the Globals struct is meant
138
/// to store inter-thread shared singleton communication media all in one convenient struct. Each
139
/// thread has a handle to the struct's shared state handles. Global state includes:
140
///       * The global flag as to whether or not the miner thread can be running
141
///       * The global shutdown flag that, when set, causes all threads to terminate
142
///       * Sender channel endpoints that can be shared between threads
143
///       * Metrics about the node's behavior (e.g. number of blocks processed, etc.)
144
///
145
/// This file may be refactored in the future into a full-fledged module.
146
use std::cmp;
147
use std::cmp::Ordering as CmpOrdering;
148
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
149
use std::io::{ErrorKind, Read, Write};
150
use std::net::SocketAddr;
151
use std::sync::mpsc::{Receiver, TrySendError};
152
use std::thread::JoinHandle;
153
use std::time::{Duration, Instant};
154
use std::{fs, mem, thread};
155

156
use clarity::boot_util::boot_code_id;
157
use clarity::vm::costs::ExecutionCost;
158
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier};
159
use libsigner::v0::messages::{
160
    MessageSlotID, MinerSlotID, MockBlock, MockProposal, MockSignature, PeerInfo, SignerMessage,
161
};
162
use libsigner::{SignerSession, StackerDBSession};
163
use stacks::burnchains::bitcoin::address::{BitcoinAddress, LegacyBitcoinAddressType};
164
use stacks::burnchains::db::BurnchainHeaderReader;
165
use stacks::burnchains::{Burnchain, BurnchainSigner, PoxConstants, Txid};
166
use stacks::chainstate::burn::db::sortdb::{SortitionDB, SortitionHandleConn};
167
use stacks::chainstate::burn::operations::leader_block_commit::{
168
    RewardSetInfo, BURN_BLOCK_MINED_AT_MODULUS,
169
};
170
use stacks::chainstate::burn::operations::{
171
    BlockstackOperationType, LeaderBlockCommitOp, LeaderKeyRegisterOp,
172
};
173
use stacks::chainstate::burn::{BlockSnapshot, ConsensusHash};
174
use stacks::chainstate::coordinator::{get_next_recipients, OnChainRewardSetProvider};
175
use stacks::chainstate::nakamoto::NakamotoChainState;
176
use stacks::chainstate::stacks::address::PoxAddress;
177
use stacks::chainstate::stacks::boot::MINERS_NAME;
178
use stacks::chainstate::stacks::db::blocks::StagingBlock;
179
use stacks::chainstate::stacks::db::{StacksChainState, StacksHeaderInfo, MINER_REWARD_MATURITY};
180
use stacks::chainstate::stacks::miner::{
181
    signal_mining_blocked, signal_mining_ready, AssembledAnchorBlock, BlockBuilderSettings,
182
    StacksMicroblockBuilder,
183
};
184
use stacks::chainstate::stacks::{
185
    CoinbasePayload, Error as ChainstateError, StacksBlock, StacksBlockBuilder, StacksBlockHeader,
186
    StacksMicroblock, StacksPublicKey, StacksTransaction, StacksTransactionSigner,
187
    TransactionAnchorMode, TransactionPayload, TransactionVersion,
188
};
189
use stacks::config::chain_data::MinerStats;
190
use stacks::config::NodeConfig;
191
use stacks::core::mempool::MemPoolDB;
192
use stacks::core::{EpochList, FIRST_BURNCHAIN_CONSENSUS_HASH, STACKS_EPOCH_3_0_MARKER};
193
use stacks::cost_estimates::metrics::{CostMetric, UnitMetric};
194
use stacks::cost_estimates::{CostEstimator, FeeEstimator, UnitEstimator};
195
use stacks::monitoring::{increment_stx_blocks_mined_counter, update_active_miners_count_gauge};
196
use stacks::net::atlas::{AtlasConfig, AtlasDB};
197
use stacks::net::db::{LocalPeer, PeerDB};
198
use stacks::net::dns::{DNSClient, DNSResolver};
199
use stacks::net::p2p::PeerNetwork;
200
use stacks::net::relay::Relayer;
201
use stacks::net::stackerdb::{StackerDBConfig, StackerDBSync, StackerDBs, MINER_SLOT_COUNT};
202
use stacks::net::{
203
    Error as NetError, NetworkResult, PeerNetworkComms, RPCHandlerArgs, ServiceFlags,
204
};
205
use stacks::util_lib::strings::{UrlString, VecDisplay};
206
use stacks::{monitoring, version_string};
207
use stacks_common::codec::StacksMessageCodec;
208
use stacks_common::types::chainstate::{
209
    BlockHeaderHash, BurnchainHeaderHash, SortitionId, StacksAddress, StacksBlockId,
210
    StacksPrivateKey, VRFSeed,
211
};
212
use stacks_common::types::net::PeerAddress;
213
use stacks_common::types::{PublicKey, StacksEpochId};
214
use stacks_common::util::hash::{to_hex, Hash160, Sha256Sum};
215
use stacks_common::util::secp256k1::Secp256k1PrivateKey;
216
use stacks_common::util::vrf::{VRFProof, VRFPublicKey};
217
use stacks_common::util::{get_epoch_time_ms, get_epoch_time_secs};
218

219
use super::{BurnchainController, Config, EventDispatcher, Keychain};
220
use crate::burnchains::bitcoin_regtest_controller::{
221
    burnchain_params_from_config, BitcoinRegtestController, OngoingBlockCommit,
222
};
223
use crate::burnchains::{make_bitcoin_indexer, Error as BurnchainControllerError};
224
use crate::globals::{NeonGlobals as Globals, RelayerDirective};
225
use crate::nakamoto_node::miner_db::MinerDB;
226
use crate::nakamoto_node::signer_coordinator::SignerCoordinator;
227
use crate::run_loop::neon::RunLoop;
228
use crate::run_loop::RegisteredKey;
229
use crate::ChainTip;
230

231
pub const RELAYER_MAX_BUFFER: usize = 100;
232
const VRF_MOCK_MINER_KEY: u64 = 1;
233

234
pub const BLOCK_PROCESSOR_STACK_SIZE: usize = 32 * 1024 * 1024; // 32 MB
235

236
type MinedBlocks = HashMap<BlockHeaderHash, (AssembledAnchorBlock, Secp256k1PrivateKey)>;
237

238
/// Result of running the miner thread.  It could produce a Stacks block or a microblock.
239
#[allow(clippy::large_enum_variant)]
240
pub(crate) enum MinerThreadResult {
241
    Block(
242
        AssembledAnchorBlock,
243
        Secp256k1PrivateKey,
244
        Option<OngoingBlockCommit>,
245
    ),
246
    Microblock(
247
        Result<Option<(StacksMicroblock, ExecutionCost)>, NetError>,
248
        MinerTip,
249
    ),
250
}
251

252
/// Miner chain tip, on top of which to build microblocks
253
#[derive(Debug, Clone, PartialEq)]
254
pub struct MinerTip {
255
    /// tip's consensus hash
256
    consensus_hash: ConsensusHash,
257
    /// tip's Stacks block header hash
258
    block_hash: BlockHeaderHash,
259
    /// Microblock private key to use to sign microblocks
260
    microblock_privkey: Secp256k1PrivateKey,
261
    /// Stacks height
262
    stacks_height: u64,
263
    /// burnchain height
264
    burn_height: u64,
265
}
266

267
impl MinerTip {
268
    pub fn new(
6,293✔
269
        ch: ConsensusHash,
6,293✔
270
        bh: BlockHeaderHash,
6,293✔
271
        pk: Secp256k1PrivateKey,
6,293✔
272
        stacks_height: u64,
6,293✔
273
        burn_height: u64,
6,293✔
274
    ) -> MinerTip {
6,293✔
275
        MinerTip {
6,293✔
276
            consensus_hash: ch,
6,293✔
277
            block_hash: bh,
6,293✔
278
            microblock_privkey: pk,
6,293✔
279
            stacks_height,
6,293✔
280
            burn_height,
6,293✔
281
        }
6,293✔
282
    }
6,293✔
283
}
284

285
/// Node implementation for both miners and followers.
286
/// This struct is used to set up the node proper and launch the p2p thread and relayer thread.
287
/// It is further used by the main thread to communicate with these two threads.
288
pub struct StacksNode {
289
    /// Atlas network configuration
290
    pub atlas_config: AtlasConfig,
291
    /// Global inter-thread communication handle
292
    pub globals: Globals,
293
    /// True if we're a miner
294
    is_miner: bool,
295
    /// handle to the p2p thread
296
    pub p2p_thread_handle: JoinHandle<Option<PeerNetwork>>,
297
    /// handle to the relayer thread
298
    pub relayer_thread_handle: JoinHandle<()>,
299
}
300

301
/// Fault injection logic to artificially increase the length of a tenure.
302
/// Only used in testing
303
#[cfg(test)]
304
pub(crate) fn fault_injection_long_tenure() {
175,234✔
305
    // simulated slow block
306
    let Ok(tenure_str) = std::env::var("STX_TEST_SLOW_TENURE") else {
175,234✔
307
        return;
175,225✔
308
    };
309
    let Ok(tenure_time) = tenure_str.parse::<u64>() else {
9✔
310
        error!("Parse error for STX_TEST_SLOW_TENURE");
×
311
        panic!();
×
312
    };
313
    info!("Fault injection: sleeping for {tenure_time} milliseconds to simulate a long tenure");
9✔
314
    stacks_common::util::sleep_ms(tenure_time);
9✔
315
}
175,234✔
316

317
#[cfg(not(test))]
318
pub(crate) fn fault_injection_long_tenure() {}
319

320
/// Fault injection to skip mining in this bitcoin block height
321
/// Only used in testing
322
#[cfg(test)]
323
pub(crate) fn fault_injection_skip_mining(rpc_bind: &str, target_burn_height: u64) -> bool {
268,120✔
324
    let Ok(disable_heights) = std::env::var("STACKS_DISABLE_MINER") else {
268,120✔
325
        return false;
268,120✔
326
    };
327
    let disable_schedule: serde_json::Value = serde_json::from_str(&disable_heights).unwrap();
×
328
    let disable_schedule = disable_schedule.as_array().unwrap();
×
329
    for disabled in disable_schedule {
×
330
        let target_miner_rpc_bind = disabled.get("rpc_bind").unwrap().as_str().unwrap();
×
331
        if target_miner_rpc_bind != rpc_bind {
×
332
            continue;
×
333
        }
×
334
        let target_block_heights = disabled.get("blocks").unwrap().as_array().unwrap();
×
335
        for target_block_value in target_block_heights {
×
336
            let target_block = u64::try_from(target_block_value.as_i64().unwrap()).unwrap();
×
337
            if target_block == target_burn_height {
×
338
                return true;
×
339
            }
×
340
        }
341
    }
342
    false
×
343
}
268,120✔
344

345
#[cfg(not(test))]
346
pub(crate) fn fault_injection_skip_mining(_rpc_bind: &str, _target_burn_height: u64) -> bool {
347
    false
348
}
349

350
/// Open the chainstate, and inject faults from the config file
351
pub(crate) fn open_chainstate_with_faults(
205,891✔
352
    config: &Config,
205,891✔
353
) -> Result<StacksChainState, ChainstateError> {
205,891✔
354
    let stacks_chainstate_path = config.get_chainstate_path_str();
205,891✔
355
    let (mut chainstate, _) = StacksChainState::open(
205,891✔
356
        config.is_mainnet(),
205,891✔
357
        config.burnchain.chain_id,
205,891✔
358
        &stacks_chainstate_path,
205,891✔
359
        Some(config.node.get_marf_opts()),
205,891✔
360
    )?;
×
361

362
    chainstate.fault_injection.hide_blocks = config.node.fault_injection_hide_blocks;
205,891✔
363
    Ok(chainstate)
205,891✔
364
}
205,891✔
365

366
/// Types of errors that can arise during mining
367
enum Error {
368
    /// Can't find the header record for the chain tip
369
    HeaderNotFoundForChainTip,
370
    /// Can't find the stacks block's offset in the burnchain block
371
    WinningVtxNotFoundForChainTip,
372
    /// Can't find the block sortition snapshot for the chain tip
373
    SnapshotNotFoundForChainTip,
374
    /// The burnchain tip changed while this operation was in progress
375
    BurnchainTipChanged,
376
    /// The coordinator channel closed
377
    CoordinatorClosed,
378
}
379

380
/// Metadata required for beginning a new tenure
381
struct ParentStacksBlockInfo {
382
    /// Header metadata for the Stacks block we're going to build on top of
383
    stacks_parent_header: StacksHeaderInfo,
384
    /// the consensus hash of the sortition that selected the Stacks block parent
385
    parent_consensus_hash: ConsensusHash,
386
    /// the burn block height of the sortition that selected the Stacks block parent
387
    parent_block_burn_height: u64,
388
    /// the total amount burned in the sortition that selected the Stacks block parent
389
    parent_block_total_burn: u64,
390
    /// offset in the burnchain block where the parent's block-commit was
391
    parent_winning_vtxindex: u16,
392
    /// nonce to use for this new block's coinbase transaction
393
    coinbase_nonce: u64,
394
}
395

396
#[derive(Clone, Default)]
397
pub enum LeaderKeyRegistrationState {
398
    /// Not started yet
399
    #[default]
400
    Inactive,
401
    /// Waiting for burnchain confirmation
402
    /// `u64` is the target block height in which we intend this key to land
403
    /// `txid` is the burnchain transaction ID
404
    Pending(u64, Txid),
405
    /// Ready to go!
406
    Active(RegisteredKey),
407
}
408

409
impl LeaderKeyRegistrationState {
410
    pub fn get_active(&self) -> Option<RegisteredKey> {
4,039✔
411
        if let Self::Active(registered_key) = self {
4,039✔
412
            Some(registered_key.clone())
4,039✔
413
        } else {
414
            None
×
415
        }
416
    }
4,039✔
417
}
418

419
/// Relayer thread
420
/// * accepts network results and stores blocks and microblocks
421
/// * forwards new blocks, microblocks, and transactions to the p2p thread
422
/// * processes burnchain state
423
/// * if mining, runs the miner and broadcasts blocks (via a subordinate MinerThread)
424
pub struct RelayerThread {
425
    /// Node config
426
    config: Config,
427
    /// Handle to the sortition DB (optional so we can take/replace it)
428
    sortdb: Option<SortitionDB>,
429
    /// Handle to the chainstate DB (optional so we can take/replace it)
430
    chainstate: Option<StacksChainState>,
431
    /// Handle to the mempool DB (optional so we can take/replace it)
432
    mempool: Option<MemPoolDB>,
433
    /// Handle to global state and inter-thread communication channels
434
    globals: Globals,
435
    /// Authoritative copy of the keychain state
436
    keychain: Keychain,
437
    /// Burnchian configuration
438
    burnchain: Burnchain,
439
    /// height of last VRF key registration request
440
    last_vrf_key_burn_height: u64,
441
    /// Set of blocks that we have mined, but are still potentially-broadcastable
442
    last_mined_blocks: MinedBlocks,
443
    /// client to the burnchain (used only for sending block-commits)
444
    bitcoin_controller: BitcoinRegtestController,
445
    /// client to the event dispatcher
446
    event_dispatcher: EventDispatcher,
447
    /// copy of the local peer state
448
    local_peer: LocalPeer,
449
    /// last time we tried to mine a block (in millis)
450
    last_tenure_issue_time: u128,
451
    /// last observed burnchain block height from the p2p thread (obtained from network results)
452
    last_network_block_height: u64,
453
    /// time at which we observed a change in the network block height (epoch time in millis)
454
    last_network_block_height_ts: u128,
455
    /// last observed number of downloader state-machine passes from the p2p thread (obtained from
456
    /// network results)
457
    last_network_download_passes: u64,
458
    /// last observed number of inventory state-machine passes from the p2p thread (obtained from
459
    /// network results)
460
    last_network_inv_passes: u64,
461
    /// minimum number of downloader state-machine passes that must take place before mining (this
462
    /// is used to ensure that the p2p thread attempts to download new Stacks block data before
463
    /// this thread tries to mine a block)
464
    min_network_download_passes: u64,
465
    /// minimum number of inventory state-machine passes that must take place before mining (this
466
    /// is used to ensure that the p2p thread attempts to download new Stacks block data before
467
    /// this thread tries to mine a block)
468
    min_network_inv_passes: u64,
469
    /// consensus hash of the last sortition we saw, even if we weren't the winner
470
    last_tenure_consensus_hash: Option<ConsensusHash>,
471
    /// tip of last tenure we won (used for mining microblocks)
472
    miner_tip: Option<MinerTip>,
473
    /// last time we mined a microblock, in millis
474
    last_microblock_tenure_time: u128,
475
    /// when should we run the next microblock tenure, in millis
476
    microblock_deadline: u128,
477
    /// cost of the last-produced microblock stream
478
    microblock_stream_cost: ExecutionCost,
479

480
    /// Inner relayer instance for forwarding broadcasted data back to the p2p thread for dispatch
481
    /// to neighbors
482
    relayer: Relayer,
483

484
    /// handle to the subordinate miner thread
485
    miner_thread: Option<JoinHandle<Option<MinerThreadResult>>>,
486
    /// if true, then the last time the miner thread was launched, it was used to mine a Stacks
487
    /// block (used to alternate between mining microblocks and Stacks blocks that confirm them)
488
    mined_stacks_block: bool,
489
    /// if true, the last time the miner thread was launched, it did not mine.
490
    last_attempt_failed: bool,
491
}
492

493
pub(crate) struct BlockMinerThread {
494
    /// node config struct
495
    config: Config,
496
    /// handle to global state
497
    globals: Globals,
498
    /// copy of the node's keychain
499
    keychain: Keychain,
500
    /// burnchain configuration
501
    burnchain: Burnchain,
502
    /// Set of blocks that we have mined, but are still potentially-broadcastable
503
    /// (copied from RelayerThread since we need the info to determine the strategy for mining the
504
    /// next block during this tenure).
505
    last_mined_blocks: MinedBlocks,
506
    /// Copy of the node's last ongoing block commit from the last time this thread was run
507
    ongoing_commit: Option<OngoingBlockCommit>,
508
    /// Copy of the node's registered VRF key
509
    registered_key: RegisteredKey,
510
    /// Burnchain block snapshot at the time this thread was initialized
511
    burn_block: BlockSnapshot,
512
    /// Handle to the node's event dispatcher
513
    event_dispatcher: EventDispatcher,
514
    /// Failed to submit last attempted block
515
    failed_to_submit_last_attempt: bool,
516
}
517

518
/// State representing the microblock miner.
519
struct MicroblockMinerThread {
520
    /// handle to global state
521
    globals: Globals,
522
    /// handle to chainstate DB (optional so we can take/replace it)
523
    chainstate: Option<StacksChainState>,
524
    /// handle to sortition DB (optional so we can take/replace it)
525
    sortdb: Option<SortitionDB>,
526
    /// handle to mempool DB (optional so we can take/replace it)
527
    mempool: Option<MemPoolDB>,
528
    /// Handle to the node's event dispatcher
529
    event_dispatcher: EventDispatcher,
530
    /// Parent Stacks block's sortition's consensus hash
531
    parent_consensus_hash: ConsensusHash,
532
    /// Parent Stacks block's hash
533
    parent_block_hash: BlockHeaderHash,
534
    /// Microblock signing key
535
    miner_key: Secp256k1PrivateKey,
536
    /// How often to make microblocks, in milliseconds
537
    frequency: u64,
538
    /// Epoch timestamp, in milliseconds, when the last microblock was produced
539
    last_mined: u128,
540
    /// How many microblocks produced so far
541
    quantity: u64,
542
    /// Block budget consumed so far by this tenure (initialized to the cost of the Stacks block
543
    /// itself; microblocks fill up the remaining budget)
544
    cost_so_far: ExecutionCost,
545
    /// Block builder settings for the microblock miner.
546
    settings: BlockBuilderSettings,
547
}
548

549
impl MicroblockMinerThread {
550
    /// Instantiate the miner thread state from the relayer thread.
551
    /// May fail if:
552
    /// * we didn't win the last sortition
553
    /// * we couldn't open or read the DBs for some reason
554
    /// * we couldn't find the anchored block (i.e. it's not processed yet)
555
    pub fn from_relayer_thread(relayer_thread: &RelayerThread) -> Option<MicroblockMinerThread> {
10,309✔
556
        let globals = relayer_thread.globals.clone();
10,309✔
557
        let config = relayer_thread.config.clone();
10,309✔
558
        let burnchain = relayer_thread.burnchain.clone();
10,309✔
559
        let miner_tip = match relayer_thread.miner_tip.clone() {
10,309✔
560
            Some(tip) => tip,
10,309✔
561
            None => {
562
                debug!("Relayer: cannot instantiate microblock miner: did not win Stacks tip sortition");
×
563
                return None;
×
564
            }
565
        };
566

567
        let stacks_chainstate_path = config.get_chainstate_path_str();
10,309✔
568
        let burn_db_path = config.get_burn_db_file_path();
10,309✔
569
        let cost_estimator = config
10,309✔
570
            .make_cost_estimator()
10,309✔
571
            .unwrap_or_else(|| Box::new(UnitEstimator));
10,309✔
572
        let metric = config
10,309✔
573
            .make_cost_metric()
10,309✔
574
            .unwrap_or_else(|| Box::new(UnitMetric));
10,309✔
575

576
        // NOTE: read-write access is needed in order to be able to query the recipient set.
577
        // This is an artifact of the way the MARF is built (see #1449)
578
        let sortdb = SortitionDB::open(
10,309✔
579
            &burn_db_path,
10,309✔
580
            true,
581
            burnchain.pox_constants,
10,309✔
582
            Some(config.node.get_marf_opts()),
10,309✔
583
        )
584
        .map_err(|e| {
10,309✔
585
            error!("Relayer: Could not open sortdb '{burn_db_path}' ({e:?}); skipping tenure");
×
586
            e
×
587
        })
×
588
        .ok()?;
10,309✔
589

590
        let mut chainstate = open_chainstate_with_faults(&config)
10,309✔
591
            .map_err(|e| {
10,309✔
592
                error!(
×
593
                    "Relayer: Could not open chainstate '{stacks_chainstate_path}' ({e:?}); skipping microblock tenure"
594
                );
595
                e
×
596
            })
×
597
            .ok()?;
10,309✔
598

599
        let mempool = MemPoolDB::open(
10,309✔
600
            config.is_mainnet(),
10,309✔
601
            config.burnchain.chain_id,
10,309✔
602
            &stacks_chainstate_path,
10,309✔
603
            cost_estimator,
10,309✔
604
            metric,
10,309✔
605
        )
606
        .expect("Database failure opening mempool");
10,309✔
607

608
        let MinerTip {
609
            consensus_hash: ch,
10,309✔
610
            block_hash: bhh,
10,309✔
611
            microblock_privkey: miner_key,
10,309✔
612
            ..
613
        } = miner_tip;
10,309✔
614

615
        debug!("Relayer: Instantiate microblock mining state off of {ch}/{bhh}");
10,309✔
616

617
        // we won a block! proceed to build a microblock tail if we've stored it
618
        match StacksChainState::get_anchored_block_header_info(chainstate.db(), &ch, &bhh) {
10,309✔
619
            Ok(Some(_)) => {
620
                let parent_index_hash = StacksBlockHeader::make_index_block_hash(&ch, &bhh);
10,309✔
621
                let cost_so_far = if relayer_thread.microblock_stream_cost == ExecutionCost::ZERO {
10,309✔
622
                    // unknown cost, or this is idempotent.
623
                    StacksChainState::get_stacks_block_anchored_cost(
10,302✔
624
                        chainstate.db(),
10,302✔
625
                        &parent_index_hash,
10,302✔
626
                    )
627
                    .expect("FATAL: failed to get anchored block cost")
10,302✔
628
                    .expect("FATAL: no anchored block cost stored for processed anchored block")
10,302✔
629
                } else {
630
                    relayer_thread.microblock_stream_cost.clone()
7✔
631
                };
632

633
                let frequency = config.node.microblock_frequency;
10,309✔
634
                let settings =
10,309✔
635
                    config.make_block_builder_settings(0, true, globals.get_miner_status());
10,309✔
636

637
                // port over unconfirmed state to this thread
638
                chainstate.unconfirmed_state = if let Some(unconfirmed_state) =
10,309✔
639
                    relayer_thread.chainstate_ref().unconfirmed_state.as_ref()
10,309✔
640
                {
641
                    Some(unconfirmed_state.make_readonly_owned().ok()?)
10,309✔
642
                } else {
643
                    None
×
644
                };
645

646
                Some(MicroblockMinerThread {
10,309✔
647
                    globals,
10,309✔
648
                    chainstate: Some(chainstate),
10,309✔
649
                    sortdb: Some(sortdb),
10,309✔
650
                    mempool: Some(mempool),
10,309✔
651
                    event_dispatcher: relayer_thread.event_dispatcher.clone(),
10,309✔
652
                    parent_consensus_hash: ch,
10,309✔
653
                    parent_block_hash: bhh,
10,309✔
654
                    miner_key,
10,309✔
655
                    frequency,
10,309✔
656
                    last_mined: 0,
10,309✔
657
                    quantity: 0,
10,309✔
658
                    cost_so_far,
10,309✔
659
                    settings,
10,309✔
660
                })
10,309✔
661
            }
662
            Ok(None) => {
663
                warn!("Relayer: No such anchored block: {ch}/{bhh}.  Cannot mine microblocks");
×
664
                None
×
665
            }
666
            Err(e) => {
×
667
                warn!("Relayer: Failed to get anchored block cost for {ch}/{bhh}: {e:?}");
×
668
                None
×
669
            }
670
        }
671
    }
10,309✔
672

673
    /// Do something with the inner chainstate DBs (borrowed mutably).
674
    /// Used to fool the borrow-checker.
675
    /// NOT COMPOSIBLE - WILL PANIC IF CALLED FROM WITHIN ITSELF.
676
    fn with_chainstate<F, R>(&mut self, func: F) -> R
10,309✔
677
    where
10,309✔
678
        F: FnOnce(&mut Self, &mut SortitionDB, &mut StacksChainState, &mut MemPoolDB) -> R,
10,309✔
679
    {
680
        let mut sortdb = self.sortdb.take().expect("FATAL: already took sortdb");
10,309✔
681
        let mut chainstate = self
10,309✔
682
            .chainstate
10,309✔
683
            .take()
10,309✔
684
            .expect("FATAL: already took chainstate");
10,309✔
685
        let mut mempool = self.mempool.take().expect("FATAL: already took mempool");
10,309✔
686

687
        let res = func(self, &mut sortdb, &mut chainstate, &mut mempool);
10,309✔
688

689
        self.sortdb = Some(sortdb);
10,309✔
690
        self.chainstate = Some(chainstate);
10,309✔
691
        self.mempool = Some(mempool);
10,309✔
692

693
        res
10,309✔
694
    }
10,309✔
695

696
    /// Unconditionally mine one microblock.
697
    /// Can fail if the miner thread gets cancelled (most likely cause), or if there's some kind of
698
    /// DB error.
699
    fn inner_mine_one_microblock(
10,309✔
700
        &mut self,
10,309✔
701
        sortdb: &SortitionDB,
10,309✔
702
        chainstate: &mut StacksChainState,
10,309✔
703
        mempool: &mut MemPoolDB,
10,309✔
704
    ) -> Result<StacksMicroblock, ChainstateError> {
10,309✔
705
        debug!(
10,309✔
706
            "Try to mine one microblock off of {}/{} (total: {})",
707
            &self.parent_consensus_hash,
×
708
            &self.parent_block_hash,
×
709
            chainstate
×
710
                .unconfirmed_state
×
711
                .as_ref()
×
712
                .map(|us| us.num_microblocks())
×
713
                .unwrap_or(0)
×
714
        );
715

716
        let block_snapshot =
10,309✔
717
            SortitionDB::get_block_snapshot_consensus(sortdb.conn(), &self.parent_consensus_hash)
10,309✔
718
                .map_err(|e| {
10,309✔
719
                    error!("Failed to find block snapshot for mined block: {e}");
×
720
                    e
×
721
                })?
×
722
                .ok_or_else(|| {
10,309✔
723
                    error!("Failed to find block snapshot for mined block");
×
724
                    ChainstateError::NoSuchBlockError
×
725
                })?;
×
726
        let burn_height = block_snapshot.block_height;
10,309✔
727

728
        let epoch_id = SortitionDB::get_stacks_epoch(sortdb.conn(), burn_height)
10,309✔
729
            .map_err(|e| {
10,309✔
730
                error!("Failed to get epoch for microblock: {e}");
×
731
                e
×
732
            })?
×
733
            .expect("FATAL: no epoch defined")
10,309✔
734
            .epoch_id;
735

736
        let mint_result = {
7✔
737
            let ic = sortdb.index_handle_at_block(
10,309✔
738
                chainstate,
10,309✔
739
                &block_snapshot.get_canonical_stacks_block_id(),
10,309✔
740
            )?;
×
741
            let mut microblock_miner = match StacksMicroblockBuilder::resume_unconfirmed(
10,309✔
742
                chainstate,
10,309✔
743
                &ic,
10,309✔
744
                &self.cost_so_far,
10,309✔
745
                self.settings.clone(),
10,309✔
746
            ) {
10,309✔
747
                Ok(x) => x,
10,309✔
748
                Err(e) => {
×
749
                    let msg = format!(
×
750
                        "Failed to create a microblock miner at chaintip {}/{}: {e:?}",
751
                        &self.parent_consensus_hash, &self.parent_block_hash
×
752
                    );
753
                    error!("{msg}");
×
754
                    return Err(e);
×
755
                }
756
            };
757

758
            let t1 = get_epoch_time_ms();
10,309✔
759

760
            let mblock = microblock_miner.mine_next_microblock(
10,309✔
761
                mempool,
10,309✔
762
                &self.miner_key,
10,309✔
763
                &self.event_dispatcher,
10,309✔
764
            )?;
10,302✔
765
            let new_cost_so_far = microblock_miner.get_cost_so_far().expect("BUG: cannot read cost so far from miner -- indicates that the underlying Clarity Tx is somehow in use still.");
7✔
766
            let t2 = get_epoch_time_ms();
7✔
767

768
            info!(
7✔
769
                "Mined microblock {} ({}) with {} transactions in {}ms",
770
                mblock.block_hash(),
4✔
771
                mblock.header.sequence,
772
                mblock.txs.len(),
4✔
773
                t2.saturating_sub(t1)
4✔
774
            );
775

776
            Ok((mblock, new_cost_so_far))
7✔
777
        };
778

779
        let (mined_microblock, new_cost) = match mint_result {
7✔
780
            Ok(x) => x,
7✔
781
            Err(e) => {
×
782
                warn!("Failed to mine microblock: {e}");
×
783
                return Err(e);
×
784
            }
785
        };
786

787
        // failsafe
788
        if !Relayer::static_check_problematic_relayed_microblock(
7✔
789
            chainstate.mainnet,
7✔
790
            epoch_id,
7✔
791
            &mined_microblock,
7✔
792
        ) {
7✔
793
            // nope!
794
            warn!(
3✔
795
                "Our mined microblock {} was problematic. Will NOT process.",
796
                &mined_microblock.block_hash()
×
797
            );
798

799
            #[cfg(test)]
800
            {
801
                use std::path::Path;
802
                if let Ok(path) = std::env::var("STACKS_BAD_BLOCKS_DIR") {
3✔
803
                    // record this microblock somewhere
804
                    if fs::metadata(&path).is_err() {
3✔
805
                        fs::create_dir_all(&path)
×
806
                            .unwrap_or_else(|_| panic!("FATAL: could not create '{path}'"));
×
807
                    }
3✔
808

809
                    let path = Path::new(&path);
3✔
810
                    let path = path.join(Path::new(&format!("{}", &mined_microblock.block_hash())));
3✔
811
                    let mut file = fs::File::create(&path)
3✔
812
                        .unwrap_or_else(|_| panic!("FATAL: could not create '{path:?}'"));
3✔
813

814
                    let mblock_bits = mined_microblock.serialize_to_vec();
3✔
815
                    let mblock_bits_hex = to_hex(&mblock_bits);
3✔
816

817
                    let mblock_json = format!(
3✔
818
                        r#"{{"microblock":"{mblock_bits_hex}","parent_consensus":"{}","parent_block":"{}"}}"#,
819
                        &self.parent_consensus_hash, &self.parent_block_hash
3✔
820
                    );
821
                    file.write_all(mblock_json.as_bytes()).unwrap_or_else(|_| {
3✔
822
                        panic!("FATAL: failed to write microblock bits to '{path:?}'")
×
823
                    });
824
                    info!(
3✔
825
                        "Fault injection: bad microblock {} saved to {}",
826
                        &mined_microblock.block_hash(),
×
827
                        &path.to_str().unwrap()
×
828
                    );
829
                }
×
830
            }
831
            return Err(ChainstateError::NoTransactionsToMine);
3✔
832
        }
4✔
833

834
        // cancelled?
835
        let is_miner_blocked = self
4✔
836
            .globals
4✔
837
            .get_miner_status()
4✔
838
            .lock()
4✔
839
            .expect("FATAL: mutex poisoned")
4✔
840
            .is_blocked();
4✔
841
        if is_miner_blocked {
4✔
842
            return Err(ChainstateError::MinerAborted);
×
843
        }
4✔
844

845
        // preprocess the microblock locally
846
        chainstate.preprocess_streamed_microblock(
4✔
847
            &self.parent_consensus_hash,
4✔
848
            &self.parent_block_hash,
4✔
849
            &mined_microblock,
4✔
850
        )?;
×
851

852
        // update unconfirmed state cost
853
        self.cost_so_far = new_cost;
4✔
854
        self.quantity += 1;
4✔
855
        Ok(mined_microblock)
4✔
856
    }
10,309✔
857

858
    /// Can this microblock miner mine off of this given tip?
859
    pub fn can_mine_on_tip(
20,618✔
860
        &self,
20,618✔
861
        consensus_hash: &ConsensusHash,
20,618✔
862
        block_hash: &BlockHeaderHash,
20,618✔
863
    ) -> bool {
20,618✔
864
        self.parent_consensus_hash == *consensus_hash && self.parent_block_hash == *block_hash
20,618✔
865
    }
20,618✔
866

867
    /// Body of try_mine_microblock()
868
    fn inner_try_mine_microblock(
10,309✔
869
        &mut self,
10,309✔
870
        miner_tip: MinerTip,
10,309✔
871
        sortdb: &SortitionDB,
10,309✔
872
        chainstate: &mut StacksChainState,
10,309✔
873
        mem_pool: &mut MemPoolDB,
10,309✔
874
    ) -> Result<Option<(StacksMicroblock, ExecutionCost)>, NetError> {
10,309✔
875
        if !self.can_mine_on_tip(&self.parent_consensus_hash, &self.parent_block_hash) {
10,309✔
876
            // not configured to mine on this tip
877
            return Ok(None);
×
878
        }
10,309✔
879
        if !self.can_mine_on_tip(&miner_tip.consensus_hash, &miner_tip.block_hash) {
10,309✔
880
            // this tip isn't what this miner is meant to mine on
881
            return Ok(None);
×
882
        }
10,309✔
883

884
        if self.last_mined + (self.frequency as u128) >= get_epoch_time_ms() {
10,309✔
885
            // too soon to mine
886
            return Ok(None);
×
887
        }
10,309✔
888

889
        let mut next_microblock_and_runtime = None;
10,309✔
890

891
        // opportunistically try and mine, but only if there are no attachable blocks in
892
        // recent history (i.e. in the last 10 minutes)
893
        let num_attachable = StacksChainState::count_attachable_staging_blocks(
10,309✔
894
            chainstate.db(),
10,309✔
895
            1,
896
            get_epoch_time_secs() - 600,
10,309✔
897
        )?;
×
898
        if num_attachable == 0 {
10,309✔
899
            match self.inner_mine_one_microblock(sortdb, chainstate, mem_pool) {
10,309✔
900
                Ok(microblock) => {
7✔
901
                    // will need to relay this
7✔
902
                    next_microblock_and_runtime = Some((microblock, self.cost_so_far.clone()));
7✔
903
                }
7✔
904
                Err(ChainstateError::NoTransactionsToMine) => {
905
                    info!("Will keep polling mempool for transactions to include in a microblock");
10,301✔
906
                }
907
                Err(e) => {
1✔
908
                    warn!("Failed to mine one microblock: {e:?}");
1✔
909
                }
910
            }
911
        } else {
912
            debug!("Will not mine microblocks yet -- have {num_attachable} attachable blocks that arrived in the last 10 minutes");
×
913
        }
914

915
        self.last_mined = get_epoch_time_ms();
10,309✔
916

917
        Ok(next_microblock_and_runtime)
10,309✔
918
    }
10,309✔
919

920
    /// Try to mine one microblock, given the current chain tip and access to the chain state DBs.
921
    /// If we succeed, return the microblock and log the tx events to the given event dispatcher.
922
    /// May return None if any of the following are true:
923
    /// * `miner_tip` does not match this miner's miner tip
924
    /// * it's been too soon (less than microblock_frequency milliseconds) since we tried this call
925
    /// * there are simply no transactions to mine
926
    /// * there are still stacks blocks to be processed in the staging db
927
    /// * the miner thread got cancelled
928
    pub fn try_mine_microblock(
10,309✔
929
        &mut self,
10,309✔
930
        cur_tip: MinerTip,
10,309✔
931
    ) -> Result<Option<(StacksMicroblock, ExecutionCost)>, NetError> {
10,309✔
932
        debug!("microblock miner thread ID is {:?}", thread::current().id());
10,309✔
933
        self.with_chainstate(|mblock_miner, sortdb, chainstate, mempool| {
10,309✔
934
            mblock_miner.inner_try_mine_microblock(cur_tip, sortdb, chainstate, mempool)
10,309✔
935
        })
10,309✔
936
    }
10,309✔
937
}
938

939
/// Candidate chain tip
940
#[derive(Debug, Clone, PartialEq)]
941
pub struct TipCandidate {
942
    pub stacks_height: u64,
943
    pub consensus_hash: ConsensusHash,
944
    pub anchored_block_hash: BlockHeaderHash,
945
    pub parent_consensus_hash: ConsensusHash,
946
    pub parent_anchored_block_hash: BlockHeaderHash,
947
    /// the block's sortition's burnchain height
948
    pub burn_height: u64,
949
    /// the number of Stacks blocks *at the same height* as this one, but from earlier sortitions
950
    /// than `burn_height`
951
    pub num_earlier_siblings: u64,
952
}
953

954
impl TipCandidate {
955
    pub fn id(&self) -> StacksBlockId {
2,229,986✔
956
        StacksBlockId::new(&self.consensus_hash, &self.anchored_block_hash)
2,229,986✔
957
    }
2,229,986✔
958

959
    pub fn parent_id(&self) -> StacksBlockId {
744,396✔
960
        StacksBlockId::new(
744,396✔
961
            &self.parent_consensus_hash,
744,396✔
962
            &self.parent_anchored_block_hash,
744,396✔
963
        )
964
    }
744,396✔
965

966
    pub fn new(tip: StagingBlock, burn_height: u64) -> Self {
743,951✔
967
        Self {
743,951✔
968
            stacks_height: tip.height,
743,951✔
969
            consensus_hash: tip.consensus_hash,
743,951✔
970
            anchored_block_hash: tip.anchored_block_hash,
743,951✔
971
            parent_consensus_hash: tip.parent_consensus_hash,
743,951✔
972
            parent_anchored_block_hash: tip.parent_anchored_block_hash,
743,951✔
973
            burn_height,
743,951✔
974
            num_earlier_siblings: 0,
743,951✔
975
        }
743,951✔
976
    }
743,951✔
977
}
978

979
impl BlockMinerThread {
980
    /// Instantiate the miner thread from its parent RelayerThread
981
    pub fn from_relayer_thread(
168,097✔
982
        rt: &RelayerThread,
168,097✔
983
        registered_key: RegisteredKey,
168,097✔
984
        burn_block: BlockSnapshot,
168,097✔
985
    ) -> BlockMinerThread {
168,097✔
986
        BlockMinerThread {
168,097✔
987
            config: rt.config.clone(),
168,097✔
988
            globals: rt.globals.clone(),
168,097✔
989
            keychain: rt.keychain.clone(),
168,097✔
990
            burnchain: rt.burnchain.clone(),
168,097✔
991
            last_mined_blocks: rt.last_mined_blocks.clone(),
168,097✔
992
            ongoing_commit: rt.bitcoin_controller.get_ongoing_commit(),
168,097✔
993
            registered_key,
168,097✔
994
            burn_block,
168,097✔
995
            event_dispatcher: rt.event_dispatcher.clone(),
168,097✔
996
            failed_to_submit_last_attempt: rt.last_attempt_failed,
168,097✔
997
        }
168,097✔
998
    }
168,097✔
999

1000
    /// Get the coinbase recipient address, if set in the config and if allowed in this epoch
1001
    fn get_coinbase_recipient(&self, epoch_id: StacksEpochId) -> Option<PrincipalData> {
25,292✔
1002
        let miner_config = self.config.get_miner_config();
25,292✔
1003
        if epoch_id < StacksEpochId::Epoch21 && miner_config.block_reward_recipient.is_some() {
25,292✔
1004
            warn!("Coinbase pay-to-contract is not supported in the current epoch");
×
1005
            None
×
1006
        } else {
1007
            miner_config.block_reward_recipient
25,292✔
1008
        }
1009
    }
25,292✔
1010

1011
    /// Create a coinbase transaction.
1012
    fn inner_generate_coinbase_tx(
25,292✔
1013
        &mut self,
25,292✔
1014
        nonce: u64,
25,292✔
1015
        epoch_id: StacksEpochId,
25,292✔
1016
    ) -> StacksTransaction {
25,292✔
1017
        let is_mainnet = self.config.is_mainnet();
25,292✔
1018
        let chain_id = self.config.burnchain.chain_id;
25,292✔
1019
        let mut tx_auth = self.keychain.get_transaction_auth().unwrap();
25,292✔
1020
        tx_auth.set_origin_nonce(nonce);
25,292✔
1021

1022
        let version = if is_mainnet {
25,292✔
1023
            TransactionVersion::Mainnet
×
1024
        } else {
1025
            TransactionVersion::Testnet
25,292✔
1026
        };
1027

1028
        let recipient_opt = self.get_coinbase_recipient(epoch_id);
25,292✔
1029
        let mut tx = StacksTransaction::new(
25,292✔
1030
            version,
25,292✔
1031
            tx_auth,
25,292✔
1032
            TransactionPayload::Coinbase(CoinbasePayload([0u8; 32]), recipient_opt, None),
25,292✔
1033
        );
1034
        tx.chain_id = chain_id;
25,292✔
1035
        tx.anchor_mode = TransactionAnchorMode::OnChainOnly;
25,292✔
1036
        let mut tx_signer = StacksTransactionSigner::new(&tx);
25,292✔
1037
        self.keychain.sign_as_origin(&mut tx_signer);
25,292✔
1038

1039
        tx_signer.get_tx().unwrap()
25,292✔
1040
    }
25,292✔
1041

1042
    /// Create a poison microblock transaction.
1043
    fn inner_generate_poison_microblock_tx(
×
1044
        &mut self,
×
1045
        nonce: u64,
×
1046
        poison_payload: TransactionPayload,
×
1047
    ) -> StacksTransaction {
×
1048
        let is_mainnet = self.config.is_mainnet();
×
1049
        let chain_id = self.config.burnchain.chain_id;
×
1050
        let mut tx_auth = self.keychain.get_transaction_auth().unwrap();
×
1051
        tx_auth.set_origin_nonce(nonce);
×
1052

1053
        let version = if is_mainnet {
×
1054
            TransactionVersion::Mainnet
×
1055
        } else {
1056
            TransactionVersion::Testnet
×
1057
        };
1058
        let mut tx = StacksTransaction::new(version, tx_auth, poison_payload);
×
1059
        tx.chain_id = chain_id;
×
1060
        tx.anchor_mode = TransactionAnchorMode::OnChainOnly;
×
1061
        let mut tx_signer = StacksTransactionSigner::new(&tx);
×
1062
        self.keychain.sign_as_origin(&mut tx_signer);
×
1063

1064
        tx_signer.get_tx().unwrap()
×
1065
    }
×
1066

1067
    /// Constructs and returns a LeaderBlockCommitOp out of the provided params.
1068
    #[allow(clippy::too_many_arguments)]
1069
    fn inner_generate_block_commit_op(
21,723✔
1070
        &self,
21,723✔
1071
        block_header_hash: BlockHeaderHash,
21,723✔
1072
        burn_fee: u64,
21,723✔
1073
        key: &RegisteredKey,
21,723✔
1074
        parent_burnchain_height: u32,
21,723✔
1075
        parent_winning_vtx: u16,
21,723✔
1076
        vrf_seed: VRFSeed,
21,723✔
1077
        commit_outs: Vec<PoxAddress>,
21,723✔
1078
        sunset_burn: u64,
21,723✔
1079
        current_burn_height: u64,
21,723✔
1080
    ) -> BlockstackOperationType {
21,723✔
1081
        let (parent_block_ptr, parent_vtxindex) = (parent_burnchain_height, parent_winning_vtx);
21,723✔
1082
        let burn_parent_modulus = (current_burn_height % BURN_BLOCK_MINED_AT_MODULUS) as u8;
21,723✔
1083
        let sender = self.keychain.get_burnchain_signer();
21,723✔
1084
        BlockstackOperationType::LeaderBlockCommit(LeaderBlockCommitOp {
21,723✔
1085
            treatment: vec![],
21,723✔
1086
            sunset_burn,
21,723✔
1087
            block_header_hash,
21,723✔
1088
            burn_fee,
21,723✔
1089
            input: (Txid([0; 32]), 0),
21,723✔
1090
            apparent_sender: sender,
21,723✔
1091
            key_block_ptr: key.block_height as u32,
21,723✔
1092
            key_vtxindex: key.op_vtxindex as u16,
21,723✔
1093
            memo: vec![STACKS_EPOCH_3_0_MARKER],
21,723✔
1094
            new_seed: vrf_seed,
21,723✔
1095
            parent_block_ptr,
21,723✔
1096
            parent_vtxindex,
21,723✔
1097
            vtxindex: 0,
21,723✔
1098
            txid: Txid([0u8; 32]),
21,723✔
1099
            block_height: 0,
21,723✔
1100
            burn_header_hash: BurnchainHeaderHash::zero(),
21,723✔
1101
            burn_parent_modulus,
21,723✔
1102
            commit_outs,
21,723✔
1103
        })
21,723✔
1104
    }
21,723✔
1105

1106
    /// Get references to the inner assembled anchor block data we've produced for a given burnchain block height
1107
    fn find_inflight_mined_blocks(
174,669✔
1108
        burn_height: u64,
174,669✔
1109
        last_mined_blocks: &MinedBlocks,
174,669✔
1110
    ) -> Vec<&AssembledAnchorBlock> {
174,669✔
1111
        let mut ret = vec![];
174,669✔
1112
        for (_, (assembled_block, _)) in last_mined_blocks.iter() {
175,829✔
1113
            if assembled_block.burn_block_height >= burn_height {
159,721✔
1114
                ret.push(assembled_block);
158,390✔
1115
            }
158,392✔
1116
        }
1117
        ret
174,669✔
1118
    }
174,669✔
1119

1120
    /// Is a given Stacks staging block on the canonical burnchain fork?
1121
    pub(crate) fn is_on_canonical_burnchain_fork(
1,476,165✔
1122
        candidate_ch: &ConsensusHash,
1,476,165✔
1123
        candidate_bh: &BlockHeaderHash,
1,476,165✔
1124
        sortdb_tip_handle: &SortitionHandleConn,
1,476,165✔
1125
    ) -> bool {
1,476,165✔
1126
        let candidate_burn_ht = match SortitionDB::get_block_snapshot_consensus(
1,476,165✔
1127
            sortdb_tip_handle.conn(),
1,476,165✔
1128
            candidate_ch,
1,476,165✔
1129
        ) {
1130
            Ok(Some(x)) => x.block_height,
1,476,165✔
1131
            Ok(None) => {
1132
                warn!("Tried to evaluate potential chain tip with an unknown consensus hash";
×
1133
                      "consensus_hash" => %candidate_ch,
1134
                      "stacks_block_hash" => %candidate_bh);
1135
                return false;
×
1136
            }
1137
            Err(e) => {
×
1138
                warn!("Error while trying to evaluate potential chain tip with an unknown consensus hash";
×
1139
                      "consensus_hash" => %candidate_ch,
1140
                      "stacks_block_hash" => %candidate_bh,
1141
                      "err" => ?e);
1142
                return false;
×
1143
            }
1144
        };
1145
        let tip_ch = match sortdb_tip_handle.get_consensus_at(candidate_burn_ht) {
1,476,165✔
1146
            Ok(Some(x)) => x,
1,476,164✔
1147
            Ok(None) => {
1148
                warn!("Tried to evaluate potential chain tip with a consensus hash ahead of canonical tip";
1✔
1149
                      "consensus_hash" => %candidate_ch,
1150
                      "stacks_block_hash" => %candidate_bh);
1151
                return false;
1✔
1152
            }
1153
            Err(e) => {
×
1154
                warn!("Error while trying to evaluate potential chain tip with an unknown consensus hash";
×
1155
                      "consensus_hash" => %candidate_ch,
1156
                      "stacks_block_hash" => %candidate_bh,
1157
                      "err" => ?e);
1158
                return false;
×
1159
            }
1160
        };
1161
        &tip_ch == candidate_ch
1,476,164✔
1162
    }
1,476,165✔
1163

1164
    /// Load all candidate tips upon which to build.  This is all Stacks blocks whose heights are
1165
    /// less than or equal to at `at_stacks_height` (or the canonical chain tip height, if not given),
1166
    /// but greater than or equal to this end height minus `max_depth`.
1167
    /// Returns the list of all Stacks blocks up to max_depth blocks beneath it.
1168
    /// The blocks will be sorted first by stacks height, and then by burnchain height
1169
    pub(crate) fn load_candidate_tips(
189,398✔
1170
        burn_db: &mut SortitionDB,
189,398✔
1171
        chain_state: &mut StacksChainState,
189,398✔
1172
        max_depth: u64,
189,398✔
1173
        at_stacks_height: Option<u64>,
189,398✔
1174
    ) -> Vec<TipCandidate> {
189,398✔
1175
        let stacks_tips = if let Some(start_height) = at_stacks_height {
189,398✔
1176
            chain_state
×
1177
                .get_stacks_chain_tips_at_height(start_height)
×
1178
                .expect("FATAL: could not query chain tips at start height")
×
1179
        } else {
1180
            chain_state
189,398✔
1181
                .get_stacks_chain_tips(burn_db)
189,398✔
1182
                .expect("FATAL: could not query chain tips")
189,398✔
1183
        };
1184

1185
        if stacks_tips.is_empty() {
189,398✔
1186
            return vec![];
281✔
1187
        }
189,117✔
1188

1189
        let sortdb_tip_handle = burn_db.index_handle_at_tip();
189,117✔
1190

1191
        let stacks_tips: Vec<_> = stacks_tips
189,117✔
1192
            .into_iter()
189,117✔
1193
            .filter(|candidate| {
189,804✔
1194
                Self::is_on_canonical_burnchain_fork(
189,804✔
1195
                    &candidate.consensus_hash,
189,804✔
1196
                    &candidate.anchored_block_hash,
189,804✔
1197
                    &sortdb_tip_handle,
189,804✔
1198
                )
1199
            })
189,804✔
1200
            .collect();
189,117✔
1201

1202
        if stacks_tips.is_empty() {
189,117✔
1203
            return vec![];
×
1204
        }
189,117✔
1205

1206
        let mut considered = HashSet::new();
189,117✔
1207
        let mut candidates = vec![];
189,117✔
1208
        let end_height = stacks_tips[0].height;
189,117✔
1209

1210
        // process these tips
1211
        for tip in stacks_tips.into_iter() {
189,235✔
1212
            let index_block_hash =
189,233✔
1213
                StacksBlockId::new(&tip.consensus_hash, &tip.anchored_block_hash);
189,233✔
1214
            let burn_height = burn_db
189,233✔
1215
                .get_consensus_hash_height(&tip.consensus_hash)
189,233✔
1216
                .expect("FATAL: could not query burnchain block height")
189,233✔
1217
                .expect("FATAL: no burnchain block height for Stacks tip");
189,233✔
1218
            let candidate = TipCandidate::new(tip, burn_height);
189,233✔
1219
            candidates.push(candidate);
189,233✔
1220
            considered.insert(index_block_hash);
189,233✔
1221
        }
189,233✔
1222

1223
        // process earlier tips, back to max_depth
1224
        for cur_height in end_height.saturating_sub(max_depth)..end_height {
560,382✔
1225
            let stacks_tips = chain_state
560,382✔
1226
                .get_stacks_chain_tips_at_height(cur_height)
560,382✔
1227
                .expect("FATAL: could not query chain tips at height")
560,382✔
1228
                .into_iter()
560,382✔
1229
                .filter(|candidate| {
561,063✔
1230
                    Self::is_on_canonical_burnchain_fork(
555,171✔
1231
                        &candidate.consensus_hash,
555,171✔
1232
                        &candidate.anchored_block_hash,
555,171✔
1233
                        &sortdb_tip_handle,
555,171✔
1234
                    )
1235
                });
555,171✔
1236

1237
            for tip in stacks_tips {
560,700✔
1238
                let index_block_hash =
554,718✔
1239
                    StacksBlockId::new(&tip.consensus_hash, &tip.anchored_block_hash);
554,718✔
1240

1241
                if considered.insert(index_block_hash) {
554,718✔
1242
                    let burn_height = burn_db
554,717✔
1243
                        .get_consensus_hash_height(&tip.consensus_hash)
554,717✔
1244
                        .expect("FATAL: could not query burnchain block height")
554,717✔
1245
                        .expect("FATAL: no burnchain block height for Stacks tip");
554,717✔
1246
                    let candidate = TipCandidate::new(tip, burn_height);
554,717✔
1247
                    candidates.push(candidate);
554,717✔
1248
                }
554,717✔
1249
            }
1250
        }
1251
        Self::sort_and_populate_candidates(candidates)
189,117✔
1252
    }
189,398✔
1253

1254
    /// Put all tip candidates in order by stacks height, breaking ties with burnchain height.
1255
    /// Also, count up the number of earliersiblings each tip has -- i.e. the number of stacks
1256
    /// blocks that have the same height, but a later burnchain sortition.
1257
    pub(crate) fn sort_and_populate_candidates(
189,119✔
1258
        mut candidates: Vec<TipCandidate>,
189,119✔
1259
    ) -> Vec<TipCandidate> {
189,119✔
1260
        if candidates.is_empty() {
189,119✔
1261
            return candidates;
1✔
1262
        }
189,118✔
1263
        candidates.sort_by(|tip1, tip2| {
922,775✔
1264
            // stacks block height, then burnchain block height
1265
            let ord = tip1.stacks_height.cmp(&tip2.stacks_height);
922,764✔
1266
            if ord == CmpOrdering::Equal {
922,764✔
1267
                return tip1.burn_height.cmp(&tip2.burn_height);
468✔
1268
            }
922,296✔
1269
            ord
922,296✔
1270
        });
922,764✔
1271

1272
        // calculate the number of earlier siblings for each block.
1273
        // this is the number of stacks blocks at the same height, but later burnchain heights.
1274
        let mut idx = 0;
189,118✔
1275
        let mut cur_stacks_height = candidates[idx].stacks_height;
189,118✔
1276
        let mut num_siblings = 0;
189,118✔
1277
        loop {
1278
            idx += 1;
743,963✔
1279
            if idx >= candidates.len() {
743,963✔
1280
                break;
189,118✔
1281
            }
554,845✔
1282
            if cur_stacks_height == candidates[idx].stacks_height {
554,845✔
1283
                // same stacks height, so this block has one more earlier sibling than the last
468✔
1284
                num_siblings += 1;
468✔
1285
                candidates[idx].num_earlier_siblings = num_siblings;
468✔
1286
            } else {
554,377✔
1287
                // new stacks height, so no earlier siblings
554,377✔
1288
                num_siblings = 0;
554,377✔
1289
                cur_stacks_height = candidates[idx].stacks_height;
554,377✔
1290
                candidates[idx].num_earlier_siblings = 0;
554,377✔
1291
            }
554,377✔
1292
        }
1293

1294
        candidates
189,118✔
1295
    }
189,119✔
1296

1297
    /// Select the best tip to mine the next block on. Potential tips are all
1298
    /// leaf nodes where the Stacks block height is <= the max height -
1299
    /// max_reorg_depth. Each potential tip is then scored based on the amount
1300
    /// of orphans that its chain has caused -- that is, the number of orphans
1301
    /// that the tip _and all of its ancestors_ (up to `max_depth`) created.
1302
    /// The tip with the lowest score is composed of blocks that collectively made the fewest
1303
    /// orphans, and is thus the "nicest" chain with the least orphaning.  This is the tip that is
1304
    /// selected.
1305
    pub fn pick_best_tip(
189,398✔
1306
        globals: &Globals,
189,398✔
1307
        config: &Config,
189,398✔
1308
        burn_db: &mut SortitionDB,
189,398✔
1309
        chain_state: &mut StacksChainState,
189,398✔
1310
        at_stacks_height: Option<u64>,
189,398✔
1311
    ) -> Option<TipCandidate> {
189,398✔
1312
        debug!("Picking best Stacks tip");
189,398✔
1313
        let miner_config = config.get_miner_config();
189,398✔
1314
        let max_depth = miner_config.max_reorg_depth;
189,398✔
1315

1316
        // There could be more than one possible chain tip. Go find them.
1317
        let stacks_tips =
189,398✔
1318
            Self::load_candidate_tips(burn_db, chain_state, max_depth, at_stacks_height);
189,398✔
1319

1320
        let mut previous_best_tips = HashMap::new();
189,398✔
1321
        let sortdb_tip_handle = burn_db.index_handle_at_tip();
189,398✔
1322
        for tip in stacks_tips.iter() {
743,951✔
1323
            let Some(prev_best_tip) = globals.get_best_tip(tip.stacks_height) else {
743,950✔
1324
                continue;
12,761✔
1325
            };
1326
            if !Self::is_on_canonical_burnchain_fork(
731,189✔
1327
                &prev_best_tip.consensus_hash,
731,189✔
1328
                &prev_best_tip.anchored_block_hash,
731,189✔
1329
                &sortdb_tip_handle,
731,189✔
1330
            ) {
731,189✔
1331
                continue;
225✔
1332
            }
730,964✔
1333
            previous_best_tips.insert(tip.stacks_height, prev_best_tip);
730,964✔
1334
        }
1335

1336
        let best_tip_opt = Self::inner_pick_best_tip(stacks_tips, previous_best_tips);
189,398✔
1337
        if let Some(best_tip) = best_tip_opt.as_ref() {
189,398✔
1338
            globals.add_best_tip(best_tip.stacks_height, best_tip.clone(), max_depth);
189,117✔
1339
        } else {
189,117✔
1340
            // no best-tip found; revert to old tie-breaker logic
1341
            debug!("No best-tips found; using old tie-breaking logic");
281✔
1342
            return chain_state
281✔
1343
                .get_stacks_chain_tip(burn_db)
281✔
1344
                .expect("FATAL: could not load chain tip")
281✔
1345
                .map(|staging_block| {
281✔
1346
                    let burn_height = burn_db
×
1347
                        .get_consensus_hash_height(&staging_block.consensus_hash)
×
1348
                        .expect("FATAL: could not query burnchain block height")
×
1349
                        .expect("FATAL: no burnchain block height for Stacks tip");
×
1350
                    TipCandidate::new(staging_block, burn_height)
×
1351
                });
×
1352
        }
1353
        best_tip_opt
189,117✔
1354
    }
189,398✔
1355

1356
    /// Given a list of sorted candidate tips, pick the best one.  See `Self::pick_best_tip()`.
1357
    /// Takes the list of stacks tips that are eligible to be built on, and a map of
1358
    /// previously-chosen best tips (so if we chose a tip in the past, we keep confirming it, even
1359
    /// if subsequent stacks blocks show up).  The previous best tips should be from recent Stacks
1360
    /// heights; it's important that older best-tips are forgotten in order to ensure that miners
1361
    /// will eventually (e.g. after `max_reorg_depth` Stacks blocks pass) stop trying to confirm a
1362
    /// now-orphaned previously-chosen best-tip.  If there are multiple best-tips that conflict in
1363
    /// `previosu_best_tips`, then only the highest one which the leaf could confirm will be
1364
    /// considered (since the node updates its understanding of the best-tip on each RunTenure).
1365
    pub(crate) fn inner_pick_best_tip(
189,422✔
1366
        stacks_tips: Vec<TipCandidate>,
189,422✔
1367
        previous_best_tips: HashMap<u64, TipCandidate>,
189,422✔
1368
    ) -> Option<TipCandidate> {
189,422✔
1369
        // identify leaf tips -- i.e. blocks with no children
1370
        let parent_consensus_hashes: HashSet<_> = stacks_tips
189,422✔
1371
            .iter()
189,422✔
1372
            .map(|x| x.parent_consensus_hash.clone())
744,036✔
1373
            .collect();
189,422✔
1374

1375
        let mut leaf_tips: Vec<_> = stacks_tips
189,422✔
1376
            .iter()
189,422✔
1377
            .filter(|x| !parent_consensus_hashes.contains(&x.consensus_hash))
744,036✔
1378
            .collect();
189,422✔
1379

1380
        if leaf_tips.is_empty() {
189,422✔
1381
            return None;
282✔
1382
        }
189,140✔
1383

1384
        // Make scoring deterministic in the case of a tie.
1385
        // Prefer leafs that were mined earlier on the burnchain,
1386
        // but which pass through previously-determined best tips.
1387
        leaf_tips.sort_by(|tip1, tip2| {
189,144✔
1388
            // stacks block height, then burnchain block height
1389
            let ord = tip1.stacks_height.cmp(&tip2.stacks_height);
492✔
1390
            if ord == CmpOrdering::Equal {
492✔
1391
                return tip1.burn_height.cmp(&tip2.burn_height);
138✔
1392
            }
354✔
1393
            ord
354✔
1394
        });
492✔
1395

1396
        let mut scores = BTreeMap::new();
189,140✔
1397
        for (i, leaf_tip) in leaf_tips.iter().enumerate() {
189,632✔
1398
            let leaf_id = leaf_tip.id();
189,632✔
1399
            // Score each leaf tip as the number of preceding Stacks blocks that are _not_ an
1400
            // ancestor.  Because stacks_tips are in order by stacks height, a linear scan of this
1401
            // list will allow us to match all ancestors in the last max_depth Stacks blocks.
1402
            // `ancestor_ptr` tracks the next expected ancestor.
1403
            let mut ancestor_ptr = leaf_tip.parent_id();
189,632✔
1404
            let mut score: u64 = 0;
189,632✔
1405
            let mut score_summaries = vec![];
189,632✔
1406

1407
            // find the highest stacks_tip we must confirm
1408
            let mut must_confirm = None;
189,632✔
1409
            for tip in stacks_tips.iter().rev() {
379,296✔
1410
                if let Some(prev_best_tip) = previous_best_tips.get(&tip.stacks_height) {
379,296✔
1411
                    if leaf_id != prev_best_tip.id() {
367,390✔
1412
                        // the `ancestor_ptr` must pass through this prior best-tip
1413
                        must_confirm = Some(prev_best_tip.clone());
185,307✔
1414
                        break;
185,307✔
1415
                    }
182,083✔
1416
                }
11,906✔
1417
            }
1418

1419
            for tip in stacks_tips.iter().rev() {
746,252✔
1420
                if let Some(required_ancestor) = must_confirm.as_ref() {
746,252✔
1421
                    if tip.stacks_height < required_ancestor.stacks_height
372,074✔
1422
                        && leaf_tip.stacks_height >= required_ancestor.stacks_height
1,522✔
1423
                    {
1424
                        // This leaf does not confirm a previous-best-tip, so assign it the
1425
                        // worst-possible score.
1426
                        info!("Tip #{i} {}/{} at {}:{} conflicts with a previous best-tip {}/{} at {}:{}",
140✔
1427
                              &leaf_tip.consensus_hash,
140✔
1428
                              &leaf_tip.anchored_block_hash,
140✔
1429
                              leaf_tip.burn_height,
1430
                              leaf_tip.stacks_height,
1431
                              &required_ancestor.consensus_hash,
140✔
1432
                              &required_ancestor.anchored_block_hash,
140✔
1433
                              required_ancestor.burn_height,
1434
                              required_ancestor.stacks_height
1435
                        );
1436
                        score = u64::MAX;
140✔
1437
                        score_summaries.push(format!("{} (best-tip reorged)", u64::MAX));
140✔
1438
                        break;
140✔
1439
                    }
371,934✔
1440
                }
374,178✔
1441
                if tip.id() == leaf_id {
746,112✔
1442
                    // we can't orphan ourselves
1443
                    continue;
189,632✔
1444
                }
556,480✔
1445
                if leaf_tip.stacks_height < tip.stacks_height {
556,480✔
1446
                    // this tip is further along than leaf_tip, so canonicalizing leaf_tip would
708✔
1447
                    // orphan `tip.stacks_height - leaf_tip.stacks_height` blocks.
708✔
1448
                    score = score.saturating_add(tip.stacks_height - leaf_tip.stacks_height);
708✔
1449
                    score_summaries.push(format!(
708✔
1450
                        "{} (stx height diff)",
708✔
1451
                        tip.stacks_height - leaf_tip.stacks_height
708✔
1452
                    ));
708✔
1453
                } else if leaf_tip.stacks_height == tip.stacks_height
555,772✔
1454
                    && leaf_tip.burn_height > tip.burn_height
646✔
1455
                {
496✔
1456
                    // this tip has the same stacks height as the leaf, but its sortition happened
496✔
1457
                    // earlier. This means that the leaf is trying to orphan this block and all
496✔
1458
                    // blocks sortition'ed up to this leaf.  The miner should have instead tried to
496✔
1459
                    // confirm this existing tip, instead of mine a sibling.
496✔
1460
                    score = score.saturating_add(tip.num_earlier_siblings + 1);
496✔
1461
                    score_summaries.push(format!("{} (uncles)", tip.num_earlier_siblings + 1));
496✔
1462
                }
555,276✔
1463
                if tip.id() == ancestor_ptr {
556,480✔
1464
                    // did we confirm a previous best-tip? If so, then clear this
1465
                    if let Some(required_ancestor) = must_confirm.take() {
554,764✔
1466
                        if required_ancestor.id() != tip.id() {
185,186✔
1467
                            // did not confirm, so restoroe
366✔
1468
                            must_confirm = Some(required_ancestor);
366✔
1469
                        }
184,826✔
1470
                    }
369,578✔
1471

1472
                    // this stacks tip is the next ancestor.  However, that ancestor may have
1473
                    // earlier-sortition'ed siblings that confirming this tip would orphan, so count those.
1474
                    ancestor_ptr = tip.parent_id();
554,764✔
1475
                    score = score.saturating_add(tip.num_earlier_siblings);
554,764✔
1476
                    score_summaries.push(format!("{} (earlier sibs)", tip.num_earlier_siblings));
554,764✔
1477
                } else {
1,716✔
1478
                    // this stacks tip is not an ancestor, and would be orphaned if leaf_tip is
1,716✔
1479
                    // canonical.
1,716✔
1480
                    score = score.saturating_add(1);
1,716✔
1481
                    score_summaries.push(format!("{} (non-ancestor)", 1));
1,716✔
1482
                }
1,716✔
1483
            }
1484

1485
            debug!(
189,632✔
1486
                "Tip #{i} {}/{} at {}:{} has score {score} ({})",
1487
                &leaf_tip.consensus_hash,
×
1488
                &leaf_tip.anchored_block_hash,
×
1489
                leaf_tip.burn_height,
1490
                leaf_tip.stacks_height,
1491
                score_summaries.join(" + ").to_string()
×
1492
            );
1493
            if score < u64::MAX {
189,632✔
1494
                scores.insert(i, score);
189,492✔
1495
            }
189,492✔
1496
        }
1497

1498
        if scores.is_empty() {
189,140✔
1499
            // revert to prior tie-breaking scheme
1500
            return None;
3✔
1501
        }
189,137✔
1502

1503
        // The lowest score is the "nicest" tip (least amount of orphaning)
1504
        let best_tip_idx = scores
189,137✔
1505
            .iter()
189,137✔
1506
            .min_by_key(|(_, score)| *score)
189,137✔
1507
            .expect("FATAL: candidates should not be empty here")
189,137✔
1508
            .0;
1509

1510
        let best_tip = leaf_tips
189,137✔
1511
            .get(*best_tip_idx)
189,137✔
1512
            .expect("FATAL: candidates should not be empty");
189,137✔
1513

1514
        debug!(
189,137✔
1515
            "Best tip is #{best_tip_idx} {}/{}",
1516
            &best_tip.consensus_hash, &best_tip.anchored_block_hash
×
1517
        );
1518
        Some((*best_tip).clone())
189,137✔
1519
    }
189,422✔
1520

1521
    // TODO: add tests from mutation testing results #4870
1522
    #[cfg_attr(test, mutants::skip)]
1523
    /// Load up the parent block info for mining.
1524
    /// If there's no parent because this is the first block, then return the genesis block's info.
1525
    /// If we can't find the parent in the DB but we expect one, return None.
1526
    fn load_block_parent_info(
168,095✔
1527
        &self,
168,095✔
1528
        burn_db: &mut SortitionDB,
168,095✔
1529
        chain_state: &mut StacksChainState,
168,095✔
1530
    ) -> (Option<ParentStacksBlockInfo>, bool) {
168,095✔
1531
        if let Some(stacks_tip) = chain_state
168,095✔
1532
            .get_stacks_chain_tip(burn_db)
168,095✔
1533
            .expect("FATAL: could not query chain tip")
168,095✔
1534
        {
1535
            let best_stacks_tip =
167,675✔
1536
                Self::pick_best_tip(&self.globals, &self.config, burn_db, chain_state, None)
167,675✔
1537
                    .expect("FATAL: no best chain tip");
167,675✔
1538
            let miner_address = self
167,675✔
1539
                .keychain
167,675✔
1540
                .origin_address(self.config.is_mainnet())
167,675✔
1541
                .unwrap();
167,675✔
1542
            let parent_info = match ParentStacksBlockInfo::lookup(
167,675✔
1543
                chain_state,
167,675✔
1544
                burn_db,
167,675✔
1545
                &self.burn_block,
167,675✔
1546
                miner_address,
167,675✔
1547
                &best_stacks_tip.consensus_hash,
167,675✔
1548
                &best_stacks_tip.anchored_block_hash,
167,675✔
1549
            ) {
1550
                Ok(parent_info) => Some(parent_info),
167,633✔
1551
                Err(Error::BurnchainTipChanged) => {
1552
                    self.globals.counters.bump_missed_tenures();
42✔
1553
                    None
42✔
1554
                }
1555
                Err(..) => None,
×
1556
            };
1557
            if parent_info.is_none() {
167,675✔
1558
                warn!(
42✔
1559
                    "No parent for best-tip {}/{}",
1560
                    &best_stacks_tip.consensus_hash, &best_stacks_tip.anchored_block_hash
42✔
1561
                );
1562
            }
167,633✔
1563
            let canonical = best_stacks_tip.consensus_hash == stacks_tip.consensus_hash
167,675✔
1564
                && best_stacks_tip.anchored_block_hash == stacks_tip.anchored_block_hash;
167,618✔
1565
            (parent_info, canonical)
167,675✔
1566
        } else {
1567
            debug!("No Stacks chain tip known, will return a genesis block");
420✔
1568
            let burnchain_params = burnchain_params_from_config(&self.config.burnchain);
420✔
1569

1570
            let chain_tip = ChainTip::genesis(
420✔
1571
                &burnchain_params.first_block_hash,
420✔
1572
                burnchain_params.first_block_height,
420✔
1573
                burnchain_params.first_block_timestamp.into(),
420✔
1574
            );
1575

1576
            (
420✔
1577
                Some(ParentStacksBlockInfo {
420✔
1578
                    stacks_parent_header: chain_tip.metadata,
420✔
1579
                    parent_consensus_hash: FIRST_BURNCHAIN_CONSENSUS_HASH,
420✔
1580
                    parent_block_burn_height: 0,
420✔
1581
                    parent_block_total_burn: 0,
420✔
1582
                    parent_winning_vtxindex: 0,
420✔
1583
                    coinbase_nonce: 0,
420✔
1584
                }),
420✔
1585
                true,
420✔
1586
            )
420✔
1587
        }
1588
    }
168,095✔
1589

1590
    /// Determine which attempt this will be when mining a block, and whether or not an attempt
1591
    /// should even be made.
1592
    /// Returns Some(attempt, max-txs) if we should attempt to mine (and what attempt it will be)
1593
    /// Returns None if we should not mine.
1594
    fn get_mine_attempt(
168,052✔
1595
        &self,
168,052✔
1596
        chain_state: &StacksChainState,
168,052✔
1597
        parent_block_info: &ParentStacksBlockInfo,
168,052✔
1598
        force: bool,
168,052✔
1599
    ) -> Option<(u64, u64)> {
168,052✔
1600
        let parent_consensus_hash = &parent_block_info.parent_consensus_hash;
168,052✔
1601
        let stacks_parent_header = &parent_block_info.stacks_parent_header;
168,052✔
1602
        let parent_block_burn_height = parent_block_info.parent_block_burn_height;
168,052✔
1603

1604
        let last_mined_blocks =
168,052✔
1605
            Self::find_inflight_mined_blocks(self.burn_block.block_height, &self.last_mined_blocks);
168,052✔
1606

1607
        // has the tip changed from our previously-mined block for this epoch?
1608
        let should_unconditionally_mine = last_mined_blocks.is_empty()
168,052✔
1609
            || (last_mined_blocks.len() == 1 && !self.failed_to_submit_last_attempt);
158,289✔
1610
        let (attempt, max_txs) = if should_unconditionally_mine {
168,052✔
1611
            // always mine if we've not mined a block for this epoch yet, or
1612
            // if we've mined just one attempt, unconditionally try again (so we
1613
            // can use `subsequent_miner_time_ms` in this attempt)
1614
            if last_mined_blocks.len() == 1 {
25,292✔
1615
                info!("Have only attempted one block; unconditionally trying again");
15,529✔
1616
            }
9,763✔
1617
            let attempt = last_mined_blocks.len() as u64 + 1;
25,292✔
1618
            let mut max_txs = 0;
25,292✔
1619
            for last_mined_block in last_mined_blocks.iter() {
25,292✔
1620
                max_txs = cmp::max(max_txs, last_mined_block.anchored_block.txs.len());
15,529✔
1621
            }
15,529✔
1622
            (attempt, max_txs)
25,292✔
1623
        } else {
1624
            let mut best_attempt = 0;
142,760✔
1625
            let mut max_txs = 0;
142,760✔
1626
            debug!(
142,760✔
1627
                "Consider {} in-flight Stacks tip(s)",
1628
                &last_mined_blocks.len()
×
1629
            );
1630
            for prev_block in last_mined_blocks.iter() {
142,779✔
1631
                debug!(
142,779✔
1632
                    "Consider in-flight block {} on Stacks tip {}/{} in {} with {} txs",
1633
                    &prev_block.anchored_block.block_hash(),
×
1634
                    &prev_block.parent_consensus_hash,
×
1635
                    &prev_block.anchored_block.header.parent_block,
×
1636
                    &prev_block.burn_hash,
×
1637
                    &prev_block.anchored_block.txs.len()
×
1638
                );
1639
                max_txs = cmp::max(max_txs, prev_block.anchored_block.txs.len());
142,779✔
1640

1641
                if prev_block.parent_consensus_hash == *parent_consensus_hash
142,779✔
1642
                    && prev_block.burn_hash == self.burn_block.burn_header_hash
142,777✔
1643
                    && prev_block.anchored_block.header.parent_block
142,777✔
1644
                        == stacks_parent_header.anchored_header.block_hash()
142,777✔
1645
                {
1646
                    // the anchored chain tip hasn't changed since we attempted to build a block.
1647
                    // But, have discovered any new microblocks worthy of being mined?
1648
                    if let Ok(Some(stream)) =
34✔
1649
                        StacksChainState::load_descendant_staging_microblock_stream(
142,777✔
1650
                            chain_state.db(),
142,777✔
1651
                            &StacksBlockHeader::make_index_block_hash(
142,777✔
1652
                                &prev_block.parent_consensus_hash,
142,777✔
1653
                                &stacks_parent_header.anchored_header.block_hash(),
142,777✔
1654
                            ),
142,777✔
1655
                            0,
1656
                            u16::MAX,
1657
                        )
1658
                    {
1659
                        if (prev_block.anchored_block.header.parent_microblock
34✔
1660
                            == BlockHeaderHash([0u8; 32])
34✔
1661
                            && stream.is_empty())
17✔
1662
                            || (prev_block.anchored_block.header.parent_microblock
34✔
1663
                                != BlockHeaderHash([0u8; 32])
34✔
1664
                                && stream.len()
17✔
1665
                                    <= (prev_block.anchored_block.header.parent_microblock_sequence
17✔
1666
                                        as usize)
17✔
1667
                                        + 1)
17✔
1668
                        {
1669
                            if !force {
17✔
1670
                                // the chain tip hasn't changed since we attempted to build a block.  Use what we
1671
                                // already have.
1672
                                debug!("Relayer: Stacks tip is unchanged since we last tried to mine a block off of {}/{} at height {} with {} txs, in {} at burn height {parent_block_burn_height}, and no new microblocks ({} <= {} + 1)",
17✔
1673
                                       &prev_block.parent_consensus_hash, &prev_block.anchored_block.header.parent_block, prev_block.anchored_block.header.total_work.work,
×
1674
                                       prev_block.anchored_block.txs.len(), prev_block.burn_hash, stream.len(), prev_block.anchored_block.header.parent_microblock_sequence);
×
1675

1676
                                return None;
17✔
1677
                            }
×
1678
                        } else {
1679
                            // there are new microblocks!
1680
                            // TODO: only consider rebuilding our anchored block if we (a) have
1681
                            // time, and (b) the new microblocks are worth more than the new BTC
1682
                            // fee minus the old BTC fee
1683
                            debug!("Relayer: Stacks tip is unchanged since we last tried to mine a block off of {}/{} at height {} with {} txs, in {} at burn height {parent_block_burn_height}, but there are new microblocks ({} > {} + 1)",
17✔
1684
                                   &prev_block.parent_consensus_hash, &prev_block.anchored_block.header.parent_block, prev_block.anchored_block.header.total_work.work,
×
1685
                                   prev_block.anchored_block.txs.len(), prev_block.burn_hash, stream.len(), prev_block.anchored_block.header.parent_microblock_sequence);
×
1686

1687
                            best_attempt = cmp::max(best_attempt, prev_block.attempt);
17✔
1688
                        }
1689
                    } else if !force {
142,743✔
1690
                        // no microblock stream to confirm, and the stacks tip hasn't changed
1691
                        debug!("Relayer: Stacks tip is unchanged since we last tried to mine a block off of {}/{} at height {} with {} txs, in {} at burn height {parent_block_burn_height}, and no microblocks present",
142,743✔
1692
                                &prev_block.parent_consensus_hash, &prev_block.anchored_block.header.parent_block, prev_block.anchored_block.header.total_work.work,
×
1693
                                prev_block.anchored_block.txs.len(), prev_block.burn_hash);
×
1694

1695
                        return None;
142,743✔
1696
                    }
×
1697
                } else if self.burn_block.burn_header_hash == prev_block.burn_hash {
2✔
1698
                    // only try and re-mine if there was no sortition since the last chain tip
1699
                    info!("Relayer: Stacks tip has changed to {parent_consensus_hash}/{} since we last tried to mine a block in {} at burn height {parent_block_burn_height}; attempt was {} (for Stacks tip {}/{})",
2✔
1700
                            stacks_parent_header.anchored_header.block_hash(), prev_block.burn_hash, prev_block.attempt, &prev_block.parent_consensus_hash, &prev_block.anchored_block.header.parent_block);
2✔
1701
                    best_attempt = cmp::max(best_attempt, prev_block.attempt);
2✔
1702
                    // Since the chain tip has changed, we should try to mine a new block, even
1703
                    // if it has less transactions than the previous block we mined, since that
1704
                    // previous block would now be a reorg.
1705
                    max_txs = 0;
2✔
1706
                } else {
1707
                    info!("Relayer: Burn tip has changed to {} ({}) since we last tried to mine a block in {}",
×
1708
                            &self.burn_block.burn_header_hash, self.burn_block.block_height, &prev_block.burn_hash);
×
1709
                }
1710
            }
1711
            (best_attempt + 1, max_txs)
×
1712
        };
1713
        Some((attempt, u64::try_from(max_txs).expect("too many txs")))
25,292✔
1714
    }
168,052✔
1715

1716
    /// Generate the VRF proof for the block we're going to build.
1717
    /// Returns Some(proof) if we could make the proof
1718
    /// Return None if we could not make the proof
1719
    fn make_vrf_proof(&mut self) -> Option<VRFProof> {
25,292✔
1720
        // if we're a mock miner, then make sure that the keychain has a keypair for the mocked VRF
1721
        // key
1722
        let vrf_proof = if self.config.get_node_config(false).mock_mining {
25,292✔
1723
            self.keychain.generate_proof(
88✔
1724
                VRF_MOCK_MINER_KEY,
1725
                self.burn_block.sortition_hash.as_bytes(),
88✔
1726
            )
1727
        } else {
1728
            self.keychain.generate_proof(
25,204✔
1729
                self.registered_key.target_block_height,
25,204✔
1730
                self.burn_block.sortition_hash.as_bytes(),
25,204✔
1731
            )
1732
        };
1733

1734
        let Some(vrf_proof) = vrf_proof else {
25,292✔
1735
            error!(
×
1736
                "Unable to generate VRF proof, will be unable to mine";
1737
                "burn_block_sortition_hash" => %self.burn_block.sortition_hash,
1738
                "burn_block_block_height" => %self.burn_block.block_height,
1739
                "burn_block_hash" => %self.burn_block.burn_header_hash,
1740
                "vrf_pubkey" => &self.registered_key.vrf_public_key.to_hex()
×
1741
            );
1742
            return None;
×
1743
        };
1744

1745
        debug!(
25,292✔
1746
            "Generated VRF Proof: {} over {} ({},{}) with key {}",
1747
            vrf_proof.to_hex(),
×
1748
            &self.burn_block.sortition_hash,
×
1749
            &self.burn_block.block_height,
×
1750
            &self.burn_block.burn_header_hash,
×
1751
            &self.registered_key.vrf_public_key.to_hex()
×
1752
        );
1753
        Some(vrf_proof)
25,292✔
1754
    }
25,292✔
1755

1756
    /// Get the microblock private key we'll be using for this tenure, should we win.
1757
    /// Return the private key.
1758
    ///
1759
    /// In testing, we ignore the parent stacks block hash because we don't have an easy way to
1760
    /// reproduce it in integration tests.
1761
    #[cfg(not(test))]
1762
    fn make_microblock_private_key(
1763
        &mut self,
1764
        parent_stacks_hash: &StacksBlockId,
1765
    ) -> Secp256k1PrivateKey {
1766
        // Generates a new secret key for signing the trail of microblocks
1767
        // of the upcoming tenure.
1768
        self.keychain
1769
            .make_microblock_secret_key(self.burn_block.block_height, &parent_stacks_hash.0)
1770
    }
1771

1772
    /// Get the microblock private key we'll be using for this tenure, should we win.
1773
    /// Return the private key on success
1774
    #[cfg(test)]
1775
    fn make_microblock_private_key(
25,292✔
1776
        &mut self,
25,292✔
1777
        _parent_stacks_hash: &StacksBlockId,
25,292✔
1778
    ) -> Secp256k1PrivateKey {
25,292✔
1779
        // Generates a new secret key for signing the trail of microblocks
1780
        // of the upcoming tenure.
1781
        warn!("test version of make_microblock_secret_key");
25,292✔
1782
        self.keychain.make_microblock_secret_key(
25,292✔
1783
            self.burn_block.block_height,
25,292✔
1784
            &self.burn_block.block_height.to_be_bytes(),
25,292✔
1785
        )
1786
    }
25,292✔
1787

1788
    /// Load the parent microblock stream and vet it for the absence of forks.
1789
    /// If there is a fork, then mine and relay a poison microblock transaction.
1790
    /// Update stacks_parent_header's microblock tail to point to the end of the stream we load.
1791
    /// Return the microblocks we'll confirm, if there are any.
1792
    fn load_and_vet_parent_microblocks(
25,292✔
1793
        &mut self,
25,292✔
1794
        chain_state: &mut StacksChainState,
25,292✔
1795
        sortdb: &SortitionDB,
25,292✔
1796
        mem_pool: &mut MemPoolDB,
25,292✔
1797
        parent_block_info: &mut ParentStacksBlockInfo,
25,292✔
1798
    ) -> Option<Vec<StacksMicroblock>> {
25,292✔
1799
        let parent_consensus_hash = &parent_block_info.parent_consensus_hash;
25,292✔
1800
        let stacks_parent_header = &mut parent_block_info.stacks_parent_header;
25,292✔
1801

1802
        let microblock_info_opt =
25,292✔
1803
            match StacksChainState::load_descendant_staging_microblock_stream_with_poison(
25,292✔
1804
                chain_state.db(),
25,292✔
1805
                &StacksBlockHeader::make_index_block_hash(
25,292✔
1806
                    parent_consensus_hash,
25,292✔
1807
                    &stacks_parent_header.anchored_header.block_hash(),
25,292✔
1808
                ),
25,292✔
1809
                0,
25,292✔
1810
                u16::MAX,
25,292✔
1811
            ) {
25,292✔
1812
                Ok(x) => {
25,292✔
1813
                    let num_mblocks = x.as_ref().map(|(mblocks, ..)| mblocks.len()).unwrap_or(0);
25,292✔
1814
                    debug!(
25,292✔
1815
                        "Loaded {num_mblocks} microblocks descending from {parent_consensus_hash}/{} (data: {})",
1816
                        &stacks_parent_header.anchored_header.block_hash(),
×
1817
                        x.is_some()
×
1818
                    );
1819
                    x
25,292✔
1820
                }
1821
                Err(e) => {
×
1822
                    warn!(
×
1823
                        "Failed to load descendant microblock stream from {parent_consensus_hash}/{}: {e:?}",
1824
                        &stacks_parent_header.anchored_header.block_hash()
×
1825
                    );
1826
                    None
×
1827
                }
1828
            };
1829

1830
        if let Some((ref microblocks, ref poison_opt)) = &microblock_info_opt {
25,292✔
1831
            if let Some(tail) = microblocks.last() {
11✔
1832
                debug!(
11✔
1833
                    "Confirm microblock stream tailed at {} (seq {})",
1834
                    &tail.block_hash(),
×
1835
                    tail.header.sequence
1836
                );
1837
            }
×
1838

1839
            // try and confirm as many microblocks as we can (but note that the stream itself may
1840
            // be too long; we'll try again if that happens).
1841
            stacks_parent_header.microblock_tail = microblocks.last().map(|blk| blk.header.clone());
11✔
1842

1843
            if let Some(poison_payload) = poison_opt {
11✔
1844
                debug!("Detected poisoned microblock fork: {poison_payload:?}");
×
1845

1846
                // submit it multiple times with different nonces, so it'll have a good chance of
1847
                // eventually getting picked up (even if the miner sends other transactions from
1848
                // the same address)
1849
                for i in 0..10 {
×
1850
                    let poison_microblock_tx = self.inner_generate_poison_microblock_tx(
×
1851
                        parent_block_info.coinbase_nonce + 1 + i,
×
1852
                        poison_payload.clone(),
×
1853
                    );
1854

1855
                    // submit the poison payload, privately, so we'll mine it when building the
1856
                    // anchored block.
1857
                    if let Err(e) = mem_pool.miner_submit(
×
1858
                        chain_state,
×
1859
                        sortdb,
×
1860
                        parent_consensus_hash,
×
1861
                        &stacks_parent_header.anchored_header.block_hash(),
×
1862
                        &poison_microblock_tx,
×
1863
                        Some(&self.event_dispatcher),
×
1864
                        1_000_000_000.0, // prioritize this for inclusion
×
1865
                    ) {
×
1866
                        warn!("Detected but failed to mine poison-microblock transaction: {e:?}");
×
1867
                    } else {
1868
                        debug!("Submit poison-microblock transaction {poison_microblock_tx:?}");
×
1869
                    }
1870
                }
1871
            }
11✔
1872
        }
25,281✔
1873

1874
        microblock_info_opt.map(|(stream, _)| stream)
25,292✔
1875
    }
25,292✔
1876

1877
    /// Get the list of possible burn addresses this miner is using
1878
    pub fn get_miner_addrs(config: &Config, keychain: &Keychain) -> Vec<String> {
×
1879
        let mut op_signer = keychain.generate_op_signer();
×
1880
        let mut btc_addrs = vec![
×
1881
            // legacy
1882
            BitcoinAddress::from_bytes_legacy(
×
1883
                config.burnchain.get_bitcoin_network().1,
×
1884
                LegacyBitcoinAddressType::PublicKeyHash,
×
1885
                &Hash160::from_data(&op_signer.get_public_key().to_bytes()).0,
×
1886
            )
1887
            .expect("FATAL: failed to construct legacy bitcoin address"),
×
1888
        ];
1889
        if config.miner.segwit {
×
1890
            btc_addrs.push(
×
1891
                // segwit p2wpkh
×
1892
                BitcoinAddress::from_bytes_segwit_p2wpkh(
×
1893
                    config.burnchain.get_bitcoin_network().1,
×
1894
                    &Hash160::from_data(&op_signer.get_public_key().to_bytes_compressed()).0,
×
1895
                )
×
1896
                .expect("FATAL: failed to construct segwit p2wpkh address"),
×
1897
            );
×
1898
        }
×
1899
        btc_addrs
×
1900
            .into_iter()
×
1901
            .map(|addr| format!("{addr}"))
×
1902
            .collect()
×
1903
    }
×
1904

1905
    /// Obtain the target burn fee cap, when considering how well this miner is performing.
1906
    #[allow(clippy::too_many_arguments)]
1907
    pub fn get_mining_spend_amount<F, G>(
21,723✔
1908
        config: &Config,
21,723✔
1909
        keychain: &Keychain,
21,723✔
1910
        burnchain: &Burnchain,
21,723✔
1911
        sortdb: &SortitionDB,
21,723✔
1912
        recipients: &[PoxAddress],
21,723✔
1913
        start_mine_height: u64,
21,723✔
1914
        at_burn_block: Option<u64>,
21,723✔
1915
        mut get_prior_winning_prob: F,
21,723✔
1916
        mut set_prior_winning_prob: G,
21,723✔
1917
    ) -> u64
21,723✔
1918
    where
21,723✔
1919
        F: FnMut(u64) -> f64,
21,723✔
1920
        G: FnMut(u64, f64),
21,723✔
1921
    {
1922
        let config_file_burn_fee_cap = config.get_burnchain_config().burn_fee_cap;
21,723✔
1923
        let miner_config = config.get_miner_config();
21,723✔
1924

1925
        if miner_config.target_win_probability < 0.00001 {
21,723✔
1926
            // this field is effectively zero
1927
            return config_file_burn_fee_cap;
21,723✔
1928
        }
×
1929
        let Some(miner_stats) = config.get_miner_stats() else {
×
1930
            return config_file_burn_fee_cap;
×
1931
        };
1932

1933
        let Ok(tip) = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).map_err(|e| {
×
1934
            warn!("Failed to load canonical burn chain tip: {e:?}");
×
1935
            e
×
1936
        }) else {
×
1937
            return config_file_burn_fee_cap;
×
1938
        };
1939
        let tip = if let Some(at_burn_block) = at_burn_block.as_ref() {
×
1940
            let ih = sortdb.index_handle(&tip.sortition_id);
×
1941
            let Ok(Some(ancestor_tip)) = ih.get_block_snapshot_by_height(*at_burn_block) else {
×
1942
                warn!("Failed to load ancestor tip at burn height {at_burn_block}");
×
1943
                return config_file_burn_fee_cap;
×
1944
            };
1945
            ancestor_tip
×
1946
        } else {
1947
            tip
×
1948
        };
1949

1950
        let Ok(active_miners_and_commits) = MinerStats::get_active_miners(sortdb, at_burn_block)
×
1951
            .map_err(|e| {
×
1952
                warn!("Failed to get active miners: {e:?}");
×
1953
                e
×
1954
            })
×
1955
        else {
1956
            return config_file_burn_fee_cap;
×
1957
        };
1958
        if active_miners_and_commits.is_empty() {
×
1959
            warn!("No active miners detected; using config file burn_fee_cap");
×
1960
            return config_file_burn_fee_cap;
×
1961
        }
×
1962

1963
        let active_miners: Vec<_> = active_miners_and_commits
×
1964
            .iter()
×
1965
            .map(|(miner, _cmt)| miner.as_str())
×
1966
            .collect();
×
1967

1968
        info!("Active miners: {active_miners:?}");
×
1969

1970
        let Ok(unconfirmed_block_commits) = miner_stats
×
1971
            .get_unconfirmed_commits(tip.block_height + 1, &active_miners)
×
1972
            .map_err(|e| {
×
1973
                warn!("Failed to find unconfirmed block-commits: {e}");
×
1974
                e
×
1975
            })
×
1976
        else {
1977
            return config_file_burn_fee_cap;
×
1978
        };
1979

1980
        let unconfirmed_miners_and_amounts: Vec<(String, u64)> = unconfirmed_block_commits
×
1981
            .iter()
×
1982
            .map(|cmt| (cmt.apparent_sender.to_string(), cmt.burn_fee))
×
1983
            .collect();
×
1984

1985
        info!("Found unconfirmed block-commits: {unconfirmed_miners_and_amounts:?}");
×
1986

1987
        let (spend_dist, _total_spend) = MinerStats::get_spend_distribution(
×
1988
            &active_miners_and_commits,
×
1989
            &unconfirmed_block_commits,
×
1990
            recipients,
×
1991
        );
×
1992
        let win_probs = if miner_config.fast_rampup {
×
1993
            // look at spends 6+ blocks in the future
1994
            MinerStats::get_future_win_distribution(
×
1995
                &active_miners_and_commits,
×
1996
                &unconfirmed_block_commits,
×
1997
                recipients,
×
1998
            )
1999
        } else {
2000
            // look at the current spends
2001
            let Ok(unconfirmed_burn_dist) = miner_stats
×
2002
                .get_unconfirmed_burn_distribution(
×
2003
                    burnchain,
×
2004
                    sortdb,
×
2005
                    &active_miners_and_commits,
×
2006
                    unconfirmed_block_commits,
×
2007
                    recipients,
×
2008
                    at_burn_block,
×
2009
                )
2010
                .map_err(|e| {
×
2011
                    warn!("Failed to get unconfirmed burn distribution: {e:?}");
×
2012
                    e
×
2013
                })
×
2014
            else {
2015
                return config_file_burn_fee_cap;
×
2016
            };
2017

2018
            MinerStats::burn_dist_to_prob_dist(&unconfirmed_burn_dist)
×
2019
        };
2020

2021
        info!("Unconfirmed spend distribution: {spend_dist:?}");
×
2022
        info!(
×
2023
            "Unconfirmed win probabilities (fast_rampup={}): {win_probs:?}",
2024
            miner_config.fast_rampup
2025
        );
2026

2027
        let miner_addrs = Self::get_miner_addrs(config, keychain);
×
2028
        let win_prob = miner_addrs
×
2029
            .iter()
×
2030
            .find_map(|x| win_probs.get(x))
×
2031
            .copied()
×
2032
            .unwrap_or(0.0);
×
2033

2034
        info!(
×
2035
            "This miner's win probability at {} is {win_prob}",
2036
            tip.block_height
2037
        );
2038
        set_prior_winning_prob(tip.block_height, win_prob);
×
2039

2040
        if win_prob < config.miner.target_win_probability {
×
2041
            // no mining strategy is viable, so just quit.
2042
            // Unless we're spinning up, that is.
2043
            if start_mine_height + 6 < tip.block_height
×
2044
                && config.miner.underperform_stop_threshold.is_some()
×
2045
            {
2046
                let underperform_stop_threshold =
×
2047
                    config.miner.underperform_stop_threshold.unwrap_or(0);
×
2048
                info!(
×
2049
                    "Miner is spun up, but is not meeting target win probability as of {}",
2050
                    tip.block_height
2051
                );
2052
                // we've spun up and we're underperforming. How long do we tolerate this?
2053
                let mut underperformed_count = 0;
×
2054
                for depth in 0..underperform_stop_threshold {
×
2055
                    let prior_burn_height = tip.block_height.saturating_sub(depth);
×
2056
                    let prior_win_prob = get_prior_winning_prob(prior_burn_height);
×
2057
                    if prior_win_prob < config.miner.target_win_probability {
×
2058
                        info!(
×
2059
                            "Miner underperformed in block {prior_burn_height} ({underperformed_count}/{underperform_stop_threshold})"
2060
                        );
2061
                        underperformed_count += 1;
×
2062
                    }
×
2063
                }
2064
                if underperformed_count == underperform_stop_threshold {
×
2065
                    warn!(
×
2066
                        "Miner underperformed since burn height {}; spinning down",
2067
                        start_mine_height + 6 + underperform_stop_threshold
×
2068
                    );
2069
                    return 0;
×
2070
                }
×
2071
            }
×
2072
        }
×
2073

2074
        config_file_burn_fee_cap
×
2075
    }
21,723✔
2076

2077
    /// Produce the block-commit for this anchored block, if we can.
2078
    /// Returns the op on success
2079
    /// Returns None if we fail somehow.
2080
    #[allow(clippy::too_many_arguments)]
2081
    pub fn make_block_commit(
21,723✔
2082
        &self,
21,723✔
2083
        burn_db: &mut SortitionDB,
21,723✔
2084
        chain_state: &mut StacksChainState,
21,723✔
2085
        block_hash: BlockHeaderHash,
21,723✔
2086
        parent_block_burn_height: u64,
21,723✔
2087
        parent_winning_vtxindex: u16,
21,723✔
2088
        vrf_proof: &VRFProof,
21,723✔
2089
        target_epoch_id: StacksEpochId,
21,723✔
2090
    ) -> Option<BlockstackOperationType> {
21,723✔
2091
        // let's figure out the recipient set!
2092
        let recipients = match get_next_recipients(
21,723✔
2093
            &self.burn_block,
21,723✔
2094
            chain_state,
21,723✔
2095
            burn_db,
21,723✔
2096
            &self.burnchain,
21,723✔
2097
            &OnChainRewardSetProvider::new(),
21,723✔
2098
        ) {
21,723✔
2099
            Ok(x) => x,
21,723✔
2100
            Err(e) => {
×
2101
                error!("Relayer: Failure fetching recipient set: {e:?}");
×
2102
                return None;
×
2103
            }
2104
        };
2105

2106
        let commit_outs = if !self
21,723✔
2107
            .burnchain
21,723✔
2108
            .pox_constants
21,723✔
2109
            .is_after_pox_sunset_end(self.burn_block.block_height, target_epoch_id)
21,723✔
2110
            && !self
21,707✔
2111
                .burnchain
21,707✔
2112
                .is_in_prepare_phase(self.burn_block.block_height + 1)
21,707✔
2113
        {
2114
            RewardSetInfo::into_commit_outs(recipients, self.config.is_mainnet())
12,562✔
2115
        } else {
2116
            vec![PoxAddress::standard_burn_address(self.config.is_mainnet())]
9,161✔
2117
        };
2118

2119
        let burn_fee_cap = Self::get_mining_spend_amount(
21,723✔
2120
            &self.config,
21,723✔
2121
            &self.keychain,
21,723✔
2122
            &self.burnchain,
21,723✔
2123
            burn_db,
21,723✔
2124
            &commit_outs,
21,723✔
2125
            self.globals.get_start_mining_height(),
21,723✔
2126
            None,
21,723✔
2127
            |block_height| {
×
2128
                self.globals
×
2129
                    .get_estimated_win_prob(block_height)
×
2130
                    .unwrap_or(0.0)
×
2131
            },
×
2132
            |block_height, win_prob| self.globals.add_estimated_win_prob(block_height, win_prob),
×
2133
        );
2134
        if burn_fee_cap == 0 {
21,723✔
2135
            warn!("Calculated burn_fee_cap is 0; will not mine");
×
2136
            return None;
×
2137
        }
21,723✔
2138
        let sunset_burn = self.burnchain.expected_sunset_burn(
21,723✔
2139
            self.burn_block.block_height + 1,
21,723✔
2140
            burn_fee_cap,
21,723✔
2141
            target_epoch_id,
21,723✔
2142
        );
2143
        let rest_commit = burn_fee_cap - sunset_burn;
21,723✔
2144

2145
        // let's commit, but target the current burnchain tip with our modulus
2146
        let op = self.inner_generate_block_commit_op(
21,723✔
2147
            block_hash,
21,723✔
2148
            rest_commit,
21,723✔
2149
            &self.registered_key,
21,723✔
2150
            parent_block_burn_height
21,723✔
2151
                .try_into()
21,723✔
2152
                .expect("Could not convert parent block height into u32"),
21,723✔
2153
            parent_winning_vtxindex,
21,723✔
2154
            VRFSeed::from_proof(vrf_proof),
21,723✔
2155
            commit_outs,
21,723✔
2156
            sunset_burn,
21,723✔
2157
            self.burn_block.block_height,
21,723✔
2158
        );
2159
        Some(op)
21,723✔
2160
    }
21,723✔
2161

2162
    /// Are there enough unprocessed blocks that we shouldn't mine?
2163
    fn unprocessed_blocks_prevent_mining(
288,229✔
2164
        burnchain: &Burnchain,
288,229✔
2165
        sortdb: &SortitionDB,
288,229✔
2166
        chainstate: &StacksChainState,
288,229✔
2167
        unprocessed_block_deadline: u64,
288,229✔
2168
    ) -> bool {
288,229✔
2169
        let sort_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
288,229✔
2170
            .expect("FATAL: could not query canonical sortition DB tip");
288,229✔
2171

2172
        if let Some(stacks_tip) =
288,229✔
2173
            NakamotoChainState::get_canonical_block_header(chainstate.db(), sortdb)
288,229✔
2174
                .expect("FATAL: could not query canonical Stacks chain tip")
288,229✔
2175
        {
2176
            // if a block hasn't been processed within some deadline seconds of receipt, don't block
2177
            //  mining
2178
            let process_deadline = get_epoch_time_secs() - unprocessed_block_deadline;
288,229✔
2179
            let has_unprocessed = StacksChainState::has_higher_unprocessed_blocks(
288,229✔
2180
                chainstate.db(),
288,229✔
2181
                stacks_tip.anchored_header.height(),
288,229✔
2182
                process_deadline,
288,229✔
2183
            )
2184
            .expect("FATAL: failed to query staging blocks");
288,229✔
2185
            if has_unprocessed {
288,229✔
2186
                let highest_unprocessed_opt = StacksChainState::get_highest_unprocessed_block(
1✔
2187
                    chainstate.db(),
1✔
2188
                    process_deadline,
1✔
2189
                )
2190
                .expect("FATAL: failed to query staging blocks");
1✔
2191

2192
                if let Some(highest_unprocessed) = highest_unprocessed_opt {
1✔
2193
                    let highest_unprocessed_block_sn_opt =
1✔
2194
                        SortitionDB::get_block_snapshot_consensus(
1✔
2195
                            sortdb.conn(),
1✔
2196
                            &highest_unprocessed.consensus_hash,
1✔
2197
                        )
2198
                        .expect("FATAL: could not query sortition DB");
1✔
2199

2200
                    // NOTE: this could be None if it's not part of the canonical PoX fork any
2201
                    // longer
2202
                    if let Some(highest_unprocessed_block_sn) = highest_unprocessed_block_sn_opt {
1✔
2203
                        if stacks_tip.anchored_header.height()
1✔
2204
                            + u64::from(burnchain.pox_constants.prepare_length)
1✔
2205
                            > highest_unprocessed.height
1✔
2206
                            && highest_unprocessed_block_sn.block_height
×
2207
                                + u64::from(burnchain.pox_constants.prepare_length)
×
2208
                                > sort_tip.block_height
×
2209
                        {
2210
                            // we're close enough to the chain tip that it's a bad idea for us to mine
2211
                            // -- we'll likely create an orphan
2212
                            return true;
×
2213
                        }
1✔
2214
                    }
×
2215
                }
×
2216
            }
288,228✔
2217
        }
×
2218
        // we can mine
2219
        false
288,229✔
2220
    }
288,229✔
2221

2222
    /// Only used in mock signing to generate a peer info view
2223
    fn generate_peer_info(&self) -> PeerInfo {
102✔
2224
        // Create a peer info view of the current state
2225
        let server_version = version_string("stacks-node", option_env!("STACKS_NODE_VERSION"));
102✔
2226
        let stacks_tip_height = self.burn_block.canonical_stacks_tip_height;
102✔
2227
        let stacks_tip = self.burn_block.canonical_stacks_tip_hash.clone();
102✔
2228
        let stacks_tip_consensus_hash = self.burn_block.canonical_stacks_tip_consensus_hash.clone();
102✔
2229
        let pox_consensus = self.burn_block.consensus_hash.clone();
102✔
2230
        let burn_block_height = self.burn_block.block_height;
102✔
2231

2232
        PeerInfo {
102✔
2233
            burn_block_height,
102✔
2234
            stacks_tip_consensus_hash,
102✔
2235
            stacks_tip,
102✔
2236
            stacks_tip_height,
102✔
2237
            pox_consensus,
102✔
2238
            server_version,
102✔
2239
            network_id: self.config.get_burnchain_config().chain_id,
102✔
2240
        }
102✔
2241
    }
102✔
2242

2243
    /// Only used in mock signing to retrieve the mock signatures for the given mock proposal
2244
    fn wait_for_mock_signatures(
49✔
2245
        &self,
49✔
2246
        mock_proposal: &MockProposal,
49✔
2247
        stackerdbs: &StackerDBs,
49✔
2248
        timeout: Duration,
49✔
2249
    ) -> Result<Vec<MockSignature>, ChainstateError> {
49✔
2250
        let reward_cycle = self
49✔
2251
            .burnchain
49✔
2252
            .block_height_to_reward_cycle(self.burn_block.block_height)
49✔
2253
            .expect("BUG: block commit exists before first block height");
49✔
2254
        let signers_contract_id = MessageSlotID::BlockResponse
49✔
2255
            .stacker_db_contract(self.config.is_mainnet(), reward_cycle);
49✔
2256
        let slot_ids: Vec<_> = stackerdbs
49✔
2257
            .get_signers(&signers_contract_id)
49✔
2258
            .expect("FATAL: could not get signers from stacker DB")
49✔
2259
            .into_iter()
49✔
2260
            .enumerate()
49✔
2261
            .map(|(slot_id, _)| {
100✔
2262
                u32::try_from(slot_id).expect("FATAL: too many signers to fit into u32 range")
100✔
2263
            })
100✔
2264
            .collect();
49✔
2265
        let mock_poll_start = Instant::now();
49✔
2266
        let mut mock_signatures = vec![];
49✔
2267
        // Because we don't care really if all signers reach quorum and this is just for testing purposes,
2268
        // we don't need to wait for ALL signers to sign the mock proposal and should not slow down mining too much
2269
        // Just wait a min amount of time for the mock signatures to come in
2270
        while mock_signatures.len() < slot_ids.len() && mock_poll_start.elapsed() < timeout {
123,833✔
2271
            let chunks = stackerdbs.get_latest_chunks(&signers_contract_id, &slot_ids)?;
123,784✔
2272
            for chunk in chunks.into_iter().flatten() {
618,920✔
2273
                if let Ok(SignerMessage::MockSignature(mock_signature)) =
616,988✔
2274
                    SignerMessage::consensus_deserialize(&mut chunk.as_slice())
618,920✔
2275
                {
2276
                    if mock_signature.mock_proposal == *mock_proposal
616,988✔
2277
                        && !mock_signatures.contains(&mock_signature)
99,473✔
2278
                    {
100✔
2279
                        mock_signatures.push(mock_signature);
100✔
2280
                    }
616,888✔
2281
                }
1,932✔
2282
            }
2283
        }
2284
        Ok(mock_signatures)
49✔
2285
    }
49✔
2286

2287
    /// Only used in mock signing to determine if the peer info view was already signed across
2288
    fn mock_block_exists(&self, peer_info: &PeerInfo) -> bool {
102✔
2289
        let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet());
102✔
2290
        let mut miners_stackerdb = StackerDBSession::new(
102✔
2291
            &self.config.node.rpc_bind,
102✔
2292
            miner_contract_id,
102✔
2293
            self.config.miner.stackerdb_timeout,
102✔
2294
        );
2295
        let miner_slot_ids: Vec<_> = (0..MINER_SLOT_COUNT * 2).collect();
102✔
2296
        if let Ok(messages) = miners_stackerdb.get_latest_chunks(&miner_slot_ids) {
102✔
2297
            for message in messages.into_iter().flatten() {
366✔
2298
                if message.is_empty() {
366✔
2299
                    continue;
6✔
2300
                }
360✔
2301
                let Ok(SignerMessage::MockBlock(mock_block)) =
180✔
2302
                    SignerMessage::consensus_deserialize(&mut message.as_slice())
360✔
2303
                else {
2304
                    continue;
180✔
2305
                };
2306
                if mock_block.mock_proposal.peer_info == *peer_info {
180✔
2307
                    return true;
53✔
2308
                }
127✔
2309
            }
2310
        }
×
2311
        false
49✔
2312
    }
102✔
2313

2314
    /// Read any mock signatures from stackerdb and respond to them
2315
    pub fn send_mock_miner_messages(&mut self) -> Result<(), String> {
168,097✔
2316
        let burn_db_path = self.config.get_burn_db_file_path();
168,097✔
2317
        let burn_db = SortitionDB::open(
168,097✔
2318
            &burn_db_path,
168,097✔
2319
            false,
2320
            self.burnchain.pox_constants.clone(),
168,097✔
2321
            Some(self.config.node.get_marf_opts()),
168,097✔
2322
        )
2323
        .expect("FATAL: could not open sortition DB");
168,097✔
2324
        let epoch_id = SortitionDB::get_stacks_epoch(burn_db.conn(), self.burn_block.block_height)
168,097✔
2325
            .map_err(|e| e.to_string())?
168,097✔
2326
            .expect("FATAL: no epoch defined")
168,097✔
2327
            .epoch_id;
2328
        if epoch_id != StacksEpochId::Epoch25 {
168,097✔
2329
            debug!("Mock miner messaging is disabled for non-epoch 2.5 blocks.";
12,889✔
2330
                "epoch_id" => epoch_id.to_string()
×
2331
            );
2332
            return Ok(());
12,889✔
2333
        }
155,208✔
2334

2335
        let miner_config = self.config.get_miner_config();
155,208✔
2336
        if !miner_config.pre_nakamoto_mock_signing {
155,208✔
2337
            debug!("Pre-Nakamoto mock signing is disabled");
155,106✔
2338
            return Ok(());
155,106✔
2339
        }
102✔
2340

2341
        let mining_key = miner_config
102✔
2342
            .mining_key
102✔
2343
            .expect("Cannot mock sign without mining key");
102✔
2344

2345
        // Create a peer info view of the current state
2346
        let peer_info = self.generate_peer_info();
102✔
2347
        if self.mock_block_exists(&peer_info) {
102✔
2348
            debug!(
52✔
2349
                "Already sent mock miner block proposal for current peer info view. Not sending another mock proposal."
2350
            );
2351
            return Ok(());
52✔
2352
        }
50✔
2353

2354
        // find out which slot we're in. If we are not the latest sortition winner, we should not be sending anymore messages anyway
2355
        let ih = burn_db.index_handle(&self.burn_block.sortition_id);
50✔
2356
        let last_winner_snapshot = ih
50✔
2357
            .get_last_snapshot_with_sortition(self.burn_block.block_height)
50✔
2358
            .map_err(|e| e.to_string())?;
50✔
2359

2360
        if last_winner_snapshot.miner_pk_hash
50✔
2361
            != Some(Hash160::from_node_public_key(
50✔
2362
                &StacksPublicKey::from_private(&mining_key),
50✔
2363
            ))
50✔
2364
        {
2365
            return Ok(());
×
2366
        }
50✔
2367
        let election_sortition = last_winner_snapshot.consensus_hash;
50✔
2368
        let mock_proposal = MockProposal::new(peer_info, &mining_key);
50✔
2369

2370
        info!("Sending mock proposal to stackerdb: {mock_proposal:?}");
50✔
2371

2372
        let stackerdbs = StackerDBs::connect(&self.config.get_stacker_db_file_path(), false)
50✔
2373
            .map_err(|e| e.to_string())?;
50✔
2374
        let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet());
50✔
2375
        let mut miners_stackerdb = StackerDBSession::new(
50✔
2376
            &self.config.node.rpc_bind,
50✔
2377
            miner_contract_id,
50✔
2378
            self.config.miner.stackerdb_timeout,
50✔
2379
        );
2380
        let miner_db = MinerDB::open_with_config(&self.config).map_err(|e| e.to_string())?;
50✔
2381

2382
        SignerCoordinator::send_miners_message(
50✔
2383
            &mining_key,
50✔
2384
            &burn_db,
50✔
2385
            &self.burn_block,
50✔
2386
            &stackerdbs,
50✔
2387
            SignerMessage::MockProposal(mock_proposal.clone()),
50✔
2388
            MinerSlotID::BlockProposal, // There is no specific slot for mock miner messages so we use BlockProposal for MockProposal as well.
50✔
2389
            self.config.is_mainnet(),
50✔
2390
            &mut miners_stackerdb,
50✔
2391
            &election_sortition,
50✔
2392
            &miner_db,
50✔
2393
        )
2394
        .map_err(|e| {
50✔
2395
            warn!("Failed to write mock proposal to stackerdb.");
×
2396
            e.to_string()
×
2397
        })?;
×
2398

2399
        // Retrieve any MockSignatures from stackerdb
2400
        info!("Waiting for mock signatures...");
50✔
2401
        let mock_signatures = self
50✔
2402
            .wait_for_mock_signatures(&mock_proposal, &stackerdbs, Duration::from_secs(10))
50✔
2403
            .map_err(|e| e.to_string())?;
50✔
2404

2405
        let mock_block = MockBlock {
50✔
2406
            mock_proposal,
50✔
2407
            mock_signatures,
50✔
2408
        };
50✔
2409

2410
        info!("Sending mock block to stackerdb: {mock_block:?}");
50✔
2411
        SignerCoordinator::send_miners_message(
50✔
2412
            &mining_key,
50✔
2413
            &burn_db,
50✔
2414
            &self.burn_block,
50✔
2415
            &stackerdbs,
50✔
2416
            SignerMessage::MockBlock(mock_block),
50✔
2417
            MinerSlotID::BlockPushed, // There is no specific slot for mock miner messages. Let's use BlockPushed for MockBlock since MockProposal uses BlockProposal.
50✔
2418
            self.config.is_mainnet(),
50✔
2419
            &mut miners_stackerdb,
50✔
2420
            &election_sortition,
50✔
2421
            &miner_db,
50✔
2422
        )
2423
        .map_err(|e| {
50✔
2424
            warn!("Failed to write mock block to stackerdb.");
×
2425
            e.to_string()
×
2426
        })?;
×
2427
        Ok(())
50✔
2428
    }
168,097✔
2429

2430
    // TODO: add tests from mutation testing results #4871
2431
    #[cfg_attr(test, mutants::skip)]
2432
    /// Try to mine a Stacks block by assembling one from mempool transactions and sending a
2433
    /// burnchain block-commit transaction.  If we succeed, then return the assembled block data as
2434
    /// well as the microblock private key to use to produce microblocks.
2435
    /// Return None if we couldn't build a block for whatever reason.
2436
    pub fn run_tenure(&mut self) -> Option<MinerThreadResult> {
168,096✔
2437
        debug!("block miner thread ID is {:?}", thread::current().id());
168,096✔
2438
        fault_injection_long_tenure();
168,096✔
2439

2440
        let burn_db_path = self.config.get_burn_db_file_path();
168,096✔
2441
        let stacks_chainstate_path = self.config.get_chainstate_path_str();
168,096✔
2442

2443
        let cost_estimator = self
168,096✔
2444
            .config
168,096✔
2445
            .make_cost_estimator()
168,096✔
2446
            .unwrap_or_else(|| Box::new(UnitEstimator));
168,096✔
2447
        let metric = self
168,096✔
2448
            .config
168,096✔
2449
            .make_cost_metric()
168,096✔
2450
            .unwrap_or_else(|| Box::new(UnitMetric));
168,096✔
2451

2452
        let mut bitcoin_controller = BitcoinRegtestController::new_ongoing_dummy(
168,096✔
2453
            self.config.clone(),
168,096✔
2454
            self.ongoing_commit.clone(),
168,096✔
2455
        );
2456

2457
        let miner_config = self.config.get_miner_config();
168,096✔
2458
        let last_miner_config_opt = self.globals.get_last_miner_config();
168,096✔
2459
        let force_remine = if let Some(last_miner_config) = last_miner_config_opt {
168,096✔
2460
            last_miner_config != miner_config
167,835✔
2461
        } else {
2462
            false
261✔
2463
        };
2464
        if force_remine {
168,096✔
2465
            info!("Miner config changed; forcing a re-mine attempt");
×
2466
        }
168,096✔
2467

2468
        self.globals.set_last_miner_config(miner_config);
168,096✔
2469

2470
        // NOTE: read-write access is needed in order to be able to query the recipient set.
2471
        // This is an artifact of the way the MARF is built (see #1449)
2472
        let mut burn_db = SortitionDB::open(
168,096✔
2473
            &burn_db_path,
168,096✔
2474
            true,
2475
            self.burnchain.pox_constants.clone(),
168,096✔
2476
            Some(self.config.node.get_marf_opts()),
168,096✔
2477
        )
2478
        .expect("FATAL: could not open sortition DB");
168,096✔
2479

2480
        let mut chain_state =
168,096✔
2481
            open_chainstate_with_faults(&self.config).expect("FATAL: could not open chainstate DB");
168,096✔
2482

2483
        let mut mem_pool = MemPoolDB::open(
168,096✔
2484
            self.config.is_mainnet(),
168,096✔
2485
            self.config.burnchain.chain_id,
168,096✔
2486
            &stacks_chainstate_path,
168,096✔
2487
            cost_estimator,
168,096✔
2488
            metric,
168,096✔
2489
        )
2490
        .expect("Database failure opening mempool");
168,096✔
2491

2492
        let tenure_begin = get_epoch_time_ms();
168,096✔
2493

2494
        let target_epoch_id =
168,096✔
2495
            SortitionDB::get_stacks_epoch(burn_db.conn(), self.burn_block.block_height + 1)
168,096✔
2496
                .ok()?
168,096✔
2497
                .expect("FATAL: no epoch defined")
168,096✔
2498
                .epoch_id;
2499

2500
        let (Some(mut parent_block_info), _) =
168,054✔
2501
            self.load_block_parent_info(&mut burn_db, &mut chain_state)
168,096✔
2502
        else {
2503
            return None;
42✔
2504
        };
2505
        let (attempt, max_txs) =
25,294✔
2506
            self.get_mine_attempt(&chain_state, &parent_block_info, force_remine)?;
168,054✔
2507
        let vrf_proof = self.make_vrf_proof()?;
25,294✔
2508

2509
        // Generates a new secret key for signing the trail of microblocks
2510
        // of the upcoming tenure.
2511
        let microblock_private_key = self.make_microblock_private_key(
25,294✔
2512
            &parent_block_info.stacks_parent_header.index_block_hash(),
25,294✔
2513
        );
2514
        let mblock_pubkey_hash = {
25,294✔
2515
            let mut pubkh = Hash160::from_node_public_key(&StacksPublicKey::from_private(
25,294✔
2516
                &microblock_private_key,
25,294✔
2517
            ));
25,294✔
2518
            if cfg!(test) {
25,294✔
2519
                if let Ok(mblock_pubkey_hash_str) = std::env::var("STACKS_MICROBLOCK_PUBKEY_HASH") {
25,294✔
2520
                    if let Ok(bad_pubkh) = Hash160::from_hex(&mblock_pubkey_hash_str) {
2✔
2521
                        debug!("Fault injection: set microblock public key hash to {bad_pubkh}");
2✔
2522
                        pubkh = bad_pubkh
2✔
2523
                    }
×
2524
                }
25,292✔
2525
            }
×
2526
            pubkh
25,294✔
2527
        };
2528

2529
        // create our coinbase
2530
        let coinbase_tx =
25,294✔
2531
            self.inner_generate_coinbase_tx(parent_block_info.coinbase_nonce, target_epoch_id);
25,294✔
2532

2533
        // find the longest microblock tail we can build off of and vet microblocks for forks
2534
        self.load_and_vet_parent_microblocks(
25,294✔
2535
            &mut chain_state,
25,294✔
2536
            &burn_db,
25,294✔
2537
            &mut mem_pool,
25,294✔
2538
            &mut parent_block_info,
25,294✔
2539
        );
2540

2541
        let burn_tip = SortitionDB::get_canonical_burn_chain_tip(burn_db.conn())
25,294✔
2542
            .expect("FATAL: failed to read current burnchain tip");
25,294✔
2543
        let microblocks_disabled =
25,294✔
2544
            SortitionDB::are_microblocks_disabled(burn_db.conn(), burn_tip.block_height)
25,294✔
2545
                .expect("FATAL: failed to query epoch's microblock status");
25,294✔
2546

2547
        // build the block itself
2548
        let mut builder_settings = self.config.make_block_builder_settings(
25,294✔
2549
            attempt,
25,294✔
2550
            false,
2551
            self.globals.get_miner_status(),
25,294✔
2552
        );
2553
        if microblocks_disabled {
25,294✔
2554
            builder_settings.confirm_microblocks = false;
13,989✔
2555
            if cfg!(test)
13,989✔
2556
                && std::env::var("STACKS_TEST_CONFIRM_MICROBLOCKS_POST_25").as_deref() == Ok("1")
13,989✔
2557
            {
×
2558
                builder_settings.confirm_microblocks = true;
×
2559
            }
13,989✔
2560
        }
11,305✔
2561
        let (anchored_block, _, _) = match StacksBlockBuilder::build_anchored_block(
25,294✔
2562
            &chain_state,
25,294✔
2563
            &burn_db.index_handle(&burn_tip.sortition_id),
25,294✔
2564
            &mut mem_pool,
25,294✔
2565
            &parent_block_info.stacks_parent_header,
25,294✔
2566
            parent_block_info.parent_block_total_burn,
25,294✔
2567
            &vrf_proof,
25,294✔
2568
            &mblock_pubkey_hash,
25,294✔
2569
            &coinbase_tx,
25,294✔
2570
            builder_settings,
25,294✔
2571
            Some(&self.event_dispatcher),
25,294✔
2572
            &self.burnchain,
25,294✔
2573
        ) {
2574
            Ok(block) => block,
24,936✔
2575
            Err(ChainstateError::InvalidStacksMicroblock(msg, mblock_header_hash)) => {
×
2576
                // part of the parent microblock stream is invalid, so try again
2577
                info!(
×
2578
                    "Parent microblock stream is invalid; trying again without microblocks";
2579
                    "microblock_offender" => %mblock_header_hash,
2580
                    "error" => &msg
×
2581
                );
2582

2583
                let mut builder_settings = self.config.make_block_builder_settings(
×
2584
                    attempt,
×
2585
                    false,
2586
                    self.globals.get_miner_status(),
×
2587
                );
2588
                builder_settings.confirm_microblocks = false;
×
2589

2590
                // try again
2591
                match StacksBlockBuilder::build_anchored_block(
×
2592
                    &chain_state,
×
2593
                    &burn_db.index_handle(&burn_tip.sortition_id),
×
2594
                    &mut mem_pool,
×
2595
                    &parent_block_info.stacks_parent_header,
×
2596
                    parent_block_info.parent_block_total_burn,
×
2597
                    &vrf_proof,
×
2598
                    &mblock_pubkey_hash,
×
2599
                    &coinbase_tx,
×
2600
                    builder_settings,
×
2601
                    Some(&self.event_dispatcher),
×
2602
                    &self.burnchain,
×
2603
                ) {
×
2604
                    Ok(block) => block,
×
2605
                    Err(e) => {
×
2606
                        error!("Relayer: Failure mining anchor block even after removing offending microblock {mblock_header_hash}: {e}");
×
2607
                        return None;
×
2608
                    }
2609
                }
2610
            }
2611
            Err(e) => {
358✔
2612
                error!("Relayer: Failure mining anchored block: {e}");
358✔
2613
                return None;
358✔
2614
            }
2615
        };
2616

2617
        let miner_config = self.config.get_miner_config();
24,936✔
2618

2619
        if attempt > 1
24,936✔
2620
            && miner_config.min_tx_count > 0
15,183✔
2621
            && u64::try_from(anchored_block.txs.len()).expect("too many txs")
×
2622
                < miner_config.min_tx_count
×
2623
        {
2624
            info!("Relayer: Succeeded assembling subsequent block with {} txs, but expected at least {}", anchored_block.txs.len(), miner_config.min_tx_count);
×
2625
            return None;
×
2626
        }
24,936✔
2627

2628
        if miner_config.only_increase_tx_count
24,936✔
2629
            && max_txs > u64::try_from(anchored_block.txs.len()).expect("too many txs")
×
2630
        {
2631
            info!("Relayer: Succeeded assembling subsequent block with {} txs, but had previously produced a block with {max_txs} txs", anchored_block.txs.len());
×
2632
            return None;
×
2633
        }
24,936✔
2634

2635
        info!(
24,936✔
2636
            "Relayer: Succeeded assembling {} block #{}: {}, with {} txs, attempt {attempt}",
2637
            if parent_block_info.parent_block_total_burn == 0 {
24,933✔
2638
                "Genesis"
282✔
2639
            } else {
2640
                "Stacks"
24,651✔
2641
            },
2642
            anchored_block.header.total_work.work,
2643
            anchored_block.block_hash(),
24,933✔
2644
            anchored_block.txs.len()
24,933✔
2645
        );
2646

2647
        // let's commit
2648
        #[cfg(test)]
2649
        if self.globals.counters.skip_commit_op.get() {
24,936✔
2650
            debug!("Relayer: fault injection: skip block commit");
3,210✔
2651
            return None;
3,210✔
2652
        }
21,726✔
2653
        let op = self.make_block_commit(
21,726✔
2654
            &mut burn_db,
21,726✔
2655
            &mut chain_state,
21,726✔
2656
            anchored_block.block_hash(),
21,726✔
2657
            parent_block_info.parent_block_burn_height,
21,726✔
2658
            parent_block_info.parent_winning_vtxindex,
21,726✔
2659
            &vrf_proof,
21,726✔
2660
            target_epoch_id,
21,726✔
2661
        )?;
×
2662
        let burn_fee = if let BlockstackOperationType::LeaderBlockCommit(ref op) = &op {
21,726✔
2663
            op.burn_fee
21,726✔
2664
        } else {
2665
            0
×
2666
        };
2667

2668
        // last chance -- confirm that the stacks tip is unchanged (since it could have taken long
2669
        // enough to build this block that another block could have arrived), and confirm that all
2670
        // Stacks blocks with heights higher than the canoincal tip are processed.
2671
        let cur_burn_chain_tip = SortitionDB::get_canonical_burn_chain_tip(burn_db.conn())
21,726✔
2672
            .expect("FATAL: failed to query sortition DB for canonical burn chain tip");
21,726✔
2673

2674
        if let Some(stacks_tip) = Self::pick_best_tip(
21,726✔
2675
            &self.globals,
21,726✔
2676
            &self.config,
21,726✔
2677
            &mut burn_db,
21,726✔
2678
            &mut chain_state,
21,726✔
2679
            None,
21,726✔
2680
        ) {
21,726✔
2681
            let is_miner_blocked = self
21,445✔
2682
                .globals
21,445✔
2683
                .get_miner_status()
21,445✔
2684
                .lock()
21,445✔
2685
                .expect("FATAL: mutex poisoned")
21,445✔
2686
                .is_blocked();
21,445✔
2687

2688
            let has_unprocessed = Self::unprocessed_blocks_prevent_mining(
21,445✔
2689
                &self.burnchain,
21,445✔
2690
                &burn_db,
21,445✔
2691
                &chain_state,
21,445✔
2692
                miner_config.unprocessed_block_deadline_secs,
21,445✔
2693
            );
2694

2695
            if stacks_tip.anchored_block_hash != anchored_block.header.parent_block
21,445✔
2696
                || parent_block_info.parent_consensus_hash != stacks_tip.consensus_hash
21,439✔
2697
                || cur_burn_chain_tip.burn_header_hash != self.burn_block.burn_header_hash
21,439✔
2698
                || is_miner_blocked
21,413✔
2699
                || has_unprocessed
21,400✔
2700
            {
2701
                info!(
42✔
2702
                    "Relayer: Cancel block-commit; chain tip(s) have changed or cancelled";
2703
                    "block_hash" => %anchored_block.block_hash(),
42✔
2704
                    "tx_count" => anchored_block.txs.len(),
42✔
2705
                    "target_height" => %anchored_block.header.total_work.work,
2706
                    "parent_consensus_hash" => %parent_block_info.parent_consensus_hash,
2707
                    "parent_block_hash" => %anchored_block.header.parent_block,
2708
                    "parent_microblock_hash" => %anchored_block.header.parent_microblock,
2709
                    "parent_microblock_seq" => anchored_block.header.parent_microblock_sequence,
42✔
2710
                    "old_tip_burn_block_hash" => %self.burn_block.burn_header_hash,
2711
                    "old_tip_burn_block_height" => self.burn_block.block_height,
42✔
2712
                    "old_tip_burn_block_sortition_id" => %self.burn_block.sortition_id,
2713
                    "attempt" => attempt,
42✔
2714
                    "new_stacks_tip_block_hash" => %stacks_tip.anchored_block_hash,
2715
                    "new_stacks_tip_consensus_hash" => %stacks_tip.consensus_hash,
2716
                    "new_tip_burn_block_height" => cur_burn_chain_tip.block_height,
42✔
2717
                    "new_tip_burn_block_sortition_id" => %cur_burn_chain_tip.sortition_id,
2718
                    "new_burn_block_sortition_id" => %cur_burn_chain_tip.sortition_id,
2719
                    "miner_blocked" => %is_miner_blocked,
2720
                    "has_unprocessed" => %has_unprocessed
2721
                );
2722
                self.globals.counters.bump_missed_tenures();
42✔
2723
                return None;
42✔
2724
            }
21,403✔
2725
        }
281✔
2726

2727
        let mut op_signer = self.keychain.generate_op_signer();
21,684✔
2728
        info!(
21,684✔
2729
            "Relayer: Submit block-commit";
2730
            "burn_fee" => burn_fee,
21,681✔
2731
            "block_hash" => %anchored_block.block_hash(),
21,681✔
2732
            "tx_count" => anchored_block.txs.len(),
21,681✔
2733
            "target_height" => anchored_block.header.total_work.work,
21,681✔
2734
            "parent_consensus_hash" => %parent_block_info.parent_consensus_hash,
2735
            "parent_block_hash" => %anchored_block.header.parent_block,
2736
            "parent_microblock_hash" => %anchored_block.header.parent_microblock,
2737
            "parent_microblock_seq" => anchored_block.header.parent_microblock_sequence,
21,681✔
2738
            "tip_burn_block_hash" => %self.burn_block.burn_header_hash,
2739
            "tip_burn_block_height" => self.burn_block.block_height,
21,681✔
2740
            "tip_burn_block_sortition_id" => %self.burn_block.sortition_id,
2741
            "cur_burn_block_hash" => %cur_burn_chain_tip.burn_header_hash,
2742
            "cur_burn_block_height" => %cur_burn_chain_tip.block_height,
2743
            "cur_burn_block_sortition_id" => %cur_burn_chain_tip.sortition_id,
2744
            "attempt" => attempt
21,681✔
2745
        );
2746

2747
        let NodeConfig {
2748
            mock_mining,
21,684✔
2749
            mock_mining_output_dir,
21,684✔
2750
            ..
2751
        } = self.config.get_node_config(false);
21,684✔
2752

2753
        let res = bitcoin_controller.submit_operation(target_epoch_id, op, &mut op_signer);
21,684✔
2754
        match res {
15,064✔
2755
            Ok(_) => {
6,535✔
2756
                self.failed_to_submit_last_attempt = false;
6,535✔
2757
                self.globals
6,535✔
2758
                    .counters
6,535✔
2759
                    .bump_neon_submitted_commits(self.burn_block.block_height);
6,535✔
2760
            }
6,535✔
2761
            Err(_) if mock_mining => {
85✔
2762
                debug!("Relayer: Mock-mining enabled; not sending Bitcoin transaction");
85✔
2763
                self.failed_to_submit_last_attempt = true;
85✔
2764
            }
2765
            Err(BurnchainControllerError::IdenticalOperation) => {
2766
                info!("Relayer: Block-commit already submitted");
15,064✔
2767
                self.failed_to_submit_last_attempt = true;
15,064✔
2768
                return None;
15,064✔
2769
            }
2770
            Err(e) => {
×
2771
                warn!("Relayer: Failed to submit Bitcoin transaction: {e:?}");
×
2772
                self.failed_to_submit_last_attempt = true;
×
2773
                return None;
×
2774
            }
2775
        };
2776

2777
        let assembled_block = AssembledAnchorBlock {
6,620✔
2778
            parent_consensus_hash: parent_block_info.parent_consensus_hash.clone(),
6,620✔
2779
            consensus_hash: cur_burn_chain_tip.consensus_hash.clone(),
6,620✔
2780
            burn_hash: cur_burn_chain_tip.burn_header_hash.clone(),
6,620✔
2781
            burn_block_height: cur_burn_chain_tip.block_height,
6,620✔
2782
            orig_burn_hash: self.burn_block.burn_header_hash.clone(),
6,620✔
2783
            anchored_block,
6,620✔
2784
            attempt,
6,620✔
2785
            tenure_begin,
6,620✔
2786
        };
6,620✔
2787

2788
        if mock_mining {
6,620✔
2789
            let stacks_block_height = assembled_block.anchored_block.header.total_work.work;
85✔
2790
            info!("Mock mined Stacks block {stacks_block_height}");
85✔
2791
            if let Some(dir) = mock_mining_output_dir {
85✔
2792
                info!("Writing mock mined Stacks block {stacks_block_height} to file");
27✔
2793
                fs::create_dir_all(&dir).unwrap_or_else(|e| match e.kind() {
27✔
2794
                    ErrorKind::AlreadyExists => { /* This is fine */ }
×
2795
                    _ => error!("Failed to create directory '{dir:?}': {e}"),
×
2796
                });
×
2797
                let filename = format!("{stacks_block_height}.json");
27✔
2798
                let filepath = dir.join(filename);
27✔
2799
                assembled_block
27✔
2800
                    .serialize_to_file(&filepath)
27✔
2801
                    .unwrap_or_else(|e| match e.kind() {
27✔
2802
                        ErrorKind::AlreadyExists => {
2803
                            error!("Failed to overwrite file '{filepath:?}'")
×
2804
                        }
2805
                        _ => error!("Failed to write to file '{filepath:?}': {e}"),
×
2806
                    });
×
2807
            }
58✔
2808
        }
6,535✔
2809

2810
        Some(MinerThreadResult::Block(
6,620✔
2811
            assembled_block,
6,620✔
2812
            microblock_private_key,
6,620✔
2813
            bitcoin_controller.get_ongoing_commit(),
6,620✔
2814
        ))
6,620✔
2815
    }
168,096✔
2816
}
2817

2818
impl RelayerThread {
2819
    /// Instantiate off of a StacksNode, a runloop, and a relayer.
2820
    pub fn new(runloop: &RunLoop, local_peer: LocalPeer, relayer: Relayer) -> RelayerThread {
275✔
2821
        let config = runloop.config().clone();
275✔
2822
        let globals = runloop.get_globals();
275✔
2823
        let burn_db_path = config.get_burn_db_file_path();
275✔
2824
        let stacks_chainstate_path = config.get_chainstate_path_str();
275✔
2825
        let is_mainnet = config.is_mainnet();
275✔
2826
        let chain_id = config.burnchain.chain_id;
275✔
2827

2828
        let sortdb = SortitionDB::open(
275✔
2829
            &burn_db_path,
275✔
2830
            true,
2831
            runloop.get_burnchain().pox_constants,
275✔
2832
            Some(config.node.get_marf_opts()),
275✔
2833
        )
2834
        .expect("FATAL: failed to open burnchain DB");
275✔
2835

2836
        let chainstate =
275✔
2837
            open_chainstate_with_faults(&config).expect("FATAL: failed to open chainstate DB");
275✔
2838

2839
        let cost_estimator = config
275✔
2840
            .make_cost_estimator()
275✔
2841
            .unwrap_or_else(|| Box::new(UnitEstimator));
275✔
2842
        let metric = config
275✔
2843
            .make_cost_metric()
275✔
2844
            .unwrap_or_else(|| Box::new(UnitMetric));
275✔
2845

2846
        let mempool = MemPoolDB::open(
275✔
2847
            is_mainnet,
275✔
2848
            chain_id,
275✔
2849
            &stacks_chainstate_path,
275✔
2850
            cost_estimator,
275✔
2851
            metric,
275✔
2852
        )
2853
        .expect("Database failure opening mempool");
275✔
2854

2855
        let keychain = Keychain::default(config.node.seed.clone());
275✔
2856
        let bitcoin_controller = BitcoinRegtestController::new_dummy(config.clone());
275✔
2857

2858
        RelayerThread {
275✔
2859
            config: config.clone(),
275✔
2860
            sortdb: Some(sortdb),
275✔
2861
            chainstate: Some(chainstate),
275✔
2862
            mempool: Some(mempool),
275✔
2863
            globals,
275✔
2864
            keychain,
275✔
2865
            burnchain: runloop.get_burnchain(),
275✔
2866
            last_vrf_key_burn_height: 0,
275✔
2867
            last_mined_blocks: MinedBlocks::new(),
275✔
2868
            bitcoin_controller,
275✔
2869
            event_dispatcher: runloop.get_event_dispatcher(),
275✔
2870
            local_peer,
275✔
2871

275✔
2872
            last_tenure_issue_time: 0,
275✔
2873
            last_network_block_height: 0,
275✔
2874
            last_network_block_height_ts: 0,
275✔
2875
            last_network_download_passes: 0,
275✔
2876
            min_network_download_passes: 0,
275✔
2877
            last_network_inv_passes: 0,
275✔
2878
            min_network_inv_passes: 0,
275✔
2879

275✔
2880
            last_tenure_consensus_hash: None,
275✔
2881
            miner_tip: None,
275✔
2882
            last_microblock_tenure_time: 0,
275✔
2883
            microblock_deadline: 0,
275✔
2884
            microblock_stream_cost: ExecutionCost::ZERO,
275✔
2885

275✔
2886
            relayer,
275✔
2887

275✔
2888
            miner_thread: None,
275✔
2889
            mined_stacks_block: false,
275✔
2890
            last_attempt_failed: false,
275✔
2891
        }
275✔
2892
    }
275✔
2893

2894
    /// Get an immutible ref to the sortdb
2895
    pub fn sortdb_ref(&self) -> &SortitionDB {
1,351,596✔
2896
        self.sortdb
1,351,596✔
2897
            .as_ref()
1,351,596✔
2898
            .expect("FATAL: tried to access sortdb while taken")
1,351,596✔
2899
    }
1,351,596✔
2900

2901
    /// Get an immutible ref to the chainstate
2902
    pub fn chainstate_ref(&self) -> &StacksChainState {
302,269✔
2903
        self.chainstate
302,269✔
2904
            .as_ref()
302,269✔
2905
            .expect("FATAL: tried to access chainstate while it was taken")
302,269✔
2906
    }
302,269✔
2907

2908
    /// Fool the borrow checker into letting us do something with the chainstate databases.
2909
    /// DOES NOT COMPOSE -- do NOT call this, or self.sortdb_ref(), or self.chainstate_ref(), within
2910
    /// `func`.  You will get a runtime panic.
2911
    pub fn with_chainstate<F, R>(&mut self, func: F) -> R
1,405,280✔
2912
    where
1,405,280✔
2913
        F: FnOnce(&mut RelayerThread, &mut SortitionDB, &mut StacksChainState, &mut MemPoolDB) -> R,
1,405,280✔
2914
    {
2915
        let mut sortdb = self
1,405,280✔
2916
            .sortdb
1,405,280✔
2917
            .take()
1,405,280✔
2918
            .expect("FATAL: tried to take sortdb while taken");
1,405,280✔
2919
        let mut chainstate = self
1,405,280✔
2920
            .chainstate
1,405,280✔
2921
            .take()
1,405,280✔
2922
            .expect("FATAL: tried to take chainstate while taken");
1,405,280✔
2923
        let mut mempool = self
1,405,280✔
2924
            .mempool
1,405,280✔
2925
            .take()
1,405,280✔
2926
            .expect("FATAL: tried to take mempool while taken");
1,405,280✔
2927
        let res = func(self, &mut sortdb, &mut chainstate, &mut mempool);
1,405,280✔
2928
        self.sortdb = Some(sortdb);
1,405,280✔
2929
        self.chainstate = Some(chainstate);
1,405,280✔
2930
        self.mempool = Some(mempool);
1,405,280✔
2931
        res
1,405,280✔
2932
    }
1,405,280✔
2933

2934
    /// have we waited for the right conditions under which to start mining a block off of our
2935
    /// chain tip?
2936
    pub fn has_waited_for_latest_blocks(&self) -> bool {
881,743✔
2937
        // a network download pass took place
2938
        self.min_network_download_passes <= self.last_network_download_passes
881,743✔
2939
        // we waited long enough for a download pass, but timed out waiting
2940
        || self.last_network_block_height_ts + (self.config.node.wait_time_for_blocks as u128) < get_epoch_time_ms()
688,958✔
2941
        // we're not supposed to wait at all
2942
        || !self.config.miner.wait_for_block_download
151,598✔
2943
    }
881,743✔
2944

2945
    /// Return debug string for waiting for latest blocks
2946
    pub fn debug_waited_for_latest_blocks(&self) -> String {
×
2947
        format!(
×
2948
            "({} <= {} && {} <= {}) || {} + {} < {} || {}",
2949
            self.min_network_download_passes,
2950
            self.last_network_download_passes,
2951
            self.min_network_inv_passes,
2952
            self.last_network_inv_passes,
2953
            self.last_network_block_height_ts,
2954
            self.config.node.wait_time_for_blocks,
2955
            get_epoch_time_ms(),
×
2956
            self.config.miner.wait_for_block_download
2957
        )
2958
    }
×
2959

2960
    /// Handle a NetworkResult from the p2p/http state machine.  Usually this is the act of
2961
    /// * preprocessing and storing new blocks and microblocks
2962
    /// * relaying blocks, microblocks, and transactions
2963
    /// * updating unconfirmed state views
2964
    pub fn process_network_result(&mut self, mut net_result: NetworkResult) {
693,199✔
2965
        debug!(
693,199✔
2966
            "Relayer: Handle network result (from {})",
2967
            net_result.burn_height
2968
        );
2969

2970
        if self.last_network_block_height != net_result.burn_height {
693,199✔
2971
            // burnchain advanced; disable mining until we also do a download pass.
2972
            self.last_network_block_height = net_result.burn_height;
19,273✔
2973
            self.min_network_download_passes = net_result.num_download_passes + 1;
19,273✔
2974
            self.min_network_inv_passes = net_result.num_inv_sync_passes + 1;
19,273✔
2975
            self.last_network_block_height_ts = get_epoch_time_ms();
19,273✔
2976
            debug!(
19,273✔
2977
                "Relayer: block mining until the next download pass {}",
2978
                self.min_network_download_passes
2979
            );
2980
            signal_mining_blocked(self.globals.get_miner_status());
19,273✔
2981
        }
673,926✔
2982

2983
        let net_receipts = self.with_chainstate(|relayer_thread, sortdb, chainstate, mempool| {
693,199✔
2984
            relayer_thread
693,199✔
2985
                .relayer
693,199✔
2986
                .process_network_result(
693,199✔
2987
                    &relayer_thread.local_peer,
693,199✔
2988
                    &mut net_result,
693,199✔
2989
                    &relayer_thread.burnchain,
693,199✔
2990
                    sortdb,
693,199✔
2991
                    chainstate,
693,199✔
2992
                    mempool,
693,199✔
2993
                    relayer_thread.globals.sync_comms.get_ibd(),
693,199✔
2994
                    Some(&relayer_thread.globals.coord_comms),
693,199✔
2995
                    Some(&relayer_thread.event_dispatcher),
693,199✔
2996
                )
2997
                .expect("BUG: failure processing network results")
693,199✔
2998
        });
693,199✔
2999

3000
        if net_receipts.num_new_blocks > 0 || net_receipts.num_new_confirmed_microblocks > 0 {
693,199✔
3001
            // if we received any new block data that could invalidate our view of the chain tip,
3002
            // then stop mining until we process it
3003
            debug!("Relayer: block mining to process newly-arrived blocks or microblocks");
1,079✔
3004
            signal_mining_blocked(self.globals.get_miner_status());
1,079✔
3005
        }
692,120✔
3006

3007
        let mempool_txs_added = net_receipts.mempool_txs_added.len();
693,199✔
3008
        if mempool_txs_added > 0 {
693,199✔
3009
            self.event_dispatcher
1,119✔
3010
                .process_new_mempool_txs(net_receipts.mempool_txs_added);
1,119✔
3011
        }
692,080✔
3012

3013
        let num_unconfirmed_microblock_tx_receipts =
693,199✔
3014
            net_receipts.processed_unconfirmed_state.receipts.len();
693,199✔
3015
        if num_unconfirmed_microblock_tx_receipts > 0 {
693,199✔
3016
            if let Some(unconfirmed_state) = self.chainstate_ref().unconfirmed_state.as_ref() {
1✔
3017
                self.event_dispatcher.process_new_microblocks(
1✔
3018
                    &unconfirmed_state.confirmed_chain_tip,
1✔
3019
                    &net_receipts.processed_unconfirmed_state,
1✔
3020
                );
1✔
3021
            } else {
1✔
3022
                warn!("Relayer: oops, unconfirmed state is uninitialized but there are microblock events");
×
3023
            }
3024
        }
693,198✔
3025

3026
        // Dispatch retrieved attachments, if any.
3027
        if net_result.has_attachments() {
693,199✔
3028
            self.event_dispatcher
×
3029
                .process_new_attachments(&net_result.attachments);
×
3030
        }
693,199✔
3031

3032
        // synchronize unconfirmed tx index to p2p thread
3033
        self.with_chainstate(|relayer_thread, _sortdb, chainstate, _mempool| {
693,199✔
3034
            relayer_thread.globals.send_unconfirmed_txs(chainstate);
693,197✔
3035
        });
693,197✔
3036

3037
        // resume mining if we blocked it, and if we've done the requisite download
3038
        // passes
3039
        self.last_network_download_passes = net_result.num_download_passes;
693,199✔
3040
        self.last_network_inv_passes = net_result.num_inv_sync_passes;
693,199✔
3041
        if self.has_waited_for_latest_blocks() {
693,199✔
3042
            debug!("Relayer: did a download pass, so unblocking mining");
693,197✔
3043
            signal_mining_ready(self.globals.get_miner_status());
693,197✔
3044
        }
2✔
3045
    }
693,199✔
3046

3047
    /// Process the block and microblocks from a sortition that we won.
3048
    /// At this point, we're modifying the chainstate, and merging the artifacts from the previous tenure.
3049
    /// Blocks until the given stacks block is processed.
3050
    /// Returns true if we accepted this block as new.
3051
    /// Returns false if we already processed this block.
3052
    fn accept_winning_tenure(
6,293✔
3053
        &mut self,
6,293✔
3054
        anchored_block: &StacksBlock,
6,293✔
3055
        consensus_hash: &ConsensusHash,
6,293✔
3056
        parent_consensus_hash: &ConsensusHash,
6,293✔
3057
    ) -> Result<bool, ChainstateError> {
6,293✔
3058
        if StacksChainState::has_stored_block(
6,293✔
3059
            self.chainstate_ref().db(),
6,293✔
3060
            &self.chainstate_ref().blocks_path,
6,293✔
3061
            consensus_hash,
6,293✔
3062
            &anchored_block.block_hash(),
6,293✔
3063
        )? {
×
3064
            // already processed my tenure
3065
            return Ok(false);
×
3066
        }
6,293✔
3067
        let burn_height =
6,293✔
3068
            SortitionDB::get_block_snapshot_consensus(self.sortdb_ref().conn(), consensus_hash)
6,293✔
3069
                .map_err(|e| {
6,293✔
3070
                    error!("Failed to find block snapshot for mined block: {e}");
×
3071
                    e
×
3072
                })?
×
3073
                .ok_or_else(|| {
6,293✔
3074
                    error!("Failed to find block snapshot for mined block");
×
3075
                    ChainstateError::NoSuchBlockError
×
3076
                })?
×
3077
                .block_height;
3078

3079
        let epoch_id = SortitionDB::get_stacks_epoch(self.sortdb_ref().conn(), burn_height)?
6,293✔
3080
            .expect("FATAL: no epoch defined")
6,293✔
3081
            .epoch_id;
3082

3083
        // failsafe
3084
        if !Relayer::static_check_problematic_relayed_block(
6,293✔
3085
            self.chainstate_ref().mainnet,
6,293✔
3086
            epoch_id,
6,293✔
3087
            anchored_block,
6,293✔
3088
        ) {
6,293✔
3089
            // nope!
3090
            warn!(
×
3091
                "Our mined block {} was problematic. Will NOT process.",
3092
                &anchored_block.block_hash()
×
3093
            );
3094
            #[cfg(any(test, feature = "testing"))]
3095
            {
3096
                use std::path::Path;
3097
                if let Ok(path) = std::env::var("STACKS_BAD_BLOCKS_DIR") {
×
3098
                    // record this block somewhere
3099
                    if fs::metadata(&path).is_err() {
×
3100
                        fs::create_dir_all(&path)
×
3101
                            .unwrap_or_else(|_| panic!("FATAL: could not create '{path}'"));
×
3102
                    }
×
3103

3104
                    let path = Path::new(&path);
×
3105
                    let path = path.join(Path::new(&format!("{}", &anchored_block.block_hash())));
×
3106
                    let mut file = fs::File::create(&path)
×
3107
                        .unwrap_or_else(|_| panic!("FATAL: could not create '{path:?}'"));
×
3108

3109
                    let block_bits = anchored_block.serialize_to_vec();
×
3110
                    let block_bits_hex = to_hex(&block_bits);
×
3111
                    let block_json =
×
3112
                        format!(r#"{{"block":"{block_bits_hex}","consensus":"{consensus_hash}"}}"#);
×
3113
                    file.write_all(block_json.as_bytes()).unwrap_or_else(|_| {
×
3114
                        panic!("FATAL: failed to write block bits to '{path:?}'")
×
3115
                    });
3116
                    info!(
×
3117
                        "Fault injection: bad block {} saved to {}",
3118
                        &anchored_block.block_hash(),
×
3119
                        &path.to_str().unwrap()
×
3120
                    );
3121
                }
×
3122
            }
3123
            return Err(ChainstateError::NoTransactionsToMine);
×
3124
        }
6,293✔
3125

3126
        // Preprocess the anchored block
3127
        self.with_chainstate(|_relayer_thread, sort_db, chainstate, _mempool| {
6,293✔
3128
            let ic = sort_db.index_conn();
6,293✔
3129
            chainstate.preprocess_anchored_block(
6,293✔
3130
                &ic,
6,293✔
3131
                consensus_hash,
6,293✔
3132
                anchored_block,
6,293✔
3133
                parent_consensus_hash,
6,293✔
3134
                0,
3135
            )
3136
        })?;
6,293✔
3137

3138
        Ok(true)
6,293✔
3139
    }
6,293✔
3140

3141
    /// Process a new block we mined
3142
    /// Return true if we processed it
3143
    /// Return false if we timed out waiting for it
3144
    /// Return Err(..) if we couldn't reach the chains coordiantor thread
3145
    fn process_new_block(&self) -> Result<bool, Error> {
6,293✔
3146
        // process the block
3147
        let stacks_blocks_processed = self.globals.coord_comms.get_stacks_blocks_processed();
6,293✔
3148
        if !self.globals.coord_comms.announce_new_stacks_block() {
6,293✔
3149
            return Err(Error::CoordinatorClosed);
×
3150
        }
6,293✔
3151
        if !self
6,293✔
3152
            .globals
6,293✔
3153
            .coord_comms
6,293✔
3154
            .wait_for_stacks_blocks_processed(stacks_blocks_processed, u64::MAX)
6,293✔
3155
        {
3156
            // basically unreachable
3157
            warn!("ChainsCoordinator timed out while waiting for new stacks block to be processed");
×
3158
            return Ok(false);
×
3159
        }
6,293✔
3160
        debug!("Relayer: Stacks block has been processed");
6,293✔
3161

3162
        Ok(true)
6,293✔
3163
    }
6,293✔
3164

3165
    /// Given the two miner tips, return the newer tip.
3166
    fn pick_higher_tip(cur: Option<MinerTip>, new: Option<MinerTip>) -> Option<MinerTip> {
15,197✔
3167
        match (cur, new) {
15,197✔
3168
            (Some(cur), None) => Some(cur),
25✔
3169
            (None, Some(new)) => Some(new),
6,516✔
3170
            (None, None) => None,
2,588✔
3171
            (Some(cur), Some(new)) => {
6,068✔
3172
                if cur.stacks_height < new.stacks_height {
6,068✔
3173
                    Some(new)
6,062✔
3174
                } else if cur.stacks_height > new.stacks_height {
6✔
3175
                    Some(cur)
4✔
3176
                } else if cur.burn_height < new.burn_height {
2✔
3177
                    Some(new)
2✔
3178
                } else if cur.burn_height > new.burn_height {
×
3179
                    Some(cur)
×
3180
                } else {
3181
                    assert_eq!(cur, new);
×
3182
                    Some(cur)
×
3183
                }
3184
            }
3185
        }
3186
    }
15,197✔
3187

3188
    /// Given the pointer to a recently-discovered tenure, see if we won the sortition and if so,
3189
    /// store it, preprocess it, and forward it to our neighbors.  All the while, keep track of the
3190
    /// latest Stacks mining tip we have produced so far.
3191
    ///
3192
    /// Returns (true, Some(tip)) if the coordinator is still running and we have a miner tip to
3193
    /// build on (i.e. we won this last sortition).
3194
    ///
3195
    /// Returns (true, None) if the coordinator is still running, and we do NOT have a miner tip to
3196
    /// build on (i.e. we did not win this last sortition)
3197
    ///
3198
    /// Returns (false, _) if the coordinator could not be reached, meaning this thread should die.
3199
    pub fn process_one_tenure(
7,599✔
3200
        &mut self,
7,599✔
3201
        consensus_hash: ConsensusHash,
7,599✔
3202
        block_header_hash: BlockHeaderHash,
7,599✔
3203
        burn_hash: BurnchainHeaderHash,
7,599✔
3204
    ) -> (bool, Option<MinerTip>) {
7,599✔
3205
        let mut miner_tip = None;
7,599✔
3206
        let sn =
7,599✔
3207
            SortitionDB::get_block_snapshot_consensus(self.sortdb_ref().conn(), &consensus_hash)
7,599✔
3208
                .expect("FATAL: failed to query sortition DB")
7,599✔
3209
                .expect("FATAL: unknown consensus hash");
7,599✔
3210

3211
        debug!(
7,599✔
3212
            "Relayer: Process tenure {consensus_hash}/{block_header_hash} in {burn_hash} burn height {}",
3213
            sn.block_height
3214
        );
3215

3216
        if let Some((last_mined_block_data, microblock_privkey)) =
6,293✔
3217
            self.last_mined_blocks.remove(&block_header_hash)
7,599✔
3218
        {
3219
            // we won!
3220
            let AssembledAnchorBlock {
3221
                parent_consensus_hash,
6,293✔
3222
                anchored_block: mined_block,
6,293✔
3223
                burn_hash: mined_burn_hash,
6,293✔
3224
                attempt: _,
3225
                ..
3226
            } = last_mined_block_data;
6,293✔
3227

3228
            let reward_block_height = mined_block.header.total_work.work + MINER_REWARD_MATURITY;
6,293✔
3229
            info!(
6,293✔
3230
                "Relayer: Won sortition! Mining reward will be received in {MINER_REWARD_MATURITY} blocks (block #{reward_block_height})"
3231
            );
3232
            debug!("Relayer: Won sortition!";
6,293✔
3233
                  "stacks_header" => %block_header_hash,
3234
                  "burn_hash" => %mined_burn_hash,
3235
            );
3236

3237
            increment_stx_blocks_mined_counter();
6,293✔
3238
            let has_new_data = match self.accept_winning_tenure(
6,293✔
3239
                &mined_block,
6,293✔
3240
                &consensus_hash,
6,293✔
3241
                &parent_consensus_hash,
6,293✔
3242
            ) {
3243
                Ok(accepted) => accepted,
6,293✔
3244
                Err(ChainstateError::ChannelClosed(_)) => {
3245
                    warn!("Coordinator stopped, stopping relayer thread...");
×
3246
                    return (false, None);
×
3247
                }
3248
                Err(e) => {
×
3249
                    warn!("Error processing my tenure, bad block produced: {e}");
×
3250
                    warn!(
×
3251
                        "Bad block";
3252
                        "stacks_header" => %block_header_hash,
3253
                        "data" => %to_hex(&mined_block.serialize_to_vec()),
×
3254
                    );
3255
                    return (true, None);
×
3256
                }
3257
            };
3258

3259
            // advertize _and_ push blocks for now
3260
            let blocks_available = Relayer::load_blocks_available_data(
6,293✔
3261
                self.sortdb_ref(),
6,293✔
3262
                vec![consensus_hash.clone()],
6,293✔
3263
            )
3264
            .expect("Failed to obtain block information for a block we mined.");
6,293✔
3265

3266
            let block_data = {
6,293✔
3267
                let mut bd = HashMap::new();
6,293✔
3268
                bd.insert(consensus_hash.clone(), mined_block.clone());
6,293✔
3269
                bd
6,293✔
3270
            };
3271

3272
            if let Err(e) = self.relayer.advertize_blocks(blocks_available, block_data) {
6,293✔
3273
                warn!("Failed to advertise new block: {e}");
×
3274
            }
6,293✔
3275

3276
            let snapshot = SortitionDB::get_block_snapshot_consensus(
6,293✔
3277
                self.sortdb_ref().conn(),
6,293✔
3278
                &consensus_hash,
6,293✔
3279
            )
3280
            .expect("Failed to obtain snapshot for block")
6,293✔
3281
            .expect("Failed to obtain snapshot for block");
6,293✔
3282

3283
            if !snapshot.pox_valid {
6,293✔
3284
                warn!(
×
3285
                    "Snapshot for {consensus_hash} is no longer valid; discarding {}...",
3286
                    &mined_block.block_hash()
×
3287
                );
3288
                miner_tip = Self::pick_higher_tip(miner_tip, None);
×
3289
            } else {
3290
                let ch = snapshot.consensus_hash.clone();
6,293✔
3291
                let bh = mined_block.block_hash();
6,293✔
3292
                let height = mined_block.header.total_work.work;
6,293✔
3293

3294
                let mut broadcast = true;
6,293✔
3295
                if self.chainstate_ref().fault_injection.hide_blocks
6,293✔
3296
                    && Relayer::fault_injection_is_block_hidden(
×
3297
                        &mined_block.header,
×
3298
                        snapshot.block_height,
×
3299
                    )
3300
                {
×
3301
                    broadcast = false;
×
3302
                }
6,293✔
3303
                if broadcast {
6,293✔
3304
                    if let Err(e) = self
6,293✔
3305
                        .relayer
6,293✔
3306
                        .broadcast_block(snapshot.consensus_hash, mined_block)
6,293✔
3307
                    {
3308
                        warn!("Failed to push new block: {e}");
×
3309
                    }
6,293✔
3310
                }
×
3311

3312
                // proceed to mine microblocks
3313
                miner_tip = Some(MinerTip::new(
6,293✔
3314
                    ch,
6,293✔
3315
                    bh,
6,293✔
3316
                    microblock_privkey,
6,293✔
3317
                    height,
6,293✔
3318
                    snapshot.block_height,
6,293✔
3319
                ));
6,293✔
3320
            }
3321

3322
            if has_new_data {
6,293✔
3323
                // process the block, now that we've advertized it
3324
                if let Err(Error::CoordinatorClosed) = self.process_new_block() {
6,293✔
3325
                    // coordiantor stopped
3326
                    return (false, None);
×
3327
                }
6,293✔
3328
            }
×
3329
        } else {
3330
            debug!(
1,306✔
3331
                "Relayer: Did not win sortition in {burn_hash}, winning block was {consensus_hash}/{block_header_hash}"
3332
            );
3333
            miner_tip = None;
1,306✔
3334
        }
3335

3336
        (true, miner_tip)
7,599✔
3337
    }
7,599✔
3338

3339
    // TODO: add tests from mutation testing results #4872
3340
    #[cfg_attr(test, mutants::skip)]
3341
    /// Process all new tenures that we're aware of.
3342
    /// Clear out stale tenure artifacts as well.
3343
    /// Update the miner tip if we won the highest tenure (or clear it if we didn't).
3344
    /// If we won any sortitions, send the block and microblock data to the p2p thread.
3345
    /// Return true if we can still continue to run; false if not.
3346
    pub fn process_new_tenures(
7,598✔
3347
        &mut self,
7,598✔
3348
        consensus_hash: ConsensusHash,
7,598✔
3349
        burn_hash: BurnchainHeaderHash,
7,598✔
3350
        block_header_hash: BlockHeaderHash,
7,598✔
3351
    ) -> bool {
7,598✔
3352
        let mut miner_tip = None;
7,598✔
3353
        let mut num_sortitions = 0;
7,598✔
3354

3355
        // process all sortitions between the last-processed consensus hash and this
3356
        // one.  ProcessTenure(..) messages can get lost.
3357
        let burn_tip = SortitionDB::get_canonical_burn_chain_tip(self.sortdb_ref().conn())
7,598✔
3358
            .expect("FATAL: failed to read current burnchain tip");
7,598✔
3359
        let mut microblocks_disabled =
7,598✔
3360
            SortitionDB::are_microblocks_disabled(self.sortdb_ref().conn(), burn_tip.block_height)
7,598✔
3361
                .expect("FATAL: failed to query epoch's microblock status");
7,598✔
3362

3363
        let tenures = if let Some(last_ch) = self.last_tenure_consensus_hash.as_ref() {
7,598✔
3364
            let mut tenures = vec![];
7,331✔
3365
            let last_sn =
7,331✔
3366
                SortitionDB::get_block_snapshot_consensus(self.sortdb_ref().conn(), last_ch)
7,331✔
3367
                    .expect("FATAL: failed to query sortition DB")
7,331✔
3368
                    .expect("FATAL: unknown prior consensus hash");
7,331✔
3369

3370
            debug!(
7,331✔
3371
                "Relayer: query tenures between burn block heights {} and {}",
3372
                last_sn.block_height + 1,
×
3373
                burn_tip.block_height + 1
×
3374
            );
3375
            for block_to_process in (last_sn.block_height + 1)..(burn_tip.block_height + 1) {
7,360✔
3376
                num_sortitions += 1;
7,360✔
3377
                let sn = {
7,360✔
3378
                    let ic = self.sortdb_ref().index_conn();
7,360✔
3379
                    SortitionDB::get_ancestor_snapshot(
7,360✔
3380
                        &ic,
7,360✔
3381
                        block_to_process,
7,360✔
3382
                        &burn_tip.sortition_id,
7,360✔
3383
                    )
3384
                    .expect("FATAL: failed to read ancestor snapshot from sortition DB")
7,360✔
3385
                    .expect("Failed to find block in fork processed by burnchain indexer")
7,360✔
3386
                };
3387
                if !sn.sortition {
7,360✔
3388
                    debug!(
28✔
3389
                        "Relayer: Skipping tenure {}/{} at burn hash/height {},{} -- no sortition",
3390
                        &sn.consensus_hash,
×
3391
                        &sn.winning_stacks_block_hash,
×
3392
                        &sn.burn_header_hash,
×
3393
                        sn.block_height
3394
                    );
3395
                    continue;
28✔
3396
                }
7,332✔
3397
                debug!(
7,332✔
3398
                    "Relayer: Will process tenure {}/{} at burn hash/height {},{}",
3399
                    &sn.consensus_hash,
×
3400
                    &sn.winning_stacks_block_hash,
×
3401
                    &sn.burn_header_hash,
×
3402
                    sn.block_height
3403
                );
3404
                tenures.push((
7,332✔
3405
                    sn.consensus_hash,
7,332✔
3406
                    sn.burn_header_hash,
7,332✔
3407
                    sn.winning_stacks_block_hash,
7,332✔
3408
                ));
7,332✔
3409
            }
3410
            tenures
7,331✔
3411
        } else {
3412
            // first-ever tenure processed
3413
            vec![(consensus_hash, burn_hash, block_header_hash)]
267✔
3414
        };
3415

3416
        debug!("Relayer: will process {} tenures", &tenures.len());
7,598✔
3417
        let num_tenures = tenures.len();
7,598✔
3418
        if num_tenures > 0 {
7,598✔
3419
            // temporarily halt mining
3420
            debug!(
7,354✔
3421
                "Relayer: block mining to process {} tenures",
3422
                &tenures.len()
×
3423
            );
3424
            signal_mining_blocked(self.globals.get_miner_status());
7,354✔
3425
        }
244✔
3426

3427
        for (consensus_hash, burn_hash, block_header_hash) in tenures.into_iter() {
7,599✔
3428
            self.miner_thread_try_join();
7,599✔
3429
            let (continue_thread, new_miner_tip) =
7,599✔
3430
                self.process_one_tenure(consensus_hash.clone(), block_header_hash, burn_hash);
7,599✔
3431
            if !continue_thread {
7,599✔
3432
                // coordinator thread hang-up
3433
                return false;
×
3434
            }
7,599✔
3435
            miner_tip = Self::pick_higher_tip(miner_tip, new_miner_tip);
7,599✔
3436

3437
            // clear all blocks up to this consensus hash
3438
            let this_burn_tip = SortitionDB::get_block_snapshot_consensus(
7,599✔
3439
                self.sortdb_ref().conn(),
7,599✔
3440
                &consensus_hash,
7,599✔
3441
            )
3442
            .expect("FATAL: failed to query sortition DB")
7,599✔
3443
            .expect("FATAL: no snapshot for consensus hash");
7,599✔
3444

3445
            let old_last_mined_blocks = mem::take(&mut self.last_mined_blocks);
7,599✔
3446
            self.last_mined_blocks =
7,599✔
3447
                Self::clear_stale_mined_blocks(this_burn_tip.block_height, old_last_mined_blocks);
7,599✔
3448

3449
            // update last-tenure pointer
3450
            self.last_tenure_consensus_hash = Some(consensus_hash);
7,599✔
3451
        }
3452

3453
        if let Some(mtip) = miner_tip.take() {
7,598✔
3454
            // sanity check -- is this also the canonical tip?
3455
            let (stacks_tip_consensus_hash, stacks_tip_block_hash) =
6,292✔
3456
                self.with_chainstate(|_relayer_thread, sortdb, _chainstate, _| {
6,292✔
3457
                    SortitionDB::get_canonical_stacks_chain_tip_hash(sortdb.conn()).expect(
6,292✔
3458
                        "FATAL: failed to query sortition DB for canonical stacks chain tip hashes",
6,292✔
3459
                    )
3460
                });
6,292✔
3461

3462
            if mtip.consensus_hash != stacks_tip_consensus_hash
6,292✔
3463
                || mtip.block_hash != stacks_tip_block_hash
6,291✔
3464
            {
3465
                debug!(
1✔
3466
                    "Relayer: miner tip {}/{} is NOT canonical ({stacks_tip_consensus_hash}/{stacks_tip_block_hash})",
3467
                    &mtip.consensus_hash,
×
3468
                    &mtip.block_hash,
×
3469
                );
3470
                miner_tip = None;
1✔
3471
            } else {
3472
                debug!(
6,291✔
3473
                    "Relayer: Microblock miner tip is now {}/{} ({})",
3474
                    mtip.consensus_hash,
3475
                    mtip.block_hash,
3476
                    StacksBlockHeader::make_index_block_hash(
×
3477
                        &mtip.consensus_hash,
×
3478
                        &mtip.block_hash
×
3479
                    )
3480
                );
3481

3482
                self.with_chainstate(|relayer_thread, sortdb, chainstate, _mempool| {
6,291✔
3483
                    Relayer::refresh_unconfirmed(chainstate, sortdb);
6,291✔
3484
                    relayer_thread.globals.send_unconfirmed_txs(chainstate);
6,291✔
3485
                });
6,291✔
3486

3487
                miner_tip = Some(mtip);
6,291✔
3488
            }
3489
        }
1,306✔
3490

3491
        // update state for microblock mining
3492
        self.setup_microblock_mining_state(miner_tip);
7,598✔
3493

3494
        if cfg!(test)
7,598✔
3495
            && std::env::var("STACKS_TEST_FORCE_MICROBLOCKS_POST_25").as_deref() == Ok("1")
7,598✔
3496
        {
3497
            debug!("Allowing miner to mine microblocks because STACKS_TEST_FORCE_MICROBLOCKS_POST_25 = 1");
×
3498
            microblocks_disabled = false;
×
3499
        }
7,598✔
3500

3501
        // resume mining if we blocked it
3502
        if num_tenures > 0 || num_sortitions > 0 {
7,598✔
3503
            if self.miner_tip.is_some() {
7,357✔
3504
                // we won the highest tenure
3505
                if self.config.node.mine_microblocks && !microblocks_disabled {
6,316✔
3506
                    // mine a microblock first
386✔
3507
                    self.mined_stacks_block = true;
386✔
3508
                } else {
6,316✔
3509
                    // mine a Stacks block first -- we won't build microblocks
5,930✔
3510
                    self.mined_stacks_block = false;
5,930✔
3511
                }
5,930✔
3512
            } else {
1,041✔
3513
                // mine a Stacks block first -- we didn't win
1,041✔
3514
                self.mined_stacks_block = false;
1,041✔
3515
            }
1,041✔
3516
            signal_mining_ready(self.globals.get_miner_status());
7,357✔
3517
        }
241✔
3518
        true
7,598✔
3519
    }
7,598✔
3520

3521
    /// Update the miner tip with a new tip.  If it's changed, then clear out the microblock stream
3522
    /// cost since we won't be mining it anymore.
3523
    fn setup_microblock_mining_state(&mut self, new_miner_tip: Option<MinerTip>) {
7,598✔
3524
        // update state
3525
        let my_miner_tip = std::mem::take(&mut self.miner_tip);
7,598✔
3526
        let best_tip = Self::pick_higher_tip(my_miner_tip.clone(), new_miner_tip.clone());
7,598✔
3527
        if best_tip == new_miner_tip && best_tip != my_miner_tip {
7,598✔
3528
            // tip has changed
3529
            debug!("Relayer: Best miner tip went from {my_miner_tip:?} to {new_miner_tip:?}");
6,287✔
3530
            self.microblock_stream_cost = ExecutionCost::ZERO;
6,287✔
3531
        }
1,311✔
3532
        self.miner_tip = best_tip;
7,598✔
3533
    }
7,598✔
3534

3535
    /// Try to resume microblock mining if we don't need to build an anchored block
3536
    fn try_resume_microblock_mining(&mut self) {
12,995✔
3537
        if self.miner_tip.is_some() {
12,995✔
3538
            // we won the highest tenure
3539
            if self.config.node.mine_microblocks {
9,803✔
3540
                // mine a microblock first
9,698✔
3541
                self.mined_stacks_block = true;
9,698✔
3542
            } else {
9,803✔
3543
                // mine a Stacks block first -- we won't build microblocks
105✔
3544
                self.mined_stacks_block = false;
105✔
3545
            }
105✔
3546
        } else {
3,192✔
3547
            // mine a Stacks block first -- we didn't win
3,192✔
3548
            self.mined_stacks_block = false;
3,192✔
3549
        }
3,192✔
3550
    }
12,995✔
3551

3552
    /// Constructs and returns a LeaderKeyRegisterOp out of the provided params
3553
    fn inner_generate_leader_key_register_op(
225✔
3554
        vrf_public_key: VRFPublicKey,
225✔
3555
        consensus_hash: ConsensusHash,
225✔
3556
        miner_pk: Option<&StacksPublicKey>,
225✔
3557
    ) -> BlockstackOperationType {
225✔
3558
        let memo = if let Some(pk) = miner_pk {
225✔
3559
            Hash160::from_node_public_key(pk).as_bytes().to_vec()
193✔
3560
        } else {
3561
            vec![]
32✔
3562
        };
3563
        BlockstackOperationType::LeaderKeyRegister(LeaderKeyRegisterOp {
225✔
3564
            public_key: vrf_public_key,
225✔
3565
            memo,
225✔
3566
            consensus_hash,
225✔
3567
            vtxindex: 0,
225✔
3568
            txid: Txid([0u8; 32]),
225✔
3569
            block_height: 0,
225✔
3570
            burn_header_hash: BurnchainHeaderHash::zero(),
225✔
3571
        })
225✔
3572
    }
225✔
3573

3574
    /// Create and broadcast a VRF public key registration transaction.
3575
    /// Returns true if we succeed in doing so; false if not.
3576
    pub fn rotate_vrf_and_register(&mut self, burn_block: &BlockSnapshot) {
225✔
3577
        if burn_block.block_height == self.last_vrf_key_burn_height {
225✔
3578
            // already in-flight
3579
            return;
×
3580
        }
225✔
3581
        let cur_epoch =
225✔
3582
            SortitionDB::get_stacks_epoch(self.sortdb_ref().conn(), burn_block.block_height)
225✔
3583
                .expect("FATAL: failed to query sortition DB")
225✔
3584
                .expect("FATAL: no epoch defined")
225✔
3585
                .epoch_id;
225✔
3586
        let (vrf_pk, _) = self.keychain.make_vrf_keypair(burn_block.block_height);
225✔
3587

3588
        debug!(
225✔
3589
            "Submit leader-key-register for {} {}",
3590
            &vrf_pk.to_hex(),
×
3591
            burn_block.block_height
3592
        );
3593

3594
        let burnchain_tip_consensus_hash = burn_block.consensus_hash.clone();
225✔
3595
        // if the miner has set a mining key in preparation for epoch-3.0, register it as part of their VRF key registration
3596
        // once implemented in the nakamoto_node, this will allow miners to transition from 2.5 to 3.0 without submitting a new
3597
        // VRF key registration.
3598
        let miner_pk = self
225✔
3599
            .config
225✔
3600
            .miner
225✔
3601
            .mining_key
225✔
3602
            .as_ref()
225✔
3603
            .map(StacksPublicKey::from_private);
225✔
3604
        let op = Self::inner_generate_leader_key_register_op(
225✔
3605
            vrf_pk,
225✔
3606
            burnchain_tip_consensus_hash,
225✔
3607
            miner_pk.as_ref(),
225✔
3608
        );
3609

3610
        let mut one_off_signer = self.keychain.generate_op_signer();
225✔
3611
        if let Ok(txid) =
225✔
3612
            self.bitcoin_controller
225✔
3613
                .submit_operation(cur_epoch, op, &mut one_off_signer)
225✔
3614
        {
225✔
3615
            // advance key registration state
225✔
3616
            self.last_vrf_key_burn_height = burn_block.block_height;
225✔
3617
            self.globals
225✔
3618
                .set_pending_leader_key_registration(burn_block.block_height, txid);
225✔
3619
        }
225✔
3620
    }
225✔
3621

3622
    /// Remove any block state we've mined for the given burnchain height.
3623
    /// Return the filtered `last_mined_blocks`
3624
    fn clear_stale_mined_blocks(burn_height: u64, last_mined_blocks: MinedBlocks) -> MinedBlocks {
7,599✔
3625
        let mut ret = HashMap::new();
7,599✔
3626
        for (stacks_bhh, (assembled_block, microblock_privkey)) in last_mined_blocks.into_iter() {
7,599✔
3627
            if assembled_block.burn_block_height < burn_height {
97✔
3628
                debug!(
95✔
3629
                    "Stale mined block: {stacks_bhh} (as of {},{})",
3630
                    &assembled_block.burn_hash, assembled_block.burn_block_height
×
3631
                );
3632
                continue;
95✔
3633
            }
2✔
3634
            debug!(
2✔
3635
                "Mined block in-flight: {stacks_bhh} (as of {},{})",
3636
                &assembled_block.burn_hash, assembled_block.burn_block_height
×
3637
            );
3638
            ret.insert(stacks_bhh, (assembled_block, microblock_privkey));
2✔
3639
        }
3640
        ret
7,599✔
3641
    }
7,599✔
3642

3643
    /// Create the block miner thread state.
3644
    /// Only proceeds if all of the following are true:
3645
    ///   * The miner is not blocked
3646
    ///   * `last_burn_block` corresponds to the canonical sortition DB's chain tip
3647
    ///   * The time of issuance is sufficiently recent
3648
    ///   * There are no unprocessed stacks blocks in the staging DB
3649
    ///   * The relayer has already tried a download scan that included this sortition (which, if a
3650
    ///     block was found, would have placed it into the staging DB and marked it as
3651
    ///     unprocessed)
3652
    ///   * A miner thread is not running already
3653
    fn create_block_miner(
267,052✔
3654
        &mut self,
267,052✔
3655
        registered_key: RegisteredKey,
267,052✔
3656
        last_burn_block: BlockSnapshot,
267,052✔
3657
        issue_timestamp_ms: u128,
267,052✔
3658
    ) -> Option<BlockMinerThread> {
267,052✔
3659
        if self
267,052✔
3660
            .globals
267,052✔
3661
            .get_miner_status()
267,052✔
3662
            .lock()
267,052✔
3663
            .expect("FATAL: mutex poisoned")
267,052✔
3664
            .is_blocked()
267,052✔
3665
        {
3666
            debug!(
248✔
3667
                "Relayer: miner is blocked as of {}; cannot mine Stacks block at this time",
3668
                &last_burn_block.burn_header_hash
×
3669
            );
3670
            return None;
248✔
3671
        }
266,804✔
3672

3673
        if fault_injection_skip_mining(&self.config.node.rpc_bind, last_burn_block.block_height) {
266,804✔
3674
            debug!(
×
3675
                "Relayer: fault injection skip mining at block height {}",
3676
                last_burn_block.block_height
3677
            );
3678
            return None;
×
3679
        }
266,804✔
3680

3681
        // start a new tenure
3682
        if let Some(cur_sortition) = self.globals.get_last_sortition() {
266,804✔
3683
            if last_burn_block.sortition_id != cur_sortition.sortition_id {
266,804✔
3684
                debug!(
9✔
3685
                    "Relayer: Drop stale RunTenure for {}: current sortition is for {}",
3686
                    &last_burn_block.burn_header_hash, &cur_sortition.burn_header_hash
×
3687
                );
3688
                self.globals.counters.bump_missed_tenures();
9✔
3689
                return None;
9✔
3690
            }
266,795✔
3691
        }
×
3692

3693
        let burn_header_hash = last_burn_block.burn_header_hash.clone();
266,795✔
3694
        let burn_chain_sn = SortitionDB::get_canonical_burn_chain_tip(self.sortdb_ref().conn())
266,795✔
3695
            .expect("FATAL: failed to query sortition DB for canonical burn chain tip");
266,795✔
3696

3697
        let burn_chain_tip = burn_chain_sn.burn_header_hash;
266,795✔
3698

3699
        if burn_chain_tip != burn_header_hash {
266,795✔
3700
            debug!(
8✔
3701
                "Relayer: Drop stale RunTenure for {burn_header_hash}: current sortition is for {burn_chain_tip}"
3702
            );
3703
            self.globals.counters.bump_missed_tenures();
8✔
3704
            return None;
8✔
3705
        }
266,787✔
3706

3707
        let miner_config = self.config.get_miner_config();
266,787✔
3708

3709
        let has_unprocessed = BlockMinerThread::unprocessed_blocks_prevent_mining(
266,787✔
3710
            &self.burnchain,
266,787✔
3711
            self.sortdb_ref(),
266,787✔
3712
            self.chainstate_ref(),
266,787✔
3713
            miner_config.unprocessed_block_deadline_secs,
266,787✔
3714
        );
3715
        if has_unprocessed {
266,787✔
3716
            debug!(
×
3717
                "Relayer: Drop RunTenure for {burn_header_hash} because there are fewer than {} pending blocks",
3718
                self.burnchain.pox_constants.prepare_length - 1
×
3719
            );
3720
            return None;
×
3721
        }
266,787✔
3722

3723
        if burn_chain_sn.block_height != self.last_network_block_height
266,787✔
3724
            || !self.has_waited_for_latest_blocks()
188,546✔
3725
        {
3726
            debug!("Relayer: network has not had a chance to process in-flight blocks ({} != {} || !({}))",
78,241✔
3727
                    burn_chain_sn.block_height, self.last_network_block_height, self.debug_waited_for_latest_blocks());
×
3728
            return None;
78,241✔
3729
        }
188,546✔
3730

3731
        let tenure_cooldown = if self.config.node.mine_microblocks {
188,546✔
3732
            self.config.node.wait_time_for_microblocks as u128
29,435✔
3733
        } else {
3734
            0
159,111✔
3735
        };
3736

3737
        // no burnchain change, so only re-run block tenure every so often in order
3738
        // to give microblocks a chance to collect
3739
        if issue_timestamp_ms < self.last_tenure_issue_time + tenure_cooldown {
188,546✔
3740
            debug!("Relayer: will NOT run tenure since issuance at {} is too fresh (wait until {} + {} = {})",
20,449✔
3741
                    issue_timestamp_ms / 1000, self.last_tenure_issue_time / 1000, tenure_cooldown / 1000, (self.last_tenure_issue_time + tenure_cooldown) / 1000);
×
3742
            return None;
20,449✔
3743
        }
168,097✔
3744

3745
        // if we're still mining on this burn block, then do nothing
3746
        if self.miner_thread.is_some() {
168,097✔
3747
            debug!("Relayer: will NOT run tenure since miner thread is already running for burn tip {burn_chain_tip}");
×
3748
            return None;
×
3749
        }
168,097✔
3750

3751
        debug!(
168,097✔
3752
            "Relayer: Spawn tenure thread";
3753
            "height" => last_burn_block.block_height,
×
3754
            "burn_header_hash" => %burn_header_hash,
3755
        );
3756

3757
        let miner_thread_state =
168,097✔
3758
            BlockMinerThread::from_relayer_thread(self, registered_key, last_burn_block);
168,097✔
3759
        Some(miner_thread_state)
168,097✔
3760
    }
267,052✔
3761

3762
    /// Try to start up a block miner thread with this given VRF key and current burnchain tip.
3763
    /// Returns true if the thread was started; false if it was not (for any reason)
3764
    #[allow(clippy::incompatible_msrv)]
3765
    pub fn block_miner_thread_try_start(
339,435✔
3766
        &mut self,
339,435✔
3767
        registered_key: RegisteredKey,
339,435✔
3768
        last_burn_block: BlockSnapshot,
339,435✔
3769
        issue_timestamp_ms: u128,
339,435✔
3770
    ) -> bool {
339,435✔
3771
        if !self.miner_thread_try_join() {
339,435✔
3772
            return false;
57,830✔
3773
        }
281,605✔
3774

3775
        if !self.config.get_node_config(false).mock_mining {
281,605✔
3776
            // mock miner can't mine microblocks yet, so don't stop it from trying multiple
3777
            // anchored blocks
3778
            if self.mined_stacks_block && self.config.node.mine_microblocks {
280,822✔
3779
                debug!("Relayer: mined a Stacks block already; waiting for microblock miner");
14,553✔
3780
                return false;
14,553✔
3781
            }
266,269✔
3782
        }
783✔
3783

3784
        let Some(mut miner_thread_state) =
168,097✔
3785
            self.create_block_miner(registered_key, last_burn_block, issue_timestamp_ms)
267,052✔
3786
        else {
3787
            return false;
98,955✔
3788
        };
3789

3790
        if let Ok(miner_handle) = thread::Builder::new()
168,097✔
3791
            .name(format!("miner-block-{}", self.local_peer.data_url))
168,097✔
3792
            .stack_size(BLOCK_PROCESSOR_STACK_SIZE)
168,097✔
3793
            .spawn(move || {
168,097✔
3794
                if let Err(e) = miner_thread_state.send_mock_miner_messages() {
168,097✔
3795
                    warn!("Failed to send mock miner messages: {e}");
1✔
3796
                }
168,096✔
3797
                miner_thread_state.run_tenure()
168,097✔
3798
            })
168,097✔
3799
            .inspect_err(|e| error!("Relayer: Failed to start tenure thread: {e:?}"))
168,097✔
3800
        {
168,097✔
3801
            self.miner_thread = Some(miner_handle);
168,097✔
3802
        }
168,097✔
3803

3804
        true
168,097✔
3805
    }
339,435✔
3806

3807
    // TODO: add tests from mutation testing results #4872
3808
    #[cfg_attr(test, mutants::skip)]
3809
    /// See if we should run a microblock tenure now.
3810
    /// Return true if so; false if not
3811
    fn can_run_microblock_tenure(&mut self) -> bool {
1,040,490✔
3812
        if !self.config.node.mine_microblocks {
1,040,490✔
3813
            // not enabled
3814
            test_debug!("Relayer: not configured to mine microblocks");
836,935✔
3815
            return false;
836,935✔
3816
        }
203,555✔
3817

3818
        let burn_tip = SortitionDB::get_canonical_burn_chain_tip(self.sortdb_ref().conn())
203,555✔
3819
            .expect("FATAL: failed to read current burnchain tip");
203,555✔
3820
        let microblocks_disabled =
203,555✔
3821
            SortitionDB::are_microblocks_disabled(self.sortdb_ref().conn(), burn_tip.block_height)
203,555✔
3822
                .expect("FATAL: failed to query epoch's microblock status");
203,555✔
3823

3824
        if microblocks_disabled {
203,555✔
3825
            if cfg!(test)
966✔
3826
                && std::env::var("STACKS_TEST_FORCE_MICROBLOCKS_POST_25").as_deref() == Ok("1")
966✔
3827
            {
3828
                debug!("Allowing miner to mine microblocks because STACKS_TEST_FORCE_MICROBLOCKS_POST_25 = 1");
×
3829
            } else {
3830
                return false;
966✔
3831
            }
3832
        }
202,589✔
3833

3834
        if !self.miner_thread_try_join() {
202,589✔
3835
            // already running (for an anchored block or microblock)
3836
            test_debug!("Relayer: miner thread already running so cannot mine microblock");
53,260✔
3837
            return false;
53,260✔
3838
        }
149,329✔
3839
        if self.microblock_deadline > get_epoch_time_ms() {
149,329✔
3840
            debug!(
9,917✔
3841
                "Relayer: Too soon to start a microblock tenure ({} > {})",
3842
                self.microblock_deadline,
3843
                get_epoch_time_ms()
×
3844
            );
3845
            return false;
9,917✔
3846
        }
139,412✔
3847
        if self.miner_tip.is_none() {
139,412✔
3848
            debug!("Relayer: did not win last block, so cannot mine microblocks");
93,009✔
3849
            return false;
93,009✔
3850
        }
46,403✔
3851
        if !self.mined_stacks_block {
46,403✔
3852
            // have not tried to mine a stacks block yet that confirms previously-mined unconfirmed
3853
            // state (or have not tried to mine a new Stacks block yet for this active tenure);
3854
            debug!("Relayer: Did not mine a block yet, so will not mine a microblock");
35,985✔
3855
            return false;
35,985✔
3856
        }
10,418✔
3857
        if self.globals.get_last_sortition().is_none() {
10,418✔
3858
            debug!("Relayer: no first sortition yet");
×
3859
            return false;
×
3860
        }
10,418✔
3861

3862
        // go ahead
3863
        true
10,418✔
3864
    }
1,040,490✔
3865

3866
    /// Start up a microblock miner thread if possible:
3867
    ///   * No miner thread must be running already
3868
    ///   * The miner must not be blocked
3869
    ///   * We must have won the sortition on the Stacks chain tip
3870
    ///
3871
    /// Returns `true` if the thread was started; `false` if not.
3872
    #[allow(clippy::incompatible_msrv)]
3873
    pub fn microblock_miner_thread_try_start(&mut self) -> bool {
10,418✔
3874
        let miner_tip = match self.miner_tip.as_ref() {
10,418✔
3875
            Some(tip) => tip.clone(),
10,418✔
3876
            None => {
3877
                debug!("Relayer: did not win last block, so cannot mine microblocks");
×
3878
                return false;
×
3879
            }
3880
        };
3881

3882
        let burnchain_tip = match self.globals.get_last_sortition() {
10,418✔
3883
            Some(sn) => sn,
10,418✔
3884
            None => {
3885
                debug!("Relayer: no first sortition yet");
×
3886
                return false;
×
3887
            }
3888
        };
3889

3890
        debug!(
10,418✔
3891
            "Relayer: mined Stacks block {}/{} so can mine microblocks",
3892
            &miner_tip.consensus_hash, &miner_tip.block_hash
×
3893
        );
3894

3895
        if !self.miner_thread_try_join() {
10,418✔
3896
            // already running (for an anchored block or microblock)
3897
            debug!("Relayer: miner thread already running so cannot mine microblock");
×
3898
            return false;
×
3899
        }
10,418✔
3900
        if self
10,418✔
3901
            .globals
10,418✔
3902
            .get_miner_status()
10,418✔
3903
            .lock()
10,418✔
3904
            .expect("FATAL: mutex poisoned")
10,418✔
3905
            .is_blocked()
10,418✔
3906
        {
3907
            debug!(
109✔
3908
                "Relayer: miner is blocked as of {}; cannot mine microblock at this time",
3909
                &burnchain_tip.burn_header_hash
×
3910
            );
3911
            self.globals.counters.set_microblocks_processed(0);
109✔
3912
            return false;
109✔
3913
        }
10,309✔
3914

3915
        let parent_consensus_hash = &miner_tip.consensus_hash;
10,309✔
3916
        let parent_block_hash = &miner_tip.block_hash;
10,309✔
3917

3918
        debug!("Relayer: Run microblock tenure for {parent_consensus_hash}/{parent_block_hash}");
10,309✔
3919

3920
        let Some(mut microblock_thread_state) = MicroblockMinerThread::from_relayer_thread(self)
10,309✔
3921
        else {
3922
            return false;
×
3923
        };
3924

3925
        if let Ok(miner_handle) = thread::Builder::new()
10,309✔
3926
            .name(format!("miner-microblock-{}", self.local_peer.data_url))
10,309✔
3927
            .stack_size(BLOCK_PROCESSOR_STACK_SIZE)
10,309✔
3928
            .spawn(move || {
10,309✔
3929
                Some(MinerThreadResult::Microblock(
10,309✔
3930
                    microblock_thread_state.try_mine_microblock(miner_tip.clone()),
10,309✔
3931
                    miner_tip,
10,309✔
3932
                ))
10,309✔
3933
            })
10,309✔
3934
            .inspect_err(|e| error!("Relayer: Failed to start tenure thread: {e:?}"))
10,309✔
3935
        {
10,309✔
3936
            // thread started!
10,309✔
3937
            self.miner_thread = Some(miner_handle);
10,309✔
3938
            self.microblock_deadline =
10,309✔
3939
                get_epoch_time_ms() + (self.config.node.microblock_frequency as u128);
10,309✔
3940
        }
10,309✔
3941

3942
        true
10,309✔
3943
    }
10,418✔
3944

3945
    /// Inner body of Self::miner_thread_try_join
3946
    fn inner_miner_thread_try_join(
289,424✔
3947
        &mut self,
289,424✔
3948
        thread_handle: JoinHandle<Option<MinerThreadResult>>,
289,424✔
3949
    ) -> Option<JoinHandle<Option<MinerThreadResult>>> {
289,424✔
3950
        // tenure run already in progress; try and join
3951
        if !thread_handle.is_finished() {
289,424✔
3952
            debug!("Relayer: RunTenure thread not finished / is in-progress");
111,193✔
3953
            return Some(thread_handle);
111,193✔
3954
        }
178,231✔
3955
        let last_mined_block_opt = thread_handle
178,231✔
3956
            .join()
178,231✔
3957
            .expect("FATAL: failed to join miner thread");
178,231✔
3958
        self.last_attempt_failed = false;
178,231✔
3959
        if let Some(miner_result) = last_mined_block_opt {
178,231✔
3960
            match miner_result {
16,923✔
3961
                MinerThreadResult::Block(
3962
                    last_mined_block,
6,617✔
3963
                    microblock_privkey,
6,617✔
3964
                    ongoing_commit_opt,
6,617✔
3965
                ) => {
3966
                    // finished mining a block
3967
                    if BlockMinerThread::find_inflight_mined_blocks(
6,617✔
3968
                        last_mined_block.burn_block_height,
6,617✔
3969
                        &self.last_mined_blocks,
6,617✔
3970
                    )
6,617✔
3971
                    .is_empty()
6,617✔
3972
                    {
3973
                        // first time we've mined a block in this burnchain block
3974
                        debug!(
6,536✔
3975
                            "Bump block processed for burnchain block {}",
3976
                            &last_mined_block.burn_block_height
×
3977
                        );
3978
                        self.globals.counters.bump_blocks_processed();
6,536✔
3979
                    }
81✔
3980

3981
                    debug!(
6,617✔
3982
                        "Relayer: RunTenure thread joined; got Stacks block {}",
3983
                        &last_mined_block.anchored_block.block_hash()
×
3984
                    );
3985

3986
                    let bhh = last_mined_block.burn_hash.clone();
6,617✔
3987
                    let orig_bhh = last_mined_block.orig_burn_hash.clone();
6,617✔
3988
                    let tenure_begin = last_mined_block.tenure_begin;
6,617✔
3989

3990
                    self.last_mined_blocks.insert(
6,617✔
3991
                        last_mined_block.anchored_block.block_hash(),
6,617✔
3992
                        (last_mined_block, microblock_privkey),
6,617✔
3993
                    );
3994

3995
                    self.last_tenure_issue_time = get_epoch_time_ms();
6,617✔
3996
                    self.bitcoin_controller
6,617✔
3997
                        .set_ongoing_commit(ongoing_commit_opt);
6,617✔
3998

3999
                    debug!(
6,617✔
4000
                        "Relayer: RunTenure finished at {} (in {}ms) targeting {bhh} (originally {orig_bhh})",
4001
                        self.last_tenure_issue_time,
4002
                        self.last_tenure_issue_time.saturating_sub(tenure_begin)
×
4003
                    );
4004

4005
                    // this stacks block confirms all in-flight microblocks we know about,
4006
                    // including the ones we produced.
4007
                    self.mined_stacks_block = true;
6,617✔
4008
                }
4009
                MinerThreadResult::Microblock(microblock_result, miner_tip) => {
10,306✔
4010
                    // finished mining a microblock
4011
                    match microblock_result {
10,306✔
4012
                        Ok(Some((next_microblock, new_cost))) => {
4✔
4013
                            // apply it
4014
                            let microblock_hash = next_microblock.block_hash();
4✔
4015

4016
                            let (processed_unconfirmed_state, num_mblocks) = self.with_chainstate(
4✔
4017
                                |_relayer_thread, sortdb, chainstate, _mempool| {
4✔
4018
                                    let processed_unconfirmed_state =
4✔
4019
                                        Relayer::refresh_unconfirmed(chainstate, sortdb);
4✔
4020
                                    let num_mblocks = chainstate
4✔
4021
                                        .unconfirmed_state
4✔
4022
                                        .as_ref()
4✔
4023
                                        .map(|unconfirmed| unconfirmed.num_microblocks())
4✔
4024
                                        .unwrap_or(0);
4✔
4025

4026
                                    (processed_unconfirmed_state, num_mblocks)
4✔
4027
                                },
4✔
4028
                            );
4029

4030
                            info!(
4✔
4031
                                "Mined one microblock: {microblock_hash} seq {} txs {} (total processed: {num_mblocks})",
4032
                                next_microblock.header.sequence,
4033
                                next_microblock.txs.len()
4✔
4034
                            );
4035
                            self.globals.counters.set_microblocks_processed(num_mblocks);
4✔
4036

4037
                            let parent_index_block_hash = StacksBlockHeader::make_index_block_hash(
4✔
4038
                                &miner_tip.consensus_hash,
4✔
4039
                                &miner_tip.block_hash,
4✔
4040
                            );
4041
                            self.event_dispatcher.process_new_microblocks(
4✔
4042
                                &parent_index_block_hash,
4✔
4043
                                &processed_unconfirmed_state,
4✔
4044
                            );
4045

4046
                            // send it off
4047
                            if let Err(e) = self.relayer.broadcast_microblock(
4✔
4048
                                &miner_tip.consensus_hash,
4✔
4049
                                &miner_tip.block_hash,
4✔
4050
                                next_microblock,
4✔
4051
                            ) {
4✔
4052
                                error!(
×
4053
                                    "Failure trying to broadcast microblock {microblock_hash}: {e}"
4054
                                );
4055
                            }
4✔
4056

4057
                            self.last_microblock_tenure_time = get_epoch_time_ms();
4✔
4058
                            self.microblock_stream_cost = new_cost;
4✔
4059

4060
                            // synchronise state
4061
                            self.with_chainstate(
4✔
4062
                                |relayer_thread, _sortdb, chainstate, _mempool| {
4✔
4063
                                    relayer_thread.globals.send_unconfirmed_txs(chainstate);
4✔
4064
                                },
4✔
4065
                            );
4066

4067
                            // have not yet mined a stacks block that confirms this microblock, so
4068
                            // do that on the next run
4069
                            self.mined_stacks_block = false;
4✔
4070
                        }
4071
                        Ok(None) => {
4072
                            debug!("Relayer: did not mine microblock in this tenure");
10,302✔
4073

4074
                            // switch back to block mining
4075
                            self.mined_stacks_block = false;
10,302✔
4076
                        }
4077
                        Err(e) => {
×
4078
                            warn!("Relayer: Failed to mine next microblock: {e:?}");
×
4079

4080
                            // switch back to block mining
4081
                            self.mined_stacks_block = false;
×
4082
                        }
4083
                    }
4084
                }
4085
            }
4086
        } else {
4087
            self.last_attempt_failed = true;
161,308✔
4088
            // if we tried and failed to make an anchored block (e.g. because there's nothing to
4089
            // do), then resume microblock mining
4090
            if !self.mined_stacks_block {
161,308✔
4091
                self.try_resume_microblock_mining();
12,995✔
4092
            }
158,766✔
4093
        }
4094
        None
178,231✔
4095
    }
289,424✔
4096

4097
    /// Try to join with the miner thread. If successful, join the thread and return `true`.
4098
    /// Otherwise, if the thread is still running, return `false`.
4099
    ///
4100
    /// Updates internal state gleaned from the miner, such as:
4101
    ///   * New Stacks block data
4102
    ///   * New keychain state
4103
    ///   * New metrics
4104
    ///   * New unconfirmed state
4105
    ///
4106
    /// Returns `true` if joined; `false` if not.
4107
    pub fn miner_thread_try_join(&mut self) -> bool {
560,040✔
4108
        if let Some(thread_handle) = self.miner_thread.take() {
560,040✔
4109
            let new_thread_handle = self.inner_miner_thread_try_join(thread_handle);
289,424✔
4110
            self.miner_thread = new_thread_handle;
289,424✔
4111
        }
483,266✔
4112
        self.miner_thread.is_none()
560,040✔
4113
    }
560,040✔
4114

4115
    /// Try loading up a saved VRF key
4116
    pub(crate) fn load_saved_vrf_key(path: &str) -> Option<RegisteredKey> {
175✔
4117
        let mut f = match fs::File::open(path) {
175✔
4118
            Ok(f) => f,
36✔
4119
            Err(e) => {
139✔
4120
                warn!("Could not open {path}: {e:?}");
139✔
4121
                return None;
139✔
4122
            }
4123
        };
4124
        let mut registered_key_bytes = vec![];
36✔
4125
        if let Err(e) = f.read_to_end(&mut registered_key_bytes) {
36✔
4126
            warn!("Failed to read registered key bytes from {path}: {e:?}");
×
4127
            return None;
×
4128
        }
36✔
4129

4130
        let Ok(registered_key) = serde_json::from_slice(&registered_key_bytes) else {
36✔
4131
            warn!("Did not load registered key from {path}: could not decode JSON");
×
4132
            return None;
×
4133
        };
4134

4135
        info!("Loaded registered key from {path}");
36✔
4136
        Some(registered_key)
36✔
4137
    }
175✔
4138

4139
    /// Top-level dispatcher
4140
    pub fn handle_directive(&mut self, directive: RelayerDirective) -> bool {
1,041,480✔
4141
        debug!("Relayer: received next directive");
1,041,480✔
4142
        let continue_running = match directive {
1,041,480✔
4143
            RelayerDirective::HandleNetResult(net_result) => {
693,199✔
4144
                debug!("Relayer: directive Handle network result");
693,199✔
4145
                self.process_network_result(net_result);
693,199✔
4146
                debug!("Relayer: directive Handled network result");
693,199✔
4147
                true
693,199✔
4148
            }
4149
            RelayerDirective::RegisterKey(last_burn_block) => {
260✔
4150
                let mut saved_key_opt = None;
260✔
4151
                if let Some(path) = self.config.miner.activated_vrf_key_path.as_ref() {
260✔
4152
                    saved_key_opt = Self::load_saved_vrf_key(path);
174✔
4153
                }
260✔
4154
                if let Some(saved_key) = saved_key_opt {
260✔
4155
                    self.globals.resume_leader_key(saved_key);
35✔
4156
                } else {
35✔
4157
                    self.rotate_vrf_and_register(&last_burn_block);
225✔
4158
                    debug!("Relayer: directive Registered VRF key");
225✔
4159
                }
4160
                self.globals.counters.bump_blocks_processed();
260✔
4161
                true
260✔
4162
            }
4163
            RelayerDirective::ProcessTenure(consensus_hash, burn_hash, block_header_hash) => {
7,598✔
4164
                debug!("Relayer: directive Process tenures");
7,598✔
4165
                let res = self.process_new_tenures(consensus_hash, burn_hash, block_header_hash);
7,598✔
4166
                debug!("Relayer: directive Processed tenures");
7,598✔
4167
                res
7,598✔
4168
            }
4169
            RelayerDirective::RunTenure(registered_key, last_burn_block, issue_timestamp_ms) => {
340,423✔
4170
                debug!("Relayer: directive Run tenure");
340,423✔
4171
                let Ok(Some(next_block_epoch)) = SortitionDB::get_stacks_epoch(
340,423✔
4172
                    self.sortdb_ref().conn(),
340,423✔
4173
                    last_burn_block.block_height.saturating_add(1),
340,423✔
4174
                ) else {
4175
                    warn!("Failed to load Stacks Epoch for next burn block, skipping RunTenure directive");
×
4176
                    return true;
×
4177
                };
4178
                if next_block_epoch.epoch_id.uses_nakamoto_blocks() {
340,423✔
4179
                    info!("Next burn block is in Nakamoto epoch, skipping RunTenure directive for 2.x node");
988✔
4180
                    return true;
988✔
4181
                }
339,435✔
4182
                self.block_miner_thread_try_start(
339,435✔
4183
                    registered_key,
339,435✔
4184
                    last_burn_block,
339,435✔
4185
                    issue_timestamp_ms,
339,435✔
4186
                );
4187
                debug!("Relayer: directive Ran tenure");
339,435✔
4188
                true
339,435✔
4189
            }
4190
            RelayerDirective::NakamotoTenureStartProcessed(_, _) => {
4191
                warn!("Relayer: Nakamoto tenure start notification received while still operating 2.x neon node");
×
4192
                true
×
4193
            }
4194
            RelayerDirective::Exit => false,
×
4195
        };
4196
        if !continue_running {
1,040,492✔
4197
            return false;
2✔
4198
        }
1,040,490✔
4199

4200
        // see if we need to run a microblock tenure
4201
        if self.can_run_microblock_tenure() {
1,040,490✔
4202
            self.microblock_miner_thread_try_start();
10,418✔
4203
        }
1,030,072✔
4204
        continue_running
1,040,490✔
4205
    }
1,041,480✔
4206
}
4207

4208
impl ParentStacksBlockInfo {
4209
    /// Determine where in the set of forks to attempt to mine the next anchored block.
4210
    /// `mine_tip_ch` and `mine_tip_bhh` identify the parent block on top of which to mine.
4211
    /// `check_burn_block` identifies what we believe to be the burn chain's sortition history tip.
4212
    /// This is used to mitigate (but not eliminate) a TOCTTOU issue with mining: the caller's
4213
    /// conception of the sortition history tip may have become stale by the time they call this
4214
    /// method, in which case, mining should *not* happen (since the block will be invalid).
4215
    pub fn lookup(
167,674✔
4216
        chain_state: &mut StacksChainState,
167,674✔
4217
        burn_db: &mut SortitionDB,
167,674✔
4218
        check_burn_block: &BlockSnapshot,
167,674✔
4219
        miner_address: StacksAddress,
167,674✔
4220
        mine_tip_ch: &ConsensusHash,
167,674✔
4221
        mine_tip_bh: &BlockHeaderHash,
167,674✔
4222
    ) -> Result<ParentStacksBlockInfo, Error> {
167,674✔
4223
        let stacks_tip_header = StacksChainState::get_anchored_block_header_info(
167,674✔
4224
            chain_state.db(),
167,674✔
4225
            mine_tip_ch,
167,674✔
4226
            mine_tip_bh,
167,674✔
4227
        )
4228
        .unwrap()
167,674✔
4229
        .ok_or_else(|| {
167,674✔
4230
            error!(
×
4231
                "Could not mine new tenure, since could not find header for known chain tip.";
4232
                "tip_consensus_hash" => %mine_tip_ch,
4233
                "tip_stacks_block_hash" => %mine_tip_bh
4234
            );
4235
            Error::HeaderNotFoundForChainTip
×
4236
        })?;
×
4237

4238
        // the stacks block I'm mining off of's burn header hash and vtxindex:
4239
        let parent_snapshot =
167,674✔
4240
            SortitionDB::get_block_snapshot_consensus(burn_db.conn(), mine_tip_ch)
167,674✔
4241
                .expect("Failed to look up block's parent snapshot")
167,674✔
4242
                .expect("Failed to look up block's parent snapshot");
167,674✔
4243

4244
        let parent_sortition_id = &parent_snapshot.sortition_id;
167,674✔
4245

4246
        let (parent_block_height, parent_winning_vtxindex, parent_block_total_burn) = if mine_tip_ch
167,674✔
4247
            == &FIRST_BURNCHAIN_CONSENSUS_HASH
167,674✔
4248
        {
4249
            (0, 0, 0)
×
4250
        } else {
4251
            let parent_winning_vtxindex =
167,674✔
4252
                SortitionDB::get_block_winning_vtxindex(burn_db.conn(), parent_sortition_id)
167,674✔
4253
                    .expect("SortitionDB failure.")
167,674✔
4254
                    .ok_or_else(|| {
167,674✔
4255
                        error!(
×
4256
                            "Failed to find winning vtx index for the parent sortition";
4257
                            "parent_sortition_id" => %parent_sortition_id
4258
                        );
4259
                        Error::WinningVtxNotFoundForChainTip
×
4260
                    })?;
×
4261

4262
            let parent_block = SortitionDB::get_block_snapshot(burn_db.conn(), parent_sortition_id)
167,674✔
4263
                .expect("SortitionDB failure.")
167,674✔
4264
                .ok_or_else(|| {
167,674✔
4265
                    error!(
×
4266
                        "Failed to find block snapshot for the parent sortition";
4267
                        "parent_sortition_id" => %parent_sortition_id
4268
                    );
4269
                    Error::SnapshotNotFoundForChainTip
×
4270
                })?;
×
4271

4272
            (
167,674✔
4273
                parent_block.block_height,
167,674✔
4274
                parent_winning_vtxindex,
167,674✔
4275
                parent_block.total_burn,
167,674✔
4276
            )
167,674✔
4277
        };
4278

4279
        // don't mine off of an old burnchain block
4280
        let burn_chain_tip = SortitionDB::get_canonical_burn_chain_tip(burn_db.conn())
167,674✔
4281
            .expect("FATAL: failed to query sortition DB for canonical burn chain tip");
167,674✔
4282

4283
        if burn_chain_tip.consensus_hash != check_burn_block.consensus_hash {
167,674✔
4284
            info!(
42✔
4285
                "New canonical burn chain tip detected. Will not try to mine.";
4286
                "new_consensus_hash" => %burn_chain_tip.consensus_hash,
4287
                "old_consensus_hash" => %check_burn_block.consensus_hash,
4288
                "new_burn_height" => burn_chain_tip.block_height,
42✔
4289
                "old_burn_height" => check_burn_block.block_height
42✔
4290
            );
4291
            return Err(Error::BurnchainTipChanged);
42✔
4292
        }
167,632✔
4293

4294
        debug!("Mining tenure's last consensus hash: {} (height {} hash {}), stacks tip consensus hash: {mine_tip_ch} (height {} hash {})",
167,632✔
4295
               &check_burn_block.consensus_hash, check_burn_block.block_height, &check_burn_block.burn_header_hash,
×
4296
               parent_snapshot.block_height, &parent_snapshot.burn_header_hash);
×
4297

4298
        let coinbase_nonce = {
167,632✔
4299
            let principal = miner_address.into();
167,632✔
4300
            let account = chain_state
167,632✔
4301
                .with_read_only_clarity_tx(
167,632✔
4302
                    &burn_db.index_handle(&burn_chain_tip.sortition_id),
167,632✔
4303
                    &StacksBlockHeader::make_index_block_hash(mine_tip_ch, mine_tip_bh),
167,632✔
4304
                    |conn| StacksChainState::get_account(conn, &principal),
167,632✔
4305
                )
4306
                .unwrap_or_else(|| {
167,632✔
4307
                    panic!(
×
4308
                        "BUG: stacks tip block {mine_tip_ch}/{mine_tip_bh} no longer exists after we queried it"
4309
                    )
4310
                });
4311
            account.nonce
167,632✔
4312
        };
4313

4314
        Ok(ParentStacksBlockInfo {
167,632✔
4315
            stacks_parent_header: stacks_tip_header,
167,632✔
4316
            parent_consensus_hash: mine_tip_ch.clone(),
167,632✔
4317
            parent_block_burn_height: parent_block_height,
167,632✔
4318
            parent_block_total_burn,
167,632✔
4319
            parent_winning_vtxindex,
167,632✔
4320
            coinbase_nonce,
167,632✔
4321
        })
167,632✔
4322
    }
167,674✔
4323
}
4324

4325
/// Thread that runs the network state machine, handling both p2p and http requests.
4326
pub struct PeerThread {
4327
    /// Node config
4328
    config: Config,
4329
    /// instance of the peer network. Made optional in order to trick the borrow checker.
4330
    net: Option<PeerNetwork>,
4331
    /// handle to global inter-thread comms
4332
    globals: Globals,
4333
    /// how long to wait for network messages on each poll, in millis
4334
    poll_timeout: u64,
4335
    /// handle to the sortition DB (optional so we can take/replace it)
4336
    sortdb: Option<SortitionDB>,
4337
    /// handle to the chainstate DB (optional so we can take/replace it)
4338
    chainstate: Option<StacksChainState>,
4339
    /// handle to the mempool DB (optional so we can take/replace it)
4340
    mempool: Option<MemPoolDB>,
4341
    /// buffer of relayer commands with block data that couldn't be sent to the relayer just yet
4342
    /// (i.e. due to backpressure).  We track this separately, instead of just using a bigger
4343
    /// channel, because we need to know when backpressure occurs in order to throttle the p2p
4344
    /// thread's downloader.
4345
    results_with_data: VecDeque<RelayerDirective>,
4346
    /// total number of p2p state-machine passes so far. Used to signal when to download the next
4347
    /// reward cycle of blocks
4348
    num_p2p_state_machine_passes: u64,
4349
    /// total number of inventory state-machine passes so far. Used to signal when to download the
4350
    /// next reward cycle of blocks.
4351
    num_inv_sync_passes: u64,
4352
    /// total number of download state-machine passes so far. Used to signal when to download the
4353
    /// next reward cycle of blocks.
4354
    num_download_passes: u64,
4355
    /// last burnchain block seen in the PeerNetwork's chain view since the last run
4356
    last_burn_block_height: u64,
4357
}
4358

4359
impl PeerThread {
4360
    /// set up the mempool DB connection
4361
    pub fn connect_mempool_db(config: &Config) -> MemPoolDB {
275✔
4362
        // create estimators, metric instances for RPC handler
4363
        let cost_estimator = config
275✔
4364
            .make_cost_estimator()
275✔
4365
            .unwrap_or_else(|| Box::new(UnitEstimator));
275✔
4366
        let metric = config
275✔
4367
            .make_cost_metric()
275✔
4368
            .unwrap_or_else(|| Box::new(UnitMetric));
275✔
4369

4370
        MemPoolDB::open(
275✔
4371
            config.is_mainnet(),
275✔
4372
            config.burnchain.chain_id,
275✔
4373
            &config.get_chainstate_path_str(),
275✔
4374
            cost_estimator,
275✔
4375
            metric,
275✔
4376
        )
4377
        .expect("Database failure opening mempool")
275✔
4378
    }
275✔
4379

4380
    /// Instantiate the p2p thread.
4381
    /// Binds the addresses in the config (which may panic if the port is blocked).
4382
    /// This is so the node will crash "early" before any new threads start if there's going to be
4383
    /// a bind error anyway.
4384
    pub fn new(runloop: &RunLoop, net: PeerNetwork) -> PeerThread {
275✔
4385
        Self::new_all(
275✔
4386
            runloop.get_globals(),
275✔
4387
            runloop.config(),
275✔
4388
            runloop.get_burnchain().pox_constants,
275✔
4389
            net,
275✔
4390
        )
4391
    }
275✔
4392

4393
    pub fn new_all(
275✔
4394
        globals: Globals,
275✔
4395
        config: &Config,
275✔
4396
        pox_constants: PoxConstants,
275✔
4397
        mut net: PeerNetwork,
275✔
4398
    ) -> Self {
275✔
4399
        let config = config.clone();
275✔
4400
        let mempool = Self::connect_mempool_db(&config);
275✔
4401
        let burn_db_path = config.get_burn_db_file_path();
275✔
4402

4403
        let sortdb = SortitionDB::open(
275✔
4404
            &burn_db_path,
275✔
4405
            false,
4406
            pox_constants,
275✔
4407
            Some(config.node.get_marf_opts()),
275✔
4408
        )
4409
        .expect("FATAL: could not open sortition DB");
275✔
4410

4411
        let chainstate =
275✔
4412
            open_chainstate_with_faults(&config).expect("FATAL: could not open chainstate DB");
275✔
4413

4414
        let p2p_sock: SocketAddr = config
275✔
4415
            .node
275✔
4416
            .p2p_bind
275✔
4417
            .parse()
275✔
4418
            .unwrap_or_else(|_| panic!("Failed to parse socket: {}", &config.node.p2p_bind));
275✔
4419
        let rpc_sock = config
275✔
4420
            .node
275✔
4421
            .rpc_bind
275✔
4422
            .parse()
275✔
4423
            .unwrap_or_else(|_| panic!("Failed to parse socket: {}", &config.node.rpc_bind));
275✔
4424

4425
        net.bind(&p2p_sock, &rpc_sock)
275✔
4426
            .expect("BUG: PeerNetwork could not bind or is already bound");
275✔
4427

4428
        let poll_timeout = config.get_poll_time();
275✔
4429

4430
        PeerThread {
275✔
4431
            config,
275✔
4432
            net: Some(net),
275✔
4433
            globals,
275✔
4434
            poll_timeout,
275✔
4435
            sortdb: Some(sortdb),
275✔
4436
            chainstate: Some(chainstate),
275✔
4437
            mempool: Some(mempool),
275✔
4438
            results_with_data: VecDeque::new(),
275✔
4439
            num_p2p_state_machine_passes: 0,
275✔
4440
            num_inv_sync_passes: 0,
275✔
4441
            num_download_passes: 0,
275✔
4442
            last_burn_block_height: 0,
275✔
4443
        }
275✔
4444
    }
275✔
4445

4446
    /// Do something with mutable references to the mempool, sortdb, and chainstate
4447
    /// Fools the borrow checker.
4448
    /// NOT COMPOSIBLE
4449
    fn with_chainstate<F, R>(&mut self, func: F) -> R
1,742,935✔
4450
    where
1,742,935✔
4451
        F: FnOnce(&mut PeerThread, &mut SortitionDB, &mut StacksChainState, &mut MemPoolDB) -> R,
1,742,935✔
4452
    {
4453
        let mut sortdb = self.sortdb.take().expect("BUG: sortdb already taken");
1,742,935✔
4454
        let mut chainstate = self
1,742,935✔
4455
            .chainstate
1,742,935✔
4456
            .take()
1,742,935✔
4457
            .expect("BUG: chainstate already taken");
1,742,935✔
4458
        let mut mempool = self.mempool.take().expect("BUG: mempool already taken");
1,742,935✔
4459

4460
        let res = func(self, &mut sortdb, &mut chainstate, &mut mempool);
1,742,935✔
4461

4462
        self.sortdb = Some(sortdb);
1,742,935✔
4463
        self.chainstate = Some(chainstate);
1,742,935✔
4464
        self.mempool = Some(mempool);
1,742,935✔
4465

4466
        res
1,742,935✔
4467
    }
1,742,935✔
4468

4469
    /// Get an immutable ref to the inner network.
4470
    /// DO NOT USE WITHIN with_network()
4471
    fn get_network(&self) -> &PeerNetwork {
867,444✔
4472
        self.net.as_ref().expect("BUG: did not replace net")
867,444✔
4473
    }
867,444✔
4474

4475
    /// Do something with mutable references to the network.
4476
    /// Fools the borrow checker.
4477
    /// NOT COMPOSIBLE. DO NOT CALL THIS OR get_network() IN func
4478
    fn with_network<F, R>(&mut self, func: F) -> R
871,467✔
4479
    where
871,467✔
4480
        F: FnOnce(&mut PeerThread, &mut PeerNetwork) -> R,
871,467✔
4481
    {
4482
        let mut net = self.net.take().expect("BUG: net already taken");
871,467✔
4483

4484
        let res = func(self, &mut net);
871,467✔
4485

4486
        self.net = Some(net);
871,467✔
4487
        res
871,467✔
4488
    }
871,467✔
4489

4490
    /// Run one pass of the p2p/http state machine
4491
    /// Return true if we should continue running passes; false if not
4492
    #[allow(clippy::borrowed_box)]
4493
    pub fn run_one_pass<B: BurnchainHeaderReader>(
871,468✔
4494
        &mut self,
871,468✔
4495
        indexer: &B,
871,468✔
4496
        dns_client_opt: Option<&mut DNSClient>,
871,468✔
4497
        event_dispatcher: &EventDispatcher,
871,468✔
4498
        cost_estimator: &Box<dyn CostEstimator>,
871,468✔
4499
        cost_metric: &Box<dyn CostMetric>,
871,468✔
4500
        fee_estimator: Option<&Box<dyn FeeEstimator>>,
871,468✔
4501
    ) -> bool {
871,468✔
4502
        // initial block download?
4503
        let ibd = self.globals.sync_comms.get_ibd();
871,468✔
4504
        let download_backpressure = !self.results_with_data.is_empty();
871,468✔
4505
        let poll_ms = if !download_backpressure && self.get_network().has_more_downloads() {
871,468✔
4506
            // keep getting those blocks -- drive the downloader state-machine
4507
            debug!(
40,439✔
4508
                "P2P: backpressure: {download_backpressure}, more downloads: {}",
4509
                self.get_network().has_more_downloads()
×
4510
            );
4511
            1
40,439✔
4512
        } else {
4513
            self.poll_timeout
831,029✔
4514
        };
4515

4516
        // move over unconfirmed state obtained from the relayer
4517
        self.with_chainstate(|p2p_thread, sortdb, chainstate, _mempool| {
871,468✔
4518
            let _ = Relayer::setup_unconfirmed_state_readonly(chainstate, sortdb);
871,468✔
4519
            p2p_thread.globals.recv_unconfirmed_txs(chainstate);
871,468✔
4520
        });
871,468✔
4521

4522
        let txindex = self.config.node.txindex;
871,468✔
4523

4524
        // do one pass
4525
        let p2p_res = self.with_chainstate(|p2p_thread, sortdb, chainstate, mempool| {
871,468✔
4526
            // NOTE: handler_args must be created such that it outlives the inner net.run() call and
4527
            // doesn't ref anything within p2p_thread.
4528
            let handler_args = RPCHandlerArgs {
871,467✔
4529
                exit_at_block_height: p2p_thread.config.burnchain.process_exit_at_block_height,
871,467✔
4530
                genesis_chainstate_hash: Sha256Sum::from_hex(stx_genesis::GENESIS_CHAINSTATE_HASH)
871,467✔
4531
                    .unwrap(),
871,467✔
4532
                event_observer: Some(event_dispatcher),
871,467✔
4533
                cost_estimator: Some(cost_estimator.as_ref()),
871,467✔
4534
                cost_metric: Some(cost_metric.as_ref()),
871,467✔
4535
                fee_estimator: fee_estimator.map(|boxed_estimator| boxed_estimator.as_ref()),
871,467✔
4536
                ..RPCHandlerArgs::default()
871,467✔
4537
            };
4538
            p2p_thread.with_network(|_, net| {
871,467✔
4539
                net.run(
871,467✔
4540
                    indexer,
871,467✔
4541
                    sortdb,
871,467✔
4542
                    chainstate,
871,467✔
4543
                    mempool,
871,467✔
4544
                    dns_client_opt,
871,467✔
4545
                    download_backpressure,
871,467✔
4546
                    ibd,
871,467✔
4547
                    poll_ms,
871,467✔
4548
                    &handler_args,
871,467✔
4549
                    txindex,
871,467✔
4550
                )
4551
            })
871,467✔
4552
        });
871,467✔
4553

4554
        match p2p_res {
871,468✔
4555
            Ok(network_result) => {
871,432✔
4556
                let mut have_update = false;
871,432✔
4557
                if self.num_p2p_state_machine_passes < network_result.num_state_machine_passes {
871,432✔
4558
                    // p2p state-machine did a full pass. Notify anyone listening.
670,660✔
4559
                    self.globals.sync_comms.notify_p2p_state_pass();
670,660✔
4560
                    self.num_p2p_state_machine_passes = network_result.num_state_machine_passes;
670,660✔
4561
                }
691,160✔
4562

4563
                if self.num_inv_sync_passes < network_result.num_inv_sync_passes {
871,432✔
4564
                    // inv-sync state-machine did a full pass. Notify anyone listening.
513,704✔
4565
                    self.globals.sync_comms.notify_inv_sync_pass();
513,704✔
4566
                    self.num_inv_sync_passes = network_result.num_inv_sync_passes;
513,704✔
4567

513,704✔
4568
                    // the relayer cares about the number of inventory passes, so pass this along
513,704✔
4569
                    have_update = true;
513,704✔
4570
                }
736,565✔
4571

4572
                if self.num_download_passes < network_result.num_download_passes {
871,432✔
4573
                    // download state-machine did a full pass.  Notify anyone listening.
167,651✔
4574
                    self.globals.sync_comms.notify_download_pass();
167,651✔
4575
                    self.num_download_passes = network_result.num_download_passes;
167,651✔
4576

167,651✔
4577
                    // the relayer cares about the number of download passes, so pass this along
167,651✔
4578
                    have_update = true;
167,651✔
4579
                }
703,781✔
4580

4581
                if network_result.has_data_to_store()
871,432✔
4582
                    || self.last_burn_block_height != network_result.burn_height
820,262✔
4583
                    || have_update
801,401✔
4584
                {
693,461✔
4585
                    // pass along if we have blocks, microblocks, or transactions, or a status
693,461✔
4586
                    // update on the network's view of the burnchain
693,461✔
4587
                    self.last_burn_block_height = network_result.burn_height;
693,461✔
4588
                    self.results_with_data
693,461✔
4589
                        .push_back(RelayerDirective::HandleNetResult(network_result));
693,461✔
4590
                }
708,548✔
4591
            }
4592
            Err(e) => {
36✔
4593
                // this is only reachable if the network is not instantiated correctly --
4594
                // i.e. you didn't connect it
4595
                panic!("P2P: Failed to process network dispatch: {e:?}");
36✔
4596
            }
4597
        };
4598

4599
        while let Some(next_result) = self.results_with_data.pop_front() {
1,564,809✔
4600
            // have blocks, microblocks, and/or transactions (don't care about anything else),
4601
            // or a directive to mine microblocks
4602
            if let Err(e) = self.globals.relay_send.try_send(next_result) {
697,485✔
4603
                debug!(
4,108✔
4604
                    "P2P: {:?}: download backpressure detected (bufferred {})",
4605
                    &self.get_network().local_peer,
×
4606
                    self.results_with_data.len()
×
4607
                );
4608
                match e {
4,108✔
4609
                    TrySendError::Full(directive) => {
4,024✔
4610
                        if let RelayerDirective::RunTenure(..) = directive {
4,024✔
4611
                            // can drop this
×
4612
                        } else {
4,024✔
4613
                            // don't lose this data -- just try it again
4,024✔
4614
                            self.results_with_data.push_front(directive);
4,024✔
4615
                        }
4,024✔
4616
                        break;
4,024✔
4617
                    }
4618
                    TrySendError::Disconnected(_) => {
4619
                        info!("P2P: Relayer hang up with p2p channel");
84✔
4620
                        self.globals.signal_stop();
84✔
4621
                        return false;
84✔
4622
                    }
4623
                }
4624
            } else {
4625
                debug!("P2P: Dispatched result to Relayer!");
693,377✔
4626
            }
4627
        }
4628

4629
        true
871,348✔
4630
    }
871,432✔
4631
}
4632

4633
impl StacksNode {
4634
    /// Create a StacksPrivateKey from a given seed buffer
4635
    pub fn make_node_private_key_from_seed(seed: &[u8]) -> StacksPrivateKey {
×
4636
        let node_privkey = {
×
4637
            let mut re_hashed_seed = seed.to_vec();
×
4638
            let my_private_key = loop {
×
4639
                match Secp256k1PrivateKey::from_slice(&re_hashed_seed[..]) {
×
4640
                    Ok(sk) => break sk,
×
4641
                    Err(_) => {
4642
                        re_hashed_seed = Sha256Sum::from_data(&re_hashed_seed[..])
×
4643
                            .as_bytes()
×
4644
                            .to_vec()
×
4645
                    }
4646
                }
4647
            };
4648
            my_private_key
×
4649
        };
4650
        node_privkey
×
4651
    }
×
4652

4653
    /// Set up the mempool DB by making sure it exists.
4654
    /// Panics on failure.
4655
    fn setup_mempool_db(config: &Config) -> MemPoolDB {
275✔
4656
        // force early mempool instantiation
4657
        let cost_estimator = config
275✔
4658
            .make_cost_estimator()
275✔
4659
            .unwrap_or_else(|| Box::new(UnitEstimator));
275✔
4660
        let metric = config
275✔
4661
            .make_cost_metric()
275✔
4662
            .unwrap_or_else(|| Box::new(UnitMetric));
275✔
4663

4664
        MemPoolDB::open(
275✔
4665
            config.is_mainnet(),
275✔
4666
            config.burnchain.chain_id,
275✔
4667
            &config.get_chainstate_path_str(),
275✔
4668
            cost_estimator,
275✔
4669
            metric,
275✔
4670
        )
4671
        .expect("BUG: failed to instantiate mempool")
275✔
4672
    }
275✔
4673

4674
    /// Set up the Peer DB and update any soft state from the config file. This includes:
4675
    ///   * Blacklisted/whitelisted nodes
4676
    ///   * Node keys
4677
    ///   * Bootstrap nodes
4678
    ///
4679
    /// Returns the instantiated `PeerDB`.
4680
    ///
4681
    /// Panics on failure.
4682
    fn setup_peer_db(
278✔
4683
        config: &Config,
278✔
4684
        burnchain: &Burnchain,
278✔
4685
        stackerdb_contract_ids: &[QualifiedContractIdentifier],
278✔
4686
    ) -> PeerDB {
278✔
4687
        let data_url = UrlString::try_from(config.node.data_url.to_string()).unwrap();
278✔
4688
        let initial_neighbors = config.node.bootstrap_node.clone();
278✔
4689
        if !initial_neighbors.is_empty() {
278✔
4690
            info!(
51✔
4691
                "Will bootstrap from peers {}",
4692
                VecDisplay(&initial_neighbors)
51✔
4693
            );
4694
        } else {
4695
            warn!("Without a peer to bootstrap from, the node will start mining a new chain");
227✔
4696
        }
4697

4698
        let p2p_sock: SocketAddr = config
278✔
4699
            .node
278✔
4700
            .p2p_bind
278✔
4701
            .parse()
278✔
4702
            .unwrap_or_else(|_| panic!("Failed to parse socket: {}", &config.node.p2p_bind));
278✔
4703
        let p2p_addr: SocketAddr = config
278✔
4704
            .node
278✔
4705
            .p2p_address
278✔
4706
            .parse()
278✔
4707
            .unwrap_or_else(|_| panic!("Failed to parse socket: {}", &config.node.p2p_address));
278✔
4708
        let node_privkey = Secp256k1PrivateKey::from_seed(&config.node.local_peer_seed);
278✔
4709

4710
        let mut peerdb = PeerDB::connect(
278✔
4711
            &config.get_peer_db_file_path(),
278✔
4712
            true,
4713
            config.burnchain.chain_id,
278✔
4714
            burnchain.network_id,
278✔
4715
            Some(node_privkey),
278✔
4716
            config.connection_options.private_key_lifetime,
278✔
4717
            PeerAddress::from_socketaddr(&p2p_addr),
278✔
4718
            p2p_sock.port(),
278✔
4719
            data_url,
278✔
4720
            &[],
278✔
4721
            Some(&initial_neighbors),
278✔
4722
            stackerdb_contract_ids,
278✔
4723
        )
4724
        .map_err(|e| {
278✔
4725
            eprintln!("Failed to open {}: {e:?}", &config.get_peer_db_file_path());
×
4726
            panic!();
×
4727
        })
4728
        .unwrap();
278✔
4729

4730
        // allow all bootstrap nodes
4731
        {
4732
            let tx = peerdb.tx_begin().unwrap();
278✔
4733
            for initial_neighbor in initial_neighbors.iter() {
278✔
4734
                // update peer in case public key changed
51✔
4735
                PeerDB::update_peer(&tx, initial_neighbor).unwrap();
51✔
4736
                PeerDB::set_allow_peer(
51✔
4737
                    &tx,
51✔
4738
                    initial_neighbor.addr.network_id,
51✔
4739
                    &initial_neighbor.addr.addrbytes,
51✔
4740
                    initial_neighbor.addr.port,
51✔
4741
                    -1,
51✔
4742
                )
51✔
4743
                .unwrap();
51✔
4744
            }
51✔
4745
            tx.commit().unwrap();
278✔
4746
        }
4747

4748
        if !config.node.deny_nodes.is_empty() {
278✔
4749
            warn!("Will ignore nodes {:?}", &config.node.deny_nodes);
×
4750
        }
278✔
4751

4752
        // deny all config-denied peers
4753
        {
4754
            let tx = peerdb.tx_begin().unwrap();
278✔
4755
            for denied in config.node.deny_nodes.iter() {
278✔
4756
                PeerDB::set_deny_peer(
×
4757
                    &tx,
×
4758
                    denied.addr.network_id,
×
4759
                    &denied.addr.addrbytes,
×
4760
                    denied.addr.port,
×
4761
                    get_epoch_time_secs() + 24 * 365 * 3600,
×
4762
                )
×
4763
                .unwrap();
×
4764
            }
×
4765
            tx.commit().unwrap();
278✔
4766
        }
4767

4768
        // update services to indicate we can support mempool sync and stackerdb
4769
        {
278✔
4770
            let tx = peerdb.tx_begin().unwrap();
278✔
4771
            PeerDB::set_local_services(
278✔
4772
                &tx,
278✔
4773
                (ServiceFlags::RPC as u16)
278✔
4774
                    | (ServiceFlags::RELAY as u16)
278✔
4775
                    | (ServiceFlags::STACKERDB as u16),
278✔
4776
            )
278✔
4777
            .unwrap();
278✔
4778
            tx.commit().unwrap();
278✔
4779
        }
278✔
4780

4781
        peerdb
278✔
4782
    }
278✔
4783

4784
    /// Set up the PeerNetwork, but do not bind it.
4785
    pub(crate) fn setup_peer_network(
278✔
4786
        config: &Config,
278✔
4787
        atlas_config: &AtlasConfig,
278✔
4788
        burnchain: Burnchain,
278✔
4789
    ) -> PeerNetwork {
278✔
4790
        let sortdb = SortitionDB::open(
278✔
4791
            &config.get_burn_db_file_path(),
278✔
4792
            true,
4793
            burnchain.pox_constants.clone(),
278✔
4794
            Some(config.node.get_marf_opts()),
278✔
4795
        )
4796
        .expect("Error while instantiating sor/tition db");
278✔
4797

4798
        let epochs_vec = SortitionDB::get_stacks_epochs(sortdb.conn())
278✔
4799
            .expect("Error while loading stacks epochs");
278✔
4800
        let epochs = EpochList::new(&epochs_vec);
278✔
4801

4802
        let view = {
278✔
4803
            let sortition_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
278✔
4804
                .expect("Failed to get sortition tip");
278✔
4805
            SortitionDB::get_burnchain_view(&sortdb.index_conn(), &burnchain, &sortition_tip)
278✔
4806
                .unwrap()
278✔
4807
        };
4808

4809
        let atlasdb =
278✔
4810
            AtlasDB::connect(atlas_config.clone(), &config.get_atlas_db_file_path(), true).unwrap();
278✔
4811

4812
        let mut chainstate =
278✔
4813
            open_chainstate_with_faults(config).expect("FATAL: could not open chainstate DB");
278✔
4814

4815
        let mut stackerdb_machines = HashMap::new();
278✔
4816
        let mut stackerdbs = StackerDBs::connect(&config.get_stacker_db_file_path(), true).unwrap();
278✔
4817

4818
        let mut stackerdb_configs = HashMap::new();
278✔
4819
        for contract in config.node.stacker_dbs.iter() {
6,544✔
4820
            stackerdb_configs.insert(contract.clone(), StackerDBConfig::noop());
6,507✔
4821
        }
6,507✔
4822
        let stackerdb_configs = stackerdbs
278✔
4823
            .create_or_reconfigure_stackerdbs(
278✔
4824
                &mut chainstate,
278✔
4825
                &sortdb,
278✔
4826
                stackerdb_configs,
278✔
4827
                &config.connection_options,
278✔
4828
            )
4829
            .unwrap();
278✔
4830

4831
        let stackerdb_contract_ids: Vec<QualifiedContractIdentifier> =
278✔
4832
            stackerdb_configs.keys().cloned().collect();
278✔
4833
        for (contract_id, stackerdb_config) in stackerdb_configs {
6,544✔
4834
            let stackerdbs = StackerDBs::connect(&config.get_stacker_db_file_path(), true).unwrap();
6,507✔
4835
            let stacker_db_sync = StackerDBSync::new(
6,507✔
4836
                contract_id.clone(),
6,507✔
4837
                &stackerdb_config,
6,507✔
4838
                PeerNetworkComms::new(),
6,507✔
4839
                stackerdbs,
6,507✔
4840
            );
6,507✔
4841
            stackerdb_machines.insert(contract_id, (stackerdb_config, stacker_db_sync));
6,507✔
4842
        }
6,507✔
4843
        let peerdb = Self::setup_peer_db(config, &burnchain, &stackerdb_contract_ids);
278✔
4844
        let burnchain_db = burnchain
278✔
4845
            .open_burnchain_db(false)
278✔
4846
            .expect("Failed to open burnchain DB");
278✔
4847

4848
        let local_peer = match PeerDB::get_local_peer(peerdb.conn()) {
278✔
4849
            Ok(local_peer) => local_peer,
278✔
4850
            _ => panic!("Unable to retrieve local peer"),
×
4851
        };
4852

4853
        PeerNetwork::new(
278✔
4854
            peerdb,
278✔
4855
            atlasdb,
278✔
4856
            stackerdbs,
278✔
4857
            burnchain_db,
278✔
4858
            local_peer,
278✔
4859
            config.burnchain.peer_version,
278✔
4860
            burnchain,
278✔
4861
            view,
278✔
4862
            config.connection_options.clone(),
278✔
4863
            stackerdb_machines,
278✔
4864
            epochs,
278✔
4865
        )
4866
    }
278✔
4867

4868
    /// Main loop of the relayer.
4869
    /// Runs in a separate thread.
4870
    /// Continuously receives
4871
    pub fn relayer_main(mut relayer_thread: RelayerThread, relay_recv: Receiver<RelayerDirective>) {
275✔
4872
        while let Ok(directive) = relay_recv.recv() {
1,041,752✔
4873
            if !relayer_thread.globals.keep_running() {
1,041,752✔
4874
                break;
272✔
4875
            }
1,041,480✔
4876

4877
            if !relayer_thread.handle_directive(directive) {
1,041,480✔
4878
                break;
3✔
4879
            }
1,041,477✔
4880
        }
4881

4882
        // kill miner if it's running
4883
        signal_mining_blocked(relayer_thread.globals.get_miner_status());
275✔
4884

4885
        // set termination flag so other threads die
4886
        relayer_thread.globals.signal_stop();
275✔
4887

4888
        debug!("Relayer exit!");
275✔
4889
    }
275✔
4890

4891
    /// Main loop of the p2p thread.
4892
    /// Runs in a separate thread.
4893
    /// Continuously receives, until told otherwise.
4894
    pub fn p2p_main(
275✔
4895
        mut p2p_thread: PeerThread,
275✔
4896
        event_dispatcher: EventDispatcher,
275✔
4897
    ) -> Option<PeerNetwork> {
275✔
4898
        let should_keep_running = p2p_thread.globals.should_keep_running.clone();
275✔
4899
        let (mut dns_resolver, mut dns_client) = DNSResolver::new(10);
275✔
4900

4901
        // spawn a daemon thread that runs the DNS resolver.
4902
        // It will die when the rest of the system dies.
4903
        {
4904
            let _jh = thread::Builder::new()
275✔
4905
                .name("dns-resolver".to_string())
275✔
4906
                .spawn(move || {
275✔
4907
                    debug!("DNS resolver thread ID is {:?}", thread::current().id());
275✔
4908
                    dns_resolver.thread_main();
275✔
4909
                })
275✔
4910
                .unwrap();
275✔
4911
        }
4912

4913
        // NOTE: these must be instantiated in the thread context, since it can't be safely sent
4914
        // between threads
4915
        let fee_estimator_opt = p2p_thread.config.make_fee_estimator();
275✔
4916
        let cost_estimator = p2p_thread
275✔
4917
            .config
275✔
4918
            .make_cost_estimator()
275✔
4919
            .unwrap_or_else(|| Box::new(UnitEstimator));
275✔
4920
        let cost_metric = p2p_thread
275✔
4921
            .config
275✔
4922
            .make_cost_metric()
275✔
4923
            .unwrap_or_else(|| Box::new(UnitMetric));
275✔
4924

4925
        let indexer = make_bitcoin_indexer(&p2p_thread.config, Some(should_keep_running));
275✔
4926

4927
        // receive until we can't reach the receiver thread
4928
        loop {
4929
            if !p2p_thread.globals.keep_running() {
871,623✔
4930
                break;
155✔
4931
            }
871,468✔
4932
            if !p2p_thread.run_one_pass(
871,468✔
4933
                &indexer,
871,468✔
4934
                Some(&mut dns_client),
871,468✔
4935
                &event_dispatcher,
871,468✔
4936
                &cost_estimator,
871,468✔
4937
                &cost_metric,
871,468✔
4938
                fee_estimator_opt.as_ref(),
871,468✔
4939
            ) {
871,468✔
4940
                break;
120✔
4941
            }
871,348✔
4942
        }
4943

4944
        // kill miner
4945
        signal_mining_blocked(p2p_thread.globals.get_miner_status());
275✔
4946

4947
        // set termination flag so other threads die
4948
        p2p_thread.globals.signal_stop();
275✔
4949

4950
        // thread exited, so signal to the relayer thread to die.
4951
        while let Err(TrySendError::Full(_)) = p2p_thread
239✔
4952
            .globals
239✔
4953
            .relay_send
239✔
4954
            .try_send(RelayerDirective::Exit)
239✔
4955
        {
4956
            warn!("Failed to direct relayer thread to exit, sleeping and trying again");
6✔
4957
            thread::sleep(Duration::from_secs(5));
6✔
4958
        }
4959
        info!("P2P thread exit!");
275✔
4960
        p2p_thread.net
275✔
4961
    }
275✔
4962

4963
    /// This function sets the global var `GLOBAL_BURNCHAIN_SIGNER`.
4964
    ///
4965
    /// This variable is used for prometheus monitoring (which only
4966
    /// runs when the feature flag `monitoring_prom` is activated).
4967
    /// The address is set using the single-signature BTC address
4968
    /// associated with `keychain`'s public key. This address always
4969
    /// assumes Epoch-2.1 rules for the miner address: if the
4970
    /// node is configured for segwit, then the miner address generated
4971
    /// is a segwit address, otherwise it is a p2pkh.
4972
    ///
4973
    fn set_monitoring_miner_address(keychain: &Keychain, relayer_thread: &RelayerThread) {
275✔
4974
        let public_key = keychain.get_pub_key();
275✔
4975
        let miner_addr = relayer_thread
275✔
4976
            .bitcoin_controller
275✔
4977
            .get_miner_address(StacksEpochId::Epoch21, &public_key);
275✔
4978
        let miner_addr_str = miner_addr.to_string();
275✔
4979
        let _ = monitoring::set_burnchain_signer(BurnchainSigner(miner_addr_str)).map_err(|e| {
275✔
4980
            warn!("Failed to set global burnchain signer: {e:?}");
49✔
4981
            e
49✔
4982
        });
49✔
4983
    }
275✔
4984

4985
    pub fn spawn(
275✔
4986
        runloop: &RunLoop,
275✔
4987
        globals: Globals,
275✔
4988
        // relay receiver endpoint for the p2p thread, so the relayer can feed it data to push
275✔
4989
        relay_recv: Receiver<RelayerDirective>,
275✔
4990
    ) -> StacksNode {
275✔
4991
        let config = runloop.config().clone();
275✔
4992
        let is_miner = runloop.is_miner();
275✔
4993
        let burnchain = runloop.get_burnchain();
275✔
4994
        let atlas_config = config.atlas.clone();
275✔
4995
        let keychain = Keychain::default(config.node.seed.clone());
275✔
4996

4997
        let _ = Self::setup_mempool_db(&config);
275✔
4998

4999
        let mut p2p_net = Self::setup_peer_network(&config, &atlas_config, burnchain);
275✔
5000

5001
        let stackerdbs = StackerDBs::connect(&config.get_stacker_db_file_path(), true)
275✔
5002
            .expect("FATAL: failed to connect to stacker DB");
275✔
5003

5004
        let relayer = Relayer::from_p2p(&mut p2p_net, stackerdbs);
275✔
5005

5006
        let local_peer = p2p_net.local_peer.clone();
275✔
5007

5008
        let NodeConfig {
5009
            mock_mining, miner, ..
275✔
5010
        } = config.get_node_config(false);
275✔
5011

5012
        // setup initial key registration
5013
        let leader_key_registration_state = if mock_mining {
275✔
5014
            // mock mining, pretend to have a registered key
5015
            let (vrf_public_key, _) = keychain.make_vrf_keypair(VRF_MOCK_MINER_KEY);
5✔
5016
            LeaderKeyRegistrationState::Active(RegisteredKey {
5✔
5017
                target_block_height: VRF_MOCK_MINER_KEY,
5✔
5018
                block_height: 1,
5✔
5019
                op_vtxindex: 1,
5✔
5020
                vrf_public_key,
5✔
5021
                memo: vec![],
5✔
5022
            })
5✔
5023
        } else {
5024
            // Warn the user that they need to set up a miner key
5025
            if miner && config.miner.mining_key.is_none() {
270✔
5026
                warn!("`[miner.mining_key]` not set in config file. This will be required to mine in Epoch 3.0!")
34✔
5027
            }
236✔
5028
            LeaderKeyRegistrationState::Inactive
270✔
5029
        };
5030
        globals.set_initial_leader_key_registration_state(leader_key_registration_state);
275✔
5031

5032
        let relayer_thread = RelayerThread::new(runloop, local_peer.clone(), relayer);
275✔
5033

5034
        StacksNode::set_monitoring_miner_address(&keychain, &relayer_thread);
275✔
5035

5036
        let relayer_thread_handle = thread::Builder::new()
275✔
5037
            .name(format!("relayer-{}", &local_peer.data_url))
275✔
5038
            .stack_size(BLOCK_PROCESSOR_STACK_SIZE)
275✔
5039
            .spawn(move || {
275✔
5040
                debug!("relayer thread ID is {:?}", thread::current().id());
275✔
5041
                Self::relayer_main(relayer_thread, relay_recv);
275✔
5042
            })
275✔
5043
            .expect("FATAL: failed to start relayer thread");
275✔
5044

5045
        let p2p_event_dispatcher = runloop.get_event_dispatcher();
275✔
5046
        let p2p_thread = PeerThread::new(runloop, p2p_net);
275✔
5047
        let p2p_thread_handle = thread::Builder::new()
275✔
5048
            .stack_size(BLOCK_PROCESSOR_STACK_SIZE)
275✔
5049
            .name(format!(
275✔
5050
                "p2p-({},{})",
5051
                &config.node.p2p_bind, &config.node.rpc_bind
275✔
5052
            ))
5053
            .spawn(move || {
275✔
5054
                debug!("p2p thread ID is {:?}", thread::current().id());
275✔
5055
                Self::p2p_main(p2p_thread, p2p_event_dispatcher)
275✔
5056
            })
275✔
5057
            .expect("FATAL: failed to start p2p thread");
275✔
5058

5059
        info!("Start HTTP server on: {}", &config.node.rpc_bind);
275✔
5060
        info!("Start P2P server on: {}", &config.node.p2p_bind);
275✔
5061

5062
        StacksNode {
275✔
5063
            atlas_config,
275✔
5064
            globals,
275✔
5065
            is_miner,
275✔
5066
            p2p_thread_handle,
275✔
5067
            relayer_thread_handle,
275✔
5068
        }
275✔
5069
    }
275✔
5070

5071
    /// Manage the VRF public key registration state machine.
5072
    /// Tell the relayer thread to fire off a tenure and a block commit op,
5073
    /// if it is time to do so.
5074
    /// `ibd` indicates whether or not we're in the initial block download.  Used to control when
5075
    /// to try and register VRF keys.
5076
    /// Called from the main thread.
5077
    /// Return true if we succeeded in carrying out the next task of the operation.
5078
    pub fn relayer_issue_tenure(&mut self, ibd: bool) -> bool {
343,801✔
5079
        if !self.is_miner {
343,801✔
5080
            // node is a follower, don't try to issue a tenure
5081
            return true;
2,501✔
5082
        }
341,300✔
5083

5084
        if let Some(burnchain_tip) = self.globals.get_last_sortition() {
341,300✔
5085
            if !ibd {
341,300✔
5086
                // try and register a VRF key before issuing a tenure
5087
                let leader_key_registration_state =
341,300✔
5088
                    self.globals.get_leader_key_registration_state();
341,300✔
5089
                match leader_key_registration_state {
341,300✔
5090
                    LeaderKeyRegistrationState::Active(ref key) => {
340,516✔
5091
                        debug!(
340,516✔
5092
                            "Tenure: Using key {:?} off of {}",
5093
                            &key.vrf_public_key, &burnchain_tip.burn_header_hash
×
5094
                        );
5095

5096
                        self.globals
340,516✔
5097
                            .relay_send
340,516✔
5098
                            .send(RelayerDirective::RunTenure(
340,516✔
5099
                                key.clone(),
340,516✔
5100
                                burnchain_tip,
340,516✔
5101
                                get_epoch_time_ms(),
340,516✔
5102
                            ))
340,516✔
5103
                            .is_ok()
340,516✔
5104
                    }
5105
                    LeaderKeyRegistrationState::Inactive => {
5106
                        warn!(
260✔
5107
                            "Tenure: skipped tenure because no active VRF key. Trying to register one."
5108
                        );
5109
                        self.globals
260✔
5110
                            .relay_send
260✔
5111
                            .send(RelayerDirective::RegisterKey(burnchain_tip))
260✔
5112
                            .is_ok()
260✔
5113
                    }
5114
                    LeaderKeyRegistrationState::Pending(..) => true,
524✔
5115
                }
5116
            } else {
5117
                // still sync'ing so just try again later
5118
                true
×
5119
            }
5120
        } else {
5121
            warn!("Tenure: Do not know the last burn block. As a miner, this is bad.");
×
5122
            true
×
5123
        }
5124
    }
343,801✔
5125

5126
    /// Notify the relayer of a sortition, telling it to process the block
5127
    ///  and advertize it if it was mined by the node.
5128
    /// returns _false_ if the relayer hung up the channel.
5129
    /// Called from the main thread.
5130
    pub fn relayer_sortition_notify(&self) -> bool {
61,475✔
5131
        if !self.is_miner {
61,475✔
5132
            // node is a follower, don't try to process my own tenure.
5133
            return true;
1,136✔
5134
        }
60,339✔
5135

5136
        if let Some(snapshot) = self.globals.get_last_sortition() {
60,339✔
5137
            debug!(
60,339✔
5138
                "Tenure: Notify sortition!";
5139
                "consensus_hash" => %snapshot.consensus_hash,
5140
                "burn_block_hash" => %snapshot.burn_header_hash,
5141
                "winning_stacks_block_hash" => %snapshot.winning_stacks_block_hash,
5142
                "burn_block_height" => &snapshot.block_height,
×
5143
                "sortition_id" => %snapshot.sortition_id
5144
            );
5145
            if snapshot.sortition {
60,339✔
5146
                return self
7,696✔
5147
                    .globals
7,696✔
5148
                    .relay_send
7,696✔
5149
                    .send(RelayerDirective::ProcessTenure(
7,696✔
5150
                        snapshot.consensus_hash,
7,696✔
5151
                        snapshot.parent_burn_header_hash,
7,696✔
5152
                        snapshot.winning_stacks_block_hash,
7,696✔
5153
                    ))
7,696✔
5154
                    .is_ok();
7,696✔
5155
            }
52,643✔
5156
        } else {
5157
            debug!("Tenure: Notify sortition! No last burn block");
×
5158
        }
5159
        true
52,643✔
5160
    }
61,475✔
5161

5162
    /// Process a state coming from the burnchain, by extracting the validated KeyRegisterOp
5163
    /// and inspecting if a sortition was won.
5164
    /// `ibd`: boolean indicating whether or not we are in the initial block download
5165
    /// Called from the main thread.
5166
    pub fn process_burnchain_state(
61,475✔
5167
        &mut self,
61,475✔
5168
        config: &Config,
61,475✔
5169
        sortdb: &SortitionDB,
61,475✔
5170
        sort_id: &SortitionId,
61,475✔
5171
        ibd: bool,
61,475✔
5172
    ) -> Option<BlockSnapshot> {
61,475✔
5173
        let mut last_sortitioned_block = None;
61,475✔
5174

5175
        let ic = sortdb.index_conn();
61,475✔
5176

5177
        let block_snapshot = SortitionDB::get_block_snapshot(&ic, sort_id)
61,475✔
5178
            .expect("Failed to obtain block snapshot for processed burn block.")
61,475✔
5179
            .expect("Failed to obtain block snapshot for processed burn block.");
61,475✔
5180
        let block_height = block_snapshot.block_height;
61,475✔
5181

5182
        let block_commits =
61,475✔
5183
            SortitionDB::get_block_commits_by_block(&ic, &block_snapshot.sortition_id)
61,475✔
5184
                .expect("Unexpected SortitionDB error fetching block commits");
61,475✔
5185

5186
        let num_block_commits = block_commits.len();
61,475✔
5187

5188
        update_active_miners_count_gauge(block_commits.len() as i64);
61,475✔
5189

5190
        for op in block_commits.into_iter() {
61,475✔
5191
            if op.txid == block_snapshot.winning_block_txid {
7,879✔
5192
                info!(
7,827✔
5193
                    "Received burnchain block #{block_height} including block_commit_op (winning) - {} ({})",
5194
                    op.apparent_sender, &op.block_header_hash
7,827✔
5195
                );
5196
                last_sortitioned_block = Some((block_snapshot.clone(), op.vtxindex));
7,827✔
5197
            } else if self.is_miner {
52✔
5198
                info!(
52✔
5199
                    "Received burnchain block #{block_height} including block_commit_op - {} ({})",
5200
                    op.apparent_sender, &op.block_header_hash
52✔
5201
                );
5202
            }
×
5203
        }
5204

5205
        let key_registers =
61,475✔
5206
            SortitionDB::get_leader_keys_by_block(&ic, &block_snapshot.sortition_id)
61,475✔
5207
                .expect("Unexpected SortitionDB error fetching key registers");
61,475✔
5208

5209
        self.globals.set_last_sortition(block_snapshot);
61,475✔
5210
        let ret = last_sortitioned_block.map(|x| x.0);
61,475✔
5211

5212
        let num_key_registers = key_registers.len();
61,475✔
5213
        debug!(
61,475✔
5214
            "Processed burnchain state at height {block_height}: {num_key_registers} leader keys, {num_block_commits} block-commits (ibd = {ibd})"
5215
        );
5216

5217
        // save the registered VRF key
5218
        let activated_key_opt = self
61,475✔
5219
            .globals
61,475✔
5220
            .try_activate_leader_key_registration(block_height, key_registers);
61,475✔
5221

5222
        let Some(activated_key) = activated_key_opt else {
61,475✔
5223
            return ret;
61,251✔
5224
        };
5225

5226
        let Some(path) = config.miner.activated_vrf_key_path.as_ref() else {
224✔
5227
            return ret;
85✔
5228
        };
5229

5230
        info!("Activated VRF key; saving to {path}");
139✔
5231

5232
        let Ok(key_json) = serde_json::to_string(&activated_key) else {
139✔
5233
            warn!("Failed to serialize VRF key");
×
5234
            return ret;
×
5235
        };
5236

5237
        let mut f = match fs::File::create(path) {
139✔
5238
            Ok(f) => f,
139✔
5239
            Err(e) => {
×
5240
                warn!("Failed to create {path}: {e:?}");
×
5241
                return ret;
×
5242
            }
5243
        };
5244

5245
        if let Err(e) = f.write_all(key_json.as_bytes()) {
139✔
5246
            warn!("Failed to write activated VRF key to {path}: {e:?}");
×
5247
            return ret;
×
5248
        }
139✔
5249

5250
        info!("Saved activated VRF key to {path}");
139✔
5251
        ret
139✔
5252
    }
61,475✔
5253

5254
    /// Join all inner threads
5255
    pub fn join(self) -> Option<PeerNetwork> {
239✔
5256
        self.relayer_thread_handle.join().unwrap();
239✔
5257
        self.p2p_thread_handle.join().unwrap()
239✔
5258
    }
239✔
5259
}
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