Skip to content

Commit 3144969

Browse files
committed
CRC: add a TenureExtendReason to determine what ot check in check_tenure_timers
Signed-off-by: Jacinta Ferrant <jacinta@trustmachines.co>
1 parent dbf0bd9 commit 3144969

File tree

2 files changed

+106
-105
lines changed

2 files changed

+106
-105
lines changed

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

Lines changed: 103 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -250,28 +250,44 @@ impl MinerStopHandle {
250250
}
251251
}
252252

253+
/// The reason for issuing a tenure extend
254+
#[derive(PartialEq, Eq, Debug, Clone)]
255+
pub enum TenureExtendReason {
256+
/// There was an empty sortition
257+
EmptySortition,
258+
/// There was a bad sortition winner
259+
BadSortitionWinner,
260+
/// We are waiting for the current winner to produce a block.
261+
UnresponsiveWinner,
262+
}
263+
253264
/// Information necessary to determine when to extend a tenure
265+
#[derive(Clone)]
254266
pub struct TenureExtendTime {
255267
/// The time at which we determined that we should tenure-extend
256268
time: Instant,
257269
/// The amount of time we should wait before tenure-extending
258270
timeout: Duration,
271+
/// The reason for tenure-extending
272+
reason: TenureExtendReason,
259273
}
260274

