Skip to content

Commit 18ada14

Browse files
committed
Merge branch 'develop' of https://github.com/stacks-network/stacks-core into feat/miner-continues-tenure-if-tenure-empty
2 parents 5e3da7b + efb5681 commit 18ada14

File tree

9 files changed

+124
-3
lines changed

9 files changed

+124
-3
lines changed

stacks-signer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1010
## Added
1111

1212
- Introduced the `reorg_attempts_activity_timeout_ms` configuration option for signers which is used to determine the length of time after the last block of a tenure is confirmed that an incoming miner's attempts to reorg it are considered valid miner activity.
13+
- Add signer configuration option `tenure_idle_timeout_buffer_secs` to specify the number of seconds of buffer the signer will add to its tenure extend time that it sends to miners. The idea is to allow for some clock skew between the miner and signers, preventing the case where the miner attempts to tenure extend too early.
1314

1415
### Changed
1516

stacks-signer/src/chainstate.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ pub struct ProposalEvalConfig {
124124
pub tenure_last_block_proposal_timeout: Duration,
125125
/// How much idle time must pass before allowing a tenure extend
126126
pub tenure_idle_timeout: Duration,
127+
/// How much buffer to add to the tenure idle timeout sent to miners to account for clock skew
128+
pub tenure_idle_timeout_buffer: Duration,
127129
/// Time following the last block of the previous tenure's global acceptance that a signer will consider an attempt by
128130
/// the new miner to reorg it as valid towards miner activity
129131
pub reorg_attempts_activity_timeout: Duration,
@@ -137,6 +139,7 @@ impl From<&SignerConfig> for ProposalEvalConfig {
137139
tenure_last_block_proposal_timeout: value.tenure_last_block_proposal_timeout,
138140
tenure_idle_timeout: value.tenure_idle_timeout,
139141
reorg_attempts_activity_timeout: value.reorg_attempts_activity_timeout,
142+
tenure_idle_timeout_buffer: value.tenure_idle_timeout_buffer,
140143
}
141144
}
142145
}

stacks-signer/src/client/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ pub(crate) mod tests {
416416
tenure_last_block_proposal_timeout: config.tenure_last_block_proposal_timeout,
417417
block_proposal_validation_timeout: config.block_proposal_validation_timeout,
418418
tenure_idle_timeout: config.tenure_idle_timeout,
419+
tenure_idle_timeout_buffer: config.tenure_idle_timeout_buffer,
419420
block_proposal_max_age_secs: config.block_proposal_max_age_secs,
420421
reorg_attempts_activity_timeout: config.reorg_attempts_activity_timeout,
421422
}

stacks-signer/src/config.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ const DEFAULT_TENURE_LAST_BLOCK_PROPOSAL_TIMEOUT_SECS: u64 = 30;
4242
const DEFAULT_DRY_RUN: bool = false;
4343
const TENURE_IDLE_TIMEOUT_SECS: u64 = 120;
4444
const DEFAULT_REORG_ATTEMPTS_ACTIVITY_TIMEOUT_MS: u64 = 200_000;
45+
/// Default number of seconds to add to the tenure extend time, after computing the idle timeout,
46+
/// to allow for clock skew between the signer and the miner
47+
const DEFAULT_TENURE_IDLE_TIMEOUT_BUFFER_SECS: u64 = 2;
4548

