@@ -250,28 +250,44 @@ impl MinerStopHandle {
250
250
}
251
251
}
252
252
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
+
253
264
/// Information necessary to determine when to extend a tenure
265
+ #[ derive( Clone ) ]
254
266
pub struct TenureExtendTime {
255
267
/// The time at which we determined that we should tenure-extend
256
268
time : Instant ,
257
269
/// The amount of time we should wait before tenure-extending
258
270
timeout : Duration ,
271
+ /// The reason for tenure-extending
272
+ reason : TenureExtendReason ,
259
273
}
260
274
261
275
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 {
264
278
Self {
265
279
time : Instant :: now ( ) ,
266
280
timeout,
281
+ reason : TenureExtendReason :: UnresponsiveWinner ,
267
282
}
268
283
}
269
284
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 {
272
287
Self {
273
288
time : Instant :: now ( ) ,
274
- timeout : Duration :: from_secs ( 0 ) ,
289
+ timeout : Duration :: from_millis ( 0 ) ,
290
+ reason,
275
291
}
276
292
}
277
293
@@ -290,6 +306,17 @@ impl TenureExtendTime {
290
306
pub fn timeout ( & self ) -> Duration {
291
307
self . timeout
292
308
}
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
+ }
293
320
}
294
321
295
322
/// Relayer thread
@@ -521,10 +548,10 @@ impl RelayerThread {
521
548
fn choose_directive_sortition_with_winner (
522
549
& mut self ,
523
550
sn : BlockSnapshot ,
524
- mining_pk : Hash160 ,
551
+ mining_pkh : Hash160 ,
525
552
committed_index_hash : StacksBlockId ,
526
553
) -> Option < MinerDirective > {
527
- let won_sortition = sn. miner_pk_hash == Some ( mining_pk ) ;
554
+ let won_sortition = sn. miner_pk_hash == Some ( mining_pkh ) ;
528
555
if won_sortition || self . config . get_node_config ( false ) . mock_mining {
529
556
// a sortition happenend, and we won
530
557
info ! ( "Won sortition; begin tenure." ;
@@ -551,10 +578,10 @@ impl RelayerThread {
551
578
. expect ( "FATAL: no sortition for canonical stacks tip" ) ;
552
579
553
580
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 ) ;
555
582
if won_ongoing_tenure_sortition {
556
583
// 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 (
558
585
& self . sortdb ,
559
586
& mut self . chainstate ,
560
587
& sn,
@@ -564,12 +591,14 @@ impl RelayerThread {
564
591
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." ;
565
592
"tenure_extend_wait_timeout_ms" => self . config. miner. tenure_extend_wait_timeout. as_millis( ) ,
566
593
) ;
567
- self . tenure_extend_time = Some ( TenureExtendTime :: delayed (
594
+ self . tenure_extend_time = Some ( TenureExtendTime :: unresponsive_winner (
568
595
self . config . miner . tenure_extend_wait_timeout ,
569
596
) ) ;
570
597
} else {
571
598
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
+ ) ) ;
573
602
}
574
603
}
575
604
}
@@ -654,7 +683,9 @@ impl RelayerThread {
654
683
& last_winning_snapshot. consensus_hash
655
684
) ;
656
685
// 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
+ ) ) ;
658
689
return Some ( MinerDirective :: BeginTenure {
659
690
parent_tenure_start : StacksBlockId (
660
691
last_winning_snapshot. winning_stacks_block_hash . clone ( ) . 0 ,
@@ -687,7 +718,7 @@ impl RelayerThread {
687
718
// by someone else -- there's a chance that this other miner will produce a
688
719
// BlockFound in the interim.
689
720
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 (
691
722
self . config . miner . tenure_extend_wait_timeout ,
692
723
) ) ;
693
724
return None ;
@@ -714,56 +745,26 @@ impl RelayerThread {
714
745
// we won the last non-empty sortition. Has there been a BlockFound issued for it?
715
746
// This would be true if the stacks tip's tenure is at or descends from this snapshot.
716
747
// 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 ) ;
720
748
if canonical_stacks_snapshot. block_height > last_winning_snapshot. block_height {
721
749
// 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
+ ) ;
723
755
false
724
756
} else if canonical_stacks_snapshot. block_height == last_winning_snapshot. block_height
725
757
&& canonical_stacks_snapshot. consensus_hash == last_winning_snapshot. consensus_hash
726
758
{
727
759
// this is the ongoing tenure snapshot. A BlockFound has already been issued.
728
760
test_debug ! (
729
- "Relayer: ongoing tenure {} already represents last-winning snapshot" ,
761
+ "Ongoing tenure {} already represents last-winning snapshot" ,
730
762
& canonical_stacks_snapshot. consensus_hash
731
763
) ;
732
764
false
733
765
} 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
767
768
}
768
769
}
769
770
@@ -1345,18 +1346,15 @@ impl RelayerThread {
1345
1346
Ok ( true )
1346
1347
}
1347
1348
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.
1354
1352
///
1355
1353
/// Returns Ok(Some(..)) if such a sortition is found, and is higher than that of
1356
1354
/// `elected_tenure_id`.
1357
1355
/// Returns Ok(None) if no such sortition is found.
1358
1356
/// Returns Err(..) on DB errors.
1359
- fn find_highest_valid_sortition (
1357
+ fn find_highest_sortition_commits_to_stacks_tip_tenure (
1360
1358
sortdb : & SortitionDB ,
1361
1359
chain_state : & mut StacksChainState ,
1362
1360
sort_tip : & BlockSnapshot ,
@@ -1761,30 +1759,28 @@ impl RelayerThread {
1761
1759
1762
1760
/// Try to start up a tenure-extend if the tenure_extend_time has expired.
1763
1761
///
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.
1769
1765
///
1770
1766
/// Note: tenure_extend_time is only set to Some(_) if during sortition processing, the sortition
1771
1767
/// 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 ) {
1773
1769
// 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 {
1784
1771
// No tenure extend time set, so nothing to do.
1785
1772
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 ;
1786
1781
}
1787
- let Some ( mining_pk) = self . get_mining_key_pkh ( ) else {
1782
+
1783
+ let Some ( mining_pkh) = self . get_mining_key_pkh ( ) else {
1788
1784
// This shouldn't really ever hit, but just in case.
1789
1785
warn ! ( "Will not tenure extend -- no mining key" ) ;
1790
1786
// If we don't have a mining key set, don't bother checking again.
@@ -1793,9 +1789,9 @@ impl RelayerThread {
1793
1789
} ;
1794
1790
// reset timer so we can try again if for some reason a miner was already running (e.g. a
1795
1791
// 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 ) ) ;
1799
1795
// try to extend, but only if we aren't already running a thread for the current or newer
1800
1796
// burnchain view
1801
1797
let Ok ( burn_tip) = SortitionDB :: get_canonical_burn_chain_tip ( self . sortdb . conn ( ) )
@@ -1825,36 +1821,41 @@ impl RelayerThread {
1825
1821
SortitionDB :: get_block_snapshot_consensus ( self . sortdb . conn ( ) , & canonical_stacks_tip_ch)
1826
1822
. expect ( "FATAL: failed to query sortiiton DB for epoch" )
1827
1823
. 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 => { }
1845
1849
}
1850
+
1846
1851
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 ) ;
1848
1853
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)
1853
1854
debug ! ( "Will not tenure extend. Did not win ongoing tenure sortition" ;
1854
1855
"burn_chain_sortition_tip_ch" => %burn_tip. consensus_hash,
1855
1856
"canonical_stacks_tip_ch" => %canonical_stacks_tip_ch,
1856
1857
"burn_chain_sortition_tip_mining_pk" => ?burn_tip. miner_pk_hash,
1857
- "mining_pk" => %mining_pk ,
1858
+ "mining_pk" => %mining_pkh
1858
1859
) ;
1859
1860
self . tenure_extend_time = None ;
1860
1861
return ;
@@ -1902,7 +1903,7 @@ impl RelayerThread {
1902
1903
let poll_frequency_ms = 1_000 ;
1903
1904
1904
1905
while self . globals . keep_running ( ) {
1905
- self . try_continue_tenure ( ) ;
1906
+ self . check_tenure_timers ( ) ;
1906
1907
let raised_initiative = self . globals . take_initiative ( ) ;
1907
1908
let timed_out = Instant :: now ( ) >= self . next_initiative ;
1908
1909
let mut initiative_directive = if raised_initiative. is_some ( ) || timed_out {
0 commit comments