Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[signer] Add block_proposal_max_age_secs signer configuration to drop old proposals without further processing #5549

Merged
merged 7 commits into from
Dec 12, 2024
1 change: 1 addition & 0 deletions .github/workflows/bitcoin-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ jobs:
- tests::signer::v0::continue_after_fast_block_no_sortition
- tests::signer::v0::block_validation_response_timeout
- tests::signer::v0::tenure_extend_after_bad_commit
- tests::signer::v0::block_proposal_max_age_rejections
- tests::nakamoto_integrations::burn_ops_integration_test
- tests::nakamoto_integrations::check_block_heights
- tests::nakamoto_integrations::clarity_burn_state
Expand Down
2 changes: 2 additions & 0 deletions stacks-signer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE

## Added

- Introduced the `block_proposal_max_age_secs` configuration option for signers, enabling them to automatically ignore block proposals that exceed the specified age in seconds.

## Changed

## [3.1.0.0.1.0]
Expand Down
2 changes: 1 addition & 1 deletion stacks-signer/src/chainstate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ impl SortitionsView {
tenure_extend.burn_view_consensus_hash != sortition_consensus_hash;
let extend_timestamp = signer_db.calculate_tenure_extend_timestamp(
self.config.tenure_idle_timeout,
&block,
block,
false,
);
let epoch_time = get_epoch_time_secs();
Expand Down
1 change: 1 addition & 0 deletions stacks-signer/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ pub(crate) mod tests {
tenure_last_block_proposal_timeout: config.tenure_last_block_proposal_timeout,
block_proposal_validation_timeout: config.block_proposal_validation_timeout,
tenure_idle_timeout: config.tenure_idle_timeout,
block_proposal_max_age_secs: config.block_proposal_max_age_secs,
}
}

Expand Down
12 changes: 12 additions & 0 deletions stacks-signer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const BLOCK_PROPOSAL_VALIDATION_TIMEOUT_MS: u64 = 120_000;
const DEFAULT_FIRST_PROPOSAL_BURN_BLOCK_TIMING_SECS: u64 = 60;
const DEFAULT_TENURE_LAST_BLOCK_PROPOSAL_TIMEOUT_SECS: u64 = 30;
const TENURE_IDLE_TIMEOUT_SECS: u64 = 300;
const DEFAULT_BLOCK_PROPOSAL_MAX_AGE_SECS: u64 = 600;
jferrant marked this conversation as resolved.
Show resolved Hide resolved

#[derive(thiserror::Error, Debug)]
/// An error occurred parsing the provided configuration
Expand Down Expand Up @@ -138,6 +139,8 @@ pub struct SignerConfig {
pub block_proposal_validation_timeout: Duration,
/// How much idle time must pass before allowing a tenure extend
pub tenure_idle_timeout: Duration,
/// The maximum age of a block proposal in seconds that will be processed by the signer
pub block_proposal_max_age_secs: u64,
}

/// The parsed configuration for the signer
Expand Down Expand Up @@ -176,6 +179,8 @@ pub struct GlobalConfig {
pub block_proposal_validation_timeout: Duration,
/// How much idle time must pass before allowing a tenure extend
pub tenure_idle_timeout: Duration,
/// The maximum age of a block proposal that will be processed by the signer
pub block_proposal_max_age_secs: u64,
}

/// Internal struct for loading up the config file
Expand Down Expand Up @@ -213,6 +218,8 @@ struct RawConfigFile {
pub block_proposal_validation_timeout_ms: Option<u64>,
/// How much idle time (in seconds) must pass before a tenure extend is allowed
pub tenure_idle_timeout_secs: Option<u64>,
/// The maximum age of a block proposal (in secs) that will be processed by the signer.
pub block_proposal_max_age_secs: Option<u64>,
}