261275
impl TenureExtendTime {
262-
/// Create a new `TenureExtendTime` with a delayed `timeout`
263-
pub fn delayed(timeout: Duration) -> Self {
276+
/// Create a new `TenureExtendTime` for an UnresponsiveWinner with the specified `timeout`
277+
pub fn unresponsive_winner(timeout: Duration) -> Self {
264278
Self {
265279
time: Instant::now(),
266280
timeout,
281+
reason: TenureExtendReason::UnresponsiveWinner,
267282
}
268283
}
269284

270-
/// Create a new `TenureExtendTime` with no `timeout`
271-
pub fn immediate() -> Self {
285+
/// Create a new `TenureExtendTime` with the provided `reason` and no `timeout`
286+
pub fn immediate(reason: TenureExtendReason) -> Self {
272287
Self {
273288
time: Instant::now(),
274-
timeout: Duration::from_secs(0),
289+
timeout: Duration::from_millis(0),
290+
reason,
275291
}
276292
}
277293

@@ -290,6 +306,17 @@ impl TenureExtendTime {
290306
pub fn timeout(&self) -> Duration {
291307
self.timeout
292308
}
309+
310+
/// The reason for tenure-extending
311+
pub fn reason(&self) -> &TenureExtendReason {
312+
&self.reason
313+
}
314+
315+
/// Update the timeout for this `TenureExtendTime` and reset the time
316+
pub fn refresh(&mut self, timeout: Duration) {
317+
self.timeout = timeout;
318+
self.time = Instant::now();
319+
}
293320
}
294321

295322
/// Relayer thread
@@ -521,10 +548,10 @@ impl RelayerThread {
521548
fn choose_directive_sortition_with_winner(
522549
&mut self,
523550
sn: BlockSnapshot,
524-
mining_pk: Hash160,
551+
mining_pkh: Hash160,
525552
committed_index_hash: StacksBlockId,
526553
) -> Option<MinerDirective> {
527-
let won_sortition = sn.miner_pk_hash == Some(mining_pk);
554+
let won_sortition = sn.miner_pk_hash == Some(mining_pkh);
528555
if won_sortition || self.config.get_node_config(false).mock_mining {
529556
// a sortition happenend, and we won
530557
info!("Won sortition; begin tenure.";
@@ -551,10 +578,10 @@ impl RelayerThread {
551578
.expect("FATAL: no sortition for canonical stacks tip");
552579

553580
let won_ongoing_tenure_sortition =
554-
canonical_stacks_snapshot.miner_pk_hash == Some(mining_pk);
581+
canonical_stacks_snapshot.miner_pk_hash == Some(mining_pkh);
555582
if won_ongoing_tenure_sortition {
556583
// we won the current ongoing tenure, but not the most recent sortition. Should we attempt to extend immediately or wait for the incoming miner?
557-
if let Ok(result) = Self::find_highest_valid_sortition(
584+
if let Ok(result) = Self::find_highest_sortition_commits_to_stacks_tip_tenure(
558585
&self.sortdb,
559586
&mut self.chainstate,
560587
&sn,
@@ -564,12 +591,14 @@ impl RelayerThread {
564591
debug!("Relayer: Did not win current sortition but won the prior valid sortition. Will attempt to extend tenure after allowing the new miner some time to come online.";
565592
"tenure_extend_wait_timeout_ms" => self.config.miner.tenure_extend_wait_timeout.as_millis(),
566593
);
567-
self.tenure_extend_time = Some(TenureExtendTime::delayed(
594+
self.tenure_extend_time = Some(TenureExtendTime::unresponsive_winner(
568595
self.config.miner.tenure_extend_wait_timeout,
569596
));
570597
} else {
571598
info!("Relayer: no valid sortition since our last winning sortition. Will extend tenure.");
572-
self.tenure_extend_time = Some(TenureExtendTime::immediate());
599+
self.tenure_extend_time = Some(TenureExtendTime::immediate(
600+
TenureExtendReason::BadSortitionWinner,
601+
));
573602
}
574603
}
575604
}
@@ -654,7 +683,9 @@ impl RelayerThread {
654683
&last_winning_snapshot.consensus_hash
655684
);
656685
// prepare to immediately extend after our BlockFound gets mined.
657-
self.tenure_extend_time = Some(TenureExtendTime::immediate());
686+
self.tenure_extend_time = Some(TenureExtendTime::immediate(
687+
TenureExtendReason::EmptySortition,
688+
));
658689
return Some(MinerDirective::BeginTenure {
659690
parent_tenure_start: StacksBlockId(
660691
last_winning_snapshot.winning_stacks_block_hash.clone().0,
@@ -687,7 +718,7 @@ impl RelayerThread {
687718
// by someone else -- there's a chance that this other miner will produce a
688719
// BlockFound in the interim.
689720
debug!("Relayer: Did not win last winning snapshot despite mining the ongoing tenure. Will attempt to extend tenure after allowing the new miner some time to produce a block.");
690-
self.tenure_extend_time = Some(TenureExtendTime::delayed(
721+
self.tenure_extend_time = Some(TenureExtendTime::unresponsive_winner(
691722
self.config.miner.tenure_extend_wait_timeout,
692723
));
693724
return None;
@@ -714,56 +745,26 @@ impl RelayerThread {
714745
// we won the last non-empty sortition. Has there been a BlockFound issued for it?
715746
// This would be true if the stacks tip's tenure is at or descends from this snapshot.
716747
// If there has _not_ been a BlockFound, then we should issue one.
717-
let ih = self
718-
.sortdb
719-
.index_handle(&last_winning_snapshot.sortition_id);
720748
if canonical_stacks_snapshot.block_height > last_winning_snapshot.block_height {
721749
// stacks tip is ahead of this snapshot, so no BlockFound can be issued.
722-
test_debug!("Relayer: stacks_tip_sn.block_height ({}) > last_winning_snapshot.block_height ({})", canonical_stacks_snapshot.block_height, last_winning_snapshot.block_height);
750+
test_debug!(
751+
"Stacks_tip_sn.block_height ({}) > last_winning_snapshot.block_height ({})",
752+
canonical_stacks_snapshot.block_height,
753+
last_winning_snapshot.block_height
754+
);
723755
false
724756
} else if canonical_stacks_snapshot.block_height == last_winning_snapshot.block_height
725757
&& canonical_stacks_snapshot.consensus_hash == last_winning_snapshot.consensus_hash
726758
{
727759
// this is the ongoing tenure snapshot. A BlockFound has already been issued.
728760
test_debug!(
729-
"Relayer: ongoing tenure {} already represents last-winning snapshot",
761+
"Ongoing tenure {} already represents last-winning snapshot",
730762
&canonical_stacks_snapshot.consensus_hash
731763
);
732764
false
733765
} else {
734-
// stacks tip's snapshot may be an ancestor of the last-won sortition.
735-
// If so, then we can issue a BlockFound.
736-
SortitionDB::get_ancestor_snapshot(
737-
&ih,
738-
canonical_stacks_snapshot.block_height,
739-
&last_winning_snapshot.sortition_id,
740-
)
741-
.map_err(|e| {
742-
error!("Relayer: Failed to load ancestor snapshot: {e:?}");
743-
e
744-
})
745-
.ok()
746-
.flatten()
747-
.map(|sn| {
748-
let need_blockfound = sn.consensus_hash == canonical_stacks_snapshot.consensus_hash;
749-
if !need_blockfound {
750-
test_debug!(
751-
"Relayer: canonical_stacks_tip_ch ({}) != sn_consensus_hash ({})",
752-
&canonical_stacks_snapshot.consensus_hash,
753-
&sn.consensus_hash
754-
);
755-
}
756-
need_blockfound
757-
})
758-
.unwrap_or_else(|| {
759-
test_debug!(
760-
"Relayer: no ancestor at height {} off of sortition {} height {}",
761-
canonical_stacks_snapshot.block_height,
762-
&last_winning_snapshot.consensus_hash,
763-
last_winning_snapshot.block_height
764-
);
765-
false
766-
})
766+
// The stacks tip is behind the last-won sortition, so a BlockFound is still needed.
767+
true
767768
}
768769
}
769770

@@ -1345,18 +1346,15 @@ impl RelayerThread {
13451346
Ok(true)
13461347
}
13471348

1348-
/// Determine the highest valid sortition higher than `elected_tenure_id`, but no higher than
1349-
/// `sort_tip`.
1350-
///
1351-
/// This is the highest non-empty sortition (up to and including `sort_tip`)
1352-
/// whose winning commit's parent tenure ID matches the
1353-
/// Stacks tip, and whose consensus hash matches the Stacks tip's tenure ID.
1349+
/// Determine the highest sortition higher than `elected_tenure_id`, but no higher than
1350+
/// `sort_tip` whose winning commit's parent tenure ID matches the `stacks_tip`,
1351+
/// and whose consensus hash matches the `stacks_tip`'s tenure ID.
13541352
///
13551353
/// Returns Ok(Some(..)) if such a sortition is found, and is higher than that of
13561354
/// `elected_tenure_id`.
13571355
/// Returns Ok(None) if no such sortition is found.
13581356
/// Returns Err(..) on DB errors.
1359-
fn find_highest_valid_sortition(
1357+
fn find_highest_sortition_commits_to_stacks_tip_tenure(
13601358
sortdb: &SortitionDB,
13611359
chain_state: &mut StacksChainState,
13621360
sort_tip: &BlockSnapshot,
@@ -1761,30 +1759,28 @@ impl RelayerThread {
17611759

17621760
/// Try to start up a tenure-extend if the tenure_extend_time has expired.
17631761
///
1764-
/// Will check if the tenure-extend time was set and has expired and one of the following is true:
1765-
/// - this miner won the highest valid sortition but the burn view has changed.
1766-
/// - the subsequent miner appears to be offline.
1767-
/// If so, it will stop any existing tenure and attempt to start a new one with an Extended reason.
1768-
/// Otherwise, it will do nothing.
1762+
/// Will check if the tenure-extend time was set and has expired. If so, will
1763+
/// check if the current miner thread needs to issue a BlockFound or if it can
1764+
/// immediately tenure-extend.
17691765
///
17701766
/// Note: tenure_extend_time is only set to Some(_) if during sortition processing, the sortition
17711767
/// winner commit is corrupted or the winning miner has yet to produce a block.
1772-
fn try_continue_tenure(&mut self) {
1768+
fn check_tenure_timers(&mut self) {
17731769
// Should begin a tenure-extend?
1774-
if let Some(tenure_extend_time) = &self.tenure_extend_time {
1775-
if !tenure_extend_time.should_extend() {
1776-
test_debug!(
1777-
"Relayer: will not try to tenure-extend yet ({} <= {})",
1778-
tenure_extend_time.elapsed().as_secs(),
1779-
tenure_extend_time.timeout().as_secs()
1780-
);
1781-
return;
1782-
}
1783-
} else {
1770+
let Some(tenure_extend_time) = self.tenure_extend_time.clone() else {
17841771
// No tenure extend time set, so nothing to do.
17851772
return;
1773+
};
1774+
if !tenure_extend_time.should_extend() {
1775+
test_debug!(
1776+
"Relayer: will not try to tenure-extend yet ({} <= {})",
1777+
tenure_extend_time.elapsed().as_secs(),
1778+
tenure_extend_time.timeout().as_secs()
1779+
);
1780+
return;
17861781
}
1787-
let Some(mining_pk) = self.get_mining_key_pkh() else {
1782+
1783+
let Some(mining_pkh) = self.get_mining_key_pkh() else {
17881784
// This shouldn't really ever hit, but just in case.
17891785
warn!("Will not tenure extend -- no mining key");
17901786
// If we don't have a mining key set, don't bother checking again.
@@ -1793,9 +1789,9 @@ impl RelayerThread {
17931789
};
17941790
// reset timer so we can try again if for some reason a miner was already running (e.g. a
17951791
// blockfound from earlier).
1796-
self.tenure_extend_time = Some(TenureExtendTime::delayed(
1797-
self.config.miner.tenure_extend_poll_timeout,
1798-
));
1792+
self.tenure_extend_time
1793+
.as_mut()
1794+
.map(|t| t.refresh(self.config.miner.tenure_extend_poll_timeout));
17991795
// try to extend, but only if we aren't already running a thread for the current or newer
18001796
// burnchain view
18011797
let Ok(burn_tip) = SortitionDB::get_canonical_burn_chain_tip(self.sortdb.conn())
@@ -1825,36 +1821,41 @@ impl RelayerThread {
18251821
SortitionDB::get_block_snapshot_consensus(self.sortdb.conn(), &canonical_stacks_tip_ch)
18261822
.expect("FATAL: failed to query sortiiton DB for epoch")
18271823
.expect("FATAL: no sortition for canonical stacks tip");
1828-
let Ok(last_winning_snapshot) = Self::get_last_winning_snapshot(&self.sortdb, &burn_tip)
1829-
.inspect_err(|e| {
1830-
warn!("Failed to load last winning snapshot: {e:?}");
1831-
})
1832-
else {
1833-
// this should be unreachable, but don't tempt fate.
1834-
info!("No prior snapshots have a winning sortition. Will not try to mine.");
1835-
self.tenure_extend_time = None;
1836-
return;
1837-
};
1838-
let won_last_winning_snapshot = last_winning_snapshot.miner_pk_hash == Some(mining_pk);
1839-
if won_last_winning_snapshot
1840-
&& self.need_block_found(&canonical_stacks_snapshot, &last_winning_snapshot)
1841-
{
1842-
info!("Will not extend tenure -- need to issue a BlockFound first");
1843-
// We may manage to extend later, so don't set the timer to None.
1844-
return;
1824+
1825+
match tenure_extend_time.reason() {
1826+
TenureExtendReason::BadSortitionWinner | TenureExtendReason::EmptySortition => {
1827+
// Before we try to extend, check if we need to issue a BlockFound
1828+
let Ok(last_winning_snapshot) =
1829+
Self::get_last_winning_snapshot(&self.sortdb, &burn_tip).inspect_err(|e| {
1830+
warn!("Failed to load last winning snapshot: {e:?}");
1831+
})
1832+
else {
1833+
// this should be unreachable, but don't tempt fate.
1834+
info!("No prior snapshots have a winning sortition. Will not try to mine.");
1835+
self.tenure_extend_time = None;
1836+
return;
1837+
};
1838+
let won_last_winning_snapshot =
1839+
last_winning_snapshot.miner_pk_hash == Some(mining_pkh);
1840+
if won_last_winning_snapshot
1841+
&& self.need_block_found(&canonical_stacks_snapshot, &last_winning_snapshot)
1842+
{
1843+
info!("Will not tenure extend yet -- need to issue a BlockFound first");
1844+
// We may manage to extend later, so don't set the timer to None.
1845+
return;
1846+
}
1847+
}
1848+
TenureExtendReason::UnresponsiveWinner => {}
18451849
}
1850+
18461851
let won_ongoing_tenure_sortition =
1847-
canonical_stacks_snapshot.miner_pk_hash == Some(mining_pk);
1852+
canonical_stacks_snapshot.miner_pk_hash == Some(mining_pkh);
18481853
if !won_ongoing_tenure_sortition {
1849-
// We did not win the ongoing tenure sortition, so nothing we can even do.
1850-
// Make sure this check is done AFTER checking for the BlockFound so that
1851-
// we can set tenure_extend_time to None in this case without causing problems
1852-
// (If we need to issue a block found, we may not have won_ongoing_tenure_sortition)
18531854
debug!("Will not tenure extend. Did not win ongoing tenure sortition";
18541855
"burn_chain_sortition_tip_ch" => %burn_tip.consensus_hash,
18551856
"canonical_stacks_tip_ch" => %canonical_stacks_tip_ch,
18561857
"burn_chain_sortition_tip_mining_pk" => ?burn_tip.miner_pk_hash,
1857-
"mining_pk" => %mining_pk,
1858+
"mining_pk" => %mining_pkh
18581859
);
18591860
self.tenure_extend_time = None;
18601861
return;
@@ -1902,7 +1903,7 @@ impl RelayerThread {
19021903
let poll_frequency_ms = 1_000;
19031904

19041905
while self.globals.keep_running() {
1905-
self.try_continue_tenure();
1906+
self.check_tenure_timers();
19061907
let raised_initiative = self.globals.take_initiative();
19071908
let timed_out = Instant::now() >= self.next_initiative;
19081909
let mut initiative_directive = if raised_initiative.is_some() || timed_out {

testnet/stacks-node/src/tests/signer/v0.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14955,7 +14955,7 @@ fn prev_miner_will_not_attempt_to_extend_if_incoming_miner_produces_a_block() {
1495514955
signer_test.shutdown();
1495614956
}
1495714957

14958-
/// Test a scenario where a non-blocking minority of miners are configured to favour the incoming miner.
14958+
/// Test a scenario where a non-blocking minority of signers are configured to favour the incoming miner.
1495914959
/// The previous miner should extend its tenure and succeed as a majority are configured to favour it
1496014960
/// and its subsequent blocks should be be approved.
1496114961
/// Two miners boot to Nakamoto.
@@ -14965,7 +14965,7 @@ fn prev_miner_will_not_attempt_to_extend_if_incoming_miner_produces_a_block() {
1496514965
/// Miner 2 wins the second tenure B.
1496614966
/// A majority of signers mark miner 2 as invalid.
1496714967
/// Miner 2 proposes block N+1' with a TenureChangeCause::BlockFound
14968-
/// A majority fo signers rekect block N+1'.
14968+
/// A majority fo signers reject block N+1'.
1496914969
/// Miner 1 proposes block N+1 with a TenureChangeCause::Extended
1497014970
/// A majority of signers accept and the stacks tip advances to N+1
1497114971
/// Miner 1 proposes block N+2 with a transfer tx
@@ -15579,7 +15579,7 @@ fn non_blocking_minority_configured_to_favour_incoming_miner() {
1557915579
signer_test.shutdown();
1558015580
}
1558115581

15582-
/// Test a scenario where a non-blocking majority of miners are configured to favour the previous miner
15582+
/// Test a scenario where a non-blocking majority of signers are configured to favour the previous miner
1558315583
/// extending their tenure when the incoming miner is slow to propose a block. The incoming miner should succeed
1558415584
/// and its subsequent blocks should be be approved.
1558515585
/// Two miners boot to Nakamoto.

0 commit comments

Comments
 (0)