Skip to content

Commit ab6459b

Browse files
authored
Merge pull request #6319 from jferrant/feat/signer-two-phase-commit-impl
Feat/signer two phase commit impl
2 parents 36f0c2d + 08e3aab commit ab6459b

File tree

11 files changed

+903
-316
lines changed

11 files changed

+903
-316
lines changed

clarity/src/vm/tests/representations.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ fn prop_clarity_name_valid_patterns() {
7474
prop_assume!(name.len() <= MAX_STRING_LEN as usize);
7575

7676
let clarity_name = ClarityName::try_from(name.clone())
77-
.unwrap_or_else(|_| panic!("Should parse valid clarity name: {}", name));
77+
.unwrap_or_else(|_| panic!("Should parse valid clarity name: {name}"));
7878
prop_assert_eq!(clarity_name.as_str(), name);
7979
});
8080
}
@@ -224,7 +224,7 @@ fn prop_contract_name_valid_patterns() {
224224
prop_assume!(name.len() <= MAX_STRING_LEN as usize);
225225

226226
let contract_name = ContractName::try_from(name.clone())
227-
.unwrap_or_else(|_| panic!("Should parse valid contract name: {}", name));
227+
.unwrap_or_else(|_| panic!("Should parse valid contract name: {name}"));
228228
prop_assert_eq!(contract_name.as_str(), name);
229229
});
230230
}

libsigner/src/v0/messages.rs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ MessageSlotID {
7171
/// Block Response message from signers
7272
BlockResponse = 1,
7373
/// Signer State Machine Update
74-
StateMachineUpdate = 2
74+
StateMachineUpdate = 2,
75+
/// Block Pre-commit message from signers before they commit to a block response
76+
BlockPreCommit = 3
7577
});
7678

7779
define_u8_enum!(
@@ -114,7 +116,9 @@ SignerMessageTypePrefix {
114116
/// Mock block message from Epoch 2.5 miners
115117
MockBlock = 5,
116118
/// State machine update
117-
StateMachineUpdate = 6
119+
StateMachineUpdate = 6,
120+
/// Block Pre-commit message
121+
BlockPreCommit = 7
118122
});
119123

120124
#[cfg_attr(test, mutants::skip)]
@@ -137,7 +141,7 @@ impl MessageSlotID {
137141
#[cfg_attr(test, mutants::skip)]
138142
impl Display for MessageSlotID {
139143
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140-
write!(f, "{:?}({})", self, self.to_u8())
144+
write!(f, "{self:?}({})", self.to_u8())
141145
}
142146
}
143147

@@ -161,6 +165,7 @@ impl From<&SignerMessage> for SignerMessageTypePrefix {
161165
SignerMessage::MockSignature(_) => SignerMessageTypePrefix::MockSignature,
162166
SignerMessage::MockBlock(_) => SignerMessageTypePrefix::MockBlock,
163167
SignerMessage::StateMachineUpdate(_) => SignerMessageTypePrefix::StateMachineUpdate,
168+
SignerMessage::BlockPreCommit(_) => SignerMessageTypePrefix::BlockPreCommit,
164169
}
165170
}
166171
}
@@ -182,6 +187,8 @@ pub enum SignerMessage {
182187
MockBlock(MockBlock),
183188
/// A state machine update
184189
StateMachineUpdate(StateMachineUpdate),
190+
/// The pre-commit message from signers for other signers to observe
191+
BlockPreCommit(Sha512Trunc256Sum),
185192
}
186193

