Skip to content

Commit

Permalink
Merge pull request #5033 from stacks-network/bug/signer-node-out-of-s…
Browse files Browse the repository at this point in the history
…ync-peer-info

Bug/signer node out of sync peer info
  • Loading branch information
jferrant authored Aug 1, 2024
2 parents e3b8e29 + bf30fa6 commit 6b05a3f
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 74 deletions.
161 changes: 105 additions & 56 deletions libsigner/src/v0/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,57 +239,41 @@ pub trait StacksMessageCodecExtensions: Sized {
fn inner_consensus_deserialize<R: Read>(fd: &mut R) -> Result<Self, CodecError>;
}

/// 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<W: Write>(&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<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
let burn_block_height = read_next::<u64, _>(fd)?;
let stacks_tip_consensus_hash = read_next::<ConsensusHash, _>(fd)?;
let stacks_tip = read_next::<BlockHeaderHash, _>(fd)?;
let stacks_tip_height = read_next::<u64, _>(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)?;
Expand All @@ -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::<u64, _>(fd)?;
let peer_burn_block_height = read_next::<u64, _>(fd)?;
let pox_consensus = read_next::<ConsensusHash, _>(fd)?;
let chain_id = read_next::<u32, _>(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<W: Write>(&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<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
let peer_info = PeerInfo::consensus_deserialize(fd)?;
let event_burn_block_height = read_next::<u64, _>(fd)?;
let chain_id = read_next::<u32, _>(fd)?;
Ok(Self {
peer_info,
event_burn_block_height,
chain_id,
})
}
Expand All @@ -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");
Expand All @@ -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"),
Expand Down Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -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::<PeerInfo, _>(&mut &serialized_data[..])
.expect("Failed to deserialize PeerInfo");
assert_eq!(peer_data, deserialized_data);
}

#[test]
fn serde_mock_signature() {
let mock_signature = MockSignature {
Expand Down
4 changes: 2 additions & 2 deletions stacks-signer/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
19 changes: 15 additions & 4 deletions stacks-signer/src/client/stacks_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down Expand Up @@ -463,7 +463,7 @@ impl StacksClient {
}

/// Get the current peer info data from the stacks node
pub fn get_peer_info(&self) -> Result<RPCPeerInfoData, ClientError> {
pub fn get_peer_info(&self) -> Result<PeerInfo, ClientError> {
debug!("Getting stacks node info...");
let timer =
crate::monitoring::new_rpc_call_timer(&self.core_info_path(), &self.http_origin);
Expand All @@ -478,7 +478,7 @@ impl StacksClient {
if !response.status().is_success() {
return Err(ClientError::RequestFailure(response.status()));
}
let peer_info_data = response.json::<RPCPeerInfoData>()?;
let peer_info_data = response.json::<PeerInfo>()?;
Ok(peer_info_data)
}

Expand Down Expand Up @@ -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]
Expand Down
32 changes: 21 additions & 11 deletions stacks-signer/src/v0/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,22 @@ impl SignerTrait<SignerMessage> 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 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,
"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);
}
}
}
}
Expand Down Expand Up @@ -477,7 +487,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;
};
Expand All @@ -487,15 +497,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
Expand Down
3 changes: 2 additions & 1 deletion testnet/stacks-node/src/tests/signer/v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1809,7 +1809,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);
}
Expand Down

0 comments on commit 6b05a3f

Please sign in to comment.