Skip to content

Commit 4921f5b

Browse files
committed
versioning support for state machine update messages
* add content-length * add versioning enum with compatibility check
1 parent 32c71ca commit 4921f5b

File tree

2 files changed

+180
-52
lines changed

2 files changed

+180
-52
lines changed

libsigner/src/v0/messages.rs

Lines changed: 164 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
2626
use std::fmt::{Debug, Display};
2727
use std::io::{Read, Write};
28+
use std::marker::PhantomData;
2829
use std::net::{SocketAddr, TcpListener, TcpStream};
2930
use std::sync::atomic::{AtomicBool, Ordering};
3031
use std::sync::mpsc::Sender;
@@ -77,6 +78,9 @@ use crate::{
7778
/// Maximum size of the [BlockResponseData] serialized bytes
7879
pub const BLOCK_RESPONSE_DATA_MAX_SIZE: u32 = 2 * 1024 * 1024; // 2MB
7980

81+
/// Maximum size of the state machine update messages
82+
pub const STATE_MACHINE_UPDATE_MAX_SIZE: u32 = 2 * 1024 * 1024; // 2MB
83+
8084
define_u8_enum!(
8185
/// Enum representing the stackerdb message identifier: this is
8286
/// the contract index in the signers contracts (i.e., X in signers-0-X)
@@ -541,19 +545,31 @@ impl StacksMessageCodec for MockBlock {
541545
}
542546
}
543547

544-
/// Message for update the Signer State infos
548+
/// Message for updates to the Signer State machine
545549
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
546550
pub struct StateMachineUpdate {
547-
/// The tip burn block (i.e., the latest bitcoin block) seen by this signer
548-
pub burn_block: ConsensusHash,
549-
/// The tip burn block height (i.e., the latest bitcoin block) seen by this signer
550-
pub burn_block_height: u64,
551-
/// The signer's view of who the current miner should be (and their tenure building info)
552-
pub current_miner: StateMachineUpdateMinerState,
553551
/// The active signing protocol version
554552
pub active_signer_protocol_version: u64,
555553
/// The highest supported signing protocol by the local signer
556554
pub local_supported_signer_protocol_version: u64,
555+
/// The actual content of the state machine update message (this is a versioned enum)
556+
pub content: StateMachineUpdateContent,
557+
// Prevent manual construction of this struct
558+
no_manual_construct: PhantomData<()>,
559+
}
560+
561+
/// Versioning enum for StateMachineUpdate messages
562+
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
563+
pub enum StateMachineUpdateContent {
564+
/// Version 0
565+
V0 {
566+
/// The tip burn block (i.e., the latest bitcoin block) seen by this signer
567+
burn_block: ConsensusHash,
568+
/// The tip burn block height (i.e., the latest bitcoin block) seen by this signer
569+
burn_block_height: u64,
570+
/// The signer's view of who the current miner should be (and their tenure building info)
571+
current_miner: StateMachineUpdateMinerState,
572+
},
557573
}
558574

559575
/// Message for update the Signer State infos
@@ -578,6 +594,26 @@ pub enum StateMachineUpdateMinerState {
578594
NoValidMiner,
579595
}
580596

597+
impl StateMachineUpdate {
598+
/// Construct a StateMachineUpdate message, checking to ensure that the
599+
/// supplied content is supported by the supplied protocol versions.
600+
pub fn new(
601+
active_signer_protocol_version: u64,
602+
local_supported_signer_protocol_version: u64,
603+
content: StateMachineUpdateContent,
604+
) -> Result<Self, CodecError> {
605+
if !content.is_protocol_version_compatible(active_signer_protocol_version) {
606+
return Err(CodecError::DeserializeError(format!("StateMachineUpdateContent is incompatible with protocol version: {active_signer_protocol_version}")));
607+
}
608+
Ok(Self {
609+
active_signer_protocol_version,
610+
local_supported_signer_protocol_version,
611+
content,
612+
no_manual_construct: PhantomData,
613+
})
614+
}
615+
}
616+
581617
impl StateMachineUpdateMinerState {
582618
fn get_variant_id(&self) -> u8 {
583619
match self {
@@ -634,32 +670,89 @@ impl StacksMessageCodec for StateMachineUpdateMinerState {
634670
}
635671
}
636672

673+
impl StateMachineUpdateContent {
674+
// Is the protocol version specified one that uses self's content?
675+
fn is_protocol_version_compatible(&self, version: u64) -> bool {
676+
match self {
677+
Self::V0 { .. } => version == 0,
678+
}
679+
}
680+
681+
fn serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
682+
match self {
683+
Self::V0 {
684+
burn_block,
685+
burn_block_height,
686+
current_miner,
687+
} => {
688+
burn_block.consensus_serialize(fd)?;
689+
burn_block_height.consensus_serialize(fd)?;
690+
current_miner.consensus_serialize(fd)?;
691+
}
692+
}
693+
Ok(())
694+
}
695+
fn deserialize<R: Read>(fd: &mut R, version: u64) -> Result<Self, CodecError> {
696+
match version {
697+
0 => {
698+
let burn_block = read_next(fd)?;
699+
let burn_block_height = read_next(fd)?;
700+
let current_miner = read_next(fd)?;
701+
Ok(Self::V0 {
702+
burn_block,
703+
burn_block_height,
704+
current_miner,
705+
})
706+
}
707+
other => Err(CodecError::DeserializeError(format!(
708+
"Unknown state machine update version: {other}"
709+
))),
710+
}
711+
}
712+
}
713+
637714
impl StacksMessageCodec for StateMachineUpdate {
638715
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
639716
self.active_signer_protocol_version
640717
.consensus_serialize(fd)?;
641718
self.local_supported_signer_protocol_version
642719
.consensus_serialize(fd)?;
643-
self.burn_block.consensus_serialize(fd)?;
644-
self.burn_block_height.consensus_serialize(fd)?;
645-
self.current_miner.consensus_serialize(fd)?;
646-
Ok(())
720+
let mut buffer = Vec::new();
721+
self.content.serialize(&mut buffer)?;
722+
let buff_len = u32::try_from(buffer.len())
723+
.map_err(|_e| CodecError::SerializeError("Message length exceeded u32".into()))?;
724+
if buff_len > STATE_MACHINE_UPDATE_MAX_SIZE {
725+
return Err(CodecError::SerializeError(format!(
726+
"Message length exceeded max: {STATE_MACHINE_UPDATE_MAX_SIZE}"
727+
)));
728+
}
729+
buff_len.consensus_serialize(fd)?;
730+
fd.write_all(&buffer).map_err(CodecError::WriteError)
647731
}
648732

649733
fn consensus_deserialize<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
650734
let active_signer_protocol_version = read_next(fd)?;
651735
let local_supported_signer_protocol_version = read_next(fd)?;
652-
let burn_block = read_next(fd)?;
653-
let burn_block_height = read_next(fd)?;
654-
let current_miner = read_next(fd)?;
736+
let content_len: u32 = read_next(fd)?;
737+
if content_len > STATE_MACHINE_UPDATE_MAX_SIZE {
738+
return Err(CodecError::DeserializeError(format!(
739+
"Message length exceeded max: {STATE_MACHINE_UPDATE_MAX_SIZE}"
740+
)));
741+
}
742+
let buffer_len = usize::try_from(content_len)
743+
.expect("FATAL: cannot process signer messages when usize < u32");
744+
let mut buffer = vec![0u8; buffer_len];
745+
fd.read_exact(&mut buffer).map_err(CodecError::ReadError)?;
746+
let content = StateMachineUpdateContent::deserialize(
747+
&mut buffer.as_slice(),
748+
active_signer_protocol_version,
749+
)?;
655750

656-
Ok(Self {
657-
burn_block,
658-
burn_block_height,
659-
current_miner,
751+
Self::new(
660752
active_signer_protocol_version,
661753
local_supported_signer_protocol_version,
662-
})
754+
content,
755+
)
663756
}
664757
}
665758

