Skip to content

Commit 26cedd7

Browse files
committed
Merge branch 'fix/burn-view' of https://github.com/stacks-network/stacks-core into fix/burn-view
2 parents fa0d9ac + 7ad5743 commit 26cedd7

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
@@ -81,6 +81,9 @@ pub enum MinerDirective {
8181
/// This is the block ID of the first block in the parent tenure
8282
parent_tenure_start: StacksBlockId,
8383
/// This is the snapshot that this miner won, and will produce a tenure for
84+
election_block: BlockSnapshot,
85+
/// This is the snapshot that caused the relayer to initiate this event (may be different
86+
/// than the election block in the case where the miner is trying to mine a late block).
8487
burnchain_tip: BlockSnapshot,
8588
/// This is `true` if the snapshot above is known not to be the the latest burnchain tip,
8689
/// but an ancestor of it (for example, the burnchain tip could be an empty flash block, but the
@@ -135,6 +138,15 @@ pub enum MinerReason {
135138
},
136139
}
137140

141+
impl MinerReason {
142+
pub fn is_late_block(&self) -> bool {
143+
match self {
144+
Self::BlockFound { ref late } => *late,
145+
Self::Extended { .. } => false,
146+
}
147+
}
148+
}
149+
138150
impl std::fmt::Display for MinerReason {
139151
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140152
match self {
@@ -170,7 +182,7 @@ pub struct BlockMinerThread {
170182
burn_election_block: BlockSnapshot,
171183
/// Current burnchain tip as of the last TenureChange
172184
/// * if the last tenure-change was a BlockFound, then this is the same as the
173-
/// `burn_election_block`.
185+
/// `burn_election_block` (and it is also the `burn_view`)
174186
/// * otherwise, if the last tenure-change is an Extend, then this is the sortition of the burn
175187
/// view consensus hash in the TenureChange
176188
burn_block: BlockSnapshot,
@@ -185,6 +197,12 @@ pub struct BlockMinerThread {
185197
signer_set_cache: Option<RewardSet>,
186198
/// The time at which tenure change/extend was attempted
187199
tenure_change_time: Instant,
200+
/// The current tip when this miner thread was started.
201+
/// This *should not* be passed into any block building code, as it
202+
/// is not necessarily the burn view for the block being constructed.
203+
/// Rather, this burn block is used to determine whether or not a new
204+
/// burn block has arrived since this thread started.
205+
burn_tip_at_start: ConsensusHash,
188206
/// flag to indicate an abort driven from the relayer
189207
abort_flag: Arc<AtomicBool>,
190208
}
@@ -197,6 +215,7 @@ impl BlockMinerThread {
197215
burn_election_block: BlockSnapshot,
198216
burn_block: BlockSnapshot,
199217
parent_tenure_id: StacksBlockId,
218+
burn_tip_at_start: &ConsensusHash,
200219
reason: MinerReason,
201220
) -> BlockMinerThread {
202221
BlockMinerThread {
@@ -214,6 +233,7 @@ impl BlockMinerThread {
214233
reason,
215234
p2p_handle: rt.get_p2p_handle(),
216235
signer_set_cache: None,
236+
burn_tip_at_start: burn_tip_at_start.clone(),
217237
tenure_change_time: Instant::now(),
218238
abort_flag: Arc::new(AtomicBool::new(false)),
219239
}
@@ -338,18 +358,17 @@ impl BlockMinerThread {
338358
self.burnchain.pox_constants.clone(),
339359
)
340360
.expect("FATAL: could not open sortition DB");
341-
let burn_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
342-
.expect("FATAL: failed to query sortition DB for canonical burn chain tip");
343361

344362
// Start the signer coordinator
345363
let mut coordinator = SignerCoordinator::new(
346364
self.event_dispatcher.stackerdb_channel.clone(),
347365
self.globals.should_keep_running.clone(),
348366
&reward_set,
349-
&burn_tip,
367+
&self.burn_election_block,
350368
&self.burnchain,
351369
miner_privkey,
352370
&self.config,
371+
&self.burn_tip_at_start,
353372
)
354373
.map_err(|e| {
355374
NakamotoNodeError::SigningCoordinatorFailure(format!(
@@ -413,6 +432,16 @@ impl BlockMinerThread {
413432
"Failed to open chainstate DB. Cannot mine! {e:?}"
414433
))
415434
})?;
435+
// Late block tenures are initiated only to issue the BlockFound
436+
// tenure change tx (because they can be immediately extended to
437+
// the next burn view). This checks whether or not we're in such a
438+
// tenure and have produced a block already. If so, it exits the
439+
// mining thread to allow the tenure extension thread to take over.
440+
if self.last_block_mined.is_some() && self.reason.is_late_block() {
441+
info!("Miner: finished mining a late tenure");
442+
return Err(NakamotoNodeError::StacksTipChanged);
443+
}
444+
416445
let new_block = loop {
417446
// If we're mock mining, we may not have processed the block that the
418447
// actual tenure winner committed to yet. So, before attempting to
@@ -422,7 +451,7 @@ impl BlockMinerThread {
422451
let mut burn_db =
423452
SortitionDB::open(&burn_db_path, true, self.burnchain.pox_constants.clone())
424453
.expect("FATAL: could not open sortition DB");
425-
let burn_tip_changed = self.check_burn_tip_changed(&burn_db, &mut chain_state);
454+
let burn_tip_changed = self.check_burn_tip_changed(&burn_db);
426455
match burn_tip_changed
427456
.and_then(|_| self.load_block_parent_info(&mut burn_db, &mut chain_state))
428457
{
@@ -567,10 +596,7 @@ impl BlockMinerThread {
567596
let wait_start = Instant::now();
568597
while wait_start.elapsed() < self.config.miner.wait_on_interim_blocks {
569598
thread::sleep(Duration::from_millis(ABORT_TRY_AGAIN_MS));
570-
if self
571-
.check_burn_tip_changed(&sort_db, &mut chain_state)
572-
.is_err()
573-
{
599+
if self.check_burn_tip_changed(&sort_db).is_err() {
574600
return Err(NakamotoNodeError::BurnchainTipChanged);
575601
}
576602
}
@@ -598,13 +624,12 @@ impl BlockMinerThread {
598624
})?;
599625
coordinator.propose_block(
600626
new_block,
601-
&self.burn_block,
602627
&self.burnchain,
603628
sortdb,
604629
&mut chain_state,
605630
stackerdbs,
606631
&self.globals.counters,
607-
&self.burn_election_block.consensus_hash,
632+
&self.burn_election_block,
608633
)
609634
}
610635

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

1115-
self.check_burn_tip_changed(&burn_db, &mut chain_state)?;
1140+
self.check_burn_tip_changed(&burn_db)?;
11161141
neon_node::fault_injection_long_tenure();
11171142

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

@@ -1328,26 +1353,14 @@ impl BlockMinerThread {
13281353
/// Check if the tenure needs to change -- if so, return a BurnchainTipChanged error
13291354
/// The tenure should change if there is a new burnchain tip with a valid sortition,
13301355
/// or if the stacks chain state's burn view has advanced beyond our burn view.
1331-
fn check_burn_tip_changed(
1332-
&self,
1333-
sortdb: &SortitionDB,
1334-
_chain_state: &mut StacksChainState,
1335-
) -> Result<(), NakamotoNodeError> {
1336-
if let MinerReason::BlockFound { late } = &self.reason {
1337-
if *late && self.last_block_mined.is_none() {
1338-
// this is a late BlockFound tenure change that ought to be appended to the Stacks
1339-
// chain tip, and we haven't submitted it yet.
1340-
return Ok(());
1341-
}
1342-
}
1343-
1356+
fn check_burn_tip_changed(&self, sortdb: &SortitionDB) -> Result<(), NakamotoNodeError> {
13441357
let cur_burn_chain_tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
13451358
.expect("FATAL: failed to query sortition DB for canonical burn chain tip");
13461359

1347-
if cur_burn_chain_tip.consensus_hash != self.burn_block.consensus_hash {
1360+
if cur_burn_chain_tip.consensus_hash != self.burn_tip_at_start {
13481361
info!("Miner: Cancel block assembly; burnchain tip has changed";
13491362
"new_tip" => %cur_burn_chain_tip.consensus_hash,
1350-
"local_tip" => %self.burn_block.consensus_hash);
1363+
"local_tip" => %self.burn_tip_at_start);
13511364
self.globals.counters.bump_missed_tenures();
13521365
Err(NakamotoNodeError::BurnchainTipChanged)
13531366
} 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
}
@@ -645,7 +646,8 @@ impl RelayerThread {
645646
parent_tenure_start: StacksBlockId(
646647
last_winning_snapshot.winning_stacks_block_hash.clone().0,
647648
),
648-
burnchain_tip: last_winning_snapshot,
649+
burnchain_tip: sn,
650+
election_block: last_winning_snapshot,
649651
late: true,
650652
});
651653
}
@@ -1031,6 +1033,7 @@ impl RelayerThread {
10311033
burn_tip: BlockSnapshot,
10321034
parent_tenure_id: StacksBlockId,
10331035
reason: MinerReason,
1036+
burn_tip_at_start: &ConsensusHash,
10341037
) -> Result<BlockMinerThread, NakamotoNodeError> {
10351038
if fault_injection_skip_mining(&self.config.node.rpc_bind, burn_tip.block_height) {
10361039
debug!(
@@ -1047,14 +1050,8 @@ impl RelayerThread {
10471050

10481051
let burn_chain_tip = burn_chain_sn.burn_header_hash;
10491052

1050-
let allow_late = if let MinerReason::BlockFound { late } = &reason {
1051-
*late
1052-
} else {
1053-
false
1054-
};
1055-
1056-
if burn_chain_tip != burn_header_hash && !allow_late {
1057-
debug!(
1053+
if &burn_chain_sn.consensus_hash != burn_tip_at_start {
1054+
info!(
10581055
"Relayer: Drop stale RunTenure for {burn_header_hash}: current sortition is for {burn_chain_tip}"
10591056
);
10601057
self.globals.counters.bump_missed_tenures();
@@ -1077,6 +1074,7 @@ impl RelayerThread {
10771074
burn_election_block,
10781075
burn_tip,
10791076
parent_tenure_id,
1077+
burn_tip_at_start,
10801078
reason,
10811079
);
10821080
Ok(miner_thread_state)
@@ -1088,6 +1086,7 @@ impl RelayerThread {
10881086
block_election_snapshot: BlockSnapshot,
10891087
burn_tip: BlockSnapshot,
10901088
reason: MinerReason,
1089+
burn_tip_at_start: &ConsensusHash,
10911090
) -> Result<(), NakamotoNodeError> {
10921091
// when starting a new tenure, block the mining thread if its currently running.
10931092
// the new mining thread will join it (so that the new mining thread stalls, not the relayer)
@@ -1108,6 +1107,7 @@ impl RelayerThread {
11081107
burn_tip.clone(),
11091108
parent_tenure_start,
11101109
reason,
1110+
burn_tip_at_start,
11111111
)?;
11121112
let miner_abort_flag = new_miner_state.get_abort_flag();
11131113

@@ -1439,14 +1439,15 @@ impl RelayerThread {
14391439
StacksBlockId::new(&canonical_stacks_tip_ch, &canonical_stacks_tip_bh);
14401440

14411441
let reason = MinerReason::Extended {
1442-
burn_view_consensus_hash: new_burn_view,
1442+
burn_view_consensus_hash: new_burn_view.clone(),
14431443
};
14441444

14451445
if let Err(e) = self.start_new_tenure(
14461446
canonical_stacks_tip.clone(),
14471447
canonical_stacks_tip_election_snapshot.clone(),
14481448
burn_tip.clone(),
14491449
reason.clone(),
1450+
&new_burn_view,
14501451
) {
14511452
error!("Relayer: Failed to start new tenure: {e:?}");
14521453
} else {
@@ -1482,12 +1483,14 @@ impl RelayerThread {
14821483
MinerDirective::BeginTenure {
14831484
parent_tenure_start,
14841485
burnchain_tip,
1486+
election_block,
14851487
late,
14861488
} => match self.start_new_tenure(
14871489
parent_tenure_start,
1488-
burnchain_tip.clone(),
1489-
burnchain_tip.clone(),
1490+
election_block.clone(),
1491+
election_block.clone(),
14901492
MinerReason::BlockFound { late },
1493+
&burnchain_tip.consensus_hash,
14911494
) {
14921495
Ok(()) => {
14931496
debug!("Relayer: successfully started new tenure.";

0 commit comments

Comments
 (0)