4649
#[derive(thiserror::Error, Debug)]
4750
/// An error occurred parsing the provided configuration
@@ -162,6 +165,9 @@ pub struct SignerConfig {
162165
pub block_proposal_validation_timeout: Duration,
163166
/// How much idle time must pass before allowing a tenure extend
164167
pub tenure_idle_timeout: Duration,
168+
/// Amount of buffer time to add to the tenure extend time sent to miners to allow for
169+
/// clock skew
170+
pub tenure_idle_timeout_buffer: Duration,
165171
/// The maximum age of a block proposal in seconds that will be processed by the signer
166172
pub block_proposal_max_age_secs: u64,
167173
/// Time following the last block of the previous tenure's global acceptance that a signer will consider an attempt by
@@ -207,6 +213,9 @@ pub struct GlobalConfig {
207213
pub block_proposal_validation_timeout: Duration,
208214
/// How much idle time must pass before allowing a tenure extend
209215
pub tenure_idle_timeout: Duration,
216+
/// Amount of buffer time to add to the tenure extend time sent to miners to allow for
217+
/// clock skew
218+
pub tenure_idle_timeout_buffer: Duration,
210219
/// The maximum age of a block proposal that will be processed by the signer
211220
pub block_proposal_max_age_secs: u64,
212221
/// Time following the last block of the previous tenure's global acceptance that a signer will consider an attempt by
@@ -251,6 +260,9 @@ struct RawConfigFile {
251260
pub block_proposal_validation_timeout_ms: Option<u64>,
252261
/// How much idle time (in seconds) must pass before a tenure extend is allowed
253262
pub tenure_idle_timeout_secs: Option<u64>,
263+
/// Number of seconds of buffer to add to the tenure extend time sent to miners to allow for
264+
/// clock skew
265+
pub tenure_idle_timeout_buffer_secs: Option<u64>,
254266
/// The maximum age of a block proposal (in secs) that will be processed by the signer.
255267
pub block_proposal_max_age_secs: Option<u64>,
256268
/// Time (in millisecs) following a block's global acceptance that a signer will consider an attempt by a miner
@@ -367,6 +379,12 @@ impl TryFrom<RawConfigFile> for GlobalConfig {
367379

368380
let dry_run = raw_data.dry_run.unwrap_or(DEFAULT_DRY_RUN);
369381

382+
let tenure_idle_timeout_buffer = Duration::from_secs(
383+
raw_data
384+
.tenure_idle_timeout_buffer_secs
385+
.unwrap_or(DEFAULT_TENURE_IDLE_TIMEOUT_BUFFER_SECS),
386+
);
387+
370388
Ok(Self {
371389
node_host: raw_data.node_host,
372390
endpoint,
@@ -386,6 +404,7 @@ impl TryFrom<RawConfigFile> for GlobalConfig {
386404
block_proposal_max_age_secs,
387405
reorg_attempts_activity_timeout,
388406
dry_run,
407+
tenure_idle_timeout_buffer,
389408
})
390409
}
391410
}

stacks-signer/src/runloop.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ impl<Signer: SignerTrait<T>, T: StacksMessageCodec + Clone + Send + Debug> RunLo
314314
tenure_last_block_proposal_timeout: self.config.tenure_last_block_proposal_timeout,
315315
block_proposal_validation_timeout: self.config.block_proposal_validation_timeout,
316316
tenure_idle_timeout: self.config.tenure_idle_timeout,
317+
tenure_idle_timeout_buffer: self.config.tenure_idle_timeout_buffer,
317318
block_proposal_max_age_secs: self.config.block_proposal_max_age_secs,
318319
reorg_attempts_activity_timeout: self.config.reorg_attempts_activity_timeout,
319320
}))

stacks-signer/src/tests/chainstate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ fn setup_test_environment(
9191
block_proposal_timeout: Duration::from_secs(5),
9292
tenure_last_block_proposal_timeout: Duration::from_secs(30),
9393
tenure_idle_timeout: Duration::from_secs(300),
94+
tenure_idle_timeout_buffer: Duration::from_secs(2),
9495
reorg_attempts_activity_timeout: Duration::from_secs(3),
9596
},
9697
};

