Skip to content

Commit e797c04

Browse files
authored
Merge pull request #4569 from stacks-network/feat/signers-get-round-info
Feat/signers get round info and use it to determine if a vote has failed
2 parents 132c942 + b3f503d commit e797c04

File tree

3 files changed

+162
-40
lines changed

3 files changed

+162
-40
lines changed

stacks-signer/src/client/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,4 +518,28 @@ pub(crate) mod tests {
518518
db_path: config.db_path.clone(),
519519
}
520520
}
521+
522+
pub fn build_get_round_info_response(info: Option<(u64, u64)>) -> String {
523+
let clarity_value = if let Some((vote_count, vote_weight)) = info {
524+
ClarityValue::some(ClarityValue::Tuple(
525+
TupleData::from_data(vec![
526+
("votes-count".into(), ClarityValue::UInt(vote_count as u128)),
527+
(
528+
"votes-weight".into(),
529+
ClarityValue::UInt(vote_weight as u128),
530+
),
531+
])
532+
.expect("BUG: Failed to create clarity value from tuple data"),
533+
))
534+
.expect("BUG: Failed to create clarity value from tuple data")
535+
} else {
536+
ClarityValue::none()
537+
};
538+
build_read_only_response(&clarity_value)
539+
}
540+
541+
pub fn build_get_weight_threshold_response(threshold: u64) -> String {
542+
let clarity_value = ClarityValue::UInt(threshold as u128);
543+
build_read_only_response(&clarity_value)
544+
}
521545
}

stacks-signer/src/client/stacks_client.rs

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,11 +258,11 @@ impl StacksClient {
258258
reward_cycle: u64,
259259
) -> Result<Option<Point>, ClientError> {
260260
let function_name = ClarityName::from("get-approved-aggregate-key");
261-
let pox_contract_id = boot_code_id(SIGNERS_VOTING_NAME, self.mainnet);
261+
let voting_contract_id = boot_code_id(SIGNERS_VOTING_NAME, self.mainnet);
262262
let function_args = &[ClarityValue::UInt(reward_cycle as u128)];
263263
let value = self.read_only_contract_call(
264-
&pox_contract_id.issuer.into(),
265-
&pox_contract_id.name,
264+
&voting_contract_id.issuer.into(),
265+
&voting_contract_id.name,
266266
&function_name,
267267
function_args,
268268
)?;
@@ -273,6 +273,47 @@ impl StacksClient {
273273
)
274274
}
275275

