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

stacks-network / stacks-core / 23937115186

03 Apr 2026 06:47AM UTC coverage: 85.737% (+0.03%) from 85.712%
23937115186

Pull #7022

github

dabfc0
web-flow
Merge 2eef3211d into 461c7b5d9
Pull Request #7022: perf: fold/map/filter optimization

152 of 181 new or added lines in 3 files covered. (83.98%)

1367 existing lines in 56 files now uncovered.

186750 of 217817 relevant lines covered (85.74%)

17252481.7 hits per line

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

83.45
/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,308✔
269
        ch: ConsensusHash,
6,308✔
270
        bh: BlockHeaderHash,
6,308✔
271
        pk: Secp256k1PrivateKey,
6,308✔
272
        stacks_height: u64,
6,308✔
273
        burn_height: u64,
6,308✔
274
    ) -> MinerTip {
6,308✔
275
        MinerTip {
6,308✔
276
            consensus_hash: ch,
6,308✔
277
            block_hash: bh,
6,308✔
278
            microblock_privkey: pk,
6,308✔
279
            stacks_height,
6,308✔
280
            burn_height,
6,308✔
281
        }
6,308✔
282
    }
6,308✔
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() {
162,789✔
305
    // simulated slow block
306
    let Ok(tenure_str) = std::env::var("STX_TEST_SLOW_TENURE") else {
162,789✔
307
        return;
162,780✔
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
}
162,789✔
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 {
255,927✔
324
    let Ok(disable_heights) = std::env::var("STACKS_DISABLE_MINER") else {
255,927✔
325
        return false;
255,927✔
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
}
255,927✔
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(
195,087✔
352
    config: &Config,
195,087✔
353
) -> Result<StacksChainState, ChainstateError> {
195,087✔
354
    let stacks_chainstate_path = config.get_chainstate_path_str();
195,087✔
355
    let (mut chainstate, _) = StacksChainState::open(
195,087✔
356
        config.is_mainnet(),
195,087✔
357
        config.burnchain.chain_id,
195,087✔
358
        &stacks_chainstate_path,
195,087✔
359
        Some(config.node.get_marf_opts()),
195,087✔
360
    )?;
×
361

362
    chainstate.fault_injection.hide_blocks = config.node.fault_injection_hide_blocks;
195,087✔
363
    Ok(chainstate)
195,087✔
364
}
195,087✔
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,005✔
411
        if let Self::Active(registered_key) = self {
4,005✔
412
            Some(registered_key.clone())
4,005✔
413
        } else {
414
            None
×
415
        }
416
    }
4,005✔
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> {
11,346✔
556
        let globals = relayer_thread.globals.clone();
11,346✔
557
        let config = relayer_thread.config.clone();
11,346✔
558
        let burnchain = relayer_thread.burnchain.clone();
11,346✔
559
        let miner_tip = match relayer_thread.miner_tip.clone() {
11,346✔
560
            Some(tip) => tip,
11,346✔
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();
11,346✔
568
        let burn_db_path = config.get_burn_db_file_path();
11,346✔
569
        let cost_estimator = config
11,346✔
570
            .make_cost_estimator()
11,346✔
571
            .unwrap_or_else(|| Box::new(UnitEstimator));
11,346✔
572
        let metric = config
11,346✔
573
            .make_cost_metric()
11,346✔
574
            .unwrap_or_else(|| Box::new(UnitMetric));
11,346✔
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(
11,346✔
579
            &burn_db_path,
11,346✔
580
            true,
581
            burnchain.pox_constants,
11,346✔
582
            Some(config.node.get_marf_opts()),
11,346✔
583
        )
584
        .map_err(|e| {
11,346✔
585
            error!("Relayer: Could not open sortdb '{burn_db_path}' ({e:?}); skipping tenure");
×
586
            e
×
587
        })
×
588
        .ok()?;
11,346✔
589

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

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

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

615
        debug!("Relayer: Instantiate microblock mining state off of {ch}/{bhh}");
11,346✔
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) {
11,346✔
619
            Ok(Some(_)) => {
620
                let parent_index_hash = StacksBlockHeader::make_index_block_hash(&ch, &bhh);
11,346✔
621
                let cost_so_far = if relayer_thread.microblock_stream_cost == ExecutionCost::ZERO {
11,346✔
622
                    // unknown cost, or this is idempotent.
623
                    StacksChainState::get_stacks_block_anchored_cost(
11,334✔
624
                        chainstate.db(),
11,334✔
625
                        &parent_index_hash,
11,334✔
626
                    )
627
                    .expect("FATAL: failed to get anchored block cost")
11,334✔
628
                    .expect("FATAL: no anchored block cost stored for processed anchored block")
11,334✔
629
                } else {
630
                    relayer_thread.microblock_stream_cost.clone()
12✔
631
                };
632

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

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

646
                Some(MicroblockMinerThread {
11,346✔
647
                    globals,
11,346✔
648
                    chainstate: Some(chainstate),
11,346✔
649
                    sortdb: Some(sortdb),
11,346✔
650
                    mempool: Some(mempool),
11,346✔
651
                    event_dispatcher: relayer_thread.event_dispatcher.clone(),
11,346✔
652
                    parent_consensus_hash: ch,
11,346✔
653
                    parent_block_hash: bhh,
11,346✔
654
                    miner_key,
11,346✔
655
                    frequency,
11,346✔
656
                    last_mined: 0,
11,346✔
657
                    quantity: 0,
11,346✔
658
                    cost_so_far,
11,346✔
659
                    settings,
11,346✔
660
                })
11,346✔
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
    }
11,346✔
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
11,346✔
677
    where
11,346✔
678
        F: FnOnce(&mut Self, &mut SortitionDB, &mut StacksChainState, &mut MemPoolDB) -> R,
11,346✔
679
    {
680
        let mut sortdb = self.sortdb.take().expect("FATAL: already took sortdb");
11,346✔
681
        let mut chainstate = self
11,346✔
682
            .chainstate
11,346✔
683
            .take()
11,346✔
684
            .expect("FATAL: already took chainstate");
11,346✔
685
        let mut mempool = self.mempool.take().expect("FATAL: already took mempool");
11,346✔
686

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

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

693
        res
11,346✔
694
    }
11,346✔
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(
11,346✔
700
        &mut self,
11,346✔
701
        sortdb: &SortitionDB,
11,346✔
702
        chainstate: &mut StacksChainState,
11,346✔
703
        mempool: &mut MemPoolDB,
11,346✔
704
    ) -> Result<StacksMicroblock, ChainstateError> {
11,346✔
705
        debug!(
11,346✔
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 =
11,346✔
717
            SortitionDB::get_block_snapshot_consensus(sortdb.conn(), &self.parent_consensus_hash)
11,346✔
718
                .map_err(|e| {
11,346✔
719
                    error!("Failed to find block snapshot for mined block: {e}");
×
720
                    e
×
721
                })?
×
722
                .ok_or_else(|| {
11,346✔
723
                    error!("Failed to find block snapshot for mined block");
×
724
                    ChainstateError::NoSuchBlockError
×
725
                })?;
×
726
        let burn_height = block_snapshot.block_height;
11,346✔
727

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

736
        let mint_result = {
6✔
737
            let ic = sortdb.index_handle_at_block(
11,346✔
738
                chainstate,
11,346✔
739
                &block_snapshot.get_canonical_stacks_block_id(),
11,346✔
740
            )?;
×
741
            let mut microblock_miner = match StacksMicroblockBuilder::resume_unconfirmed(
11,346✔
742
                chainstate,
11,346✔
743
                &ic,
11,346✔
744
                &self.cost_so_far,
11,346✔
745
                self.settings.clone(),
11,346✔
746
            ) {
11,346✔
747
                Ok(x) => x,
11,346✔
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();
11,346✔
759

760
            let mblock = microblock_miner.mine_next_microblock(
11,346✔
761
                mempool,
11,346✔
762
                &self.miner_key,
11,346✔
763
                &self.event_dispatcher,
11,346✔
764
            )?;
11,340✔
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.");
6✔
766
            let t2 = get_epoch_time_ms();
6✔
767

768
            info!(
6✔
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))
6✔
777
        };
778

779
        let (mined_microblock, new_cost) = match mint_result {
6✔
780
            Ok(x) => x,
6✔
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(
6✔
789
            chainstate.mainnet,
6✔
790
            epoch_id,
6✔
791
            &mined_microblock,
6✔
792
        ) {
6✔
793
            // nope!
794
            warn!(
2✔
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") {
2✔
803
                    // record this microblock somewhere
804
                    if fs::metadata(&path).is_err() {
2✔
805
                        fs::create_dir_all(&path)
×
806
                            .unwrap_or_else(|_| panic!("FATAL: could not create '{path}'"));
×
807
                    }
2✔
808

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

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

817
                    let mblock_json = format!(
2✔
818
                        r#"{{"microblock":"{mblock_bits_hex}","parent_consensus":"{}","parent_block":"{}"}}"#,
819
                        &self.parent_consensus_hash, &self.parent_block_hash
2✔
820
                    );
821
                    file.write_all(mblock_json.as_bytes()).unwrap_or_else(|_| {
2✔
822
                        panic!("FATAL: failed to write microblock bits to '{path:?}'")
×
823
                    });
824
                    info!(
2✔
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);
2✔
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
    }
11,346✔
857

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

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

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

889
        let mut next_microblock_and_runtime = None;
11,346✔
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(
11,346✔
894
            chainstate.db(),
11,346✔
895
            1,
896
            get_epoch_time_secs() - 600,
11,346✔
897
        )?;
×
898
        if num_attachable == 0 {
11,346✔
899
            match self.inner_mine_one_microblock(sortdb, chainstate, mem_pool) {
11,346✔
900
                Ok(microblock) => {
6✔
901
                    // will need to relay this
6✔
902
                    next_microblock_and_runtime = Some((microblock, self.cost_so_far.clone()));
6✔
903
                }
6✔
904
                Err(ChainstateError::NoTransactionsToMine) => {
905
                    info!("Will keep polling mempool for transactions to include in a microblock");
11,339✔
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();
11,346✔
916

917
        Ok(next_microblock_and_runtime)
11,346✔
918
    }
11,346✔
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(
11,346✔
929
        &mut self,
11,346✔
930
        cur_tip: MinerTip,
11,346✔
931
    ) -> Result<Option<(StacksMicroblock, ExecutionCost)>, NetError> {
11,346✔
932
        debug!("microblock miner thread ID is {:?}", thread::current().id());
11,346✔
933
        self.with_chainstate(|mblock_miner, sortdb, chainstate, mempool| {
11,346✔
934
            mblock_miner.inner_try_mine_microblock(cur_tip, sortdb, chainstate, mempool)
11,346✔
935
        })
11,346✔
936
    }
11,346✔
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,091,363✔
956
        StacksBlockId::new(&self.consensus_hash, &self.anchored_block_hash)
2,091,363✔
957
    }
2,091,363✔
958

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

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

979
impl BlockMinerThread {
980
    /// Instantiate the miner thread from its parent RelayerThread
981
    pub fn from_relayer_thread(
155,406✔
982
        rt: &RelayerThread,
155,406✔
983
        registered_key: RegisteredKey,
155,406✔
984
        burn_block: BlockSnapshot,
155,406✔
985
    ) -> BlockMinerThread {
155,406✔
986
        BlockMinerThread {
155,406✔
987
            config: rt.config.clone(),
155,406✔
988
            globals: rt.globals.clone(),
155,406✔
989
            keychain: rt.keychain.clone(),
155,406✔
990
            burnchain: rt.burnchain.clone(),
155,406✔
991
            last_mined_blocks: rt.last_mined_blocks.clone(),
155,406✔
992
            ongoing_commit: rt.bitcoin_controller.get_ongoing_commit(),
155,406✔
993
            registered_key,
155,406✔
994
            burn_block,
155,406✔
995
            event_dispatcher: rt.event_dispatcher.clone(),
155,406✔
996
            failed_to_submit_last_attempt: rt.last_attempt_failed,
155,406✔
997
        }
155,406✔
998
    }
155,406✔
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> {
26,515✔
1002
        let miner_config = self.config.get_miner_config();
26,515✔
1003
        if epoch_id < StacksEpochId::Epoch21 && miner_config.block_reward_recipient.is_some() {
26,515✔
1004
            warn!("Coinbase pay-to-contract is not supported in the current epoch");
×
1005
            None
×
1006
        } else {
1007
            miner_config.block_reward_recipient
26,515✔
1008
        }
1009
    }
26,515✔
1010

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

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

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

1039
        tx_signer.get_tx().unwrap()
26,515✔
1040
    }
26,515✔
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(
22,883✔
1070
        &self,
22,883✔
1071
        block_header_hash: BlockHeaderHash,
22,883✔
1072
        burn_fee: u64,
22,883✔
1073
        key: &RegisteredKey,
22,883✔
1074
        parent_burnchain_height: u32,
22,883✔
1075
        parent_winning_vtx: u16,
22,883✔
1076
        vrf_seed: VRFSeed,
22,883✔
1077
        commit_outs: Vec<PoxAddress>,
22,883✔
1078
        sunset_burn: u64,
22,883✔
1079
        current_burn_height: u64,
22,883✔
1080
    ) -> BlockstackOperationType {
22,883✔
1081
        let (parent_block_ptr, parent_vtxindex) = (parent_burnchain_height, parent_winning_vtx);
22,883✔
1082
        let burn_parent_modulus = (current_burn_height % BURN_BLOCK_MINED_AT_MODULUS) as u8;
22,883✔
1083
        let sender = self.keychain.get_burnchain_signer();
22,883✔
1084
        BlockstackOperationType::LeaderBlockCommit(LeaderBlockCommitOp {
22,883✔
1085
            treatment: vec![],
22,883✔
1086
            sunset_burn,
22,883✔
1087
            block_header_hash,
22,883✔
1088
            burn_fee,
22,883✔
1089
            input: (Txid([0; 32]), 0),
22,883✔
1090
            apparent_sender: sender,
22,883✔
1091
            key_block_ptr: key.block_height as u32,
22,883✔
1092
            key_vtxindex: key.op_vtxindex as u16,
22,883✔
1093
            memo: vec![STACKS_EPOCH_3_0_MARKER],
22,883✔
1094
            new_seed: vrf_seed,
22,883✔
1095
            parent_block_ptr,
22,883✔
1096
            parent_vtxindex,
22,883✔
1097
            vtxindex: 0,
22,883✔
1098
            txid: Txid([0u8; 32]),
22,883✔
1099
            block_height: 0,
22,883✔
1100
            burn_header_hash: BurnchainHeaderHash::zero(),
22,883✔
1101
            burn_parent_modulus,
22,883✔
1102
            commit_outs,
22,883✔
1103
        })
22,883✔
1104
    }
22,883✔
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(
162,078✔
1108
        burn_height: u64,
162,078✔
1109
        last_mined_blocks: &MinedBlocks,
162,078✔
1110
    ) -> Vec<&AssembledAnchorBlock> {
162,078✔
1111
        let mut ret = vec![];
162,078✔
1112
        for (_, (assembled_block, _)) in last_mined_blocks.iter() {
163,470✔
1113
            if assembled_block.burn_block_height >= burn_height {
147,171✔
1114
                ret.push(assembled_block);
145,592✔
1115
            }
145,594✔
1116
        }
1117
        ret
162,078✔
1118
    }
162,078✔
1119

1120
    /// Is a given Stacks staging block on the canonical burnchain fork?
1121
    pub(crate) fn is_on_canonical_burnchain_fork(
1,382,177✔
1122
        candidate_ch: &ConsensusHash,
1,382,177✔
1123
        candidate_bh: &BlockHeaderHash,
1,382,177✔
1124
        sortdb_tip_handle: &SortitionHandleConn,
1,382,177✔
1125
    ) -> bool {
1,382,177✔
1126
        let candidate_burn_ht = match SortitionDB::get_block_snapshot_consensus(
1,382,177✔
1127
            sortdb_tip_handle.conn(),
1,382,177✔
1128
            candidate_ch,
1,382,177✔
1129
        ) {
1130
            Ok(Some(x)) => x.block_height,
1,382,177✔
1131
            Ok(None) => {
UNCOV
1132
                warn!("Tried to evaluate potential chain tip with an unknown consensus hash";
×
1133
                      "consensus_hash" => %candidate_ch,
1134
                      "stacks_block_hash" => %candidate_bh);
UNCOV
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,382,177✔
1146
            Ok(Some(x)) => x,
1,382,176✔
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,382,176✔
1162
    }
1,382,177✔
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(
177,860✔
1170
        burn_db: &mut SortitionDB,
177,860✔
1171
        chain_state: &mut StacksChainState,
177,860✔
1172
        max_depth: u64,
177,860✔
1173
        at_stacks_height: Option<u64>,
177,860✔
1174
    ) -> Vec<TipCandidate> {
177,860✔
1175
        let stacks_tips = if let Some(start_height) = at_stacks_height {
177,860✔
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
177,860✔
1181
                .get_stacks_chain_tips(burn_db)
177,860✔
1182
                .expect("FATAL: could not query chain tips")
177,860✔
1183
        };
1184

1185
        if stacks_tips.is_empty() {
177,860✔
1186
            return vec![];
291✔
1187
        }
177,569✔
1188

1189
        let sortdb_tip_handle = burn_db.index_handle_at_tip();
177,569✔
1190

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

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

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

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

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

1237
            for tip in stacks_tips {
525,875✔
1238
                let index_block_hash =
519,706✔
1239
                    StacksBlockId::new(&tip.consensus_hash, &tip.anchored_block_hash);
519,706✔
1240

1241
                if considered.insert(index_block_hash) {
519,706✔
1242
                    let burn_height = burn_db
519,706✔
1243
                        .get_consensus_hash_height(&tip.consensus_hash)
519,706✔
1244
                        .expect("FATAL: could not query burnchain block height")
519,706✔
1245
                        .expect("FATAL: no burnchain block height for Stacks tip");
519,706✔
1246
                    let candidate = TipCandidate::new(tip, burn_height);
519,706✔
1247
                    candidates.push(candidate);
519,706✔
1248
                }
519,706✔
1249
            }
1250
        }
1251
        Self::sort_and_populate_candidates(candidates)
177,569✔
1252
    }
177,860✔
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(
177,572✔
1258
        mut candidates: Vec<TipCandidate>,
177,572✔
1259
    ) -> Vec<TipCandidate> {
177,572✔
1260
        if candidates.is_empty() {
177,572✔
1261
            return candidates;
1✔
1262
        }
177,571✔
1263
        candidates.sort_by(|tip1, tip2| {
864,411✔
1264
            // stacks block height, then burnchain block height
1265
            let ord = tip1.stacks_height.cmp(&tip2.stacks_height);
864,399✔
1266
            if ord == CmpOrdering::Equal {
864,399✔
1267
                return tip1.burn_height.cmp(&tip2.burn_height);
614✔
1268
            }
863,785✔
1269
            ord
863,785✔
1270
        });
864,399✔
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;
177,571✔
1275
        let mut cur_stacks_height = candidates[idx].stacks_height;
177,571✔
1276
        let mut num_siblings = 0;
177,571✔
1277
        loop {
1278
            idx += 1;
697,431✔
1279
            if idx >= candidates.len() {
697,431✔
1280
                break;
177,571✔
1281
            }
519,860✔
1282
            if cur_stacks_height == candidates[idx].stacks_height {
519,860✔
1283
                // same stacks height, so this block has one more earlier sibling than the last
614✔
1284
                num_siblings += 1;
614✔
1285
                candidates[idx].num_earlier_siblings = num_siblings;
614✔
1286
            } else {
519,246✔
1287
                // new stacks height, so no earlier siblings
519,246✔
1288
                num_siblings = 0;
519,246✔
1289
                cur_stacks_height = candidates[idx].stacks_height;
519,246✔
1290
                candidates[idx].num_earlier_siblings = 0;
519,246✔
1291
            }
519,246✔
1292
        }
1293

1294
        candidates
177,571✔
1295
    }
177,572✔
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(
177,860✔
1306
        globals: &Globals,
177,860✔
1307
        config: &Config,
177,860✔
1308
        burn_db: &mut SortitionDB,
177,860✔
1309
        chain_state: &mut StacksChainState,
177,860✔
1310
        at_stacks_height: Option<u64>,
177,860✔
1311
    ) -> Option<TipCandidate> {
177,860✔
1312
        debug!("Picking best Stacks tip");
177,860✔
1313
        let miner_config = config.get_miner_config();
177,860✔
1314
        let max_depth = miner_config.max_reorg_depth;
177,860✔
1315

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

1320
        let mut previous_best_tips = HashMap::new();
177,860✔
1321
        let sortdb_tip_handle = burn_db.index_handle_at_tip();
177,860✔
1322
        for tip in stacks_tips.iter() {
697,418✔
1323
            let Some(prev_best_tip) = globals.get_best_tip(tip.stacks_height) else {
697,417✔
1324
                continue;
13,679✔
1325
            };
1326
            if !Self::is_on_canonical_burnchain_fork(
683,738✔
1327
                &prev_best_tip.consensus_hash,
683,738✔
1328
                &prev_best_tip.anchored_block_hash,
683,738✔
1329
                &sortdb_tip_handle,
683,738✔
1330
            ) {
683,738✔
1331
                continue;
228✔
1332
            }
683,510✔
1333
            previous_best_tips.insert(tip.stacks_height, prev_best_tip);
683,510✔
1334
        }
1335

1336
        let best_tip_opt = Self::inner_pick_best_tip(stacks_tips, previous_best_tips);
177,860✔
1337
        if let Some(best_tip) = best_tip_opt.as_ref() {
177,860✔
1338
            globals.add_best_tip(best_tip.stacks_height, best_tip.clone(), max_depth);
177,569✔
1339
        } else {
177,569✔
1340
            // no best-tip found; revert to old tie-breaker logic
1341
            debug!("No best-tips found; using old tie-breaking logic");
291✔
1342
            return chain_state
291✔
1343
                .get_stacks_chain_tip(burn_db)
291✔
1344
                .expect("FATAL: could not load chain tip")
291✔
1345
                .map(|staging_block| {
291✔
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
177,569✔
1354
    }
177,860✔
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(
177,884✔
1366
        stacks_tips: Vec<TipCandidate>,
177,884✔
1367
        previous_best_tips: HashMap<u64, TipCandidate>,
177,884✔
1368
    ) -> Option<TipCandidate> {
177,884✔
1369
        // identify leaf tips -- i.e. blocks with no children
1370
        let parent_consensus_hashes: HashSet<_> = stacks_tips
177,884✔
1371
            .iter()
177,884✔
1372
            .map(|x| x.parent_consensus_hash.clone())
697,500✔
1373
            .collect();
177,884✔
1374

1375
        let mut leaf_tips: Vec<_> = stacks_tips
177,884✔
1376
            .iter()
177,884✔
1377
            .filter(|x| !parent_consensus_hashes.contains(&x.consensus_hash))
697,500✔
1378
            .collect();
177,884✔
1379

1380
        if leaf_tips.is_empty() {
177,884✔
1381
            return None;
292✔
1382
        }
177,592✔
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| {
177,596✔
1388
            // stacks block height, then burnchain block height
1389
            let ord = tip1.stacks_height.cmp(&tip2.stacks_height);
638✔
1390
            if ord == CmpOrdering::Equal {
638✔
1391
                return tip1.burn_height.cmp(&tip2.burn_height);
164✔
1392
            }
474✔
1393
            ord
474✔
1394
        });
638✔
1395

1396
        let mut scores = BTreeMap::new();
177,592✔
1397
        for (i, leaf_tip) in leaf_tips.iter().enumerate() {
178,230✔
1398
            let leaf_id = leaf_tip.id();
178,230✔
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();
178,230✔
1404
            let mut score: u64 = 0;
178,230✔
1405
            let mut score_summaries = vec![];
178,230✔
1406

1407
            // find the highest stacks_tip we must confirm
1408
            let mut must_confirm = None;
178,230✔
1409
            for tip in stacks_tips.iter().rev() {
356,934✔
1410
                if let Some(prev_best_tip) = previous_best_tips.get(&tip.stacks_height) {
356,934✔
1411
                    if leaf_id != prev_best_tip.id() {
344,055✔
1412
                        // the `ancestor_ptr` must pass through this prior best-tip
1413
                        must_confirm = Some(prev_best_tip.clone());
173,526✔
1414
                        break;
173,526✔
1415
                    }
170,529✔
1416
                }
12,879✔
1417
            }
1418

1419
            for tip in stacks_tips.iter().rev() {
700,441✔
1420
                if let Some(required_ancestor) = must_confirm.as_ref() {
700,441✔
1421
                    if tip.stacks_height < required_ancestor.stacks_height
349,007✔
1422
                        && leaf_tip.stacks_height >= required_ancestor.stacks_height
2,017✔
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 {}:{}",
169✔
1427
                              &leaf_tip.consensus_hash,
169✔
1428
                              &leaf_tip.anchored_block_hash,
169✔
1429
                              leaf_tip.burn_height,
1430
                              leaf_tip.stacks_height,
1431
                              &required_ancestor.consensus_hash,
169✔
1432
                              &required_ancestor.anchored_block_hash,
169✔
1433
                              required_ancestor.burn_height,
1434
                              required_ancestor.stacks_height
1435
                        );
1436
                        score = u64::MAX;
169✔
1437
                        score_summaries.push(format!("{} (best-tip reorged)", u64::MAX));
169✔
1438
                        break;
169✔
1439
                    }
348,838✔
1440
                }
351,434✔
1441
                if tip.id() == leaf_id {
700,272✔
1442
                    // we can't orphan ourselves
1443
                    continue;
178,230✔
1444
                }
522,042✔
1445
                if leaf_tip.stacks_height < tip.stacks_height {
522,042✔
1446
                    // this tip is further along than leaf_tip, so canonicalizing leaf_tip would
962✔
1447
                    // orphan `tip.stacks_height - leaf_tip.stacks_height` blocks.
962✔
1448
                    score = score.saturating_add(tip.stacks_height - leaf_tip.stacks_height);
962✔
1449
                    score_summaries.push(format!(
962✔
1450
                        "{} (stx height diff)",
962✔
1451
                        tip.stacks_height - leaf_tip.stacks_height
962✔
1452
                    ));
962✔
1453
                } else if leaf_tip.stacks_height == tip.stacks_height
521,080✔
1454
                    && leaf_tip.burn_height > tip.burn_height
818✔
1455
                {
642✔
1456
                    // this tip has the same stacks height as the leaf, but its sortition happened
642✔
1457
                    // earlier. This means that the leaf is trying to orphan this block and all
642✔
1458
                    // blocks sortition'ed up to this leaf.  The miner should have instead tried to
642✔
1459
                    // confirm this existing tip, instead of mine a sibling.
642✔
1460
                    score = score.saturating_add(tip.num_earlier_siblings + 1);
642✔
1461
                    score_summaries.push(format!("{} (uncles)", tip.num_earlier_siblings + 1));
642✔
1462
                }
520,438✔
1463
                if tip.id() == ancestor_ptr {
522,042✔
1464
                    // did we confirm a previous best-tip? If so, then clear this
1465
                    if let Some(required_ancestor) = must_confirm.take() {
519,764✔
1466
                        if required_ancestor.id() != tip.id() {
173,382✔
1467
                            // did not confirm, so restoroe
485✔
1468
                            must_confirm = Some(required_ancestor);
485✔
1469
                        }
172,903✔
1470
                    }
346,382✔
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();
519,764✔
1475
                    score = score.saturating_add(tip.num_earlier_siblings);
519,764✔
1476
                    score_summaries.push(format!("{} (earlier sibs)", tip.num_earlier_siblings));
519,764✔
1477
                } else {
2,278✔
1478
                    // this stacks tip is not an ancestor, and would be orphaned if leaf_tip is
2,278✔
1479
                    // canonical.
2,278✔
1480
                    score = score.saturating_add(1);
2,278✔
1481
                    score_summaries.push(format!("{} (non-ancestor)", 1));
2,278✔
1482
                }
2,278✔
1483
            }
1484

1485
            debug!(
178,230✔
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 {
178,230✔
1494
                scores.insert(i, score);
178,061✔
1495
            }
178,061✔
1496
        }
1497

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

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

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

1514
        debug!(
177,589✔
1515
            "Best tip is #{best_tip_idx} {}/{}",
1516
            &best_tip.consensus_hash, &best_tip.anchored_block_hash
×
1517
        );
1518
        Some((*best_tip).clone())
177,589✔
1519
    }
177,884✔
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(
155,402✔
1527
        &self,
155,402✔
1528
        burn_db: &mut SortitionDB,
155,402✔
1529
        chain_state: &mut StacksChainState,
155,402✔
1530
    ) -> (Option<ParentStacksBlockInfo>, bool) {
155,402✔
1531
        if let Some(stacks_tip) = chain_state
155,402✔
1532
            .get_stacks_chain_tip(burn_db)
155,402✔
1533
            .expect("FATAL: could not query chain tip")
155,402✔
1534
        {
1535
            let best_stacks_tip =
154,977✔
1536
                Self::pick_best_tip(&self.globals, &self.config, burn_db, chain_state, None)
154,977✔
1537
                    .expect("FATAL: no best chain tip");
154,977✔
1538
            let miner_address = self
154,977✔
1539
                .keychain
154,977✔
1540
                .origin_address(self.config.is_mainnet())
154,977✔
1541
                .unwrap();
154,977✔
1542
            let parent_info = match ParentStacksBlockInfo::lookup(
154,977✔
1543
                chain_state,
154,977✔
1544
                burn_db,
154,977✔
1545
                &self.burn_block,
154,977✔
1546
                miner_address,
154,977✔
1547
                &best_stacks_tip.consensus_hash,
154,977✔
1548
                &best_stacks_tip.anchored_block_hash,
154,977✔
1549
            ) {
1550
                Ok(parent_info) => Some(parent_info),
154,936✔
1551
                Err(Error::BurnchainTipChanged) => {
1552
                    self.globals.counters.bump_missed_tenures();
41✔
1553
                    None
41✔
1554
                }
1555
                Err(..) => None,
×
1556
            };
1557
            if parent_info.is_none() {
154,977✔
1558
                warn!(
41✔
1559
                    "No parent for best-tip {}/{}",
1560
                    &best_stacks_tip.consensus_hash, &best_stacks_tip.anchored_block_hash
41✔
1561
                );
1562
            }
154,936✔
1563
            let canonical = best_stacks_tip.consensus_hash == stacks_tip.consensus_hash
154,977✔
1564
                && best_stacks_tip.anchored_block_hash == stacks_tip.anchored_block_hash;
154,912✔
1565
            (parent_info, canonical)
154,977✔
1566
        } else {
1567
            debug!("No Stacks chain tip known, will return a genesis block");
425✔
1568
            let burnchain_params = burnchain_params_from_config(&self.config.burnchain);
425✔
1569

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

1576
            (
425✔
1577
                Some(ParentStacksBlockInfo {
425✔
1578
                    stacks_parent_header: chain_tip.metadata,
425✔
1579
                    parent_consensus_hash: FIRST_BURNCHAIN_CONSENSUS_HASH,
425✔
1580
                    parent_block_burn_height: 0,
425✔
1581
                    parent_block_total_burn: 0,
425✔
1582
                    parent_winning_vtxindex: 0,
425✔
1583
                    coinbase_nonce: 0,
425✔
1584
                }),
425✔
1585
                true,
425✔
1586
            )
425✔
1587
        }
1588
    }
155,402✔
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(
155,360✔
1595
        &self,
155,360✔
1596
        chain_state: &StacksChainState,
155,360✔
1597
        parent_block_info: &ParentStacksBlockInfo,
155,360✔
1598
        force: bool,
155,360✔
1599
    ) -> Option<(u64, u64)> {
155,360✔
1600
        let parent_consensus_hash = &parent_block_info.parent_consensus_hash;
155,360✔
1601
        let stacks_parent_header = &parent_block_info.stacks_parent_header;
155,360✔
1602
        let parent_block_burn_height = parent_block_info.parent_block_burn_height;
155,360✔
1603

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

1607
        // has the tip changed from our previously-mined block for this epoch?
1608
        let should_unconditionally_mine = last_mined_blocks.is_empty()
155,360✔
1609
            || (last_mined_blocks.len() == 1 && !self.failed_to_submit_last_attempt);
145,433✔
1610
        let (attempt, max_txs) = if should_unconditionally_mine {
155,360✔
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 {
26,516✔
1615
                info!("Have only attempted one block; unconditionally trying again");
16,589✔
1616
            }
9,927✔
1617
            let attempt = last_mined_blocks.len() as u64 + 1;
26,516✔
1618
            let mut max_txs = 0;
26,516✔
1619
            for last_mined_block in last_mined_blocks.iter() {
26,516✔
1620
                max_txs = cmp::max(max_txs, last_mined_block.anchored_block.txs.len());
16,589✔
1621
            }
16,589✔
1622
            (attempt, max_txs)
26,516✔
1623
        } else {
1624
            let mut best_attempt = 0;
128,844✔
1625
            let mut max_txs = 0;
128,844✔
1626
            debug!(
128,844✔
1627
                "Consider {} in-flight Stacks tip(s)",
1628
                &last_mined_blocks.len()
×
1629
            );
1630
            for prev_block in last_mined_blocks.iter() {
128,911✔
1631
                debug!(
128,911✔
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());
128,911✔
1640

1641
                if prev_block.parent_consensus_hash == *parent_consensus_hash
128,911✔
1642
                    && prev_block.burn_hash == self.burn_block.burn_header_hash
128,861✔
1643
                    && prev_block.anchored_block.header.parent_block
128,861✔
1644
                        == stacks_parent_header.anchored_header.block_hash()
128,861✔
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(
128,861✔
1650
                            chain_state.db(),
128,861✔
1651
                            &StacksBlockHeader::make_index_block_hash(
128,861✔
1652
                                &prev_block.parent_consensus_hash,
128,861✔
1653
                                &stacks_parent_header.anchored_header.block_hash(),
128,861✔
1654
                            ),
128,861✔
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 {
128,827✔
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",
128,827✔
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;
128,827✔
1696
                    }
×
1697
                } else if self.burn_block.burn_header_hash == prev_block.burn_hash {
50✔
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 {}/{})",
50✔
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);
50✔
1701
                    best_attempt = cmp::max(best_attempt, prev_block.attempt);
50✔
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;
50✔
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
            }
UNCOV
1711
            (best_attempt + 1, max_txs)
×
1712
        };
1713
        Some((attempt, u64::try_from(max_txs).expect("too many txs")))
26,516✔
1714
    }
155,360✔
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> {
26,516✔
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 {
26,516✔
1723
            self.keychain.generate_proof(
84✔
1724
                VRF_MOCK_MINER_KEY,
1725
                self.burn_block.sortition_hash.as_bytes(),
84✔
1726
            )
1727
        } else {
1728
            self.keychain.generate_proof(
26,432✔
1729
                self.registered_key.target_block_height,
26,432✔
1730
                self.burn_block.sortition_hash.as_bytes(),
26,432✔
1731
            )
1732
        };
1733

1734
        let Some(vrf_proof) = vrf_proof else {
26,516✔
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!(
26,516✔
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)
26,516✔
1754
    }
26,516✔
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(
26,515✔
1776
        &mut self,
26,515✔
1777
        _parent_stacks_hash: &StacksBlockId,
26,515✔
1778
    ) -> Secp256k1PrivateKey {
26,515✔
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");
26,515✔
1782
        self.keychain.make_microblock_secret_key(
26,515✔
1783
            self.burn_block.block_height,
26,515✔
1784
            &self.burn_block.block_height.to_be_bytes(),
26,515✔
1785
        )
1786
    }
26,515✔
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(
26,515✔
1793
        &mut self,
26,515✔
1794
        chain_state: &mut StacksChainState,
26,515✔
1795
        sortdb: &SortitionDB,
26,515✔
1796
        mem_pool: &mut MemPoolDB,
26,515✔
1797
        parent_block_info: &mut ParentStacksBlockInfo,
26,515✔
1798
    ) -> Option<Vec<StacksMicroblock>> {
26,515✔
1799
        let parent_consensus_hash = &parent_block_info.parent_consensus_hash;
26,515✔
1800
        let stacks_parent_header = &mut parent_block_info.stacks_parent_header;
26,515✔
1801

1802
        let microblock_info_opt =
26,515✔
1803
            match StacksChainState::load_descendant_staging_microblock_stream_with_poison(
26,515✔
1804
                chain_state.db(),
26,515✔
1805
                &StacksBlockHeader::make_index_block_hash(
26,515✔
1806
                    parent_consensus_hash,
26,515✔
1807
                    &stacks_parent_header.anchored_header.block_hash(),
26,515✔
1808
                ),
26,515✔
1809
                0,
26,515✔
1810
                u16::MAX,
26,515✔
1811
            ) {
26,515✔
1812
                Ok(x) => {
26,515✔
1813
                    let num_mblocks = x.as_ref().map(|(mblocks, ..)| mblocks.len()).unwrap_or(0);
26,515✔
1814
                    debug!(
26,515✔
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
26,515✔
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 {
26,515✔
1831
            if let Some(tail) = microblocks.last() {
17✔
1832
                debug!(
17✔
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());
17✔
1842

1843
            if let Some(poison_payload) = poison_opt {
17✔
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
            }
17✔
1872
        }
26,498✔
1873

1874
        microblock_info_opt.map(|(stream, _)| stream)
26,515✔
1875
    }
26,515✔
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>(
22,883✔
1908
        config: &Config,
22,883✔
1909
        keychain: &Keychain,
22,883✔
1910
        burnchain: &Burnchain,
22,883✔
1911
        sortdb: &SortitionDB,
22,883✔
1912
        recipients: &[PoxAddress],
22,883✔
1913
        start_mine_height: u64,
22,883✔
1914
        at_burn_block: Option<u64>,
22,883✔
1915
        mut get_prior_winning_prob: F,
22,883✔
1916
        mut set_prior_winning_prob: G,
22,883✔
1917
    ) -> u64
22,883✔
1918
    where
22,883✔
1919
        F: FnMut(u64) -> f64,
22,883✔
1920
        G: FnMut(u64, f64),
22,883✔
1921
    {
1922
        let config_file_burn_fee_cap = config.get_burnchain_config().burn_fee_cap;
22,883✔
1923
        let miner_config = config.get_miner_config();
22,883✔
1924

1925
        if miner_config.target_win_probability < 0.00001 {
22,883✔
1926
            // this field is effectively zero
1927
            return config_file_burn_fee_cap;
22,883✔
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
    }
22,883✔
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(
22,883✔
2082
        &self,
22,883✔
2083
        burn_db: &mut SortitionDB,
22,883✔
2084
        chain_state: &mut StacksChainState,
22,883✔
2085
        block_hash: BlockHeaderHash,
22,883✔
2086
        parent_block_burn_height: u64,
22,883✔
2087
        parent_winning_vtxindex: u16,
22,883✔
2088
        vrf_proof: &VRFProof,
22,883✔
2089
        target_epoch_id: StacksEpochId,
22,883✔
2090
    ) -> Option<BlockstackOperationType> {
22,883✔
2091
        // let's figure out the recipient set!
2092
        let recipients = match get_next_recipients(
22,883✔
2093
            &self.burn_block,
22,883✔
2094
            chain_state,
22,883✔
2095
            burn_db,
22,883✔
2096
            &self.burnchain,
22,883✔
2097
            &OnChainRewardSetProvider::new(),
22,883✔
2098
        ) {
22,883✔
2099
            Ok(x) => x,
22,883✔
2100
            Err(e) => {
×
2101
                error!("Relayer: Failure fetching recipient set: {e:?}");
×
2102
                return None;
×
2103
            }
2104
        };
2105

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

2119
        let burn_fee_cap = Self::get_mining_spend_amount(
22,883✔
2120
            &self.config,
22,883✔
2121
            &self.keychain,
22,883✔
2122
            &self.burnchain,
22,883✔
2123
            burn_db,
22,883✔
2124
            &commit_outs,
22,883✔
2125
            self.globals.get_start_mining_height(),
22,883✔
2126
            None,
22,883✔
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 {
22,883✔
2135
            warn!("Calculated burn_fee_cap is 0; will not mine");
×
2136
            return None;
×
2137
        }
22,883✔
2138
        let sunset_burn = self.burnchain.expected_sunset_burn(
22,883✔
2139
            self.burn_block.block_height + 1,
22,883✔
2140
            burn_fee_cap,
22,883✔
2141
            target_epoch_id,
22,883✔
2142
        );
2143
        let rest_commit = burn_fee_cap - sunset_burn;
22,883✔
2144

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

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

2172
        if let Some(stacks_tip) =
277,191✔
2173
            NakamotoChainState::get_canonical_block_header(chainstate.db(), sortdb)
277,191✔
2174
                .expect("FATAL: could not query canonical Stacks chain tip")
277,191✔
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;
277,191✔
2179
            let has_unprocessed = StacksChainState::has_higher_unprocessed_blocks(
277,191✔
2180
                chainstate.db(),
277,191✔
2181
                stacks_tip.anchored_header.height(),
277,191✔
2182
                process_deadline,
277,191✔
2183
            )
2184
            .expect("FATAL: failed to query staging blocks");
277,191✔
2185
            if has_unprocessed {
277,191✔
2186
                let highest_unprocessed_opt = StacksChainState::get_highest_unprocessed_block(
2✔
2187
                    chainstate.db(),
2✔
2188
                    process_deadline,
2✔
2189
                )
2190
                .expect("FATAL: failed to query staging blocks");
2✔
2191

2192
                if let Some(highest_unprocessed) = highest_unprocessed_opt {
2✔
2193
                    let highest_unprocessed_block_sn_opt =
2✔
2194
                        SortitionDB::get_block_snapshot_consensus(
2✔
2195
                            sortdb.conn(),
2✔
2196
                            &highest_unprocessed.consensus_hash,
2✔
2197
                        )
2198
                        .expect("FATAL: could not query sortition DB");
2✔
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 {
2✔
2203
                        if stacks_tip.anchored_header.height()
2✔
2204
                            + u64::from(burnchain.pox_constants.prepare_length)
2✔
2205
                            > highest_unprocessed.height
2✔
2206
                            && highest_unprocessed_block_sn.block_height
1✔
2207
                                + u64::from(burnchain.pox_constants.prepare_length)
1✔
2208
                                > sort_tip.block_height
1✔
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;
1✔
2213
                        }
1✔
2214
                    }
×
2215
                }
×
2216
            }
277,189✔
2217
        }
×
2218
        // we can mine
2219
        false
277,190✔
2220
    }
277,191✔
2221

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

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

2243
    /// Only used in mock signing to retrieve the mock signatures for the given mock proposal
2244
    fn wait_for_mock_signatures(
98✔
2245
        &self,
98✔
2246
        mock_proposal: &MockProposal,
98✔
2247
        stackerdbs: &StackerDBs,
98✔
2248
        timeout: Duration,
98✔
2249
    ) -> Result<Vec<MockSignature>, ChainstateError> {
98✔
2250
        let reward_cycle = self
98✔
2251
            .burnchain
98✔
2252
            .block_height_to_reward_cycle(self.burn_block.block_height)
98✔
2253
            .expect("BUG: block commit exists before first block height");
98✔
2254
        let signers_contract_id = MessageSlotID::BlockResponse
98✔
2255
            .stacker_db_contract(self.config.is_mainnet(), reward_cycle);
98✔
2256
        let slot_ids: Vec<_> = stackerdbs
98✔
2257
            .get_signers(&signers_contract_id)
98✔
2258
            .expect("FATAL: could not get signers from stacker DB")
98✔
2259
            .into_iter()
98✔
2260
            .enumerate()
98✔
2261
            .map(|(slot_id, _)| {
250✔
2262
                u32::try_from(slot_id).expect("FATAL: too many signers to fit into u32 range")
250✔
2263
            })
250✔
2264
            .collect();
98✔
2265
        let mock_poll_start = Instant::now();
98✔
2266
        let mut mock_signatures = vec![];
98✔
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 {
463,162✔
2271
            let chunks = stackerdbs.get_latest_chunks(&signers_contract_id, &slot_ids)?;
463,064✔
2272
            for chunk in chunks.into_iter().flatten() {
2,315,320✔
2273
                if let Ok(SignerMessage::MockSignature(mock_signature)) =
1,779,765✔
2274
                    SignerMessage::consensus_deserialize(&mut chunk.as_slice())
2,315,320✔
2275
                {
2276
                    if mock_signature.mock_proposal == *mock_proposal
1,779,765✔
2277
                        && !mock_signatures.contains(&mock_signature)
322,217✔
2278
                    {
245✔
2279
                        mock_signatures.push(mock_signature);
245✔
2280
                    }
1,779,520✔
2281
                }
535,555✔
2282
            }
2283
        }
2284
        Ok(mock_signatures)
98✔
2285
    }
98✔
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 {
334✔
2289
        let miner_contract_id = boot_code_id(MINERS_NAME, self.config.is_mainnet());
334✔
2290
        let mut miners_stackerdb = StackerDBSession::new(
334✔
2291
            &self.config.node.rpc_bind,
334✔
2292
            miner_contract_id,
334✔
2293
            self.config.miner.stackerdb_timeout,
334✔
2294
        );
2295
        let miner_slot_ids: Vec<_> = (0..MINER_SLOT_COUNT * 2).collect();
334✔
2296
        if let Ok(messages) = miners_stackerdb.get_latest_chunks(&miner_slot_ids) {
334✔
2297
            for message in messages.into_iter().flatten() {
1,284✔
2298
                if message.is_empty() {
1,284✔
2299
                    continue;
16✔
2300
                }
1,268✔
2301
                let Ok(SignerMessage::MockBlock(mock_block)) =
634✔
2302
                    SignerMessage::consensus_deserialize(&mut message.as_slice())
1,268✔
2303
                else {
2304
                    continue;
634✔
2305
                };
2306
                if mock_block.mock_proposal.peer_info == *peer_info {
634✔
2307
                    return true;
158✔
2308
                }
476✔
2309
            }
2310
        }
×
2311
        false
176✔
2312
    }
334✔
2313

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

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

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

2345
        // Create a peer info view of the current state
2346
        let peer_info = self.generate_peer_info();
334✔
2347
        if self.mock_block_exists(&peer_info) {
334✔
2348
            debug!(
156✔
2349
                "Already sent mock miner block proposal for current peer info view. Not sending another mock proposal."
2350
            );
2351
            return Ok(());
156✔
2352
        }
178✔
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);
178✔
2356
        let last_winner_snapshot = ih
178✔
2357
            .get_last_snapshot_with_sortition(self.burn_block.block_height)
178✔
2358
            .map_err(|e| e.to_string())?;
178✔
2359

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

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

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

2382
        SignerCoordinator::send_miners_message(
100✔
2383
            &mining_key,
100✔
2384
            &burn_db,
100✔
2385
            &self.burn_block,
100✔
2386
            &stackerdbs,
100✔
2387
            SignerMessage::MockProposal(mock_proposal.clone()),
100✔
2388
            MinerSlotID::BlockProposal, // There is no specific slot for mock miner messages so we use BlockProposal for MockProposal as well.
100✔
2389
            self.config.is_mainnet(),
100✔
2390
            &mut miners_stackerdb,
100✔
2391
            &election_sortition,
100✔
2392
            &miner_db,
100✔
2393
        )
2394
        .map_err(|e| {
100✔
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...");
100✔
2401
        let mock_signatures = self
100✔
2402
            .wait_for_mock_signatures(&mock_proposal, &stackerdbs, Duration::from_secs(10))
100✔
2403
            .map_err(|e| e.to_string())?;
100✔
2404

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

2410
        info!("Sending mock block to stackerdb: {mock_block:?}");
100✔
2411
        SignerCoordinator::send_miners_message(
100✔
2412
            &mining_key,
100✔
2413
            &burn_db,
100✔
2414
            &self.burn_block,
100✔
2415
            &stackerdbs,
100✔
2416
            SignerMessage::MockBlock(mock_block),
100✔
2417
            MinerSlotID::BlockPushed, // There is no specific slot for mock miner messages. Let's use BlockPushed for MockBlock since MockProposal uses BlockProposal.
100✔
2418
            self.config.is_mainnet(),
100✔
2419
            &mut miners_stackerdb,
100✔
2420
            &election_sortition,
100✔
2421
            &miner_db,
100✔
2422
        )
2423
        .map_err(|e| {
100✔
2424
            warn!("Failed to write mock block to stackerdb.");
×
2425
            e.to_string()
×
2426
        })?;
×
2427
        Ok(())
100✔
2428
    }
155,406✔
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> {
155,404✔
2437
        debug!("block miner thread ID is {:?}", thread::current().id());
155,404✔
2438
        fault_injection_long_tenure();
155,404✔
2439

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

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

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

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

2468
        self.globals.set_last_miner_config(miner_config);
155,404✔
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(
155,404✔
2473
            &burn_db_path,
155,404✔
2474
            true,
2475
            self.burnchain.pox_constants.clone(),
155,404✔
2476
            Some(self.config.node.get_marf_opts()),
155,404✔
2477
        )
2478
        .expect("FATAL: could not open sortition DB");
155,404✔
2479

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

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

2492
        let tenure_begin = get_epoch_time_ms();
155,404✔
2493

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

2500
        let (Some(mut parent_block_info), _) =
155,363✔
2501
            self.load_block_parent_info(&mut burn_db, &mut chain_state)
155,404✔
2502
        else {
2503
            return None;
41✔
2504
        };
2505
        let (attempt, max_txs) =
26,519✔
2506
            self.get_mine_attempt(&chain_state, &parent_block_info, force_remine)?;
155,363✔
2507
        let vrf_proof = self.make_vrf_proof()?;
26,519✔
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(
26,519✔
2512
            &parent_block_info.stacks_parent_header.index_block_hash(),
26,519✔
2513
        );
2514
        let mblock_pubkey_hash = {
26,519✔
2515
            let mut pubkh = Hash160::from_node_public_key(&StacksPublicKey::from_private(
26,519✔
2516
                &microblock_private_key,
26,519✔
2517
            ));
26,519✔
2518
            if cfg!(test) {
26,519✔
2519
                if let Ok(mblock_pubkey_hash_str) = std::env::var("STACKS_MICROBLOCK_PUBKEY_HASH") {
26,519✔
2520
                    if let Ok(bad_pubkh) = Hash160::from_hex(&mblock_pubkey_hash_str) {
4✔
2521
                        debug!("Fault injection: set microblock public key hash to {bad_pubkh}");
4✔
2522
                        pubkh = bad_pubkh
4✔
2523
                    }
×
2524
                }
26,515✔
2525
            }
×
2526
            pubkh
26,519✔
2527
        };
2528

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

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

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

2547
        // build the block itself
2548
        let mut builder_settings = self.config.make_block_builder_settings(
26,519✔
2549
            attempt,
26,519✔
2550
            false,
2551
            self.globals.get_miner_status(),
26,519✔
2552
        );
2553
        if microblocks_disabled {
26,519✔
2554
            builder_settings.confirm_microblocks = false;
14,165✔
2555
            if cfg!(test)
14,165✔
2556
                && std::env::var("STACKS_TEST_CONFIRM_MICROBLOCKS_POST_25").as_deref() == Ok("1")
14,165✔
2557
            {
×
2558
                builder_settings.confirm_microblocks = true;
×
2559
            }
14,165✔
2560
        }
12,354✔
2561
        let (anchored_block, _, _) = match StacksBlockBuilder::build_anchored_block(
26,519✔
2562
            &chain_state,
26,519✔
2563
            &burn_db.index_handle(&burn_tip.sortition_id),
26,519✔
2564
            &mut mem_pool,
26,519✔
2565
            &parent_block_info.stacks_parent_header,
26,519✔
2566
            parent_block_info.parent_block_total_burn,
26,519✔
2567
            &vrf_proof,
26,519✔
2568
            &mblock_pubkey_hash,
26,519✔
2569
            &coinbase_tx,
26,519✔
2570
            builder_settings,
26,519✔
2571
            Some(&self.event_dispatcher),
26,519✔
2572
            &self.burnchain,
26,519✔
2573
        ) {
2574
            Ok(block) => block,
26,163✔
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) => {
356✔
2612
                error!("Relayer: Failure mining anchored block: {e}");
356✔
2613
                return None;
356✔
2614
            }
2615
        };
2616

2617
        let miner_config = self.config.get_miner_config();
26,163✔
2618

2619
        if attempt > 1
26,163✔
2620
            && miner_config.min_tx_count > 0
16,247✔
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
        }
26,163✔
2627

2628
        if miner_config.only_increase_tx_count
26,163✔
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
        }
26,163✔
2634

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

2647
        // let's commit
2648
        #[cfg(test)]
2649
        if self.globals.counters.skip_commit_op.get() {
26,163✔
2650
            debug!("Relayer: fault injection: skip block commit");
3,275✔
2651
            return None;
3,275✔
2652
        }
22,888✔
2653
        let op = self.make_block_commit(
22,888✔
2654
            &mut burn_db,
22,888✔
2655
            &mut chain_state,
22,888✔
2656
            anchored_block.block_hash(),
22,888✔
2657
            parent_block_info.parent_block_burn_height,
22,888✔
2658
            parent_block_info.parent_winning_vtxindex,
22,888✔
2659
            &vrf_proof,
22,888✔
2660
            target_epoch_id,
22,888✔
2661
        )?;
×
2662
        let burn_fee = if let BlockstackOperationType::LeaderBlockCommit(ref op) = &op {
22,888✔
2663
            op.burn_fee
22,888✔
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())
22,888✔
2672
            .expect("FATAL: failed to query sortition DB for canonical burn chain tip");
22,888✔
2673

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

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

2695
            if stacks_tip.anchored_block_hash != anchored_block.header.parent_block
22,597✔
2696
                || parent_block_info.parent_consensus_hash != stacks_tip.consensus_hash
22,589✔
2697
                || cur_burn_chain_tip.burn_header_hash != self.burn_block.burn_header_hash
22,589✔
2698
                || is_miner_blocked
22,557✔
2699
                || has_unprocessed
22,543✔
2700
            {
2701
                info!(
49✔
2702
                    "Relayer: Cancel block-commit; chain tip(s) have changed or cancelled";
2703
                    "block_hash" => %anchored_block.block_hash(),
49✔
2704
                    "tx_count" => anchored_block.txs.len(),
49✔
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,
49✔
2710
                    "old_tip_burn_block_hash" => %self.burn_block.burn_header_hash,
2711
                    "old_tip_burn_block_height" => self.burn_block.block_height,
49✔
2712
                    "old_tip_burn_block_sortition_id" => %self.burn_block.sortition_id,
2713
                    "attempt" => attempt,
49✔
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,
49✔
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();
49✔
2723
                return None;
49✔
2724
            }
22,548✔
2725
        }
291✔
2726

2727
        let mut op_signer = self.keychain.generate_op_signer();
22,839✔
2728
        info!(
22,839✔
2729
            "Relayer: Submit block-commit";
2730
            "burn_fee" => burn_fee,
22,834✔
2731
            "block_hash" => %anchored_block.block_hash(),
22,834✔
2732
            "tx_count" => anchored_block.txs.len(),
22,834✔
2733
            "target_height" => anchored_block.header.total_work.work,
22,834✔
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,
22,834✔
2738
            "tip_burn_block_hash" => %self.burn_block.burn_header_hash,
2739
            "tip_burn_block_height" => self.burn_block.block_height,
22,834✔
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
22,834✔
2745
        );
2746

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

2753
        let res = bitcoin_controller.submit_operation(target_epoch_id, op, &mut op_signer);
22,839✔
2754
        match res {
16,114✔
2755
            Ok(_) => {
6,644✔
2756
                self.failed_to_submit_last_attempt = false;
6,644✔
2757
                self.globals
6,644✔
2758
                    .counters
6,644✔
2759
                    .bump_neon_submitted_commits(self.burn_block.block_height);
6,644✔
2760
            }
6,644✔
2761
            Err(_) if mock_mining => {
81✔
2762
                debug!("Relayer: Mock-mining enabled; not sending Bitcoin transaction");
81✔
2763
                self.failed_to_submit_last_attempt = true;
81✔
2764
            }
2765
            Err(BurnchainControllerError::IdenticalOperation) => {
2766
                info!("Relayer: Block-commit already submitted");
16,114✔
2767
                self.failed_to_submit_last_attempt = true;
16,114✔
2768
                return None;
16,114✔
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,725✔
2778
            parent_consensus_hash: parent_block_info.parent_consensus_hash.clone(),
6,725✔
2779
            consensus_hash: cur_burn_chain_tip.consensus_hash.clone(),
6,725✔
2780
            burn_hash: cur_burn_chain_tip.burn_header_hash.clone(),
6,725✔
2781
            burn_block_height: cur_burn_chain_tip.block_height,
6,725✔
2782
            orig_burn_hash: self.burn_block.burn_header_hash.clone(),
6,725✔
2783
            anchored_block,
6,725✔
2784
            attempt,
6,725✔
2785
            tenure_begin,
6,725✔
2786
        };
6,725✔
2787

2788
        if mock_mining {
6,725✔
2789
            let stacks_block_height = assembled_block.anchored_block.header.total_work.work;
81✔
2790
            info!("Mock mined Stacks block {stacks_block_height}");
81✔
2791
            if let Some(dir) = mock_mining_output_dir {
81✔
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
            }
54✔
2808
        }
6,644✔
2809

2810
        Some(MinerThreadResult::Block(
6,725✔
2811
            assembled_block,
6,725✔
2812
            microblock_private_key,
6,725✔
2813
            bitcoin_controller.get_ongoing_commit(),
6,725✔
2814
        ))
6,725✔
2815
    }
155,404✔
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 {
277✔
2821
        let config = runloop.config().clone();
277✔
2822
        let globals = runloop.get_globals();
277✔
2823
        let burn_db_path = config.get_burn_db_file_path();
277✔
2824
        let stacks_chainstate_path = config.get_chainstate_path_str();
277✔
2825
        let is_mainnet = config.is_mainnet();
277✔
2826
        let chain_id = config.burnchain.chain_id;
277✔
2827

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

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

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

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

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

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

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

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

277✔
2886
            relayer,
277✔
2887

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

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

2901
    /// Get an immutible ref to the chainstate
2902
    pub fn chainstate_ref(&self) -> &StacksChainState {
291,256✔
2903
        self.chainstate
291,256✔
2904
            .as_ref()
291,256✔
2905
            .expect("FATAL: tried to access chainstate while it was taken")
291,256✔
2906
    }
291,256✔
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,423,301✔
2912
    where
1,423,301✔
2913
        F: FnOnce(&mut RelayerThread, &mut SortitionDB, &mut StacksChainState, &mut MemPoolDB) -> R,
1,423,301✔
2914
    {
2915
        let mut sortdb = self
1,423,301✔
2916
            .sortdb
1,423,301✔
2917
            .take()
1,423,301✔
2918
            .expect("FATAL: tried to take sortdb while taken");
1,423,301✔
2919
        let mut chainstate = self
1,423,301✔
2920
            .chainstate
1,423,301✔
2921
            .take()
1,423,301✔
2922
            .expect("FATAL: tried to take chainstate while taken");
1,423,301✔
2923
        let mut mempool = self
1,423,301✔
2924
            .mempool
1,423,301✔
2925
            .take()
1,423,301✔
2926
            .expect("FATAL: tried to take mempool while taken");
1,423,301✔
2927
        let res = func(self, &mut sortdb, &mut chainstate, &mut mempool);
1,423,301✔
2928
        self.sortdb = Some(sortdb);
1,423,301✔
2929
        self.chainstate = Some(chainstate);
1,423,301✔
2930
        self.mempool = Some(mempool);
1,423,301✔
2931
        res
1,423,301✔
2932
    }
1,423,301✔
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 {
879,938✔
2937
        // a network download pass took place
2938
        self.min_network_download_passes <= self.last_network_download_passes
879,938✔
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()
677,616✔
2941
        // we're not supposed to wait at all
2942
        || !self.config.miner.wait_for_block_download
161,699✔
2943
    }
879,938✔
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) {
702,175✔
2965
        debug!(
702,175✔
2966
            "Relayer: Handle network result (from {})",
2967
            net_result.burn_height
2968
        );
2969

2970
        if self.last_network_block_height != net_result.burn_height {
702,175✔
2971
            // burnchain advanced; disable mining until we also do a download pass.
2972
            self.last_network_block_height = net_result.burn_height;
19,126✔
2973
            self.min_network_download_passes = net_result.num_download_passes + 1;
19,126✔
2974
            self.min_network_inv_passes = net_result.num_inv_sync_passes + 1;
19,126✔
2975
            self.last_network_block_height_ts = get_epoch_time_ms();
19,126✔
2976
            debug!(
19,126✔
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,126✔
2981
        }
683,049✔
2982

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

3000
        if net_receipts.num_new_blocks > 0 || net_receipts.num_new_confirmed_microblocks > 0 {
702,175✔
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,086✔
3004
            signal_mining_blocked(self.globals.get_miner_status());
1,086✔
3005
        }
701,089✔
3006

3007
        let mempool_txs_added = net_receipts.mempool_txs_added.len();
702,175✔
3008
        if mempool_txs_added > 0 {
702,175✔
3009
            self.event_dispatcher
1,124✔
3010
                .process_new_mempool_txs(net_receipts.mempool_txs_added);
1,124✔
3011
        }
701,051✔
3012

3013
        let num_unconfirmed_microblock_tx_receipts =
702,175✔
3014
            net_receipts.processed_unconfirmed_state.receipts.len();
702,175✔
3015
        if num_unconfirmed_microblock_tx_receipts > 0 {
702,175✔
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
        }
702,174✔
3025

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

3032
        // synchronize unconfirmed tx index to p2p thread
3033
        self.with_chainstate(|relayer_thread, _sortdb, chainstate, _mempool| {
702,175✔
3034
            relayer_thread.globals.send_unconfirmed_txs(chainstate);
702,172✔
3035
        });
702,172✔
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;
702,175✔
3040
        self.last_network_inv_passes = net_result.num_inv_sync_passes;
702,175✔
3041
        if self.has_waited_for_latest_blocks() {
702,175✔
3042
            debug!("Relayer: did a download pass, so unblocking mining");
702,172✔
3043
            signal_mining_ready(self.globals.get_miner_status());
702,172✔
3044
        }
3✔
3045
    }
702,175✔
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,334✔
3053
        &mut self,
6,334✔
3054
        anchored_block: &StacksBlock,
6,334✔
3055
        consensus_hash: &ConsensusHash,
6,334✔
3056
        parent_consensus_hash: &ConsensusHash,
6,334✔
3057
    ) -> Result<bool, ChainstateError> {
6,334✔
3058
        if StacksChainState::has_stored_block(
6,334✔
3059
            self.chainstate_ref().db(),
6,334✔
3060
            &self.chainstate_ref().blocks_path,
6,334✔
3061
            consensus_hash,
6,334✔
3062
            &anchored_block.block_hash(),
6,334✔
3063
        )? {
×
3064
            // already processed my tenure
3065
            return Ok(false);
×
3066
        }
6,334✔
3067
        let burn_height =
6,334✔
3068
            SortitionDB::get_block_snapshot_consensus(self.sortdb_ref().conn(), consensus_hash)
6,334✔
3069
                .map_err(|e| {
6,334✔
3070
                    error!("Failed to find block snapshot for mined block: {e}");
×
3071
                    e
×
3072
                })?
×
3073
                .ok_or_else(|| {
6,334✔
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,334✔
3080
            .expect("FATAL: no epoch defined")
6,334✔
3081
            .epoch_id;
3082

3083
        // failsafe
3084
        if !Relayer::static_check_problematic_relayed_block(
6,334✔
3085
            self.chainstate_ref().mainnet,
6,334✔
3086
            epoch_id,
6,334✔
3087
            anchored_block,
6,334✔
3088
        ) {
6,334✔
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,334✔
3125

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

3138
        Ok(true)
6,308✔
3139
    }
6,334✔
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,308✔
3146
        // process the block
3147
        let stacks_blocks_processed = self.globals.coord_comms.get_stacks_blocks_processed();
6,308✔
3148
        if !self.globals.coord_comms.announce_new_stacks_block() {
6,308✔
3149
            return Err(Error::CoordinatorClosed);
×
3150
        }
6,308✔
3151
        if !self
6,308✔
3152
            .globals
6,308✔
3153
            .coord_comms
6,308✔
3154
            .wait_for_stacks_blocks_processed(stacks_blocks_processed, u64::MAX)
6,308✔
3155
        {
3156
            // basically unreachable
3157
            warn!("ChainsCoordinator timed out while waiting for new stacks block to be processed");
×
3158
            return Ok(false);
×
3159
        }
6,308✔
3160
        debug!("Relayer: Stacks block has been processed");
6,308✔
3161

3162
        Ok(true)
6,308✔
3163
    }
6,308✔
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,374✔
3167
        match (cur, new) {
15,374✔
3168
            (Some(cur), None) => Some(cur),
52✔
3169
            (None, Some(new)) => Some(new),
6,532✔
3170
            (None, None) => None,
2,709✔
3171
            (Some(cur), Some(new)) => {
6,081✔
3172
                if cur.stacks_height < new.stacks_height {
6,081✔
3173
                    Some(new)
6,075✔
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,374✔
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,688✔
3200
        &mut self,
7,688✔
3201
        consensus_hash: ConsensusHash,
7,688✔
3202
        block_header_hash: BlockHeaderHash,
7,688✔
3203
        burn_hash: BurnchainHeaderHash,
7,688✔
3204
    ) -> (bool, Option<MinerTip>) {
7,688✔
3205
        let mut miner_tip = None;
7,688✔
3206
        let sn =
7,688✔
3207
            SortitionDB::get_block_snapshot_consensus(self.sortdb_ref().conn(), &consensus_hash)
7,688✔
3208
                .expect("FATAL: failed to query sortition DB")
7,688✔
3209
                .expect("FATAL: unknown consensus hash");
7,688✔
3210

3211
        debug!(
7,688✔
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,334✔
3217
            self.last_mined_blocks.remove(&block_header_hash)
7,688✔
3218
        {
3219
            // we won!
3220
            let AssembledAnchorBlock {
3221
                parent_consensus_hash,
6,334✔
3222
                anchored_block: mined_block,
6,334✔
3223
                burn_hash: mined_burn_hash,
6,334✔
3224
                attempt: _,
3225
                ..
3226
            } = last_mined_block_data;
6,334✔
3227

3228
            let reward_block_height = mined_block.header.total_work.work + MINER_REWARD_MATURITY;
6,334✔
3229
            info!(
6,334✔
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,334✔
3233
                  "stacks_header" => %block_header_hash,
3234
                  "burn_hash" => %mined_burn_hash,
3235
            );
3236

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

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

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

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

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

3283
            if !snapshot.pox_valid {
6,308✔
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,308✔
3291
                let bh = mined_block.block_hash();
6,308✔
3292
                let height = mined_block.header.total_work.work;
6,308✔
3293

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

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

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

3336
        (true, miner_tip)
7,662✔
3337
    }
7,688✔
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,686✔
3347
        &mut self,
7,686✔
3348
        consensus_hash: ConsensusHash,
7,686✔
3349
        burn_hash: BurnchainHeaderHash,
7,686✔
3350
        block_header_hash: BlockHeaderHash,
7,686✔
3351
    ) -> bool {
7,686✔
3352
        let mut miner_tip = None;
7,686✔
3353
        let mut num_sortitions = 0;
7,686✔
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,686✔
3358
            .expect("FATAL: failed to read current burnchain tip");
7,686✔
3359
        let mut microblocks_disabled =
7,686✔
3360
            SortitionDB::are_microblocks_disabled(self.sortdb_ref().conn(), burn_tip.block_height)
7,686✔
3361
                .expect("FATAL: failed to query epoch's microblock status");
7,686✔
3362

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

3370
            debug!(
7,417✔
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,455✔
3376
                num_sortitions += 1;
7,455✔
3377
                let sn = {
7,455✔
3378
                    let ic = self.sortdb_ref().index_conn();
7,455✔
3379
                    SortitionDB::get_ancestor_snapshot(
7,455✔
3380
                        &ic,
7,455✔
3381
                        block_to_process,
7,455✔
3382
                        &burn_tip.sortition_id,
7,455✔
3383
                    )
3384
                    .expect("FATAL: failed to read ancestor snapshot from sortition DB")
7,455✔
3385
                    .expect("Failed to find block in fork processed by burnchain indexer")
7,455✔
3386
                };
3387
                if !sn.sortition {
7,455✔
3388
                    debug!(
36✔
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;
36✔
3396
                }
7,419✔
3397
                debug!(
7,419✔
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,419✔
3405
                    sn.consensus_hash,
7,419✔
3406
                    sn.burn_header_hash,
7,419✔
3407
                    sn.winning_stacks_block_hash,
7,419✔
3408
                ));
7,419✔
3409
            }
3410
            tenures
7,417✔
3411
        } else {
3412
            // first-ever tenure processed
3413
            vec![(consensus_hash, burn_hash, block_header_hash)]
269✔
3414
        };
3415

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

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

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

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

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

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

3462
            if mtip.consensus_hash != stacks_tip_consensus_hash
6,307✔
3463
                || mtip.block_hash != stacks_tip_block_hash
6,305✔
3464
            {
3465
                debug!(
2✔
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;
2✔
3471
            } else {
3472
                debug!(
6,305✔
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,305✔
3483
                    Relayer::refresh_unconfirmed(chainstate, sortdb);
6,305✔
3484
                    relayer_thread.globals.send_unconfirmed_txs(chainstate);
6,305✔
3485
                });
6,305✔
3486

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

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

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

3501
        // resume mining if we blocked it
3502
        if num_tenures > 0 || num_sortitions > 0 {
7,686✔
3503
            if self.miner_tip.is_some() {
7,434✔
3504
                // we won the highest tenure
3505
                if self.config.node.mine_microblocks && !microblocks_disabled {
6,357✔
3506
                    // mine a microblock first
387✔
3507
                    self.mined_stacks_block = true;
387✔
3508
                } else {
6,357✔
3509
                    // mine a Stacks block first -- we won't build microblocks
5,970✔
3510
                    self.mined_stacks_block = false;
5,970✔
3511
                }
5,970✔
3512
            } else {
1,077✔
3513
                // mine a Stacks block first -- we didn't win
1,077✔
3514
                self.mined_stacks_block = false;
1,077✔
3515
            }
1,077✔
3516
            signal_mining_ready(self.globals.get_miner_status());
7,434✔
3517
        }
252✔
3518
        true
7,686✔
3519
    }
7,686✔
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,686✔
3524
        // update state
3525
        let my_miner_tip = std::mem::take(&mut self.miner_tip);
7,686✔
3526
        let best_tip = Self::pick_higher_tip(my_miner_tip.clone(), new_miner_tip.clone());
7,686✔
3527
        if best_tip == new_miner_tip && best_tip != my_miner_tip {
7,686✔
3528
            // tip has changed
3529
            debug!("Relayer: Best miner tip went from {my_miner_tip:?} to {new_miner_tip:?}");
6,301✔
3530
            self.microblock_stream_cost = ExecutionCost::ZERO;
6,301✔
3531
        }
1,385✔
3532
        self.miner_tip = best_tip;
7,686✔
3533
    }
7,686✔
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) {
14,072✔
3537
        if self.miner_tip.is_some() {
14,072✔
3538
            // we won the highest tenure
3539
            if self.config.node.mine_microblocks {
10,817✔
3540
                // mine a microblock first
10,717✔
3541
                self.mined_stacks_block = true;
10,717✔
3542
            } else {
10,817✔
3543
                // mine a Stacks block first -- we won't build microblocks
100✔
3544
                self.mined_stacks_block = false;
100✔
3545
            }
100✔
3546
        } else {
3,255✔
3547
            // mine a Stacks block first -- we didn't win
3,255✔
3548
            self.mined_stacks_block = false;
3,255✔
3549
        }
3,255✔
3550
    }
14,072✔
3551

3552
    /// Constructs and returns a LeaderKeyRegisterOp out of the provided params
3553
    fn inner_generate_leader_key_register_op(
226✔
3554
        vrf_public_key: VRFPublicKey,
226✔
3555
        consensus_hash: ConsensusHash,
226✔
3556
        miner_pk: Option<&StacksPublicKey>,
226✔
3557
    ) -> BlockstackOperationType {
226✔
3558
        let memo = if let Some(pk) = miner_pk {
226✔
3559
            Hash160::from_node_public_key(pk).as_bytes().to_vec()
194✔
3560
        } else {
3561
            vec![]
32✔
3562
        };
3563
        BlockstackOperationType::LeaderKeyRegister(LeaderKeyRegisterOp {
226✔
3564
            public_key: vrf_public_key,
226✔
3565
            memo,
226✔
3566
            consensus_hash,
226✔
3567
            vtxindex: 0,
226✔
3568
            txid: Txid([0u8; 32]),
226✔
3569
            block_height: 0,
226✔
3570
            burn_header_hash: BurnchainHeaderHash::zero(),
226✔
3571
        })
226✔
3572
    }
226✔
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) {
226✔
3577
        if burn_block.block_height == self.last_vrf_key_burn_height {
226✔
3578
            // already in-flight
3579
            return;
×
3580
        }
226✔
3581
        let cur_epoch =
226✔
3582
            SortitionDB::get_stacks_epoch(self.sortdb_ref().conn(), burn_block.block_height)
226✔
3583
                .expect("FATAL: failed to query sortition DB")
226✔
3584
                .expect("FATAL: no epoch defined")
226✔
3585
                .epoch_id;
226✔
3586
        let (vrf_pk, _) = self.keychain.make_vrf_keypair(burn_block.block_height);
226✔
3587

3588
        debug!(
226✔
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();
226✔
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
226✔
3599
            .config
226✔
3600
            .miner
226✔
3601
            .mining_key
226✔
3602
            .as_ref()
226✔
3603
            .map(StacksPublicKey::from_private);
226✔
3604
        let op = Self::inner_generate_leader_key_register_op(
226✔
3605
            vrf_pk,
226✔
3606
            burnchain_tip_consensus_hash,
226✔
3607
            miner_pk.as_ref(),
226✔
3608
        );
3609

3610
        let mut one_off_signer = self.keychain.generate_op_signer();
226✔
3611
        if let Ok(txid) =
226✔
3612
            self.bitcoin_controller
226✔
3613
                .submit_operation(cur_epoch, op, &mut one_off_signer)
226✔
3614
        {
226✔
3615
            // advance key registration state
226✔
3616
            self.last_vrf_key_burn_height = burn_block.block_height;
226✔
3617
            self.globals
226✔
3618
                .set_pending_leader_key_registration(burn_block.block_height, txid);
226✔
3619
        }
226✔
3620
    }
226✔
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,688✔
3625
        let mut ret = HashMap::new();
7,688✔
3626
        for (stacks_bhh, (assembled_block, microblock_privkey)) in last_mined_blocks.into_iter() {
7,688✔
3627
            if assembled_block.burn_block_height < burn_height {
158✔
3628
                debug!(
153✔
3629
                    "Stale mined block: {stacks_bhh} (as of {},{})",
3630
                    &assembled_block.burn_hash, assembled_block.burn_block_height
×
3631
                );
3632
                continue;
153✔
3633
            }
5✔
3634
            debug!(
5✔
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));
5✔
3639
        }
3640
        ret
7,688✔
3641
    }
7,688✔
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(
254,851✔
3654
        &mut self,
254,851✔
3655
        registered_key: RegisteredKey,
254,851✔
3656
        last_burn_block: BlockSnapshot,
254,851✔
3657
        issue_timestamp_ms: u128,
254,851✔
3658
    ) -> Option<BlockMinerThread> {
254,851✔
3659
        if self
254,851✔
3660
            .globals
254,851✔
3661
            .get_miner_status()
254,851✔
3662
            .lock()
254,851✔
3663
            .expect("FATAL: mutex poisoned")
254,851✔
3664
            .is_blocked()
254,851✔
3665
        {
3666
            debug!(
234✔
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;
234✔
3671
        }
254,617✔
3672

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

3681
        // start a new tenure
3682
        if let Some(cur_sortition) = self.globals.get_last_sortition() {
254,617✔
3683
            if last_burn_block.sortition_id != cur_sortition.sortition_id {
254,617✔
3684
                debug!(
11✔
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();
11✔
3689
                return None;
11✔
3690
            }
254,606✔
3691
        }
×
3692

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

3697
        let burn_chain_tip = burn_chain_sn.burn_header_hash;
254,606✔
3698

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

3707
        let miner_config = self.config.get_miner_config();
254,599✔
3708

3709
        let has_unprocessed = BlockMinerThread::unprocessed_blocks_prevent_mining(
254,599✔
3710
            &self.burnchain,
254,599✔
3711
            self.sortdb_ref(),
254,599✔
3712
            self.chainstate_ref(),
254,599✔
3713
            miner_config.unprocessed_block_deadline_secs,
254,599✔
3714
        );
3715
        if has_unprocessed {
254,599✔
3716
            debug!(
1✔
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;
1✔
3721
        }
254,598✔
3722

3723
        if burn_chain_sn.block_height != self.last_network_block_height
254,598✔
3724
            || !self.has_waited_for_latest_blocks()
177,766✔
3725
        {
3726
            debug!("Relayer: network has not had a chance to process in-flight blocks ({} != {} || !({}))",
76,832✔
3727
                    burn_chain_sn.block_height, self.last_network_block_height, self.debug_waited_for_latest_blocks());
×
3728
            return None;
76,832✔
3729
        }
177,766✔
3730

3731
        let tenure_cooldown = if self.config.node.mine_microblocks {
177,766✔
3732
            self.config.node.wait_time_for_microblocks as u128
32,323✔
3733
        } else {
3734
            0
145,443✔
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 {
177,766✔
3740
            debug!("Relayer: will NOT run tenure since issuance at {} is too fresh (wait until {} + {} = {})",
22,360✔
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;
22,360✔
3743
        }
155,406✔
3744

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

3751
        debug!(
155,406✔
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 =
155,406✔
3758
            BlockMinerThread::from_relayer_thread(self, registered_key, last_burn_block);
155,406✔
3759
        Some(miner_thread_state)
155,406✔
3760
    }
254,851✔
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(
332,375✔
3766
        &mut self,
332,375✔
3767
        registered_key: RegisteredKey,
332,375✔
3768
        last_burn_block: BlockSnapshot,
332,375✔
3769
        issue_timestamp_ms: u128,
332,375✔
3770
    ) -> bool {
332,375✔
3771
        if !self.miner_thread_try_join() {
332,375✔
3772
            return false;
60,420✔
3773
        }
271,955✔
3774

3775
        if !self.config.get_node_config(false).mock_mining {
271,955✔
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 {
271,083✔
3779
                debug!("Relayer: mined a Stacks block already; waiting for microblock miner");
17,104✔
3780
                return false;
17,104✔
3781
            }
253,979✔
3782
        }
872✔
3783

3784
        let Some(mut miner_thread_state) =
155,406✔
3785
            self.create_block_miner(registered_key, last_burn_block, issue_timestamp_ms)
254,851✔
3786
        else {
3787
            return false;
99,445✔
3788
        };
3789

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

3804
        true
155,406✔
3805
    }
332,375✔
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,042,495✔
3812
        if !self.config.node.mine_microblocks {
1,042,495✔
3813
            // not enabled
3814
            test_debug!("Relayer: not configured to mine microblocks");
822,480✔
3815
            return false;
822,480✔
3816
        }
220,015✔
3817

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

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

3834
        if !self.miner_thread_try_join() {
219,010✔
3835
            // already running (for an anchored block or microblock)
3836
            test_debug!("Relayer: miner thread already running so cannot mine microblock");
57,220✔
3837
            return false;
57,220✔
3838
        }
161,790✔
3839
        if self.microblock_deadline > get_epoch_time_ms() {
161,790✔
3840
            debug!(
11,805✔
3841
                "Relayer: Too soon to start a microblock tenure ({} > {})",
3842
                self.microblock_deadline,
3843
                get_epoch_time_ms()
×
3844
            );
3845
            return false;
11,805✔
3846
        }
149,985✔
3847
        if self.miner_tip.is_none() {
149,985✔
3848
            debug!("Relayer: did not win last block, so cannot mine microblocks");
101,850✔
3849
            return false;
101,850✔
3850
        }
48,135✔
3851
        if !self.mined_stacks_block {
48,135✔
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");
36,702✔
3855
            return false;
36,702✔
3856
        }
11,433✔
3857
        if self.globals.get_last_sortition().is_none() {
11,433✔
3858
            debug!("Relayer: no first sortition yet");
×
3859
            return false;
×
3860
        }
11,433✔
3861

3862
        // go ahead
3863
        true
11,433✔
3864
    }
1,042,495✔
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 {
11,433✔
3874
        let miner_tip = match self.miner_tip.as_ref() {
11,433✔
3875
            Some(tip) => tip.clone(),
11,433✔
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() {
11,433✔
3883
            Some(sn) => sn,
11,433✔
3884
            None => {
3885
                debug!("Relayer: no first sortition yet");
×
3886
                return false;
×
3887
            }
3888
        };
3889

3890
        debug!(
11,433✔
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() {
11,433✔
3896
            // already running (for an anchored block or microblock)
3897
            debug!("Relayer: miner thread already running so cannot mine microblock");
×
3898
            return false;
×
3899
        }
11,433✔
3900
        if self
11,433✔
3901
            .globals
11,433✔
3902
            .get_miner_status()
11,433✔
3903
            .lock()
11,433✔
3904
            .expect("FATAL: mutex poisoned")
11,433✔
3905
            .is_blocked()
11,433✔
3906
        {
3907
            debug!(
87✔
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);
87✔
3912
            return false;
87✔
3913
        }
11,346✔
3914

3915
        let parent_consensus_hash = &miner_tip.consensus_hash;
11,346✔
3916
        let parent_block_hash = &miner_tip.block_hash;
11,346✔
3917

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

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

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

3942
        true
11,346✔
3943
    }
11,433✔
3944

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

3981
                    debug!(
6,718✔
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,718✔
3987
                    let orig_bhh = last_mined_block.orig_burn_hash.clone();
6,718✔
3988
                    let tenure_begin = last_mined_block.tenure_begin;
6,718✔
3989

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

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

3999
                    debug!(
6,718✔
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,718✔
4008
                }
4009
                MinerThreadResult::Microblock(microblock_result, miner_tip) => {
11,344✔
4010
                    // finished mining a microblock
4011
                    match microblock_result {
11,344✔
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");
11,340✔
4073

4074
                            // switch back to block mining
4075
                            self.mined_stacks_block = false;
11,340✔
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;
148,506✔
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 {
148,506✔
4091
                self.try_resume_microblock_mining();
14,072✔
4092
            }
145,899✔
4093
        }
4094
        None
166,568✔
4095
    }
284,306✔
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 {
570,506✔
4108
        if let Some(thread_handle) = self.miner_thread.take() {
570,506✔
4109
            let new_thread_handle = self.inner_miner_thread_try_join(thread_handle);
284,306✔
4110
            self.miner_thread = new_thread_handle;
284,306✔
4111
        }
493,238✔
4112
        self.miner_thread.is_none()
570,506✔
4113
    }
570,506✔
4114

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

4130
        let Ok(registered_key) = serde_json::from_slice(&registered_key_bytes) else {
37✔
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}");
37✔
4136
        Some(registered_key)
37✔
4137
    }
177✔
4138

4139
    /// Top-level dispatcher
4140
    pub fn handle_directive(&mut self, directive: RelayerDirective) -> bool {
1,043,691✔
4141
        debug!("Relayer: received next directive");
1,043,691✔
4142
        let continue_running = match directive {
1,043,691✔
4143
            RelayerDirective::HandleNetResult(net_result) => {
702,175✔
4144
                debug!("Relayer: directive Handle network result");
702,175✔
4145
                self.process_network_result(net_result);
702,175✔
4146
                debug!("Relayer: directive Handled network result");
702,175✔
4147
                true
702,175✔
4148
            }
4149
            RelayerDirective::RegisterKey(last_burn_block) => {
262✔
4150
                let mut saved_key_opt = None;
262✔
4151
                if let Some(path) = self.config.miner.activated_vrf_key_path.as_ref() {
262✔
4152
                    saved_key_opt = Self::load_saved_vrf_key(path);
176✔
4153
                }
262✔
4154
                if let Some(saved_key) = saved_key_opt {
262✔
4155
                    self.globals.resume_leader_key(saved_key);
36✔
4156
                } else {
36✔
4157
                    self.rotate_vrf_and_register(&last_burn_block);
226✔
4158
                    debug!("Relayer: directive Registered VRF key");
226✔
4159
                }
4160
                self.globals.counters.bump_blocks_processed();
262✔
4161
                true
262✔
4162
            }
4163
            RelayerDirective::ProcessTenure(consensus_hash, burn_hash, block_header_hash) => {
7,686✔
4164
                debug!("Relayer: directive Process tenures");
7,686✔
4165
                let res = self.process_new_tenures(consensus_hash, burn_hash, block_header_hash);
7,686✔
4166
                debug!("Relayer: directive Processed tenures");
7,686✔
4167
                res
7,686✔
4168
            }
4169
            RelayerDirective::RunTenure(registered_key, last_burn_block, issue_timestamp_ms) => {
333,568✔
4170
                debug!("Relayer: directive Run tenure");
333,568✔
4171
                let Ok(Some(next_block_epoch)) = SortitionDB::get_stacks_epoch(
333,568✔
4172
                    self.sortdb_ref().conn(),
333,568✔
4173
                    last_burn_block.block_height.saturating_add(1),
333,568✔
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() {
333,568✔
4179
                    info!("Next burn block is in Nakamoto epoch, skipping RunTenure directive for 2.x node");
1,193✔
4180
                    return true;
1,193✔
4181
                }
332,375✔
4182
                self.block_miner_thread_try_start(
332,375✔
4183
                    registered_key,
332,375✔
4184
                    last_burn_block,
332,375✔
4185
                    issue_timestamp_ms,
332,375✔
4186
                );
4187
                debug!("Relayer: directive Ran tenure");
332,375✔
4188
                true
332,375✔
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,042,498✔
4197
            return false;
3✔
4198
        }
1,042,495✔
4199

4200
        // see if we need to run a microblock tenure
4201
        if self.can_run_microblock_tenure() {
1,042,495✔
4202
            self.microblock_miner_thread_try_start();
11,433✔
4203
        }
1,031,062✔
4204
        continue_running
1,042,495✔
4205
    }
1,043,691✔
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(
154,976✔
4216
        chain_state: &mut StacksChainState,
154,976✔
4217
        burn_db: &mut SortitionDB,
154,976✔
4218
        check_burn_block: &BlockSnapshot,
154,976✔
4219
        miner_address: StacksAddress,
154,976✔
4220
        mine_tip_ch: &ConsensusHash,
154,976✔
4221
        mine_tip_bh: &BlockHeaderHash,
154,976✔
4222
    ) -> Result<ParentStacksBlockInfo, Error> {
154,976✔
4223
        let stacks_tip_header = StacksChainState::get_anchored_block_header_info(
154,976✔
4224
            chain_state.db(),
154,976✔
4225
            mine_tip_ch,
154,976✔
4226
            mine_tip_bh,
154,976✔
4227
        )
4228
        .unwrap()
154,976✔
4229
        .ok_or_else(|| {
154,976✔
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 =
154,976✔
4240
            SortitionDB::get_block_snapshot_consensus(burn_db.conn(), mine_tip_ch)
154,976✔
4241
                .expect("Failed to look up block's parent snapshot")
154,976✔
4242
                .expect("Failed to look up block's parent snapshot");
154,976✔
4243

4244
        let parent_sortition_id = &parent_snapshot.sortition_id;
154,976✔
4245

4246
        let (parent_block_height, parent_winning_vtxindex, parent_block_total_burn) = if mine_tip_ch
154,976✔
4247
            == &FIRST_BURNCHAIN_CONSENSUS_HASH
154,976✔
4248
        {
4249
            (0, 0, 0)
×
4250
        } else {
4251
            let parent_winning_vtxindex =
154,976✔
4252
                SortitionDB::get_block_winning_vtxindex(burn_db.conn(), parent_sortition_id)
154,976✔
4253
                    .expect("SortitionDB failure.")
154,976✔
4254
                    .ok_or_else(|| {
154,976✔
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)
154,976✔
4263
                .expect("SortitionDB failure.")
154,976✔
4264
                .ok_or_else(|| {
154,976✔
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
            (
154,976✔
4273
                parent_block.block_height,
154,976✔
4274
                parent_winning_vtxindex,
154,976✔
4275
                parent_block.total_burn,
154,976✔
4276
            )
154,976✔
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())
154,976✔
4281
            .expect("FATAL: failed to query sortition DB for canonical burn chain tip");
154,976✔
4282

4283
        if burn_chain_tip.consensus_hash != check_burn_block.consensus_hash {
154,976✔
4284
            info!(
41✔
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,
41✔
4289
                "old_burn_height" => check_burn_block.block_height
41✔
4290
            );
4291
            return Err(Error::BurnchainTipChanged);
41✔
4292
        }
154,935✔
4293

4294
        debug!("Mining tenure's last consensus hash: {} (height {} hash {}), stacks tip consensus hash: {mine_tip_ch} (height {} hash {})",
154,935✔
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 = {
154,935✔
4299
            let principal = miner_address.into();
154,935✔
4300
            let account = chain_state
154,935✔
4301
                .with_read_only_clarity_tx(
154,935✔
4302
                    &burn_db.index_handle(&burn_chain_tip.sortition_id),
154,935✔
4303
                    &StacksBlockHeader::make_index_block_hash(mine_tip_ch, mine_tip_bh),
154,935✔
4304
                    |conn| StacksChainState::get_account(conn, &principal),
154,935✔
4305
                )
4306
                .unwrap_or_else(|| {
154,935✔
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
154,935✔
4312
        };
4313

4314
        Ok(ParentStacksBlockInfo {
154,935✔
4315
            stacks_parent_header: stacks_tip_header,
154,935✔
4316
            parent_consensus_hash: mine_tip_ch.clone(),
154,935✔
4317
            parent_block_burn_height: parent_block_height,
154,935✔
4318
            parent_block_total_burn,
154,935✔
4319
            parent_winning_vtxindex,
154,935✔
4320
            coinbase_nonce,
154,935✔
4321
        })
154,935✔
4322
    }
154,976✔
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 {
277✔
4362
        // create estimators, metric instances for RPC handler
4363
        let cost_estimator = config
277✔
4364
            .make_cost_estimator()
277✔
4365
            .unwrap_or_else(|| Box::new(UnitEstimator));
277✔
4366
        let metric = config
277✔
4367
            .make_cost_metric()
277✔
4368
            .unwrap_or_else(|| Box::new(UnitMetric));
277✔
4369

4370
        MemPoolDB::open(
277✔
4371
            config.is_mainnet(),
277✔
4372
            config.burnchain.chain_id,
277✔
4373
            &config.get_chainstate_path_str(),
277✔
4374
            cost_estimator,
277✔
4375
            metric,
277✔
4376
        )
4377
        .expect("Database failure opening mempool")
277✔
4378
    }
277✔
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 {
277✔
4385
        Self::new_all(
277✔
4386
            runloop.get_globals(),
277✔
4387
            runloop.config(),
277✔
4388
            runloop.get_burnchain().pox_constants,
277✔
4389
            net,
277✔
4390
        )
4391
    }
277✔
4392

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

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

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

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

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

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

4430
        PeerThread {
277✔
4431
            config,
277✔
4432
            net: Some(net),
277✔
4433
            globals,
277✔
4434
            poll_timeout,
277✔
4435
            sortdb: Some(sortdb),
277✔
4436
            chainstate: Some(chainstate),
277✔
4437
            mempool: Some(mempool),
277✔
4438
            results_with_data: VecDeque::new(),
277✔
4439
            num_p2p_state_machine_passes: 0,
277✔
4440
            num_inv_sync_passes: 0,
277✔
4441
            num_download_passes: 0,
277✔
4442
            last_burn_block_height: 0,
277✔
4443
        }
277✔
4444
    }
277✔
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,778,496✔
4450
    where
1,778,496✔
4451
        F: FnOnce(&mut PeerThread, &mut SortitionDB, &mut StacksChainState, &mut MemPoolDB) -> R,
1,778,496✔
4452
    {
4453
        let mut sortdb = self.sortdb.take().expect("BUG: sortdb already taken");
1,778,496✔
4454
        let mut chainstate = self
1,778,496✔
4455
            .chainstate
1,778,496✔
4456
            .take()
1,778,496✔
4457
            .expect("BUG: chainstate already taken");
1,778,496✔
4458
        let mut mempool = self.mempool.take().expect("BUG: mempool already taken");
1,778,496✔
4459

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

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

4466
        res
1,778,496✔
4467
    }
1,778,496✔
4468

4469
    /// Get an immutable ref to the inner network.
4470
    /// DO NOT USE WITHIN with_network()
4471
    fn get_network(&self) -> &PeerNetwork {
882,507✔
4472
        self.net.as_ref().expect("BUG: did not replace net")
882,507✔
4473
    }
882,507✔
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
889,248✔
4479
    where
889,248✔
4480
        F: FnOnce(&mut PeerThread, &mut PeerNetwork) -> R,
889,248✔
4481
    {
4482
        let mut net = self.net.take().expect("BUG: net already taken");
889,248✔
4483

4484
        let res = func(self, &mut net);
889,248✔
4485

4486
        self.net = Some(net);
889,248✔
4487
        res
889,248✔
4488
    }
889,248✔
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>(
889,248✔
4494
        &mut self,
889,248✔
4495
        indexer: &B,
889,248✔
4496
        dns_client_opt: Option<&mut DNSClient>,
889,248✔
4497
        event_dispatcher: &EventDispatcher,
889,248✔
4498
        cost_estimator: &Box<dyn CostEstimator>,
889,248✔
4499
        cost_metric: &Box<dyn CostMetric>,
889,248✔
4500
        fee_estimator: Option<&Box<dyn FeeEstimator>>,
889,248✔
4501
    ) -> bool {
889,248✔
4502
        // initial block download?
4503
        let ibd = self.globals.sync_comms.get_ibd();
889,248✔
4504
        let download_backpressure = !self.results_with_data.is_empty();
889,248✔
4505
        let poll_ms = if !download_backpressure && self.get_network().has_more_downloads() {
889,248✔
4506
            // keep getting those blocks -- drive the downloader state-machine
4507
            debug!(
42,759✔
4508
                "P2P: backpressure: {download_backpressure}, more downloads: {}",
4509
                self.get_network().has_more_downloads()
×
4510
            );
4511
            1
42,759✔
4512
        } else {
4513
            self.poll_timeout
846,489✔
4514
        };
4515

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

4522
        let txindex = self.config.node.txindex;
889,248✔
4523

4524
        // do one pass
4525
        let p2p_res = self.with_chainstate(|p2p_thread, sortdb, chainstate, mempool| {
889,248✔
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 {
889,248✔
4529
                exit_at_block_height: p2p_thread.config.burnchain.process_exit_at_block_height,
889,248✔
4530
                genesis_chainstate_hash: Sha256Sum::from_hex(stx_genesis::GENESIS_CHAINSTATE_HASH)
889,248✔
4531
                    .unwrap(),
889,248✔
4532
                event_observer: Some(event_dispatcher),
889,248✔
4533
                cost_estimator: Some(cost_estimator.as_ref()),
889,248✔
4534
                cost_metric: Some(cost_metric.as_ref()),
889,248✔
4535
                fee_estimator: fee_estimator.map(|boxed_estimator| boxed_estimator.as_ref()),
889,248✔
4536
                ..RPCHandlerArgs::default()
889,248✔
4537
            };
4538
            p2p_thread.with_network(|_, net| {
889,248✔
4539
                net.run(
889,248✔
4540
                    indexer,
889,248✔
4541
                    sortdb,
889,248✔
4542
                    chainstate,
889,248✔
4543
                    mempool,
889,248✔
4544
                    dns_client_opt,
889,248✔
4545
                    download_backpressure,
889,248✔
4546
                    ibd,
889,248✔
4547
                    poll_ms,
889,248✔
4548
                    &handler_args,
889,248✔
4549
                    txindex,
889,248✔
4550
                )
4551
            })
889,248✔
4552
        });
889,248✔
4553

4554
        match p2p_res {
889,248✔
4555
            Ok(network_result) => {
889,210✔
4556
                let mut have_update = false;
889,210✔
4557
                if self.num_p2p_state_machine_passes < network_result.num_state_machine_passes {
889,210✔
4558
                    // p2p state-machine did a full pass. Notify anyone listening.
678,307✔
4559
                    self.globals.sync_comms.notify_p2p_state_pass();
678,307✔
4560
                    self.num_p2p_state_machine_passes = network_result.num_state_machine_passes;
678,307✔
4561
                }
699,751✔
4562

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

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

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

176,002✔
4577
                    // the relayer cares about the number of download passes, so pass this along
176,002✔
4578
                    have_update = true;
176,002✔
4579
                }
713,208✔
4580

4581
                if network_result.has_data_to_store()
889,210✔
4582
                    || self.last_burn_block_height != network_result.burn_height
839,265✔
4583
                    || have_update
820,534✔
4584
                {
702,429✔
4585
                    // pass along if we have blocks, microblocks, or transactions, or a status
702,429✔
4586
                    // update on the network's view of the burnchain
702,429✔
4587
                    self.last_burn_block_height = network_result.burn_height;
702,429✔
4588
                    self.results_with_data
702,429✔
4589
                        .push_back(RelayerDirective::HandleNetResult(network_result));
702,429✔
4590
                }
718,406✔
4591
            }
4592
            Err(e) => {
38✔
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:?}");
38✔
4596
            }
4597
        };
4598

4599
        while let Some(next_result) = self.results_with_data.pop_front() {
1,591,544✔
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) {
709,170✔
4603
                debug!(
6,836✔
4604
                    "P2P: {:?}: download backpressure detected (bufferred {})",
4605
                    &self.get_network().local_peer,
×
4606
                    self.results_with_data.len()
×
4607
                );
4608
                match e {
6,836✔
4609
                    TrySendError::Full(directive) => {
6,741✔
4610
                        if let RelayerDirective::RunTenure(..) = directive {
6,741✔
4611
                            // can drop this
×
4612
                        } else {
6,741✔
4613
                            // don't lose this data -- just try it again
6,741✔
4614
                            self.results_with_data.push_front(directive);
6,741✔
4615
                        }
6,741✔
4616
                        break;
6,741✔
4617
                    }
4618
                    TrySendError::Disconnected(_) => {
4619
                        info!("P2P: Relayer hang up with p2p channel");
95✔
4620
                        self.globals.signal_stop();
95✔
4621
                        return false;
95✔
4622
                    }
4623
                }
4624
            } else {
4625
                debug!("P2P: Dispatched result to Relayer!");
702,334✔
4626
            }
4627
        }
4628

4629
        true
889,115✔
4630
    }
889,210✔
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 {
277✔
4656
        // force early mempool instantiation
4657
        let cost_estimator = config
277✔
4658
            .make_cost_estimator()
277✔
4659
            .unwrap_or_else(|| Box::new(UnitEstimator));
277✔
4660
        let metric = config
277✔
4661
            .make_cost_metric()
277✔
4662
            .unwrap_or_else(|| Box::new(UnitMetric));
277✔
4663

4664
        MemPoolDB::open(
277✔
4665
            config.is_mainnet(),
277✔
4666
            config.burnchain.chain_id,
277✔
4667
            &config.get_chainstate_path_str(),
277✔
4668
            cost_estimator,
277✔
4669
            metric,
277✔
4670
        )
4671
        .expect("BUG: failed to instantiate mempool")
277✔
4672
    }
277✔
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(
280✔
4683
        config: &Config,
280✔
4684
        burnchain: &Burnchain,
280✔
4685
        stackerdb_contract_ids: &[QualifiedContractIdentifier],
280✔
4686
    ) -> PeerDB {
280✔
4687
        let data_url = UrlString::try_from(config.node.data_url.to_string()).unwrap();
280✔
4688
        let initial_neighbors = config.node.bootstrap_node.clone();
280✔
4689
        if !initial_neighbors.is_empty() {
280✔
4690
            info!(
52✔
4691
                "Will bootstrap from peers {}",
4692
                VecDisplay(&initial_neighbors)
52✔
4693
            );
4694
        } else {
4695
            warn!("Without a peer to bootstrap from, the node will start mining a new chain");
228✔
4696
        }
4697

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

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

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

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

4752
        // deny all config-denied peers
4753
        {
4754
            let tx = peerdb.tx_begin().unwrap();
280✔
4755
            for denied in config.node.deny_nodes.iter() {
280✔
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();
280✔
4766
        }
4767

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

4781
        peerdb
280✔
4782
    }
280✔
4783

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

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

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

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

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

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

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

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

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

4853
        PeerNetwork::new(
280✔
4854
            peerdb,
280✔
4855
            atlasdb,
280✔
4856
            stackerdbs,
280✔
4857
            burnchain_db,
280✔
4858
            local_peer,
280✔
4859
            config.burnchain.peer_version,
280✔
4860
            burnchain,
280✔
4861
            view,
280✔
4862
            config.connection_options.clone(),
280✔
4863
            stackerdb_machines,
280✔
4864
            epochs,
280✔
4865
        )
4866
    }
280✔
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>) {
277✔
4872
        while let Ok(directive) = relay_recv.recv() {
1,043,965✔
4873
            if !relayer_thread.globals.keep_running() {
1,043,965✔
4874
                break;
274✔
4875
            }
1,043,691✔
4876

4877
            if !relayer_thread.handle_directive(directive) {
1,043,691✔
4878
                break;
3✔
4879
            }
1,043,688✔
4880
        }
4881

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

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

4888
        debug!("Relayer exit!");
277✔
4889
    }
277✔
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(
277✔
4895
        mut p2p_thread: PeerThread,
277✔
4896
        event_dispatcher: EventDispatcher,
277✔
4897
    ) -> Option<PeerNetwork> {
277✔
4898
        let should_keep_running = p2p_thread.globals.should_keep_running.clone();
277✔
4899
        let (mut dns_resolver, mut dns_client) = DNSResolver::new(10);
277✔
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()
277✔
4905
                .name("dns-resolver".to_string())
277✔
4906
                .spawn(move || {
277✔
4907
                    debug!("DNS resolver thread ID is {:?}", thread::current().id());
277✔
4908
                    dns_resolver.thread_main();
277✔
4909
                })
277✔
4910
                .unwrap();
277✔
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();
277✔
4916
        let cost_estimator = p2p_thread
277✔
4917
            .config
277✔
4918
            .make_cost_estimator()
277✔
4919
            .unwrap_or_else(|| Box::new(UnitEstimator));
277✔
4920
        let cost_metric = p2p_thread
277✔
4921
            .config
277✔
4922
            .make_cost_metric()
277✔
4923
            .unwrap_or_else(|| Box::new(UnitMetric));
277✔
4924

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

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

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

4947
        // set termination flag so other threads die
4948
        p2p_thread.globals.signal_stop();
277✔
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");
11✔
4957
            thread::sleep(Duration::from_secs(5));
11✔
4958
        }
4959
        info!("P2P thread exit!");
277✔
4960
        p2p_thread.net
277✔
4961
    }
277✔
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) {
277✔
4974
        let public_key = keychain.get_pub_key();
277✔
4975
        let miner_addr = relayer_thread
277✔
4976
            .bitcoin_controller
277✔
4977
            .get_miner_address(StacksEpochId::Epoch21, &public_key);
277✔
4978
        let miner_addr_str = miner_addr.to_string();
277✔
4979
        let _ = monitoring::set_burnchain_signer(BurnchainSigner(miner_addr_str)).map_err(|e| {
277✔
4980
            warn!("Failed to set global burnchain signer: {e:?}");
50✔
4981
            e
50✔
4982
        });
50✔
4983
    }
277✔
4984

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

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

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

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

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

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

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

5012
        // setup initial key registration
5013
        let leader_key_registration_state = if mock_mining {
277✔
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() {
272✔
5026
                warn!("`[miner.mining_key]` not set in config file. This will be required to mine in Epoch 3.0!")
34✔
5027
            }
238✔
5028
            LeaderKeyRegistrationState::Inactive
272✔
5029
        };
5030
        globals.set_initial_leader_key_registration_state(leader_key_registration_state);
277✔
5031

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

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

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

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

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

5062
        StacksNode {
277✔
5063
            atlas_config,
277✔
5064
            globals,
277✔
5065
            is_miner,
277✔
5066
            p2p_thread_handle,
277✔
5067
            relayer_thread_handle,
277✔
5068
        }
277✔
5069
    }
277✔
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 {
336,941✔
5079
        if !self.is_miner {
336,941✔
5080
            // node is a follower, don't try to issue a tenure
5081
            return true;
2,427✔
5082
        }
334,514✔
5083

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

5096
                        self.globals
333,671✔
5097
                            .relay_send
333,671✔
5098
                            .send(RelayerDirective::RunTenure(
333,671✔
5099
                                key.clone(),
333,671✔
5100
                                burnchain_tip,
333,671✔
5101
                                get_epoch_time_ms(),
333,671✔
5102
                            ))
333,671✔
5103
                            .is_ok()
333,671✔
5104
                    }
5105
                    LeaderKeyRegistrationState::Inactive => {
5106
                        warn!(
262✔
5107
                            "Tenure: skipped tenure because no active VRF key. Trying to register one."
5108
                        );
5109
                        self.globals
262✔
5110
                            .relay_send
262✔
5111
                            .send(RelayerDirective::RegisterKey(burnchain_tip))
262✔
5112
                            .is_ok()
262✔
5113
                    }
5114
                    LeaderKeyRegistrationState::Pending(..) => true,
581✔
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
    }
336,941✔
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,970✔
5131
        if !self.is_miner {
61,970✔
5132
            // node is a follower, don't try to process my own tenure.
5133
            return true;
1,136✔
5134
        }
60,834✔
5135

5136
        if let Some(snapshot) = self.globals.get_last_sortition() {
60,834✔
5137
            debug!(
60,834✔
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,834✔
5146
                return self
7,787✔
5147
                    .globals
7,787✔
5148
                    .relay_send
7,787✔
5149
                    .send(RelayerDirective::ProcessTenure(
7,787✔
5150
                        snapshot.consensus_hash,
7,787✔
5151
                        snapshot.parent_burn_header_hash,
7,787✔
5152
                        snapshot.winning_stacks_block_hash,
7,787✔
5153
                    ))
7,787✔
5154
                    .is_ok();
7,787✔
5155
            }
53,047✔
5156
        } else {
5157
            debug!("Tenure: Notify sortition! No last burn block");
×
5158
        }
5159
        true
53,047✔
5160
    }
61,970✔
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,970✔
5167
        &mut self,
61,970✔
5168
        config: &Config,
61,970✔
5169
        sortdb: &SortitionDB,
61,970✔
5170
        sort_id: &SortitionId,
61,970✔
5171
        ibd: bool,
61,970✔
5172
    ) -> Option<BlockSnapshot> {
61,970✔
5173
        let mut last_sortitioned_block = None;
61,970✔
5174

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

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

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

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

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

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

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

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

5212
        let num_key_registers = key_registers.len();
61,970✔
5213
        debug!(
61,970✔
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,970✔
5219
            .globals
61,970✔
5220
            .try_activate_leader_key_registration(block_height, key_registers);
61,970✔
5221

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

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

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

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

5237
        let mut f = match fs::File::create(path) {
140✔
5238
            Ok(f) => f,
140✔
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()) {
140✔
5246
            warn!("Failed to write activated VRF key to {path}: {e:?}");
×
5247
            return ret;
×
5248
        }
140✔
5249

5250
        info!("Saved activated VRF key to {path}");
140✔
5251
        ret
140✔
5252
    }
61,970✔
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