impl RawConfigFile {
Expand Down Expand Up @@ -310,6 +317,10 @@ impl TryFrom<RawConfigFile> for GlobalConfig {
.unwrap_or(TENURE_IDLE_TIMEOUT_SECS),
);

let block_proposal_max_age_secs = raw_data
.block_proposal_max_age_secs
.unwrap_or(DEFAULT_BLOCK_PROPOSAL_MAX_AGE_SECS);

Ok(Self {
node_host: raw_data.node_host,
endpoint,
Expand All @@ -326,6 +337,7 @@ impl TryFrom<RawConfigFile> for GlobalConfig {
tenure_last_block_proposal_timeout,
block_proposal_validation_timeout,
tenure_idle_timeout,
block_proposal_max_age_secs,
})
}
}
Expand Down
1 change: 1 addition & 0 deletions stacks-signer/src/runloop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ impl<Signer: SignerTrait<T>, T: StacksMessageCodec + Clone + Send + Debug> RunLo
tenure_last_block_proposal_timeout: self.config.tenure_last_block_proposal_timeout,
block_proposal_validation_timeout: self.config.block_proposal_validation_timeout,
tenure_idle_timeout: self.config.tenure_idle_timeout,
block_proposal_max_age_secs: self.config.block_proposal_max_age_secs,
}))
}

Expand Down
21 changes: 21 additions & 0 deletions stacks-signer/src/v0/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ pub struct Signer {
pub block_proposal_validation_timeout: Duration,
/// The current submitted block proposal and its submission time
pub submitted_block_proposal: Option<(BlockProposal, Instant)>,
/// Maximum age of a block proposal in seconds before it is dropped without processing
pub block_proposal_max_age_secs: u64,
}

impl std::fmt::Display for Signer {
Expand Down Expand Up @@ -284,6 +286,7 @@ impl From<SignerConfig> for Signer {
proposal_config,
submitted_block_proposal: None,
block_proposal_validation_timeout: signer_config.block_proposal_validation_timeout,
block_proposal_max_age_secs: signer_config.block_proposal_max_age_secs,
}
}
}
Expand Down Expand Up @@ -344,6 +347,24 @@ impl Signer {
return;
}

if block_proposal
.block
.header
.timestamp
.saturating_add(self.block_proposal_max_age_secs)
< get_epoch_time_secs()
{
// Block is too old. Drop it with a warning. Don't even bother broadcasting to the node.
warn!("{self}: Received a block proposal that is more than {} secs old. Ignoring...", self.block_proposal_max_age_secs;
"signer_signature_hash" => %block_proposal.block.header.signer_signature_hash(),
jferrant marked this conversation as resolved.
Show resolved Hide resolved
"block_id" => %block_proposal.block.block_id(),
jferrant marked this conversation as resolved.
Show resolved Hide resolved
"block_height" => block_proposal.block.header.chain_length,
"burn_height" => block_proposal.burn_height,
"timestamp" => block_proposal.block.header.timestamp,
);
return;
}

// TODO: should add a check to ignore an old burn block height if we know its outdated. Would require us to store the burn block height we last saw on the side.
// the signer needs to be able to determine whether or not the block they're about to sign would conflict with an already-signed Stacks block
let signer_signature_hash = block_proposal.block.header.signer_signature_hash();
Expand Down
5 changes: 3 additions & 2 deletions testnet/stacks-node/src/tests/signer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
}

/// Mine a BTC block and wait for a new Stacks block to be mined
fn mine_nakamoto_block(&mut self, timeout: Duration) {
/// Note: do not use nakamoto blocks mined heuristic if running a test with multiple miners
fn mine_nakamoto_block(&mut self, timeout: Duration, use_nakamoto_blocks_mined: bool) {
let commits_submitted = self.running_nodes.commits_submitted.clone();
let mined_block_time = Instant::now();
let mined_before = self.running_nodes.nakamoto_blocks_mined.get();
Expand All @@ -327,7 +328,7 @@ impl<S: Signer<T> + Send + 'static, T: SignerEventTrait + 'static> SignerTest<Sp
let info_after = self.get_peer_info();
let blocks_mined = self.running_nodes.nakamoto_blocks_mined.get();
Ok(info_after.stacks_tip_height > info_before.stacks_tip_height
&& blocks_mined > mined_before)
&& (!use_nakamoto_blocks_mined || blocks_mined > mined_before))
})
.unwrap();
let mined_block_elapsed_time = mined_block_time.elapsed();
Expand Down
Loading
Loading