187194
impl SignerMessage {
@@ -197,6 +204,7 @@ impl SignerMessage {
197204
| Self::MockBlock(_) => None,
198205
Self::BlockResponse(_) | Self::MockSignature(_) => Some(MessageSlotID::BlockResponse), // Mock signature uses the same slot as block response since its exclusively for epoch 2.5 testing
199206
Self::StateMachineUpdate(_) => Some(MessageSlotID::StateMachineUpdate),
207+
Self::BlockPreCommit(_) => Some(MessageSlotID::BlockPreCommit),
200208
}
201209
}
202210
}
@@ -216,6 +224,9 @@ impl StacksMessageCodec for SignerMessage {
216224
SignerMessage::StateMachineUpdate(state_machine_update) => {
217225
state_machine_update.consensus_serialize(fd)
218226
}
227+
SignerMessage::BlockPreCommit(block_pre_commit) => {
228+
block_pre_commit.consensus_serialize(fd)
229+
}
219230
}?;
220231
Ok(())
221232
}
@@ -253,6 +264,10 @@ impl StacksMessageCodec for SignerMessage {
253264
let state_machine_update = StacksMessageCodec::consensus_deserialize(fd)?;
254265
SignerMessage::StateMachineUpdate(state_machine_update)
255266
}
267+
SignerMessageTypePrefix::BlockPreCommit => {
268+
let signer_signature_hash = StacksMessageCodec::consensus_deserialize(fd)?;
269+
SignerMessage::BlockPreCommit(signer_signature_hash)
270+
}
256271
};
257272
Ok(message)
258273
}
@@ -1145,6 +1160,18 @@ pub enum BlockResponse {
11451160
Rejected(BlockRejection),
11461161
}
11471162

1163+
impl From<BlockRejection> for BlockResponse {
1164+
fn from(rejection: BlockRejection) -> Self {
1165+
BlockResponse::Rejected(rejection)
1166+
}
1167+
}
1168+
1169+
impl From<BlockAccepted> for BlockResponse {
1170+
fn from(accepted: BlockAccepted) -> Self {
1171+
BlockResponse::Accepted(accepted)
1172+
}
1173+
}
1174+
11481175
#[cfg_attr(test, mutants::skip)]
11491176
impl std::fmt::Display for BlockResponse {
11501177
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -2604,4 +2631,14 @@ mod test {
26042631

26052632
assert_eq!(signer_message, signer_message_deserialized);
26062633
}
2634+
2635+
#[test]
2636+
fn serde_block_signer_message_pre_commit() {
2637+
let pre_commit = SignerMessage::BlockPreCommit(Sha512Trunc256Sum([0u8; 32]));
2638+
let serialized_pre_commit = pre_commit.serialize_to_vec();
2639+
let deserialized_pre_commit =
2640+
read_next::<SignerMessage, _>(&mut &serialized_pre_commit[..])
2641+
.expect("Failed to deserialize pre-commit");
2642+
assert_eq!(pre_commit, deserialized_pre_commit);
2643+
}
26072644
}

stacks-node/src/nakamoto_node/stackerdb_listener.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,9 @@ impl StackerDBListener {
503503
SignerMessageV0::StateMachineUpdate(update) => {
504504
self.update_global_state_evaluator(&signer_pubkey, update);
505505
}
506+
SignerMessageV0::BlockPreCommit(_) => {
507+
debug!("Received block pre-commit message. Ignoring.");
508+
}
506509
};
507510
}
508511
}

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

Lines changed: 90 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mod commands;
1717
pub mod multiversion;
1818
pub mod v0;
1919

