Skip to content

Commit 3d04553

Browse files
authored
Merge pull request #5198 from stacks-network/bugfix/5195-signers-stackerdb-config
Check that stackerdb is set before configuring the signer
2 parents 4dc2902 + 9b09f0b commit 3d04553

File tree

3 files changed

+71
-15
lines changed

3 files changed

+71
-15
lines changed

stacks-signer/src/client/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,4 +603,9 @@ pub(crate) mod tests {
603603
serde_json::to_string(header_types).expect("Failed to serialize tenure tip info");
604604
format!("HTTP/1.1 200 OK\n\n{response_json}")
605605
}
606+
607+
pub fn build_get_last_set_cycle_response(cycle: u64) -> String {
608+
let clarity_value = ClarityValue::okay(ClarityValue::UInt(cycle as u128)).unwrap();
609+
build_read_only_response(&clarity_value)
610+
}
606611
}

stacks-signer/src/client/stacks_client.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use std::net::SocketAddr;
1919
use blockstack_lib::burnchains::Txid;
2020
use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
2121
use blockstack_lib::chainstate::stacks::boot::{
22-
NakamotoSignerEntry, SIGNERS_VOTING_FUNCTION_NAME, SIGNERS_VOTING_NAME,
22+
NakamotoSignerEntry, SIGNERS_NAME, SIGNERS_VOTING_FUNCTION_NAME, SIGNERS_VOTING_NAME,
2323
};
2424
use blockstack_lib::chainstate::stacks::db::StacksBlockHeaderTypes;
2525
use blockstack_lib::chainstate::stacks::{
@@ -162,6 +162,20 @@ impl StacksClient {
162162
Ok(sortition_info)
163163
}
164164

165+
/// Get the last set reward cycle stored within the stackerdb contract
166+
pub fn get_last_set_cycle(&self) -> Result<u128, ClientError> {
167+
let signer_stackerdb_contract_id = boot_code_id(SIGNERS_NAME, self.mainnet);
168+
let function_name_str = "get-last-set-cycle";
169+
let function_name = ClarityName::from(function_name_str);
170+
let value = self.read_only_contract_call(
171+
&signer_stackerdb_contract_id.issuer.clone().into(),
172+
&signer_stackerdb_contract_id.name,
173+
&function_name,
174+
&[],
175+
)?;
176+
Ok(value.expect_result_ok()?.expect_u128()?)
177+
}
178+
165179
/// Retrieve the signer slots stored within the stackerdb contract
166180
pub fn get_stackerdb_signer_slots(
167181
&self,
@@ -962,11 +976,11 @@ mod tests {
962976
use super::*;
963977
use crate::client::tests::{
964978
build_account_nonce_response, build_get_approved_aggregate_key_response,
965-
build_get_last_round_response, build_get_medium_estimated_fee_ustx_response,
966-
build_get_peer_info_response, build_get_pox_data_response, build_get_round_info_response,
967-
build_get_tenure_tip_response, build_get_vote_for_aggregate_key_response,
968-
build_get_weight_threshold_response, build_read_only_response, write_response,
969-
MockServerClient,
979+
build_get_last_round_response, build_get_last_set_cycle_response,
980+
build_get_medium_estimated_fee_ustx_response, build_get_peer_info_response,
981+
build_get_pox_data_response, build_get_round_info_response, build_get_tenure_tip_response,
982+
build_get_vote_for_aggregate_key_response, build_get_weight_threshold_response,
983+
build_read_only_response, write_response, MockServerClient,
970984
};
971985

972986
#[test]
@@ -1623,4 +1637,14 @@ mod tests {
16231637
write_response(mock.server, response.as_bytes());
16241638
assert_eq!(h.join().unwrap().unwrap(), header);
16251639
}
1640+
1641+
#[test]
1642+
fn get_last_set_cycle_should_succeed() {
1643+
let mock = MockServerClient::new();
1644+
let reward_cycle = thread_rng().next_u64();
1645+
let response = build_get_last_set_cycle_response(reward_cycle);
1646+
let h = spawn(move || mock.client.get_last_set_cycle());
1647+
write_response(mock.server, response.as_bytes());
1648+
assert_eq!(h.join().unwrap().unwrap(), reward_cycle as u128);
1649+
}
16261650
}

stacks-signer/src/runloop.rs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ use crate::client::{retry_with_exponential_backoff, ClientError, SignerSlotID, S
3434
use crate::config::{GlobalConfig, SignerConfig};
3535
use crate::Signer as SignerTrait;
3636

37+
#[derive(thiserror::Error, Debug)]
38+
/// Configuration error type
39+
pub enum ConfigurationError {
40+
/// Error occurred while fetching data from the stacks node
41+
#[error("{0}")]
42+
ClientError(#[from] ClientError),
43+
/// The stackerdb signer config is not yet updated
44+
#[error("The stackerdb config is not yet updated")]
45+
StackerDBNotUpdated,
46+
}
47+
3748
/// The internal signer state info
3849
#[derive(PartialEq, Clone, Debug)]
3950
pub struct StateInfo {
@@ -274,24 +285,40 @@ impl<Signer: SignerTrait<T>, T: StacksMessageCodec + Clone + Send + Debug> RunLo
274285
fn get_signer_config(
275286
&mut self,
276287
reward_cycle: u64,
277-
) -> Result<Option<SignerConfig>, ClientError> {
288+
) -> Result<Option<SignerConfig>, ConfigurationError> {
278289
// We can only register for a reward cycle if a reward set exists.
279290
let signer_entries = match self.get_parsed_reward_set(reward_cycle) {
280291
Ok(Some(x)) => x,
281292
Ok(None) => return Ok(None),
282293
Err(e) => {
283294
warn!("Error while fetching reward set {reward_cycle}: {e:?}");
284-
return Err(e);
295+
return Err(e.into());
285296
}
286297
};
287-
let signer_slot_ids = match self.get_parsed_signer_slots(&self.stacks_client, reward_cycle)
288-
{
289-
Ok(x) => x,
290-
Err(e) => {
298+
299+
// Ensure that the stackerdb has been updated for the reward cycle before proceeding
300+
let last_calculated_reward_cycle =
301+
self.stacks_client.get_last_set_cycle().map_err(|e| {
302+
warn!(
303+
"Failed to fetch last calculated stackerdb cycle from stacks-node";
304+
"reward_cycle" => reward_cycle,
305+
"err" => ?e
306+
);
307+
ConfigurationError::StackerDBNotUpdated
308+
})?;
309+
if last_calculated_reward_cycle < reward_cycle as u128 {
310+
warn!(
311+
"Stackerdb has not been updated for reward cycle {reward_cycle}. Last calculated reward cycle is {last_calculated_reward_cycle}."
312+
);
313+
return Err(ConfigurationError::StackerDBNotUpdated);
314+
}
315+
316+
let signer_slot_ids = self
317+
.get_parsed_signer_slots(&self.stacks_client, reward_cycle)
318+
.map_err(|e| {
291319
warn!("Error while fetching stackerdb slots {reward_cycle}: {e:?}");
292-
return Err(e);
293-
}
294-
};
320+
e
321+
})?;
295322
let current_addr = self.stacks_client.get_signer_address();
296323

297324
let Some(signer_slot_id) = signer_slot_ids.get(current_addr) else {

0 commit comments

Comments
 (0)