stacks-signer/src/v0/signer.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,9 @@ impl Signer {
357357
block.header.signer_signature_hash(),
358358
signature,
359359
self.signer_db.calculate_tenure_extend_timestamp(
360-
self.proposal_config.tenure_idle_timeout,
360+
self.proposal_config
361+
.tenure_idle_timeout
362+
.saturating_add(self.proposal_config.tenure_idle_timeout_buffer),
361363
block,
362364
true,
363365
),
@@ -375,7 +377,9 @@ impl Signer {
375377
&self.private_key,
376378
self.mainnet,
377379
self.signer_db.calculate_tenure_extend_timestamp(
378-
self.proposal_config.tenure_idle_timeout,
380+
self.proposal_config
381+
.tenure_idle_timeout
382+
.saturating_add(self.proposal_config.tenure_idle_timeout_buffer),
379383
block,
380384
false,
381385
),
@@ -813,7 +817,9 @@ impl Signer {
813817
&self.private_key,
814818
self.mainnet,
815819
self.signer_db.calculate_tenure_extend_timestamp(
816-
self.proposal_config.tenure_idle_timeout,
820+
self.proposal_config
821+
.tenure_idle_timeout
822+
.saturating_add(self.proposal_config.tenure_idle_timeout_buffer),
817823
&block_info.block,
818824
false,
819825
),

testnet/stacks-node/src/tests/nakamoto_integrations.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6578,6 +6578,7 @@ fn signer_chainstate() {
65786578
block_proposal_timeout: Duration::from_secs(100),
65796579
tenure_last_block_proposal_timeout: Duration::from_secs(30),
65806580
tenure_idle_timeout: Duration::from_secs(300),
6581+
tenure_idle_timeout_buffer: Duration::from_secs(2),
65816582
reorg_attempts_activity_timeout: Duration::from_secs(30),
65826583
};
65836584
let mut sortitions_view =
@@ -6710,6 +6711,7 @@ fn signer_chainstate() {
67106711
block_proposal_timeout: Duration::from_secs(100),
67116712
tenure_last_block_proposal_timeout: Duration::from_secs(30),
67126713
tenure_idle_timeout: Duration::from_secs(300),
6714+
tenure_idle_timeout_buffer: Duration::from_secs(2),
67136715
reorg_attempts_activity_timeout: Duration::from_secs(30),
67146716
};
67156717
let burn_block_height = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn())
@@ -6790,6 +6792,7 @@ fn signer_chainstate() {
67906792
block_proposal_timeout: Duration::from_secs(100),
67916793
tenure_last_block_proposal_timeout: Duration::from_secs(30),
67926794
tenure_idle_timeout: Duration::from_secs(300),
6795+
tenure_idle_timeout_buffer: Duration::from_secs(2),
67936796
reorg_attempts_activity_timeout: Duration::from_secs(30),
67946797
};
67956798
let mut sortitions_view = SortitionsView::fetch_view(proposal_conf, &signer_client).unwrap();

testnet/stacks-node/src/tests/signer/v0.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,7 @@ fn block_proposal_rejection() {
499499
block_proposal_timeout: Duration::from_secs(100),
500500
tenure_last_block_proposal_timeout: Duration::from_secs(30),
501501
tenure_idle_timeout: Duration::from_secs(300),
502+
tenure_idle_timeout_buffer: Duration::from_secs(2),
502503
reorg_attempts_activity_timeout: Duration::from_secs(30),
503504
};
504505
let mut block = NakamotoBlock {
@@ -7823,6 +7824,7 @@ fn block_validation_response_timeout() {
78237824
tenure_last_block_proposal_timeout: Duration::from_secs(30),
78247825
block_proposal_timeout: Duration::from_secs(100),
78257826
tenure_idle_timeout: Duration::from_secs(300),
7827+
tenure_idle_timeout_buffer: Duration::from_secs(2),
78267828
reorg_attempts_activity_timeout: Duration::from_secs(30),
78277829
};
78287830
let mut block = NakamotoBlock {
@@ -8101,6 +8103,7 @@ fn block_validation_pending_table() {
81018103
block_proposal_timeout: Duration::from_secs(100),
81028104
tenure_last_block_proposal_timeout: Duration::from_secs(30),
81038105
tenure_idle_timeout: Duration::from_secs(300),
8106+
tenure_idle_timeout_buffer: Duration::from_secs(2),
81048107
reorg_attempts_activity_timeout: Duration::from_secs(30),
81058108
};
81068109
let mut block = NakamotoBlock {
@@ -10791,6 +10794,7 @@ fn incoming_signers_ignore_block_proposals() {
1079110794
block_proposal_timeout: Duration::from_secs(100),
1079210795
tenure_last_block_proposal_timeout: Duration::from_secs(30),
1079310796
tenure_idle_timeout: Duration::from_secs(300),
10797+
tenure_idle_timeout_buffer: Duration::from_secs(2),
1079410798
reorg_attempts_activity_timeout: Duration::from_secs(30),
1079510799
};
1079610800
let mut block = NakamotoBlock {
@@ -10967,6 +10971,7 @@ fn outgoing_signers_ignore_block_proposals() {
1096710971
block_proposal_timeout: Duration::from_secs(100),
1096810972
tenure_last_block_proposal_timeout: Duration::from_secs(30),
1096910973
tenure_idle_timeout: Duration::from_secs(300),
10974+
tenure_idle_timeout_buffer: Duration::from_secs(2),
1097010975
reorg_attempts_activity_timeout: Duration::from_secs(30),
1097110976
};
1097210977
let mut block = NakamotoBlock {
@@ -13521,6 +13526,87 @@ fn interrupt_miner_on_new_stacks_tip() {
1352113526
signer_test.shutdown();
1352213527
}
1352313528

13529+
#[test]
13530+
#[ignore]
13531+
/// This test verifies that a miner will produce a TenureExtend transaction
13532+
/// after the signers' idle timeout, plus buffer, is reached.
13533+
fn tenure_extend_after_idle_signers_with_buffer() {
13534+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
13535+
return;
13536+
}
13537+
13538+
tracing_subscriber::registry()
13539+
.with(fmt::layer())
13540+
.with(EnvFilter::from_default_env())
13541+
.init();
13542+
13543+
info!("------------------------- Test Setup -------------------------");
13544+
let num_signers = 5;
13545+
let idle_timeout = Duration::from_secs(1);
13546+
let buffer = Duration::from_secs(20);
13547+
let mut signer_test: SignerTest<SpawnedSigner> = SignerTest::new_with_config_modifications(
13548+
num_signers,
13549+
vec![],
13550+
|config| {
13551+
config.tenure_idle_timeout = idle_timeout;
13552+
config.tenure_idle_timeout_buffer = buffer;
13553+
},
13554+
|config| {
13555+
config.miner.tenure_extend_cost_threshold = 0;
13556+
},
13557+
None,
13558+
None,
13559+
);
13560+
13561+
signer_test.boot_to_epoch_3();
13562+
13563+
info!("---- Nakamoto booted, starting test ----");
13564+
// Get the unix timestamp before the block is mined
13565+
let before_timestamp = get_epoch_time_secs();
13566+
signer_test.mine_nakamoto_block(Duration::from_secs(30), true);
13567+
13568+
// Check the tenure extend timestamps to verify that they have factored in the buffer
13569+
let blocks = test_observer::get_mined_nakamoto_blocks();
13570+
let last_block = blocks.last().expect("No blocks mined");
13571+
let signatures: HashSet<_> = test_observer::get_stackerdb_chunks()
13572+
.into_iter()
13573+
.flat_map(|chunk| chunk.modified_slots)
13574+
.filter_map(|chunk| {
13575+
let message = SignerMessage::consensus_deserialize(&mut chunk.data.as_slice())
13576+
.expect("Failed to deserialize SignerMessage");
13577+
13578+
match message {
13579+
SignerMessage::BlockResponse(BlockResponse::Accepted(accepted))
13580+
if accepted.signer_signature_hash == last_block.signer_signature_hash =>
13581+
{
13582+
Some(accepted.response_data.tenure_extend_timestamp)
13583+
}
13584+
_ => None,
13585+
}
13586+
})
13587+
.collect();
13588+
for timestamp in signatures {
13589+
assert!(
13590+
timestamp >= before_timestamp + buffer.as_secs(),
13591+
"Timestamp {} is not greater than or equal to {}",
13592+
timestamp,
13593+
before_timestamp + buffer.as_secs()
13594+
);
13595+
}
13596+
13597+
info!("---- Waiting for a tenure extend ----");
13598+
13599+
// Now, wait for a block with a tenure extend, to make sure it eventually does extend
13600+
wait_for((idle_timeout + buffer * 2).as_secs(), || {
13601+
Ok(last_block_contains_tenure_change_tx(
13602+
TenureChangeCause::Extended,
13603+
))
13604+
})
13605+
.expect("Timed out waiting for a block with a tenure extend");
13606+
13607+
signer_test.shutdown();
13608+
}
13609+
1352413610
/// Test a scenario where a previous miner can extend a tenure when it is favoured by signers over the incoming miner.
1352513611
/// Two miners boot to Nakamoto.
1352613612
/// Miner 1 wins the first tenure.

0 commit comments

Comments
 (0)