20-
use std::collections::HashSet;
20+
use std::collections::{HashMap, HashSet};
2121
use std::fs::File;
2222
use std::path::PathBuf;
2323
use std::sync::atomic::{AtomicBool, Ordering};
@@ -72,6 +72,7 @@ use super::neon_integrations::{
7272
copy_dir_all, get_account, get_sortition_info_ch, submit_tx_fallible, Account,
7373
};
7474
use crate::burnchains::bitcoin::core_controller::BitcoinCoreController;
75+
use crate::nakamoto_node::miner::TEST_MINE_SKIP;
7576
use crate::neon::Counters;
7677
use crate::run_loop::boot_nakamoto;
7778
use crate::tests::nakamoto_integrations::{
@@ -81,6 +82,7 @@ use crate::tests::neon_integrations::{
8182
get_chain_info, next_block_and_wait, run_until_burnchain_height, test_observer,
8283
wait_for_runloop,
8384
};
85+
use crate::tests::signer::v0::wait_for_state_machine_update_by_miner_tenure_id;
8486
use crate::tests::to_addr;
8587
use crate::BitcoinRegtestController;
8688

@@ -544,11 +546,16 @@ impl<Z: SpawnedSignerTrait> SignerTest<Z> {
544546
}
545547

546548
pub fn mine_bitcoin_block(&self) {
549+
let mined_btc_block_time = Instant::now();
547550
let info = self.get_peer_info();
548551
next_block_and(&self.running_nodes.btc_regtest_controller, 60, || {
549552
Ok(get_chain_info(&self.running_nodes.conf).burn_block_height > info.burn_block_height)
550553
})
551554
.unwrap();
555+
info!(
556+
"Bitcoin block mine time elapsed: {:?}",
557+
mined_btc_block_time.elapsed()
558+
);
552559
}
553560

554561
/// Fetch the local signer state machine for all the signers,
@@ -1121,26 +1128,63 @@ impl<Z: SpawnedSignerTrait> SignerTest<Z> {
11211128
output
11221129
}
11231130

1124-
/// Mine a BTC block and wait for a new Stacks block to be mined
1131+
/// Mine a BTC block and wait for a new Stacks block to be mined, but do not wait for a commit
11251132
/// Note: do not use nakamoto blocks mined heuristic if running a test with multiple miners
1126-
fn mine_nakamoto_block(&self, timeout: Duration, use_nakamoto_blocks_mined: bool) {
1127-
let mined_block_time = Instant::now();
1128-
let mined_before = self.running_nodes.counters.naka_mined_blocks.get();
1129-
let info_before = self.get_peer_info();
1130-
1131-
next_block_and(
1132-
&self.running_nodes.btc_regtest_controller,
1133+
fn mine_nakamoto_block_without_commit(
1134+
&self,
1135+
timeout: Duration,
1136+
use_nakamoto_blocks_mined: bool,
1137+
) {
1138+
let info_before = get_chain_info(&self.running_nodes.conf);
1139+
info!("Pausing stacks block mining");
1140+
TEST_MINE_SKIP.set(true);
1141+
let mined_blocks = self.running_nodes.counters.naka_mined_blocks.clone();
1142+
let mined_before = mined_blocks.get();
1143+
self.mine_bitcoin_block();
1144+
wait_for_state_machine_update_by_miner_tenure_id(
11331145
timeout.as_secs(),
1134-
|| {
1135-
let info_after = self.get_peer_info();
1136-
let blocks_mined = self.running_nodes.counters.naka_mined_blocks.get();
1137-
Ok(info_after.stacks_tip_height > info_before.stacks_tip_height
1138-
&& (!use_nakamoto_blocks_mined || blocks_mined > mined_before))
1139-
},
1146+
&get_chain_info(&self.running_nodes.conf).pox_consensus,
1147+
&self.signer_addresses_versions_majority(),
11401148
)
1141-
.unwrap();
1142-
let mined_block_elapsed_time = mined_block_time.elapsed();
1143-
info!("Nakamoto block mine time elapsed: {mined_block_elapsed_time:?}");
1149+
.expect("Failed to update signer state machine");
1150+
1151+
info!("Unpausing stacks block mining");
1152+
let mined_block_time = Instant::now();
1153+
TEST_MINE_SKIP.set(false);
1154+
// Do these wait for's in two steps not only for increased timeout but for easier debugging.
1155+
// Ensure that the tenure change transaction is mined
1156+
wait_for(timeout.as_secs(), || {
1157+
Ok(get_chain_info(&self.running_nodes.conf).stacks_tip_height
1158+
> info_before.stacks_tip_height
1159+
&& (!use_nakamoto_blocks_mined || mined_blocks.get() > mined_before))
1160+
})
1161+
.expect("Failed to mine Tenure Change block");
1162+
info!(
1163+
"Nakamoto block mine time elapsed: {:?}",
1164+
mined_block_time.elapsed()
1165+
);
1166+
}
1167+
1168+
/// Mine a BTC block and wait for a new Stacks block to be mined and commit to be submitted
1169+
/// Note: do not use nakamoto blocks mined heuristic if running a test with multiple miners
1170+
fn mine_nakamoto_block(&self, timeout: Duration, use_nakamoto_blocks_mined: bool) {
1171+
let Counters {
1172+
naka_submitted_commits: commits_submitted,
1173+
naka_submitted_commit_last_burn_height: commits_last_burn_height,
1174+
naka_submitted_commit_last_stacks_tip: commits_last_stacks_tip,
1175+
..
1176+
} = self.running_nodes.counters.clone();
1177+
let commits_before = commits_submitted.get();
1178+
let commit_burn_height_before = commits_last_burn_height.get();
1179+
self.mine_nakamoto_block_without_commit(timeout, use_nakamoto_blocks_mined);
1180+
// Ensure the subsequent block commit confirms the previous Tenure Change block
1181+
let stacks_tip_height = get_chain_info(&self.running_nodes.conf).stacks_tip_height;
1182+
wait_for(timeout.as_secs(), || {
1183+
Ok(commits_submitted.get() > commits_before
1184+
&& commits_last_burn_height.get() > commit_burn_height_before
1185+
&& commits_last_stacks_tip.get() >= stacks_tip_height)
1186+
})
1187+
.expect("Failed to update Block Commit");
11441188
}
11451189

11461190
fn mine_block_wait_on_processing(
@@ -1366,7 +1410,7 @@ impl<Z: SpawnedSignerTrait> SignerTest<Z> {
13661410
.collect()
13671411
}
13681412

1369-
/// Get the signer addresses and corresponding versions
1413+
/// Get the signer addresses and corresponding versions configured versions
13701414
pub fn signer_addresses_versions(&self) -> Vec<(StacksAddress, u64)> {
13711415
self.signer_stacks_private_keys
13721416
.iter()
@@ -1380,6 +1424,33 @@ impl<Z: SpawnedSignerTrait> SignerTest<Z> {
13801424
.collect()
13811425
}
13821426

1427+
/// Get the signer addresses and corresponding majority versions
1428+
pub fn signer_addresses_versions_majority(&self) -> Vec<(StacksAddress, u64)> {
1429+
let mut signer_address_versions = self.signer_addresses_versions();
1430+
let majority = (signer_address_versions.len() * 7 / 10) as u64;
1431+
let mut protocol_versions = HashMap::new();
1432+
for (_, version) in &self.signer_addresses_versions() {
1433+
let entry = protocol_versions.entry(*version).or_insert_with(|| 0);
1434+
*entry += 1;
1435+
}
1436+
1437+
// find the highest version number supported by a threshold number of signers
1438+
let mut protocol_versions: Vec<_> = protocol_versions.into_iter().collect();
1439+
protocol_versions.sort_by_key(|(version, _)| *version);
1440+
let mut total_weight_support = 0;
1441+
for (version, weight_support) in protocol_versions.into_iter().rev() {
1442+
total_weight_support += weight_support;
1443+
if total_weight_support > majority {
1444+
// We need to actually overwrite the versions passed in since the signers will go with the majority value if they can
1445+
signer_address_versions
1446+
.iter_mut()
1447+
.for_each(|(_, v)| *v = version);
1448+
break;
1449+
}
1450+
}
1451+
signer_address_versions
1452+
}
1453+
13831454
/// Get the signer public keys for the given reward cycle
13841455
fn get_signer_public_keys(&self, reward_cycle: u64) -> Vec<StacksPublicKey> {
13851456
let entries = self.get_reward_set_signers(reward_cycle);

0 commit comments

Comments
 (0)