Skip to content

Commit ba5fdd8

Browse files
committed
Merge remote-tracking branch 'core/develop' into feat/full-tx-replay
2 parents 8c45d50 + 14230bf commit ba5fdd8

File tree

23 files changed

+923
-346
lines changed

23 files changed

+923
-346
lines changed

.github/workflows/workflow-cleanup.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,19 @@ on:
1212
workflow-ttl:
1313
description: "How many days to keep a successful workflow (default: 30)"
1414
required: false
15-
default: "30"
15+
default: "60"
1616
failed-workflow-ttl:
1717
description: "How many days to keep failed workflows (default: 15)"
1818
required: false
19-
default: "15"
19+
default: "60"
2020
schedule:
2121
## Run every day at 00:00:00
2222
- cron: "0 0 * * *"
2323

24+
permissions:
25+
actions: write # to delete workflow runs and caches
26+
contents: read # to access repo contents
27+
2428
## env vars are transferred to composite action steps
2529
env:
2630
CACHE_TTL: 7 ## number of days to keep a cache
@@ -41,7 +45,7 @@ jobs:
4145
id: cleanup
4246
uses: stacks-network/actions/cleanup/workflows@main
4347
with:
44-
token: ${{ secrets.GH_TOKEN }}
48+
token: ${{ secrets.GITHUB_TOKEN }}
4549
cache-ttl: ${{ inputs.cache-ttl || env.CACHE_TTL}}
4650
workflow-ttl: ${{ inputs.workflow-ttl || env.WORKFLOW_TTL}}
4751
failed-workflow-ttl: ${{ inputs.failed-workflow-ttl || env.FAILED_WORKFLOW_TTL }}

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to the versioning scheme outlined in the [README.md](README.md).
77

8+
## [3.1.0.0.11]
9+
10+
- Hotfix for p2p stack misbehavior in mempool syncing conditions
11+
812
## [3.1.0.0.10]
913

1014
### Added

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
@@ -545,6 +549,16 @@ impl<Signer: SignerTrait<T>, T: StacksMessageCodec + Clone + Send + Debug>
545549
}
546550
})
547551
.unwrap_or(0),
552+
signer_canonical_tips: self
553+
.stacks_signers
554+
.iter()
555+
.map(|(reward_cycle, signer)| {
556+
let ConfiguredSigner::RegisteredSigner(ref signer) = signer else {
557+
return (*reward_cycle, None);
558+
};
559+
(*reward_cycle, signer.get_canonical_tip())
560+
})
561+
.collect(),
548562
};
549563
info!("Signer status check requested: {state_info:?}");
550564

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,

0 commit comments

Comments
 (0)