Skip to content

Commit ce66233

Browse files
committed
Merge branch 'develop' into default-mempool-walk-strategy
2 parents 35da5a7 + 14230bf commit ce66233

File tree

17 files changed

+737
-213
lines changed

17 files changed

+737
-213
lines changed

stacks-signer/src/chainstate.rs

Lines changed: 74 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,9 @@ impl SortitionsView {
389389
)?;
390390
} else {
391391
// check if the new block confirms the last block in the current tenure
392-
let confirms_latest_in_tenure =
393-
Self::confirms_latest_block_in_same_tenure(block, signer_db)
394-
.map_err(SignerChainstateError::from)?;
392+
let confirms_latest_in_tenure = self
393+
.confirms_latest_block_in_same_tenure(block, signer_db, client)
394+
.map_err(SignerChainstateError::from)?;
395395
if !confirms_latest_in_tenure {
396396
return Err(RejectReason::InvalidParentBlock);
397397
}
@@ -562,8 +562,7 @@ impl SortitionsView {
562562
Ok(true)
563563
}
564564

565-
/// Get the last block from the given tenure
566-
/// Returns the last locally accepted block if it is not timed out, otherwise it will return the last globally accepted block.
565+
/// Get the last locally signed block from the given tenure if it has not timed out
567566
pub fn get_tenure_last_block_info(
568567
consensus_hash: &ConsensusHash,
569568
signer_db: &SignerDb,
@@ -573,43 +572,45 @@ impl SortitionsView {
573572
let last_locally_accepted_block = signer_db
574573
.get_last_accepted_block(consensus_hash)
575574
.map_err(|e| ClientError::InvalidResponse(e.to_string()))?;
575+
let Some(local_info) = last_locally_accepted_block else {
576+
return Ok(None);
577+
};
576578

577-
if let Some(local_info) = last_locally_accepted_block {
578-
if let Some(signed_over_time) = local_info.signed_self {
579-
if signed_over_time.saturating_add(tenure_last_block_proposal_timeout.as_secs())
580-
> get_epoch_time_secs()
581-
{
582-
// The last locally accepted block is not timed out, return it
583-
return Ok(Some(local_info));
584-
}
585-
}
579+
let Some(signed_over_time) = local_info.signed_self else {
580+
return Ok(None);
581+
};
582+
583+
if signed_over_time.saturating_add(tenure_last_block_proposal_timeout.as_secs())
584+
> get_epoch_time_secs()
585+
{
586+
// The last locally accepted block is not timed out, return it
587+
Ok(Some(local_info))
588+
} else {
589+
// The last locally accepted block is timed out
590+
Ok(None)
586591
}
587-
// The last locally accepted block is timed out, get the last globally accepted block
588-
signer_db
589-
.get_last_globally_accepted_block(consensus_hash)
590-
.map_err(|e| ClientError::InvalidResponse(e.to_string()))
591592
}
592593

593-
/// Check if the tenure change block confirms the expected parent block
594-
/// (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)
595-
/// It checks the local DB first, and if the block is not present in the local DB, it asks the
596-
/// Stacks node for the highest processed block header in the given tenure (and then caches it
597-
/// in the DB).
594+
/// Check whether or not `block` is higher than the highest block in `tenure_id`.
595+
/// returns `Ok(true)` if `block` is higher, `Ok(false)` if not.
598596
///
599-
/// The rationale here is that the signer DB can be out-of-sync with the node. For example,
600-
/// the signer may have been added to an already-running node.
601-
pub fn check_tenure_change_confirms_parent(
602-
tenure_change: &TenureChangePayload,
597+
/// If we can't look up `tenure_id`, assume `block` is higher.
598+
/// This assumption is safe because this proposal ultimately must be passed
599+
/// to the `stacks-node` for proposal processing: so, if we pass the block
600+
/// height check here, we are relying on the `stacks-node` proposal endpoint
601+
/// to do the validation on the chainstate data that it has.
602+
///
603+
/// This updates the activity timer for the miner of `block`.
604+
pub fn check_latest_block_in_tenure(
605+
tenure_id: &ConsensusHash,
603606
block: &NakamotoBlock,
604607
signer_db: &mut SignerDb,
605608
client: &StacksClient,
606609
tenure_last_block_proposal_timeout: Duration,
607610
reorg_attempts_activity_timeout: Duration,
608611
) -> Result<bool, ClientError> {
609-
// 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.
610-
// NOTE: returns the locally accepted block if it is not timed out, otherwise it will return the last globally accepted block.
611612
let last_block_info = Self::get_tenure_last_block_info(
612-
&tenure_change.prev_tenure_consensus_hash,
613+
tenure_id,
613614
signer_db,
614615
tenure_last_block_proposal_timeout,
615616
)?;
@@ -636,7 +637,7 @@ impl SortitionsView {
636637
// to give the miner some extra buffer time to wait for its chain tip to advance
637638
// The miner may just be slow, so count this invalid block proposal towards valid miner activity.
638639
if let Err(e) = signer_db.update_last_activity_time(
639-
&tenure_change.tenure_consensus_hash,
640+
&block.header.consensus_hash,
640641
get_epoch_time_secs(),
641642
) {
642643
warn!("Failed to update last activity time: {e}");
@@ -646,16 +647,16 @@ impl SortitionsView {
646647
}
647648
}
648649

649-
let tip = match client.get_tenure_tip(&tenure_change.prev_tenure_consensus_hash) {
650+
let tip = match client.get_tenure_tip(tenure_id) {
650651
Ok(tip) => tip,
651652
Err(e) => {
652653
warn!(
653-
"Miner block proposal contains a tenure change, but failed to fetch the tenure tip for the parent tenure: {e:?}. Considering proposal invalid.";
654+
"Failed to fetch the tenure tip for the parent tenure: {e:?}. Assuming proposal is higher than the parent tenure for now.";
654655
"proposed_block_consensus_hash" => %block.header.consensus_hash,
655656
"signer_signature_hash" => %block.header.signer_signature_hash(),
656-
"parent_tenure" => %tenure_change.prev_tenure_consensus_hash,
657+
"parent_tenure" => %tenure_id,
657658
);
658-
return Ok(false);
659+
return Ok(true);
659660
}
660661
};
661662
if let Some(nakamoto_tip) = tip.as_stacks_nakamoto() {
@@ -673,19 +674,33 @@ impl SortitionsView {
673674
}
674675
}
675676
}
676-
let tip_height = tip.height();
677-
if block.header.chain_length > tip_height {
678-
Ok(true)
679-
} else {
680-
warn!(
681-
"Miner's block proposal does not confirm as many blocks as we expect";
682-
"proposed_block_consensus_hash" => %block.header.consensus_hash,
683-
"signer_signature_hash" => %block.header.signer_signature_hash(),
684-
"proposed_chain_length" => block.header.chain_length,
685-
"expected_at_least" => tip_height + 1,
686-
);
687-
Ok(false)
688-
}
677+
Ok(tip.height() < block.header.chain_length)
678+
}
679+
680+
/// Check if the tenure change block confirms the expected parent block
681+
/// (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)
682+
/// It checks the local DB first, and if the block is not present in the local DB, it asks the
683+
/// Stacks node for the highest processed block header in the given tenure (and then caches it
684+
/// in the DB).
685+
///
686+
/// The rationale here is that the signer DB can be out-of-sync with the node. For example,
687+
/// the signer may have been added to an already-running node.
688+
pub fn check_tenure_change_confirms_parent(
689+
tenure_change: &TenureChangePayload,
690+
block: &NakamotoBlock,
691+
signer_db: &mut SignerDb,
692+
client: &StacksClient,
693+
tenure_last_block_proposal_timeout: Duration,
694+
reorg_attempts_activity_timeout: Duration,
695+
) -> Result<bool, ClientError> {
696+
Self::check_latest_block_in_tenure(
697+
&tenure_change.prev_tenure_consensus_hash,
698+
block,
699+
signer_db,
700+
client,
701+
tenure_last_block_proposal_timeout,
702+
reorg_attempts_activity_timeout,
703+
)
689704
}
690705

691706
/// in tenure changes, we need to check:
@@ -742,33 +757,19 @@ impl SortitionsView {
742757
}
743758

744759
fn confirms_latest_block_in_same_tenure(
760+
&self,
745761
block: &NakamotoBlock,
746-
signer_db: &SignerDb,
762+
signer_db: &mut SignerDb,
763+
client: &StacksClient,
747764
) -> Result<bool, ClientError> {
748-
let Some(last_known_block) = signer_db
749-
.get_last_accepted_block(&block.header.consensus_hash)
750-
.map_err(|e| ClientError::InvalidResponse(e.to_string()))?
751-
else {
752-
info!(
753-
"Have no accepted blocks in the tenure, assuming block confirmation is correct";
754-
"proposed_block_consensus_hash" => %block.header.consensus_hash,
755-
"signer_signature_hash" => %block.header.signer_signature_hash(),
756-
"proposed_block_height" => block.header.chain_length,
757-
);
758-
return Ok(true);
759-
};
760-
if block.header.chain_length > last_known_block.block.header.chain_length {
761-
Ok(true)
762-
} else {
763-
warn!(
764-
"Miner's block proposal does not confirm as many blocks as we expect";
765-
"proposed_block_consensus_hash" => %block.header.consensus_hash,
766-
"signer_signature_hash" => %block.header.signer_signature_hash(),
767-
"proposed_chain_length" => block.header.chain_length,
768-
"expected_at_least" => last_known_block.block.header.chain_length + 1,
769-
);
770-
Ok(false)
771-
}
765+
Self::check_latest_block_in_tenure(
766+
&block.header.consensus_hash,
767+
block,
768+
signer_db,
769+
client,
770+
self.config.tenure_last_block_proposal_timeout,
771+
self.config.reorg_attempts_activity_timeout,
772+
)
772773
}
773774

774775
/// Fetch a new view of the recent sortitions

stacks-signer/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ use chainstate::SortitionsView;
5353
use config::GlobalConfig;
5454
use libsigner::{SignerEvent, SignerEventReceiver, SignerEventTrait, VERSION_STRING};
5555
use runloop::SignerResult;
56+
use signerdb::BlockInfo;
5657
use stacks_common::{info, warn};
5758
use v0::signer_state::LocalStateMachine;
5859

@@ -81,6 +82,8 @@ pub trait Signer<T: SignerEventTrait>: Debug + Display {
8182
fn get_local_state_machine(&self) -> &LocalStateMachine;
8283
/// Get the number of pending block proposals
8384
fn get_pending_proposals_count(&self) -> u64;
85+
/// Get canonical block according to this signer's db
86+
fn get_canonical_tip(&self) -> Option<BlockInfo>;
8487
}
8588

8689
/// A wrapper around the running signer type for the signer

stacks-signer/src/runloop.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use stacks_common::{debug, error, info, warn};
2525
use crate::chainstate::SortitionsView;
2626
use crate::client::{retry_with_exponential_backoff, ClientError, StacksClient};
2727
use crate::config::{GlobalConfig, SignerConfig, SignerConfigMode};
28+
use crate::signerdb::BlockInfo;
2829
use crate::v0::signer_state::LocalStateMachine;
2930
#[cfg(any(test, feature = "testing"))]
3031
use crate::v0::tests::TEST_SKIP_SIGNER_CLEANUP;
@@ -59,6 +60,9 @@ pub struct StateInfo {
5960
pub signer_state_machines: Vec<(u64, Option<LocalStateMachine>)>,
6061
/// The number of pending block proposals for this signer
6162
pub pending_proposals_count: u64,
63+
/// The canonical tip block info according to the running signers
64+
/// as a pair of (reward-cycle, block-info)
65+
pub signer_canonical_tips: Vec<(u64, Option<BlockInfo>)>,
6266
}
6367

6468
/// The signer result that can be sent across threads
@@ -544,6 +548,16 @@ impl<Signer: SignerTrait<T>, T: StacksMessageCodec + Clone + Send + Debug>
544548
}
545549
})
546550
.unwrap_or(0),
551+
signer_canonical_tips: self
552+
.stacks_signers
553+
.iter()
554+
.map(|(reward_cycle, signer)| {
555+
let ConfiguredSigner::RegisteredSigner(ref signer) = signer else {
556+
return (*reward_cycle, None);
557+
};
558+
(*reward_cycle, signer.get_canonical_tip())
559+
})
560+
.collect(),
547561
};
548562
info!("Signer status check requested: {state_info:?}");
549563

stacks-signer/src/signerdb.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ impl FromRow<BurnBlockInfo> for BurnBlockInfo {
9898
}
9999
}
100100