276+
/// Retrieve the current consumed weight for the given reward cycle and DKG round
277+
pub fn get_round_vote_weight(
278+
&self,
279+
reward_cycle: u64,
280+
round_id: u64,
281+
) -> Result<Option<u128>, ClientError> {
282+
let function_name = ClarityName::from("get-round-info");
283+
let pox_contract_id = boot_code_id(SIGNERS_VOTING_NAME, self.mainnet);
284+
let function_args = &[
285+
ClarityValue::UInt(reward_cycle as u128),
286+
ClarityValue::UInt(round_id as u128),
287+
];
288+
let value = self.read_only_contract_call(
289+
&pox_contract_id.issuer.into(),
290+
&pox_contract_id.name,
291+
&function_name,
292+
function_args,
293+
)?;
294+
let inner_data = value.expect_optional()?;
295+
let Some(inner_data) = inner_data else {
296+
return Ok(None);
297+
};
298+
let round_info = inner_data.expect_tuple()?;
299+
let votes_weight = round_info.get("votes-weight")?.to_owned().expect_u128()?;
300+
Ok(Some(votes_weight))
301+
}
302+
303+
/// Retrieve the weight threshold required to approve a DKG vote
304+
pub fn get_vote_threshold_weight(&self, reward_cycle: u64) -> Result<u128, ClientError> {
305+
let function_name = ClarityName::from("get-threshold-weight");
306+
let pox_contract_id = boot_code_id(SIGNERS_VOTING_NAME, self.mainnet);
307+
let function_args = &[ClarityValue::UInt(reward_cycle as u128)];
308+
let value = self.read_only_contract_call(
309+
&pox_contract_id.issuer.into(),
310+
&pox_contract_id.name,
311+
&function_name,
312+
function_args,
313+
)?;
314+
Ok(value.expect_u128()?)
315+
}
316+
276317
/// Retrieve the current account nonce for the provided address
277318
pub fn get_account_nonce(&self, address: &StacksAddress) -> Result<u64, ClientError> {
278319
let account_entry = self.get_account_entry_with_retry(address)?;
@@ -643,7 +684,8 @@ mod tests {
643684
use crate::client::tests::{
644685
build_account_nonce_response, build_get_approved_aggregate_key_response,
645686
build_get_last_round_response, build_get_peer_info_response, build_get_pox_data_response,
646-
build_get_vote_for_aggregate_key_response, build_read_only_response, write_response,
687+
build_get_round_info_response, build_get_vote_for_aggregate_key_response,
688+
build_get_weight_threshold_response, build_read_only_response, write_response,
647689
MockServerClient,
648690
};
649691

@@ -1193,4 +1235,31 @@ mod tests {
11931235
write_response(mock.server, key_response.as_bytes());
11941236
assert_eq!(h.join().unwrap().unwrap(), None);
11951237
}
1238+
1239+
#[test]
1240+
fn get_round_vote_weight_should_succeed() {
1241+
let mock = MockServerClient::new();
1242+
let vote_count = rand::thread_rng().next_u64();
1243+
let weight = rand::thread_rng().next_u64();
1244+
let round_response = build_get_round_info_response(Some((vote_count, weight)));
1245+
let h = spawn(move || mock.client.get_round_vote_weight(0, 0));
1246+
write_response(mock.server, round_response.as_bytes());
1247+
assert_eq!(h.join().unwrap().unwrap(), Some(weight as u128));
1248+
1249+
let mock = MockServerClient::new();
1250+
let round_response = build_get_round_info_response(None);
1251+
let h = spawn(move || mock.client.get_round_vote_weight(0, 0));
1252+
write_response(mock.server, round_response.as_bytes());
1253+
assert_eq!(h.join().unwrap().unwrap(), None);
1254+
}
1255+
1256+
#[test]
1257+
fn get_vote_threshold_weight_should_succeed() {
1258+
let mock = MockServerClient::new();
1259+
let weight = rand::thread_rng().next_u64();
1260+
let round_response = build_get_weight_threshold_response(weight);
1261+
let h = spawn(move || mock.client.get_vote_threshold_weight(0));
1262+
write_response(mock.server, round_response.as_bytes());
1263+
assert_eq!(h.join().unwrap().unwrap(), weight as u128);
1264+
}
11961265
}

stacks-signer/src/signer.rs

Lines changed: 65 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,49 +1259,78 @@ impl Signer {
12591259
}
12601260
return Ok(());
12611261
};
1262-
let coordinator_id = self.get_coordinator(current_reward_cycle).0;
1263-
if Some(self.signer_id) == coordinator_id && self.state == State::Idle {
1264-
debug!("{self}: Checking if old vote transaction exists in StackerDB...");
1265-
// Have I already voted and have a pending transaction? Check stackerdb for the same round number and reward cycle vote transaction
1266-
// Only get the account nonce of THIS signer as we only care about our own votes, not other signer votes
1267-
let signer_address = stacks_client.get_signer_address();
1268-
let account_nonces = self.get_account_nonces(stacks_client, &[*signer_address]);
1269-
let old_transactions = self.get_signer_transactions(&account_nonces).map_err(|e| {
1262+
if self.state != State::Idle
1263+
|| Some(self.signer_id) != self.get_coordinator(current_reward_cycle).0
1264+
{
1265+
// We are not the coordinator or we are in the middle of an operation. Do not attempt to queue DKG
1266+
return Ok(());
1267+
}
1268+
debug!("{self}: Checking if old DKG vote transaction exists in StackerDB...");
1269+
// Have I already voted, but the vote is still pending in StackerDB? Check stackerdb for the same round number and reward cycle vote transaction
1270+
// Only get the account nonce of THIS signer as we only care about our own votes, not other signer votes
1271+
let signer_address = stacks_client.get_signer_address();
1272+
let account_nonces = self.get_account_nonces(stacks_client, &[*signer_address]);
1273+
let old_transactions = self.get_signer_transactions(&account_nonces).map_err(|e| {
12701274
warn!("{self}: Failed to get old signer transactions: {e:?}. May trigger DKG unnecessarily");
12711275
}).unwrap_or_default();
1272-
// Check if we have an existing vote transaction for the same round and reward cycle
1273-
for transaction in old_transactions.iter() {
1274-
let params =
1276+
// Check if we have an existing vote transaction for the same round and reward cycle
1277+
for transaction in old_transactions.iter() {
1278+
let params =
12751279
NakamotoSigners::parse_vote_for_aggregate_public_key(transaction).unwrap_or_else(|| panic!("BUG: {self}: Received an invalid {SIGNERS_VOTING_FUNCTION_NAME} transaction in an already filtered list: {transaction:?}"));
1276-
if Some(params.aggregate_key) == self.coordinator.aggregate_public_key
1277-
&& params.voting_round == self.coordinator.current_dkg_id
1278-
&& reward_cycle == self.reward_cycle
1279-
{
1280-
debug!("{self}: Not triggering a DKG round. Already have a pending vote transaction.";
1281-
"txid" => %transaction.txid(),
1282-
"aggregate_key" => %params.aggregate_key,
1283-
"voting_round" => params.voting_round
1284-
);
1285-
return Ok(());
1286-
}
1287-
}
1288-
if stacks_client
1289-
.get_vote_for_aggregate_public_key(
1290-
self.coordinator.current_dkg_id,
1291-
self.reward_cycle,
1292-
*stacks_client.get_signer_address(),
1293-
)?
1294-
.is_some()
1280+
if Some(params.aggregate_key) == self.coordinator.aggregate_public_key
1281+
&& params.voting_round == self.coordinator.current_dkg_id
1282+
&& reward_cycle == self.reward_cycle
12951283
{
1296-
// TODO Check if the vote failed and we need to retrigger the DKG round not just if we have already voted...
1297-
// TODO need logic to trigger another DKG round if a certain amount of time passes and we still have no confirmed DKG vote
1298-
debug!("{self}: Not triggering a DKG round. Already voted and we may need to wait for more votes to arrive.");
1284+
debug!("{self}: Not triggering a DKG round. Already have a pending vote transaction.";
1285+
"txid" => %transaction.txid(),
1286+
"aggregate_key" => %params.aggregate_key,
1287+
"voting_round" => params.voting_round
1288+
);
12991289
return Ok(());
13001290
}
1301-
if self.commands.front() != Some(&Command::Dkg) {
1302-
info!("{self} is the current coordinator and must trigger DKG. Queuing DKG command...");
1303-
self.commands.push_front(Command::Dkg);
1291+
}
1292+
if let Some(aggregate_key) = stacks_client.get_vote_for_aggregate_public_key(
1293+
self.coordinator.current_dkg_id,
1294+
self.reward_cycle,
1295+
*stacks_client.get_signer_address(),
1296+
)? {
1297+
let Some(round_weight) = stacks_client
1298+
.get_round_vote_weight(self.reward_cycle, self.coordinator.current_dkg_id)?
1299+
else {
1300+
// This only will happen if somehow we registered as a signer and were granted no weight which should not really ever happen.
1301+
error!("{self}: already voted for DKG, but no round vote weight found. We either have no voting power or the contract is corrupted.";
1302+
"voting_round" => self.coordinator.current_dkg_id,
1303+
"aggregate_key" => %aggregate_key
1304+
);
1305+
return Ok(());
1306+
};
1307+
let threshold_weight = stacks_client.get_vote_threshold_weight(self.reward_cycle)?;
1308+
if round_weight < threshold_weight {
1309+
// The threshold weight has not been met yet. We should wait for more votes to arrive.
1310+
// TODO: this should be on a timeout of some kind. We should not wait forever for the threshold to be met.
1311+
// See https://github.com/stacks-network/stacks-core/issues/4568
1312+
debug!("{self}: Not triggering a DKG round. Weight threshold has not been met yet. Waiting for more votes to arrive.";
1313+
"voting_round" => self.coordinator.current_dkg_id,
1314+
"aggregate_key" => %aggregate_key,
1315+
"round_weight" => round_weight,
1316+
"threshold_weight" => threshold_weight
1317+
);
1318+
return Ok(());
13041319
}
1320+
debug!("{self}: Vote for DKG failed. Triggering a DKG round.";
1321+
"voting_round" => self.coordinator.current_dkg_id,
1322+
"aggregate_key" => %aggregate_key,
1323+
"round_weight" => round_weight,
1324+
"threshold_weight" => threshold_weight
1325+
);
1326+
} else {
1327+
debug!("{self}: Triggering a DKG round.");
1328+
}
1329+
if self.commands.front() != Some(&Command::Dkg) {
1330+
info!("{self} is the current coordinator and must trigger DKG. Queuing DKG command...");
1331+
self.commands.push_front(Command::Dkg);
1332+
} else {
1333+
debug!("{self}: DKG command already queued...");
13051334
}
13061335
Ok(())
13071336
}

0 commit comments

Comments
 (0)