@@ -2121,28 +2214,53 @@ mod test {
21212214
}
21222215

21232216
#[test]
2124-
fn test_deserialize_state_machine_update() {
2125-
let signer_message = StateMachineUpdate {
2126-
burn_block: ConsensusHash([0x55; 20]),
2127-
burn_block_height: 100,
2128-
active_signer_protocol_version: 2,
2129-
local_supported_signer_protocol_version: 3,
2130-
current_miner: StateMachineUpdateMinerState::ActiveMiner {
2131-
current_miner_pkh: Hash160([0xab; 20]),
2132-
tenure_id: ConsensusHash([0x44; 20]),
2133-
parent_tenure_id: ConsensusHash([0x22; 20]),
2134-
parent_tenure_last_block: StacksBlockId([0x33; 32]),
2135-
parent_tenure_last_block_height: 1,
2217+
fn version_check_state_machine_update() {
2218+
let error = StateMachineUpdate::new(
2219+
1,
2220+
3,
2221+
StateMachineUpdateContent::V0 {
2222+
burn_block: ConsensusHash([0x55; 20]),
2223+
burn_block_height: 100,
2224+
current_miner: StateMachineUpdateMinerState::ActiveMiner {
2225+
current_miner_pkh: Hash160([0xab; 20]),
2226+
tenure_id: ConsensusHash([0x44; 20]),
2227+
parent_tenure_id: ConsensusHash([0x22; 20]),
2228+
parent_tenure_last_block: StacksBlockId([0x33; 32]),
2229+
parent_tenure_last_block_height: 1,
2230+
},
21362231
},
2137-
};
2232+
)
2233+
.unwrap_err();
2234+
assert!(matches!(error, CodecError::DeserializeError(_)));
2235+
}
2236+
2237+
#[test]
2238+
fn deserialize_state_machine_update_v0() {
2239+
let signer_message = StateMachineUpdate::new(
2240+
0,
2241+
3,
2242+
StateMachineUpdateContent::V0 {
2243+
burn_block: ConsensusHash([0x55; 20]),
2244+
burn_block_height: 100,
2245+
current_miner: StateMachineUpdateMinerState::ActiveMiner {
2246+
current_miner_pkh: Hash160([0xab; 20]),
2247+
tenure_id: ConsensusHash([0x44; 20]),
2248+
parent_tenure_id: ConsensusHash([0x22; 20]),
2249+
parent_tenure_last_block: StacksBlockId([0x33; 32]),
2250+
parent_tenure_last_block_height: 1,
2251+
},
2252+
},
2253+
)
2254+
.unwrap();
21382255

21392256
let mut bytes = vec![];
21402257
signer_message.consensus_serialize(&mut bytes).unwrap();
21412258

21422259
// check for raw content for avoiding regressions when structure changes
21432260
let raw_signer_message: Vec<&[u8]> = vec![
2144-
/* active_signer_protocol_version*/ &[0, 0, 0, 0, 0, 0, 0, 2],
2261+
/* active_signer_protocol_version*/ &[0, 0, 0, 0, 0, 0, 0, 0],
21452262
/* local_supported_signer_protocol_version*/ &[0, 0, 0, 0, 0, 0, 0, 3],
2263+
/* content_len*/ &[0, 0, 0, 129],
21462264
/* burn_block*/ &[0x55; 20],
21472265
/* burn_block_height*/ &[0, 0, 0, 0, 0, 0, 0, 100],
21482266
/* current_miner_variant */ &[0x01],
@@ -2160,21 +2278,25 @@ mod test {
21602278

21612279
assert_eq!(signer_message, signer_message_deserialized);
21622280

2163-
let signer_message = StateMachineUpdate {
2164-
burn_block: ConsensusHash([0x55; 20]),
2165-
burn_block_height: 100,
2166-
active_signer_protocol_version: 2,
2167-
local_supported_signer_protocol_version: 3,
2168-
current_miner: StateMachineUpdateMinerState::NoValidMiner,
2169-
};
2281+
let signer_message = StateMachineUpdate::new(
2282+
0,
2283+
4,
2284+
StateMachineUpdateContent::V0 {
2285+
burn_block: ConsensusHash([0x55; 20]),
2286+
burn_block_height: 100,
2287+
current_miner: StateMachineUpdateMinerState::NoValidMiner,
2288+
},
2289+
)
2290+
.unwrap();
21702291

21712292
let mut bytes = vec![];
21722293
signer_message.consensus_serialize(&mut bytes).unwrap();
21732294

21742295
// check for raw content for avoiding regressions when structure changes
21752296
let raw_signer_message: Vec<&[u8]> = vec![
2176-
/* active_signer_protocol_version*/ &[0, 0, 0, 0, 0, 0, 0, 2],
2177-
/* local_supported_signer_protocol_version*/ &[0, 0, 0, 0, 0, 0, 0, 3],
2297+
/* active_signer_protocol_version*/ &[0, 0, 0, 0, 0, 0, 0, 0],
2298+
/* local_supported_signer_protocol_version*/ &[0, 0, 0, 0, 0, 0, 0, 4],
2299+
/* content_len*/ &[0, 0, 0, 29],
21782300
/* burn_block*/ &[0x55; 20],
21792301
/* burn_block_height*/ &[0, 0, 0, 0, 0, 0, 0, 100],
21802302
/* current_miner_variant */ &[0x00],

stacks-signer/src/v0/signer_state.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ use std::time::{Duration, UNIX_EPOCH};
1818
use blockstack_lib::chainstate::burn::ConsensusHashExtensions;
1919
use blockstack_lib::chainstate::nakamoto::{NakamotoBlock, NakamotoBlockHeader};
2020
use libsigner::v0::messages::{
21-
StateMachineUpdate as StateMachineUpdateMessage, StateMachineUpdateMinerState,
21+
StateMachineUpdate as StateMachineUpdateMessage, StateMachineUpdateContent,
22+
StateMachineUpdateMinerState,
2223
};
2324
use serde::{Deserialize, Serialize};
2425
use slog::{slog_info, slog_warn};
2526
use stacks_common::bitvec::BitVec;
27+
use stacks_common::codec::Error as CodecError;
2628
use stacks_common::types::chainstate::{ConsensusHash, StacksBlockId, TrieHash};
2729
use stacks_common::util::hash::{Hash160, Sha512Trunc256Sum};
2830
use stacks_common::util::secp256k1::MessageSignature;
@@ -100,11 +102,13 @@ pub enum StateMachineUpdate {
100102
}
101103

102104
impl TryInto<StateMachineUpdateMessage> for &LocalStateMachine {
103-
type Error = SignerChainstateError;
105+
type Error = CodecError;
104106

105107
fn try_into(self) -> Result<StateMachineUpdateMessage, Self::Error> {
106108
let LocalStateMachine::Initialized(state_machine) = self else {
107-
return Err(SignerChainstateError::LocalStateMachineNotReady);
109+
return Err(CodecError::SerializeError(
110+
"Local state machine is not ready to be serialized into an update message".into(),
111+
));
108112
};
109113

110114
let current_miner = match state_machine.current_miner {
@@ -124,13 +128,15 @@ impl TryInto<StateMachineUpdateMessage> for &LocalStateMachine {
124128
MinerState::NoValidMiner => StateMachineUpdateMinerState::NoValidMiner,
125129
};
126130

127-
Ok(StateMachineUpdateMessage {
128-
burn_block: state_machine.burn_block,
129-
burn_block_height: state_machine.burn_block_height,
130-
current_miner,
131-
active_signer_protocol_version: state_machine.active_signer_protocol_version,
132-
local_supported_signer_protocol_version: SUPPORTED_SIGNER_PROTOCOL_VERSION,
133-
})
131+
StateMachineUpdateMessage::new(
132+
state_machine.active_signer_protocol_version,
133+
SUPPORTED_SIGNER_PROTOCOL_VERSION,
134+
StateMachineUpdateContent::V0 {
135+
burn_block: state_machine.burn_block,
136+
burn_block_height: state_machine.burn_block_height,
137+
current_miner,
138+
},
139+
)
134140
}
135141
}
136142

0 commit comments

Comments
 (0)