Skip to content

Commit 7ad5743

Browse files
authored
Merge pull request #5717 from stacks-network/feat/burn-view-fix-burn-checks
Fix: explicit burnchain checks in miner thread
2 parents 48567ed + abbbe54 commit 7ad5743

File tree

6 files changed

+215
-59
lines changed

6 files changed

+215
-59
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ jobs:
143143
- tests::signer::v0::outgoing_signers_ignore_block_proposals
144144
- tests::signer::v0::injected_signatures_are_ignored_across_boundaries
145145
- tests::signer::v0::fast_sortition
146+
- tests::signer::v0::single_miner_empty_sortition
147+
- tests::signer::v0::multiple_miners_empty_sortition
146148
- tests::nakamoto_integrations::burn_ops_integration_test
147149
- tests::nakamoto_integrations::check_block_heights
148150
- tests::nakamoto_integrations::clarity_burn_state

testnet/stacks-node/src/nakamoto_node/miner.rs

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ pub enum MinerDirective {
8282
/// This is the block ID of the first block in the parent tenure
8383
parent_tenure_start: StacksBlockId,
8484
/// This is the snapshot that this miner won, and will produce a tenure for
85+
election_block: BlockSnapshot,
86+
/// This is the snapshot that caused the relayer to initiate this event (may be different
87+
/// than the election block in the case where the miner is trying to mine a late block).
8588
burnchain_tip: BlockSnapshot,
8689
/// This is `true` if the snapshot above is known not to be the the latest burnchain tip,
8790
/// but an ancestor of it (for example, the burnchain tip could be an empty flash block, but the
@@ -136,6 +139,15 @@ pub enum MinerReason {
136139
},
137140
}
138141

