@@ -19,15 +19,15 @@ use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
19
19
use blockstack_lib:: chainstate:: stacks:: TenureChangePayload ;
20
20
use blockstack_lib:: net:: api:: getsortition:: SortitionInfo ;
21
21
use blockstack_lib:: util_lib:: db:: Error as DBError ;
22
- use clarity:: types:: chainstate:: BurnchainHeaderHash ;
23
22
use slog:: { slog_info, slog_warn} ;
24
- use stacks_common:: types:: chainstate:: { ConsensusHash , StacksPublicKey } ;
23
+ use stacks_common:: types:: chainstate:: { BurnchainHeaderHash , ConsensusHash , StacksPublicKey } ;
24
+ use stacks_common:: util:: get_epoch_time_secs;
25
25
use stacks_common:: util:: hash:: Hash160 ;
26
26
use stacks_common:: { info, warn} ;
27
27
28
28
use crate :: client:: { ClientError , CurrentAndLastSortition , StacksClient } ;
29
29
use crate :: config:: SignerConfig ;
30
- use crate :: signerdb:: { BlockState , SignerDb } ;
30
+ use crate :: signerdb:: { BlockInfo , BlockState , SignerDb } ;
31
31
32
32
#[ derive( thiserror:: Error , Debug ) ]
33
33
/// Error type for the signer chainstate module
@@ -119,13 +119,17 @@ pub struct ProposalEvalConfig {
119
119
pub first_proposal_burn_block_timing : Duration ,
120
120
/// Time between processing a sortition and proposing a block before the block is considered invalid
121
121
pub block_proposal_timeout : Duration ,
122
+ /// Time to wait for the last block of a tenure to be globally accepted or rejected before considering
123
+ /// a new miner's block at the same height as valid.
124
+ pub tenure_last_block_proposal_timeout : Duration ,
122
125
}
123
126
124
127
impl From < & SignerConfig > for ProposalEvalConfig {
125
128
fn from ( value : & SignerConfig ) -> Self {
126
129
Self {
127
130
first_proposal_burn_block_timing : value. first_proposal_burn_block_timing ,
128
131
block_proposal_timeout : value. block_proposal_timeout ,
132
+ tenure_last_block_proposal_timeout : value. tenure_last_block_proposal_timeout ,
129
133
}
130
134
}
131
135
}
@@ -460,7 +464,36 @@ impl SortitionsView {
460
464
Ok ( true )
461
465
}
462
466
463
- /// Check if the tenure change block confirms the expected parent block (i.e., the last globally accepted block in the parent tenure)
467
+ /// Get the last block from the given tenure
468
+ /// Returns the last locally accepted block if it is not timed out, otherwise it will return the last globally accepted block.
469
+ fn get_tenure_last_block_info (
470
+ consensus_hash : & ConsensusHash ,
471
+ signer_db : & SignerDb ,
472
+ tenure_last_block_proposal_timeout : Duration ,
473
+ ) -> Result < Option < BlockInfo > , ClientError > {
474
+ // Get the last known block in the previous tenure
475
+ let last_locally_accepted_block = signer_db
476
+ . get_last_accepted_block ( consensus_hash)
477
+ . map_err ( |e| ClientError :: InvalidResponse ( e. to_string ( ) ) ) ?;
478
+
479
+ if let Some ( local_info) = last_locally_accepted_block {
480
+ if let Some ( signed_over_time) = local_info. signed_self {
481
+ if signed_over_time + tenure_last_block_proposal_timeout. as_secs ( )
482
+ > get_epoch_time_secs ( )
483
+ {
484
+ // The last locally accepted block is not timed out, return it
485
+ return Ok ( Some ( local_info) ) ;
486
+ }
487
+ }
488
+ }
489
+ // The last locally accepted block is timed out, get the last globally accepted block
490
+ signer_db
491
+ . get_last_globally_accepted_block ( consensus_hash)
492
+ . map_err ( |e| ClientError :: InvalidResponse ( e. to_string ( ) ) )
493
+ }
494
+
495
+ /// Check if the tenure change block confirms the expected parent block
496
+ /// (i.e., the last locally accepted block in the parent tenure, or if that block is timed out, the last globally accepted block in the parent tenure)
464
497
/// It checks the local DB first, and if the block is not present in the local DB, it asks the
465
498
/// Stacks node for the highest processed block header in the given tenure (and then caches it
466
499
/// in the DB).
@@ -473,24 +506,27 @@ impl SortitionsView {
473
506
reward_cycle : u64 ,
474
507
signer_db : & mut SignerDb ,
475
508
client : & StacksClient ,
509
+ tenure_last_block_proposal_timeout : Duration ,
476
510
) -> Result < bool , ClientError > {
477
- // If the tenure change block confirms the expected parent block, it should confirm at least one more block than the last globally accepted block in the parent tenure.
478
- let last_globally_accepted_block = signer_db
479
- . get_last_globally_accepted_block ( & tenure_change. prev_tenure_consensus_hash )
480
- . map_err ( |e| ClientError :: InvalidResponse ( e. to_string ( ) ) ) ?;
511
+ // If the tenure change block confirms the expected parent block, it should confirm at least one more block than the last accepted block in the parent tenure.
512
+ let last_block_info = Self :: get_tenure_last_block_info (
513
+ & tenure_change. prev_tenure_consensus_hash ,
514
+ signer_db,
515
+ tenure_last_block_proposal_timeout,
516
+ ) ?;
481
517
482
- if let Some ( global_info ) = last_globally_accepted_block {
518
+ if let Some ( info ) = last_block_info {
483
519
// N.B. this block might not be the last globally accepted block across the network;
484
520
// it's just the highest one in this tenure that we know about. If this given block is
485
521
// no higher than it, then it's definitely no higher than the last globally accepted
486
522
// block across the network, so we can do an early rejection here.
487
- if block. header . chain_length <= global_info . block . header . chain_length {
523
+ if block. header . chain_length <= info . block . header . chain_length {
488
524
warn ! (
489
525
"Miner's block proposal does not confirm as many blocks as we expect" ;
490
526
"proposed_block_consensus_hash" => %block. header. consensus_hash,
491
527
"proposed_block_signer_sighash" => %block. header. signer_signature_hash( ) ,
492
528
"proposed_chain_length" => block. header. chain_length,
493
- "expected_at_least" => global_info . block. header. chain_length + 1 ,
529
+ "expected_at_least" => info . block. header. chain_length + 1 ,
494
530
) ;
495
531
return Ok ( false ) ;
496
532
}
@@ -558,6 +594,7 @@ impl SortitionsView {
558
594
reward_cycle,
559
595
signer_db,
560
596
client,
597
+ self . config . tenure_last_block_proposal_timeout ,
561
598
) ?;
562
599
if !confirms_expected_parent {
563
600
return Ok ( false ) ;
@@ -573,15 +610,15 @@ impl SortitionsView {
573
610
if !is_valid_parent_tenure {
574
611
return Ok ( false ) ;
575
612
}
576
- let last_in_tenure = signer_db
613
+ let last_in_current_tenure = signer_db
577
614
. get_last_globally_accepted_block ( & block. header . consensus_hash )
578
615
. map_err ( |e| ClientError :: InvalidResponse ( e. to_string ( ) ) ) ?;
579
- if let Some ( last_in_tenure ) = last_in_tenure {
616
+ if let Some ( last_in_current_tenure ) = last_in_current_tenure {
580
617
warn ! (
581
618
"Miner block proposal contains a tenure change, but we've already signed a block in this tenure. Considering proposal invalid." ;
582
619
"proposed_block_consensus_hash" => %block. header. consensus_hash,
583
620
"proposed_block_signer_sighash" => %block. header. signer_signature_hash( ) ,
584
- "last_in_tenure_signer_sighash" => %last_in_tenure . block. header. signer_signature_hash( ) ,
621
+ "last_in_tenure_signer_sighash" => %last_in_current_tenure . block. header. signer_signature_hash( ) ,
585
622
) ;
586
623
return Ok ( false ) ;
587
624
}
0 commit comments