25
25
26
26
use std:: fmt:: { Debug , Display } ;
27
27
use std:: io:: { Read , Write } ;
28
+ use std:: marker:: PhantomData ;
28
29
use std:: net:: { SocketAddr , TcpListener , TcpStream } ;
29
30
use std:: sync:: atomic:: { AtomicBool , Ordering } ;
30
31
use std:: sync:: mpsc:: Sender ;
@@ -77,6 +78,9 @@ use crate::{
77
78
/// Maximum size of the [BlockResponseData] serialized bytes
78
79
pub const BLOCK_RESPONSE_DATA_MAX_SIZE : u32 = 2 * 1024 * 1024 ; // 2MB
79
80
81
+ /// Maximum size of the state machine update messages
82
+ pub const STATE_MACHINE_UPDATE_MAX_SIZE : u32 = 2 * 1024 * 1024 ; // 2MB
83
+
80
84
define_u8_enum ! (
81
85
/// Enum representing the stackerdb message identifier: this is
82
86
/// the contract index in the signers contracts (i.e., X in signers-0-X)
@@ -541,19 +545,31 @@ impl StacksMessageCodec for MockBlock {
541
545
}
542
546
}
543
547
544
- /// Message for update the Signer State infos
548
+ /// Message for updates to the Signer State machine
545
549
#[ derive( Debug , Clone , PartialEq , Deserialize , Serialize ) ]
546
550
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 ,
553
551
/// The active signing protocol version
554
552
pub active_signer_protocol_version : u64 ,
555
553
/// The highest supported signing protocol by the local signer
556
554
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
+ } ,
557
573
}
558
574
559
575
/// Message for update the Signer State infos
@@ -578,6 +594,26 @@ pub enum StateMachineUpdateMinerState {
578
594
NoValidMiner ,
579
595
}
580
596
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
+
581
617
impl StateMachineUpdateMinerState {
582
618
fn get_variant_id ( & self ) -> u8 {
583
619
match self {
@@ -634,32 +670,89 @@ impl StacksMessageCodec for StateMachineUpdateMinerState {
634
670
}
635
671
}
636
672
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
+
637
714
impl StacksMessageCodec for StateMachineUpdate {
638
715
fn consensus_serialize < W : Write > ( & self , fd : & mut W ) -> Result < ( ) , CodecError > {
639
716
self . active_signer_protocol_version
640
717
. consensus_serialize ( fd) ?;
641
718
self . local_supported_signer_protocol_version
642
719
. 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 )
647
731
}
648
732
649
733
fn consensus_deserialize < R : Read > ( fd : & mut R ) -> Result < Self , CodecError > {
650
734
let active_signer_protocol_version = read_next ( fd) ?;
651
735
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
+ ) ?;
655
750
656
- Ok ( Self {
657
- burn_block,
658
- burn_block_height,
659
- current_miner,
751
+ Self :: new (
660
752
active_signer_protocol_version,
661
753
local_supported_signer_protocol_version,
662
- } )
754
+ content,
755
+ )
663
756
}
664
757
}
665
758
@@ -2121,28 +2214,53 @@ mod test {
2121
2214
}
2122
2215
2123
2216
#[ 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
+ } ,
2136
2231
} ,
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 ( ) ;
2138
2255
2139
2256
let mut bytes = vec ! [ ] ;
2140
2257
signer_message. consensus_serialize ( & mut bytes) . unwrap ( ) ;
2141
2258
2142
2259
// check for raw content for avoiding regressions when structure changes
2143
2260
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 ] ,
2145
2262
/* local_supported_signer_protocol_version*/ & [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 3 ] ,
2263
+ /* content_len*/ & [ 0 , 0 , 0 , 129 ] ,
2146
2264
/* burn_block*/ & [ 0x55 ; 20 ] ,
2147
2265
/* burn_block_height*/ & [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 100 ] ,
2148
2266
/* current_miner_variant */ & [ 0x01 ] ,
@@ -2160,21 +2278,25 @@ mod test {
2160
2278
2161
2279
assert_eq ! ( signer_message, signer_message_deserialized) ;
2162
2280
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 ( ) ;
2170
2291
2171
2292
let mut bytes = vec ! [ ] ;
2172
2293
signer_message. consensus_serialize ( & mut bytes) . unwrap ( ) ;
2173
2294
2174
2295
// check for raw content for avoiding regressions when structure changes
2175
2296
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 ] ,
2178
2300
/* burn_block*/ & [ 0x55 ; 20 ] ,
2179
2301
/* burn_block_height*/ & [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 100 ] ,
2180
2302
/* current_miner_variant */ & [ 0x00 ] ,
0 commit comments