101-
#[derive(Serialize, Deserialize, Debug, PartialEq, Default)]
101+
#[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone)]
102102
/// Store extra version-specific info in `BlockInfo`
103103
pub enum ExtraBlockInfo {
104104
#[default]
@@ -167,7 +167,7 @@ impl TryFrom<&str> for BlockState {
167167
}
168168

169169
/// Additional Info about a proposed block
170-
#[derive(Serialize, Deserialize, Debug, PartialEq)]
170+
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
171171
pub struct BlockInfo {
172172
/// The block we are considering
173173
pub block: NakamotoBlock,

stacks-signer/src/v0/signer.rs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,14 @@ impl SignerTrait<SignerMessage> for Signer {
344344
.map(|results| u64::try_from(results.len()).unwrap())
345345
.unwrap_or(0)
346346
}
347+
348+
fn get_canonical_tip(&self) -> Option<BlockInfo> {
349+
self.signer_db
350+
.get_canonical_tip()
351+
.inspect_err(|e| error!("{self}: Failed to check for canonical tip: {e:?}"))
352+
.ok()
353+
.flatten()
354+
}
347355
}
348356

349357
impl Signer {
@@ -953,7 +961,6 @@ impl Signer {
953961
proposed_block: &NakamotoBlock,
954962
) -> Option<BlockResponse> {
955963
let signer_signature_hash = proposed_block.header.signer_signature_hash();
956-
let proposed_block_consensus_hash = proposed_block.header.consensus_hash;
957964
// If this is a tenure change block, ensure that it confirms the correct number of blocks from the parent tenure.
958965
if let Some(tenure_change) = proposed_block.get_tenure_change_tx_payload() {
959966
// Ensure that the tenure change block confirms the expected parent block
@@ -965,7 +972,7 @@ impl Signer {
965972
self.proposal_config.tenure_last_block_proposal_timeout,
966973
self.proposal_config.reorg_attempts_activity_timeout,
967974
) {
968-
Ok(true) => {}
975+
Ok(true) => return None,
969976
Ok(false) => {
970977
return Some(self.create_block_rejection(
971978
RejectReason::SortitionViewMismatch,
@@ -988,40 +995,43 @@ impl Signer {
988995
}
989996

990997
// Ensure that the block is the last block in the chain of its current tenure.
991-
match self
992-
.signer_db
993-
.get_last_accepted_block(&proposed_block_consensus_hash)
994-
{
995-
Ok(Some(last_block_info)) => {
996-
if proposed_block.header.chain_length <= last_block_info.block.header.chain_length {
998+
match SortitionsView::check_latest_block_in_tenure(
999+
&proposed_block.header.consensus_hash,
1000+
proposed_block,
1001+
&mut self.signer_db,
1002+
stacks_client,
1003+
self.proposal_config.tenure_last_block_proposal_timeout,
1004+
self.proposal_config.reorg_attempts_activity_timeout,
1005+
) {
1006+
Ok(is_latest) => {
1007+
if !is_latest {
9971008
warn!(
9981009
"Miner's block proposal does not confirm as many blocks as we expect";
999-
"proposed_block_consensus_hash" => %proposed_block_consensus_hash,
1010+
"proposed_block_consensus_hash" => %proposed_block.header.consensus_hash,
10001011
"proposed_block_signer_signature_hash" => %signer_signature_hash,
10011012
"proposed_chain_length" => proposed_block.header.chain_length,
1002-
"expected_at_least" => last_block_info.block.header.chain_length + 1,
10031013
);
1004-
return Some(self.create_block_rejection(
1014+
Some(self.create_block_rejection(
10051015
RejectReason::SortitionViewMismatch,
10061016
proposed_block,
1007-
));
1017+
))
1018+
} else {
1019+
None
10081020
}
10091021
}
1010-
Ok(_) => {}
10111022
Err(e) => {
10121023
warn!("{self}: Failed to check block against signer db: {e}";
10131024
"signer_signature_hash" => %signer_signature_hash,
10141025
"block_id" => %proposed_block.block_id()
10151026
);
1016-
return Some(self.create_block_rejection(
1027+
Some(self.create_block_rejection(
10171028
RejectReason::ConnectivityIssues(
10181029
"failed to check block against signer db".to_string(),
10191030
),
10201031
proposed_block,
1021-
));
1032+
))
10221033
}
10231034
}
1024-
None
10251035
}
10261036

10271037
/// Handle the block validate ok response. Returns our block response if we have one

stacks-signer/src/v0/signer_state.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ impl LocalStateMachine {
757757

758758
match signerdb.get_burn_block_by_ch(tenure_id) {
759759
Ok(block) => {
760-
potential_matches.push((block.block_height, miner_state.clone()));
760+
potential_matches.push((block.block_height, miner_state));
761761
}
762762
Err(e) => {
763763
warn!("Error retrieving burn block for consensus_hash {tenure_id} from signerdb: {e}");
@@ -767,7 +767,7 @@ impl LocalStateMachine {
767767

768768
potential_matches.sort_by_key(|(block_height, _)| *block_height);
769769

770-
let new_miner = potential_matches.last().map(|(_, miner)| miner.clone());
770+
let new_miner = potential_matches.last().map(|(_, miner)| (*miner).clone());
771771
if new_miner.is_none() {
772772
crate::monitoring::actions::increment_signer_agreement_state_conflict(
773773
crate::monitoring::SignerAgreementStateConflict::MinerView,

0 commit comments

Comments
 (0)