From 7f6e541cb9dce67ba0111c03dcdb950781ef9188 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 31 Jul 2024 14:24:39 -0400 Subject: [PATCH 1/3] Add some logs to mock sign checks Signed-off-by: Jacinta Ferrant --- stacks-signer/src/v0/signer.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/stacks-signer/src/v0/signer.rs b/stacks-signer/src/v0/signer.rs index ad2a459b96..b645b46a73 100644 --- a/stacks-signer/src/v0/signer.rs +++ b/stacks-signer/src/v0/signer.rs @@ -169,12 +169,19 @@ impl SignerTrait for Signer { ); } *sortition_state = None; - if let Ok(StacksEpochId::Epoch25) = stacks_client.get_node_epoch() { - if self.reward_cycle == current_reward_cycle { - // We are in epoch 2.5, so we should mock mine to prove we are still alive. - self.mock_sign(*burn_height, stacks_client); - } + let Ok(epoch) = stacks_client.get_node_epoch() else { + warn!("{self}: Failed to determine node epoch. Cannot mock sign."); + return; }; + debug!("{self}: Epoch 2.5 signer received a new burn block event."; + "burn_height" => burn_height, + "current_reward_cycle" => current_reward_cycle, + "epoch" => ?epoch + ); + if epoch == StacksEpochId::Epoch25 && self.reward_cycle == current_reward_cycle { + // We are in epoch 2.5, so we should mock mine to prove we are still alive. + self.mock_sign(*burn_height, stacks_client); + } } } } From 5085bdad6d8b5611783093f426e8f41f159f7c09 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Wed, 31 Jul 2024 15:19:59 -0400 Subject: [PATCH 2/3] Add error log when failing to determine node epoch in mock sign Signed-off-by: Jacinta Ferrant --- stacks-signer/src/v0/signer.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/stacks-signer/src/v0/signer.rs b/stacks-signer/src/v0/signer.rs index b645b46a73..574c4d8df9 100644 --- a/stacks-signer/src/v0/signer.rs +++ b/stacks-signer/src/v0/signer.rs @@ -169,9 +169,12 @@ impl SignerTrait for Signer { ); } *sortition_state = None; - let Ok(epoch) = stacks_client.get_node_epoch() else { - warn!("{self}: Failed to determine node epoch. Cannot mock sign."); - return; + let epoch = match stacks_client.get_node_epoch() { + Ok(epoch) => epoch, + Err(e) => { + warn!("{self}: Failed to determine node epoch. Cannot mock sign: {e}"); + return; + } }; debug!("{self}: Epoch 2.5 signer received a new burn block event."; "burn_height" => burn_height, From bf30fa62e8257d8c702788761cd7c4ceefef02a9 Mon Sep 17 00:00:00 2001 From: Jacinta Ferrant Date: Thu, 1 Aug 2024 08:24:59 -0400 Subject: [PATCH 3/3] Deserialize only the necessary info from peer info to be more resilient to peer info updates in the signer Signed-off-by: Jacinta Ferrant --- libsigner/src/v0/messages.rs | 161 ++++++++++++++------- stacks-signer/src/cli.rs | 4 +- stacks-signer/src/client/stacks_client.rs | 19 ++- stacks-signer/src/v0/signer.rs | 14 +- testnet/stacks-node/src/tests/signer/v0.rs | 3 +- 5 files changed, 131 insertions(+), 70 deletions(-) diff --git a/libsigner/src/v0/messages.rs b/libsigner/src/v0/messages.rs index f16dd6d4ed..7d411f89b5 100644 --- a/libsigner/src/v0/messages.rs +++ b/libsigner/src/v0/messages.rs @@ -239,57 +239,41 @@ pub trait StacksMessageCodecExtensions: Sized { fn inner_consensus_deserialize(fd: &mut R) -> Result; } -/// A snapshot of the signer view of the stacks node to be used for mock signing. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct MockSignData { - /// The stacks tip consensus hash at the time of the mock signature +/// The signer relevant peer information from the stacks node +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PeerInfo { + /// The burn block height + pub burn_block_height: u64, + /// The consensus hash of the stacks tip pub stacks_tip_consensus_hash: ConsensusHash, - /// The stacks tip header hash at the time of the mock signature + /// The stacks tip pub stacks_tip: BlockHeaderHash, + /// The stacks tip height + pub stacks_tip_height: u64, + /// The pox consensus + pub pox_consensus: ConsensusHash, /// The server version pub server_version: String, - /// The burn block height that triggered the mock signature - pub burn_block_height: u64, - /// The burn block height of the peer view at the time of the mock signature. Note - /// that this may be different from the burn_block_height if the peer view is stale. - pub peer_burn_block_height: u64, - /// The POX consensus hash at the time of the mock signature - pub pox_consensus: ConsensusHash, - /// The chain id for the mock signature - pub chain_id: u32, -} - -impl MockSignData { - fn new(peer_view: RPCPeerInfoData, burn_block_height: u64, chain_id: u32) -> Self { - Self { - stacks_tip_consensus_hash: peer_view.stacks_tip_consensus_hash, - stacks_tip: peer_view.stacks_tip, - server_version: peer_view.server_version, - burn_block_height, - peer_burn_block_height: peer_view.burn_block_height, - pox_consensus: peer_view.pox_consensus, - chain_id, - } - } } -impl StacksMessageCodec for MockSignData { +impl StacksMessageCodec for PeerInfo { fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> { + write_next(fd, &self.burn_block_height)?; write_next(fd, self.stacks_tip_consensus_hash.as_bytes())?; write_next(fd, &self.stacks_tip)?; + write_next(fd, &self.stacks_tip_height)?; write_next(fd, &(self.server_version.as_bytes().len() as u8))?; fd.write_all(self.server_version.as_bytes()) .map_err(CodecError::WriteError)?; - write_next(fd, &self.burn_block_height)?; - write_next(fd, &self.peer_burn_block_height)?; write_next(fd, &self.pox_consensus)?; - write_next(fd, &self.chain_id)?; Ok(()) } fn consensus_deserialize(fd: &mut R) -> Result { + let burn_block_height = read_next::(fd)?; let stacks_tip_consensus_hash = read_next::(fd)?; let stacks_tip = read_next::(fd)?; + let stacks_tip_height = read_next::(fd)?; let len_byte: u8 = read_next(fd)?; let mut bytes = vec![0u8; len_byte as usize]; fd.read_exact(&mut bytes).map_err(CodecError::ReadError)?; @@ -299,17 +283,44 @@ impl StacksMessageCodec for MockSignData { "Failed to parse server version name: could not contruct from utf8".to_string(), ) })?; - let burn_block_height = read_next::(fd)?; - let peer_burn_block_height = read_next::(fd)?; let pox_consensus = read_next::(fd)?; - let chain_id = read_next::(fd)?; Ok(Self { + burn_block_height, stacks_tip_consensus_hash, stacks_tip, + stacks_tip_height, server_version, - burn_block_height, - peer_burn_block_height, pox_consensus, + }) + } +} + +/// A snapshot of the signer view of the stacks node to be used for mock signing. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct MockSignData { + /// The view of the stacks node peer information at the time of the mock signature + pub peer_info: PeerInfo, + /// The burn block height of the event that triggered the mock signature + pub event_burn_block_height: u64, + /// The chain id for the mock signature + pub chain_id: u32, +} + +impl StacksMessageCodec for MockSignData { + fn consensus_serialize(&self, fd: &mut W) -> Result<(), CodecError> { + self.peer_info.consensus_serialize(fd)?; + write_next(fd, &self.event_burn_block_height)?; + write_next(fd, &self.chain_id)?; + Ok(()) + } + + fn consensus_deserialize(fd: &mut R) -> Result { + let peer_info = PeerInfo::consensus_deserialize(fd)?; + let event_burn_block_height = read_next::(fd)?; + let chain_id = read_next::(fd)?; + Ok(Self { + peer_info, + event_burn_block_height, chain_id, }) } @@ -326,16 +337,21 @@ pub struct MockSignature { } impl MockSignature { - /// Create a new mock sign data struct from the provided peer info, burn block height, chain id, and private key. + /// Create a new mock sign data struct from the provided event burn block height, peer info, chain id, and private key. + /// Note that peer burn block height and event burn block height may not be the same if the peer view is stale. pub fn new( - peer_view: RPCPeerInfoData, - burn_block_height: u64, + event_burn_block_height: u64, + peer_info: PeerInfo, chain_id: u32, stacks_private_key: &StacksPrivateKey, ) -> Self { let mut sig = Self { signature: MessageSignature::empty(), - sign_data: MockSignData::new(peer_view, burn_block_height, chain_id), + sign_data: MockSignData { + peer_info, + event_burn_block_height, + chain_id, + }, }; sig.sign(stacks_private_key) .expect("Failed to sign MockSignature"); @@ -350,25 +366,39 @@ impl MockSignature { TupleData::from_data(vec![ ( "stacks-tip-consensus-hash".into(), - Value::buff_from(self.sign_data.stacks_tip_consensus_hash.as_bytes().into()) - .unwrap(), + Value::buff_from( + self.sign_data + .peer_info + .stacks_tip_consensus_hash + .as_bytes() + .into(), + ) + .unwrap(), ), ( "stacks-tip".into(), - Value::buff_from(self.sign_data.stacks_tip.as_bytes().into()).unwrap(), + Value::buff_from(self.sign_data.peer_info.stacks_tip.as_bytes().into()) + .unwrap(), + ), + ( + "stacks-tip-height".into(), + Value::UInt(self.sign_data.peer_info.stacks_tip_height.into()), ), ( "server-version".into(), - Value::string_ascii_from_bytes(self.sign_data.server_version.clone().into()) - .unwrap(), + Value::string_ascii_from_bytes( + self.sign_data.peer_info.server_version.clone().into(), + ) + .unwrap(), ), ( - "burn-block-height".into(), - Value::UInt(self.sign_data.burn_block_height.into()), + "event-burn-block-height".into(), + Value::UInt(self.sign_data.event_burn_block_height.into()), ), ( "pox-consensus".into(), - Value::buff_from(self.sign_data.pox_consensus.as_bytes().into()).unwrap(), + Value::buff_from(self.sign_data.peer_info.pox_consensus.as_bytes().into()) + .unwrap(), ), ]) .expect("Error creating signature hash"), @@ -822,23 +852,33 @@ mod test { assert_eq!(signer_message, deserialized_signer_message); } - fn random_mock_sign_data() -> MockSignData { + fn random_peer_data() -> PeerInfo { + let burn_block_height = thread_rng().next_u64(); let stacks_tip_consensus_byte: u8 = thread_rng().gen(); let stacks_tip_byte: u8 = thread_rng().gen(); + let stacks_tip_height = thread_rng().next_u64(); + let server_version = "0.0.0".to_string(); let pox_consensus_byte: u8 = thread_rng().gen(); + PeerInfo { + burn_block_height, + stacks_tip_consensus_hash: ConsensusHash([stacks_tip_consensus_byte; 20]), + stacks_tip: BlockHeaderHash([stacks_tip_byte; 32]), + stacks_tip_height, + server_version, + pox_consensus: ConsensusHash([pox_consensus_byte; 20]), + } + } + fn random_mock_sign_data() -> MockSignData { let chain_byte: u8 = thread_rng().gen_range(0..=1); let chain_id = if chain_byte == 1 { CHAIN_ID_TESTNET } else { CHAIN_ID_MAINNET }; + let peer_info = random_peer_data(); MockSignData { - stacks_tip_consensus_hash: ConsensusHash([stacks_tip_consensus_byte; 20]), - stacks_tip: BlockHeaderHash([stacks_tip_byte; 32]), - server_version: "0.0.0".to_string(), - burn_block_height: thread_rng().next_u64(), - peer_burn_block_height: thread_rng().next_u64(), - pox_consensus: ConsensusHash([pox_consensus_byte; 20]), + peer_info, + event_burn_block_height: thread_rng().next_u64(), chain_id, } } @@ -871,6 +911,15 @@ mod test { .expect("Failed to verify MockSignature")); } + #[test] + fn serde_peer_data() { + let peer_data = random_peer_data(); + let serialized_data = peer_data.serialize_to_vec(); + let deserialized_data = read_next::(&mut &serialized_data[..]) + .expect("Failed to deserialize PeerInfo"); + assert_eq!(peer_data, deserialized_data); + } + #[test] fn serde_mock_signature() { let mock_signature = MockSignature { diff --git a/stacks-signer/src/cli.rs b/stacks-signer/src/cli.rs index d3e998e15c..74e2cd2344 100644 --- a/stacks-signer/src/cli.rs +++ b/stacks-signer/src/cli.rs @@ -44,9 +44,9 @@ extern crate alloc; const GIT_BRANCH: Option<&'static str> = option_env!("GIT_BRANCH"); const GIT_COMMIT: Option<&'static str> = option_env!("GIT_COMMIT"); #[cfg(debug_assertions)] -const BUILD_TYPE: &'static str = "debug"; +const BUILD_TYPE: &str = "debug"; #[cfg(not(debug_assertions))] -const BUILD_TYPE: &'static str = "release"; +const BUILD_TYPE: &str = "release"; lazy_static! { static ref VERSION_STRING: String = { diff --git a/stacks-signer/src/client/stacks_client.rs b/stacks-signer/src/client/stacks_client.rs index 8c63e181f7..b6337364db 100644 --- a/stacks-signer/src/client/stacks_client.rs +++ b/stacks-signer/src/client/stacks_client.rs @@ -31,7 +31,6 @@ use blockstack_lib::net::api::get_tenures_fork_info::{ TenureForkingInfo, RPC_TENURE_FORKING_INFO_PATH, }; use blockstack_lib::net::api::getaccount::AccountEntryResponse; -use blockstack_lib::net::api::getinfo::RPCPeerInfoData; use blockstack_lib::net::api::getpoxinfo::RPCPoxInfoData; use blockstack_lib::net::api::getsortition::{SortitionInfo, RPC_SORTITION_INFO_PATH}; use blockstack_lib::net::api::getstackers::GetStackersResponse; @@ -43,6 +42,7 @@ use blockstack_lib::util_lib::boot::{boot_code_addr, boot_code_id}; use clarity::util::hash::to_hex; use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use clarity::vm::{ClarityName, ContractName, Value as ClarityValue}; +use libsigner::v0::messages::PeerInfo; use reqwest::header::AUTHORIZATION; use serde_json::json; use slog::{slog_debug, slog_warn}; @@ -463,7 +463,7 @@ impl StacksClient { } /// Get the current peer info data from the stacks node - pub fn get_peer_info(&self) -> Result { + pub fn get_peer_info(&self) -> Result { debug!("Getting stacks node info..."); let timer = crate::monitoring::new_rpc_call_timer(&self.core_info_path(), &self.http_origin); @@ -478,7 +478,7 @@ impl StacksClient { if !response.status().is_success() { return Err(ClientError::RequestFailure(response.status())); } - let peer_info_data = response.json::()?; + let peer_info_data = response.json::()?; Ok(peer_info_data) } @@ -1387,7 +1387,18 @@ mod tests { let (response, peer_info) = build_get_peer_info_response(None, None); let h = spawn(move || mock.client.get_peer_info()); write_response(mock.server, response.as_bytes()); - assert_eq!(h.join().unwrap().unwrap(), peer_info); + let reduced_peer_info = h.join().unwrap().unwrap(); + assert_eq!( + reduced_peer_info.burn_block_height, + peer_info.burn_block_height + ); + assert_eq!(reduced_peer_info.pox_consensus, peer_info.pox_consensus); + assert_eq!( + reduced_peer_info.stacks_tip_consensus_hash, + peer_info.stacks_tip_consensus_hash + ); + assert_eq!(reduced_peer_info.stacks_tip, peer_info.stacks_tip); + assert_eq!(reduced_peer_info.server_version, peer_info.server_version); } #[test] diff --git a/stacks-signer/src/v0/signer.rs b/stacks-signer/src/v0/signer.rs index 574c4d8df9..94e8fa0499 100644 --- a/stacks-signer/src/v0/signer.rs +++ b/stacks-signer/src/v0/signer.rs @@ -484,7 +484,7 @@ impl Signer { /// Send a mock signature to stackerdb to prove we are still alive fn mock_sign(&mut self, burn_block_height: u64, stacks_client: &StacksClient) { - let Ok(peer_view) = stacks_client.get_peer_info() else { + let Ok(peer_info) = stacks_client.get_peer_info() else { warn!("{self}: Failed to get peer info. Cannot mock sign."); return; }; @@ -494,15 +494,15 @@ impl Signer { CHAIN_ID_TESTNET }; info!("Mock signing for burn block {burn_block_height:?}"; - "stacks_tip_consensus_hash" => ?peer_view.stacks_tip_consensus_hash.clone(), - "stacks_tip" => ?peer_view.stacks_tip.clone(), - "peer_burn_block_height" => peer_view.burn_block_height, - "pox_consensus" => ?peer_view.pox_consensus.clone(), - "server_version" => peer_view.server_version.clone(), + "stacks_tip_consensus_hash" => ?peer_info.stacks_tip_consensus_hash.clone(), + "stacks_tip" => ?peer_info.stacks_tip.clone(), + "peer_burn_block_height" => peer_info.burn_block_height, + "pox_consensus" => ?peer_info.pox_consensus.clone(), + "server_version" => peer_info.server_version.clone(), "chain_id" => chain_id ); let mock_signature = - MockSignature::new(peer_view, burn_block_height, chain_id, &self.private_key); + MockSignature::new(burn_block_height, peer_info, chain_id, &self.private_key); let message = SignerMessage::MockSignature(mock_signature); if let Err(e) = self .stackerdb diff --git a/testnet/stacks-node/src/tests/signer/v0.rs b/testnet/stacks-node/src/tests/signer/v0.rs index e1f57097e9..edd9300ff1 100644 --- a/testnet/stacks-node/src/tests/signer/v0.rs +++ b/testnet/stacks-node/src/tests/signer/v0.rs @@ -1663,7 +1663,8 @@ fn mock_sign_epoch_25() { .expect("Failed to get message from stackerdb"); for message in messages { if let SignerMessage::MockSignature(mock_signature) = message { - if mock_signature.sign_data.burn_block_height == current_burn_block_height { + if mock_signature.sign_data.event_burn_block_height == current_burn_block_height + { if !mock_signatures.contains(&mock_signature) { mock_signatures.push(mock_signature); }