142+
impl MinerReason {
143+
pub fn is_late_block(&self) -> bool {
144+
match self {
145+
Self::BlockFound { ref late } => *late,
146+
Self::Extended { .. } => false,
147+
}
148+
}
149+
}
150+
139151
impl std::fmt::Display for MinerReason {
140152
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141153
match self {
@@ -171,7 +183,7 @@ pub struct BlockMinerThread {
171183
burn_election_block: BlockSnapshot,
172184
/// Current burnchain tip as of the last TenureChange
173185
/// * if the last tenure-change was a BlockFound, then this is the same as the
174-
/// `burn_election_block`.
186+
/// `burn_election_block` (and it is also the `burn_view`)
175187
/// * otherwise, if the last tenure-change is an Extend, then this is the sortition of the burn
176188
/// view consensus hash in the TenureChange
177189
burn_block: BlockSnapshot,
@@ -186,6 +198,12 @@ pub struct BlockMinerThread {
186198
signer_set_cache: Option<RewardSet>,
187199
/// The time at which tenure change/extend was attempted
188200
tenure_change_time: Instant,
201+
/// The current tip when this miner thread was started.
202+
/// This *should not* be passed into any block building code, as it
203+
/// is not necessarily the burn view for the block being constructed.
204+
/// Rather, this burn block is used to determine whether or not a new
205+
/// burn block has arrived since this thread started.
206+
burn_tip_at_start: ConsensusHash,
189207
/// flag to indicate an abort driven from the relayer
190208
abort_flag: Arc<AtomicBool>,
191209
}
@@ -198,6 +216,7 @@ impl BlockMinerThread {
198216
burn_election_block: BlockSnapshot,
199217
burn_block: BlockSnapshot,
200218
parent_tenure_id: StacksBlockId,
219+
burn_tip_at_start: &ConsensusHash,
201220
reason: MinerReason,
202221
) -> BlockMinerThread {
203222
BlockMinerThread {
@@ -215,6 +234,7 @@ impl BlockMinerThread {
215234
reason,
216235
p2p_handle: rt.get_p2p_handle(),
217236
signer_set_cache: None,
237+
burn_tip_at_start: burn_tip_at_start.clone(),
218238
tenure_change_time: Instant::now(),
219239
abort_flag: Arc::new(AtomicBool::new(false)),
220240
}
@@ -339,18 +359,17 @@ impl BlockMinerThread {
339359
self.burnchain.pox_constants.clone(),
340360
)
341361
.expect("FATAL: could not open sortition DB");
342-
let burn_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
343-
.expect("FATAL: failed to query sortition DB for canonical burn chain tip");
344362

345363
// Start the signer coordinator
346364
let mut coordinator = SignerCoordinator::new(
347365
self.event_dispatcher.stackerdb_channel.clone(),
348366
self.globals.should_keep_running.clone(),
349367
&reward_set,
350-
&burn_tip,
368+
&self.burn_election_block,
351369
&self.burnchain,
352370
miner_privkey,
353371
&self.config,
372+
&self.burn_tip_at_start,
354373
)
355374
.map_err(|e| {
356375
NakamotoNodeError::SigningCoordinatorFailure(format!(
@@ -414,6 +433,16 @@ impl BlockMinerThread {
414433
"Failed to open chainstate DB. Cannot mine! {e:?}"
415434
))
416435
})?;
436+
// Late block tenures are initiated only to issue the BlockFound
437+
// tenure change tx (because they can be immediately extended to
438+
// the next burn view). This checks whether or not we're in such a
439+
// tenure and have produced a block already. If so, it exits the
440+
// mining thread to allow the tenure extension thread to take over.
441+
if self.last_block_mined.is_some() && self.reason.is_late_block() {
442+
info!("Miner: finished mining a late tenure");
443+
return Err(NakamotoNodeError::StacksTipChanged);
444+
}
445+
417446
let new_block = loop {
418447
// If we're mock mining, we may not have processed the block that the
419448
// actual tenure winner committed to yet. So, before attempting to
@@ -423,7 +452,7 @@ impl BlockMinerThread {
423452
let mut burn_db =
424453
SortitionDB::open(&burn_db_path, true, self.burnchain.pox_constants.clone())
425454
.expect("FATAL: could not open sortition DB");
426-
let burn_tip_changed = self.check_burn_tip_changed(&burn_db, &mut chain_state);
455+
let burn_tip_changed = self.check_burn_tip_changed(&burn_db);
427456
match burn_tip_changed
428457
.and_then(|_| self.load_block_parent_info(&mut burn_db, &mut chain_state))
429458
{
@@ -568,10 +597,7 @@ impl BlockMinerThread {
568597
let wait_start = Instant::now();
569598
while wait_start.elapsed() < self.config.miner.wait_on_interim_blocks {
570599
thread::sleep(Duration::from_millis(ABORT_TRY_AGAIN_MS));
571-
if self
572-
.check_burn_tip_changed(&sort_db, &mut chain_state)
573-
.is_err()
574-
{
600+
if self.check_burn_tip_changed(&sort_db).is_err() {
575601
return Err(NakamotoNodeError::BurnchainTipChanged);
576602
}
577603
}
@@ -599,13 +625,12 @@ impl BlockMinerThread {
599625
})?;
600626
coordinator.propose_block(
601627
new_block,
602-
&self.burn_block,
603628
&self.burnchain,
604629
sortdb,
605630
&mut chain_state,
606631
stackerdbs,
607632
&self.globals.counters,
608-
&self.burn_election_block.consensus_hash,
633+
&self.burn_election_block,
609634
)
610635
}
611636

@@ -1113,7 +1138,7 @@ impl BlockMinerThread {
11131138
let mut chain_state = neon_node::open_chainstate_with_faults(&self.config)
11141139
.expect("FATAL: could not open chainstate DB");
11151140

1116-
self.check_burn_tip_changed(&burn_db, &mut chain_state)?;
1141+
self.check_burn_tip_changed(&burn_db)?;
11171142
neon_node::fault_injection_long_tenure();
11181143

11191144
let mut mem_pool = self
@@ -1217,7 +1242,7 @@ impl BlockMinerThread {
12171242
// last chance -- confirm that the stacks tip is unchanged (since it could have taken long
12181243
// enough to build this block that another block could have arrived), and confirm that all
12191244
// Stacks blocks with heights higher than the canonical tip are processed.
1220-
self.check_burn_tip_changed(&burn_db, &mut chain_state)?;
1245+
self.check_burn_tip_changed(&burn_db)?;
12211246
Ok(block)
12221247
}
12231248

@@ -1356,26 +1381,14 @@ impl BlockMinerThread {
13561381
/// Check if the tenure needs to change -- if so, return a BurnchainTipChanged error
13571382
/// The tenure should change if there is a new burnchain tip with a valid sortition,
13581383
/// or if the stacks chain state's burn view has advanced beyond our burn view.
1359-
fn check_burn_tip_changed(
1360-
&self,
1361-
sortdb: &SortitionDB,
1362-
_chain_state: &mut StacksChainState,
1363-
) -> Result<(), NakamotoNodeError> {
1364-
if let MinerReason::BlockFound { late } = &self.reason {
1365-
if *late && self.last_block_mined.is_none() {
1366-
// this is a late BlockFound tenure change that ought to be appended to the Stacks
1367-
// chain tip, and we haven't submitted it yet.
1368-
return Ok(());
1369-
}
1370-
}
1371-
1384+
fn check_burn_tip_changed(&self, sortdb: &SortitionDB) -> Result<(), NakamotoNodeError> {
13721385
let cur_burn_chain_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
13731386
.expect("FATAL: failed to query sortition DB for canonical burn chain tip");
13741387

1375-
if cur_burn_chain_tip.consensus_hash != self.burn_block.consensus_hash {
1388+
if cur_burn_chain_tip.consensus_hash != self.burn_tip_at_start {
13761389
info!("Miner: Cancel block assembly; burnchain tip has changed";
13771390
"new_tip" => %cur_burn_chain_tip.consensus_hash,
1378-
"local_tip" => %self.burn_block.consensus_hash);
1391+
"local_tip" => %self.burn_tip_at_start);
13791392
self.globals.counters.bump_missed_tenures();
13801393
Err(NakamotoNodeError::BurnchainTipChanged)
13811394
} else {

testnet/stacks-node/src/nakamoto_node/relayer.rs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,8 @@ impl RelayerThread {
525525
"winning_sortition" => %sn.consensus_hash);
526526
return Some(MinerDirective::BeginTenure {
527527
parent_tenure_start: committed_index_hash,
528-
burnchain_tip: sn,
528+
burnchain_tip: sn.clone(),
529+
election_block: sn,
529530
late: false,
530531
});
531532
}
@@ -663,7 +664,8 @@ impl RelayerThread {
663664
parent_tenure_start: StacksBlockId(
664665
last_winning_snapshot.winning_stacks_block_hash.clone().0,
665666
),
666-
burnchain_tip: last_winning_snapshot,
667+
burnchain_tip: sn,
668+
election_block: last_winning_snapshot,
667669
late: true,
668670
});
669671
}
@@ -1049,6 +1051,7 @@ impl RelayerThread {
10491051
burn_tip: BlockSnapshot,
10501052
parent_tenure_id: StacksBlockId,
10511053
reason: MinerReason,
1054+
burn_tip_at_start: &ConsensusHash,
10521055
) -> Result<BlockMinerThread, NakamotoNodeError> {
10531056
if fault_injection_skip_mining(&self.config.node.rpc_bind, burn_tip.block_height) {
10541057
debug!(
@@ -1065,14 +1068,8 @@ impl RelayerThread {
10651068

10661069
let burn_chain_tip = burn_chain_sn.burn_header_hash;
10671070

1068-
let allow_late = if let MinerReason::BlockFound { late } = &reason {
1069-
*late
1070-
} else {
1071-
false
1072-
};
1073-
1074-
if burn_chain_tip != burn_header_hash && !allow_late {
1075-
debug!(
1071+
if &burn_chain_sn.consensus_hash != burn_tip_at_start {
1072+
info!(
10761073
"Relayer: Drop stale RunTenure for {burn_header_hash}: current sortition is for {burn_chain_tip}"
10771074
);
10781075
self.globals.counters.bump_missed_tenures();
@@ -1095,6 +1092,7 @@ impl RelayerThread {
10951092
burn_election_block,
10961093
burn_tip,
10971094
parent_tenure_id,
1095+
burn_tip_at_start,
10981096
reason,
10991097
);
11001098
Ok(miner_thread_state)
@@ -1106,6 +1104,7 @@ impl RelayerThread {
11061104
block_election_snapshot: BlockSnapshot,
11071105
burn_tip: BlockSnapshot,
11081106
reason: MinerReason,
1107+
burn_tip_at_start: &ConsensusHash,
11091108
) -> Result<(), NakamotoNodeError> {
11101109
// when starting a new tenure, block the mining thread if its currently running.
11111110
// the new mining thread will join it (so that the new mining thread stalls, not the relayer)
@@ -1126,6 +1125,7 @@ impl RelayerThread {
11261125
burn_tip.clone(),
11271126
parent_tenure_start,
11281127
reason,
1128+
burn_tip_at_start,
11291129
)?;
11301130
let miner_abort_flag = new_miner_state.get_abort_flag();
11311131

@@ -1457,14 +1457,15 @@ impl RelayerThread {
14571457
StacksBlockId::new(&canonical_stacks_tip_ch, &canonical_stacks_tip_bh);
14581458

14591459
let reason = MinerReason::Extended {
1460-
burn_view_consensus_hash: new_burn_view,
1460+
burn_view_consensus_hash: new_burn_view.clone(),
14611461
};
14621462

14631463
if let Err(e) = self.start_new_tenure(
14641464
canonical_stacks_tip.clone(),
14651465
canonical_stacks_tip_election_snapshot.clone(),
14661466
burn_tip.clone(),
14671467
reason.clone(),
1468+
&new_burn_view,
14681469
) {
14691470
error!("Relayer: Failed to start new tenure: {e:?}");
14701471
} else {
@@ -1500,12 +1501,14 @@ impl RelayerThread {
15001501
MinerDirective::BeginTenure {
15011502
parent_tenure_start,
15021503
burnchain_tip,
1504+
election_block,
15031505
late,
15041506
} => match self.start_new_tenure(
15051507
parent_tenure_start,
1506-
burnchain_tip.clone(),
1507-
burnchain_tip.clone(),
1508+
election_block.clone(),
1509+
election_block.clone(),
15081510
MinerReason::BlockFound { late },
1511+
&burnchain_tip.consensus_hash,
15091512
) {
15101513
Ok(()) => {
15111514
debug!("Relayer: successfully started new tenure.";

0 commit comments

Comments
 (0)