Skip to content

Commit 61e4f1c

Browse files
authored
Merge pull request #6341 from jferrant/fix/use-local-protocol-version
Use the local supported version by default if no consensus is found
2 parents c03c4f7 + dd590f8 commit 61e4f1c

File tree

4 files changed

+206
-2
lines changed

4 files changed

+206
-2
lines changed

stacks-node/src/tests/signer/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1424,6 +1424,35 @@ impl<Z: SpawnedSignerTrait> SignerTest<Z> {
14241424
}
14251425
}
14261426

1427+
/// Kills the signer runloop at index `signer_idx`
1428+
/// and returns GlobalConfig of the killed signer
1429+
///
1430+
/// # Panics
1431+
/// Panics if `signer_idx` is out of bounds
1432+
fn stop_signer(&mut self, signer_idx: usize) -> stacks_signer::config::GlobalConfig {
1433+
let running_signer = self.spawned_signers.remove(signer_idx);
1434+
let _signer_key = self.signer_stacks_private_keys.remove(signer_idx);
1435+
let signer_config = self.signer_configs.remove(signer_idx);
1436+
running_signer.stop();
1437+
signer_config
1438+
}
1439+
1440+
/// (Re)starts a new signer runloop with the given private key and adds it to the list
1441+
/// of running signers, updating the list of signer_stacks_private_keys and signer_configs
1442+
fn restart_signer(
1443+
&mut self,
1444+
signer_idx: usize,
1445+
signer_config: stacks_signer::config::GlobalConfig,
1446+
) {
1447+
info!("Restarting signer");
1448+
self.signer_stacks_private_keys
1449+
.insert(signer_idx, signer_config.stacks_private_key.clone());
1450+
self.signer_configs
1451+
.insert(signer_idx, signer_config.clone());
1452+
self.spawned_signers
1453+
.insert(signer_idx, Z::new(signer_config));
1454+
}
1455+
14271456
/// Get the latest block response from the given slot
14281457
pub fn get_latest_block_response(&self, slot_id: u32) -> BlockResponse {
14291458
let mut stackerdb = StackerDB::new_normal(

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

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18195,3 +18195,160 @@ fn multiversioned_signer_protocol_version_calculation() {
1819518195
.unwrap();
1819618196
signer_test.shutdown();
1819718197
}
18198+
18199+
/// Ensure that signers can immediately start participating in signing when starting up
18200+
/// after crashing mid reward cycle.
18201+
///
18202+
/// Test scenario:
18203+
/// - Miner A wins Tenure A
18204+
/// - Miner A proposes block N (Tenure Change)
18205+
/// - All signers sign block N.
18206+
/// - Miner B wins Tenure B.
18207+
/// - Shutdown one signer.
18208+
/// - Shutdown signer is restarted.
18209+
/// - Miner B proposes block N+1 (TenureChange).
18210+
/// - All signers sign the block without issue
18211+
/// -> Verifies that updates are loaded from signerdb on init
18212+
/// - Same signer is shutdown.
18213+
/// - Shutdown signers db is cleared.
18214+
/// - Signer is restarted.
18215+
/// - Miner B proposes block N+2 (Transfer).
18216+
/// - All signers including the restarted signer sign block N+2
18217+
/// -> Verifies that updates are loaded from stackerdb on init
18218+
#[test]
18219+
#[ignore]
18220+
fn signer_loads_stackerdb_updates_on_startup() {
18221+
if env::var("BITCOIND_TEST") != Ok("1".into()) {
18222+
return;
18223+
}
18224+
18225+
let num_signers = 5;
18226+
let mut miners = MultipleMinerTest::new(num_signers, 1);
18227+
18228+
let skip_commit_op_rl1 = miners
18229+
.signer_test
18230+
.running_nodes
18231+
.counters
18232+
.naka_skip_commit_op
18233+
.clone();
18234+
let skip_commit_op_rl2 = miners.rl2_counters.naka_skip_commit_op.clone();
18235+
18236+
let (conf_1, _conf_2) = miners.get_node_configs();
18237+
let (miner_pk_1, miner_pk_2) = miners.get_miner_public_keys();
18238+
let (miner_pkh_1, miner_pkh_2) = miners.get_miner_public_key_hashes();
18239+
18240+
let all_signers = miners.signer_test.signer_test_pks();
18241+
18242+
// Pause Miner 2's commits to ensure Miner 1 wins the first sortition.
18243+
skip_commit_op_rl2.set(true);
18244+
miners.boot_to_epoch_3();
18245+
18246+
let sortdb = conf_1.get_burnchain().open_sortition_db(true).unwrap();
18247+
18248+
info!("Pausing miner 1's block commit submissions");
18249+
skip_commit_op_rl1.set(true);
18250+
18251+
info!("------------------------- Miner A Wins Tenure A -------------------------");
18252+
let info_before = get_chain_info(&conf_1);
18253+
// Let's not mine anything until we see consensus on new tenure start.
18254+
TEST_MINE_SKIP.set(true);
18255+
next_block_and(&miners.btc_regtest_controller_mut(), 60, || {
18256+
let info = get_chain_info(&conf_1);
18257+
Ok(info.burn_block_height > info_before.burn_block_height)
18258+
})
18259+
.unwrap();
18260+
let chain_after = get_chain_info(&conf_1);
18261+
wait_for_state_machine_update_by_miner_tenure_id(
18262+
30,
18263+
&chain_after.pox_consensus,
18264+
&miners.signer_test.signer_addresses_versions(),
18265+
)
18266+
.expect("Timed out waiting for the signers to update their state");
18267+
verify_sortition_winner(&sortdb, &miner_pkh_1);
18268+
18269+
info!(
18270+
"------------------------- Miner A Mines Block N (Tenure Change) -------------------------"
18271+
);
18272+
TEST_MINE_SKIP.set(false);
18273+
let block_n =
18274+
wait_for_block_pushed_by_miner_key(30, chain_after.stacks_tip_height + 1, &miner_pk_1)
18275+
.expect("Failed to mine block N");
18276+
wait_for_block_acceptance_from_signers(
18277+
30,
18278+
&block_n.header.signer_signature_hash(),
18279+
&all_signers,
18280+
)
18281+
.expect("Not all signers accepted the block");
18282+
18283+
info!("------------------------- Miner B Wins Tenure B -------------------------");
18284+
miners.submit_commit_miner_2(&sortdb);
18285+
let chain_before = get_chain_info(&conf_1);
18286+
// Let's not mine anything until we see consensus on new tenure start.
18287+
TEST_MINE_SKIP.set(true);
18288+
next_block_and(&miners.btc_regtest_controller_mut(), 60, || {
18289+
let info = get_chain_info(&conf_1);
18290+
Ok(info.burn_block_height > chain_before.burn_block_height)
18291+
})
18292+
.unwrap();
18293+
let chain_after = get_chain_info(&conf_1);
18294+
wait_for_state_machine_update_by_miner_tenure_id(
18295+
30,
18296+
&chain_after.pox_consensus,
18297+
&miners.signer_test.signer_addresses_versions(),
18298+
)
18299+
.expect("Signers failed to update their state");
18300+
verify_sortition_winner(&sortdb, &miner_pkh_2);
18301+
18302+
let stop_idx = 0;
18303+
info!("------------------------- Shutdown Signer at idx {stop_idx} -------------------------");
18304+
let stopped_signer_config = miners.signer_test.stop_signer(stop_idx);
18305+
info!("------------------------- Restart Signer at idx {stop_idx} -------------------------");
18306+
miners
18307+
.signer_test
18308+
.restart_signer(stop_idx, stopped_signer_config);
18309+
18310+
info!("------------------------- Miner B Mines Block N+1 (Tenure Change) -------------------------");
18311+
TEST_MINE_SKIP.set(false);
18312+
let block_n_1 =
18313+
wait_for_block_pushed_by_miner_key(30, chain_after.stacks_tip_height + 1, &miner_pk_2)
18314+
.expect("Failed to mine block N+1");
18315+
wait_for_block_acceptance_from_signers(
18316+
30,
18317+
&block_n_1.header.signer_signature_hash(),
18318+
&all_signers,
18319+
)
18320+
.expect("Not all signers accepted the block");
18321+
18322+
info!("------------------------- Shutdown Signer at idx {stop_idx} -------------------------");
18323+
let stopped_signer_config = miners.signer_test.stop_signer(stop_idx);
18324+
{
18325+
let mut signer_db = SignerDb::new(stopped_signer_config.db_path.clone()).unwrap();
18326+
signer_db
18327+
.clear_state_machine_updates()
18328+
.expect("Failed to clear state machine updates");
18329+
}
18330+
info!("------------------------- Restart Signer at idx {stop_idx} -------------------------");
18331+
miners
18332+
.signer_test
18333+
.restart_signer(stop_idx, stopped_signer_config);
18334+
18335+
// Wait until signer boots up BEFORE proposing the next block
18336+
miners.signer_test.wait_for_registered();
18337+
info!("------------------------- Miner B Mines Block N+2 (Transfer) -------------------------");
18338+
let (accepting, ignoring) = all_signers.split_at(4);
18339+
// Make some of the signers ignore so that we CANNOT advance without approval from the restarted signer (its at index 0)
18340+
TEST_IGNORE_ALL_BLOCK_PROPOSALS.set(ignoring.into());
18341+
miners.send_transfer_tx();
18342+
let block_n_2 =
18343+
wait_for_block_pushed_by_miner_key(30, chain_after.stacks_tip_height + 2, &miner_pk_2)
18344+
.expect("Failed to mine block N+2");
18345+
wait_for_block_acceptance_from_signers(
18346+
30,
18347+
&block_n_2.header.signer_signature_hash(),
18348+
&accepting,
18349+
)
18350+
.expect("Not all signers accepted the block");
18351+
18352+
info!("------------------------- Shutdown -------------------------");
18353+
miners.shutdown();
18354+
}

stacks-signer/src/signerdb.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,15 @@ impl SignerDb {
17051705
Ok(())
17061706
}
17071707

1708+
#[cfg(any(test, feature = "testing"))]
1709+
/// Clear out signer state machine updates for testing purposes ONLY.
1710+
pub fn clear_state_machine_updates(&mut self) -> Result<(), DBError> {
1711+
debug!("Clearing all updates.");
1712+
self.db
1713+
.execute("DELETE FROM signer_state_machine_updates", params![])?;
1714+
Ok(())
1715+
}
1716+
17081717
/// Get the most recent signer states from the signer state machine for the given reward cycle
17091718
pub fn get_signer_state_machine_updates(
17101719
&mut self,

stacks-signer/src/v0/signer.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,17 +715,26 @@ impl Signer {
715715
block: &NakamotoBlock,
716716
) -> Option<BlockResponse> {
717717
// First update our global state evaluator with our local state if we have one
718-
let version = self.get_signer_protocol_version();
718+
let local_version = self.get_signer_protocol_version();
719719
if let Ok(update) = self
720720
.local_state_machine
721-
.try_into_update_message_with_version(version)
721+
.try_into_update_message_with_version(local_version)
722722
{
723723
self.global_state_evaluator
724724
.insert_update(self.stacks_address, update);
725725
};
726726
let Some(latest_version) = self
727727
.global_state_evaluator
728728
.determine_latest_supported_signer_protocol_version()
729+
.or_else(|| {
730+
// Don't default if we are in a global consensus activation state as its pointless
731+
if SortitionStateVersion::from_protocol_version(local_version).uses_global_state() {
732+
None
733+
} else {
734+
warn!("{self}: No consensus on signer protocol version. Defaulting to local state version: {local_version}.");
735+
Some(local_version)
736+
}
737+
})
729738
else {
730739
warn!(
731740
"{self}: No consensus on signer protocol version. Unable to validate block. Rejecting.";

0 commit comments

Comments
 (0)