@@ -20,14 +20,15 @@ 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
22
use clarity:: types:: chainstate:: BurnchainHeaderHash ;
23
+ use clarity:: util:: get_epoch_time_secs;
23
24
use slog:: { slog_info, slog_warn} ;
24
25
use stacks_common:: types:: chainstate:: { ConsensusHash , StacksPublicKey } ;
25
26
use stacks_common:: util:: hash:: Hash160 ;
26
27
use stacks_common:: { info, warn} ;
27
28
28
29
use crate :: client:: { ClientError , CurrentAndLastSortition , StacksClient } ;
29
30
use crate :: config:: SignerConfig ;
30
- use crate :: signerdb:: { BlockState , SignerDb } ;
31
+ use crate :: signerdb:: { BlockInfo , BlockState , SignerDb } ;
31
32
32
33
#[ derive( thiserror:: Error , Debug ) ]
33
34
/// Error type for the signer chainstate module
@@ -119,13 +120,17 @@ pub struct ProposalEvalConfig {
119
120
pub first_proposal_burn_block_timing : Duration ,
120
121
/// Time between processing a sortition and proposing a block before the block is considered invalid
121
122
pub block_proposal_timeout : Duration ,
123
+ /// Time to wait for the last block of a tenure to be globally accepted or rejected before considering
124
+ /// a new miner's block at the same height as valid.
125
+ pub tenure_last_block_proposal_timeout : Duration ,
122
126
}
123
127
124
128
impl From < & SignerConfig > for ProposalEvalConfig {
125
129
fn from ( value : & SignerConfig ) -> Self {
126
130
Self {
127
131
first_proposal_burn_block_timing : value. first_proposal_burn_block_timing ,
128
132
block_proposal_timeout : value. block_proposal_timeout ,
133
+ tenure_last_block_proposal_timeout : value. tenure_last_block_proposal_timeout ,
129
134
}
130
135
}
131
136
}
@@ -460,7 +465,35 @@ impl SortitionsView {
460
465
Ok ( true )
461
466
}
462
467
463
- /// Check if the tenure change block confirms the expected parent block (i.e., the last globally accepted block in the parent tenure)
468
+ /// Get the last block from the given tenure
469
+ /// Returns the last locally accepted block if it is not timed out, otherwise it will return the last globally accepted block.
470
+ fn get_tenure_last_block_info (
471
+ consensus_hash : & ConsensusHash ,
472
+ signer_db : & SignerDb ,
473
+ tenure_last_block_proposal_timeout : Duration ,
474
+ ) -> Result < Option < BlockInfo > , ClientError > {
475
+ // Get the last known block in the previous tenure
476
+ let last_locally_accepted_block = signer_db
477
+ . get_last_accepted_block ( consensus_hash)
478
+ . map_err ( |e| ClientError :: InvalidResponse ( e. to_string ( ) ) ) ?;
479
+
480
+ if let Some ( local_info) = last_locally_accepted_block {
481
+ if let Some ( signed_over_time) = local_info. signed_self {
482
+ if signed_over_time + tenure_last_block_proposal_timeout. as_secs ( )
483
+ > get_epoch_time_secs ( )
484
+ {
485
+ return Ok ( Some ( local_info) ) ;
486
+ }
487
+ }
488
+ }
489
+
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