Skip to content

Commit df7c51a

Browse files
committed
feat: add versioned and backwards-compatible server version to block proposal
1 parent 91a1398 commit df7c51a

File tree

8 files changed

+110
-7
lines changed

8 files changed

+110
-7
lines changed

libsigner/src/events.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use blockstack_lib::net::api::postblock_proposal::{
3131
};
3232
use blockstack_lib::net::stackerdb::MINER_SLOT_COUNT;
3333
use blockstack_lib::util_lib::boot::boot_code_id;
34+
use blockstack_lib::version_string;
3435
use clarity::vm::types::serialization::SerializationError;
3536
use clarity::vm::types::QualifiedContractIdentifier;
3637
use serde::{Deserialize, Serialize};
@@ -45,11 +46,13 @@ use stacks_common::types::chainstate::{
4546
};
4647
use stacks_common::util::hash::{Hash160, Sha512Trunc256Sum};
4748
use stacks_common::util::HexError;
49+
use stacks_common::versions::STACKS_NODE_VERSION;
4850
use tiny_http::{
4951
Method as HttpMethod, Request as HttpRequest, Response as HttpResponse, Server as HttpServer,
5052
};
5153

5254
use crate::http::{decode_http_body, decode_http_request};
55+
use crate::v0::messages::BLOCK_RESPONSE_DATA_MAX_SIZE;
5356
use crate::EventError;
5457

5558
/// Define the trait for the event processor
@@ -69,24 +72,114 @@ pub struct BlockProposal {
6972
pub burn_height: u64,
7073
/// The reward cycle the block is mined during
7174
pub reward_cycle: u64,
75+
/// Versioned and backwards-compatible block proposal data
76+
pub block_proposal_data: BlockProposalData,
7277
}
7378

7479
impl StacksMessageCodec for BlockProposal {
7580
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
7681
self.block.consensus_serialize(fd)?;
7782
self.burn_height.consensus_serialize(fd)?;
7883
self.reward_cycle.consensus_serialize(fd)?;
84+
self.block_proposal_data.consensus_serialize(fd)?;
7985
Ok(())
8086
}
8187

8288
fn consensus_deserialize<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
8389
let block = NakamotoBlock::consensus_deserialize(fd)?;
8490
let burn_height = u64::consensus_deserialize(fd)?;
8591
let reward_cycle = u64::consensus_deserialize(fd)?;
92+
let block_proposal_data = BlockProposalData::consensus_deserialize(fd)?;
8693
Ok(BlockProposal {
8794
block,
8895
burn_height,
8996
reward_cycle,
97+
block_proposal_data,
98+
})
99+
}
100+
}
101+
102+
/// The latest version of the block response data
103+
pub const BLOCK_PROPOSAL_DATA_VERSION: u8 = 2;
104+
105+
/// Versioned, backwards-compatible struct for block response data
106+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
107+
pub struct BlockProposalData {
108+
/// The version of the block proposal data
109+
pub version: u8,
110+
/// The miner's server version
111+
pub server_version: String,
112+
/// When deserializing future versions,
113+
/// there may be extra bytes that we don't know about
114+
pub unknown_bytes: Vec<u8>,
115+
}
116+
117+
impl BlockProposalData {
118+
/// Create a new BlockProposalData for the provided server version and unknown bytes
119+
pub fn new(server_version: String) -> Self {
120+
Self {
121+
version: BLOCK_PROPOSAL_DATA_VERSION,
122+
server_version,
123+
unknown_bytes: vec![],
124+
}
125+
}
126+
127+
/// Create a new BlockProposalData with the current build's version
128+
pub fn from_current_version() -> Self {
129+
let server_version = version_string(
130+
"stacks-node",
131+
option_env!("STACKS_NODE_VERSION").or(Some(STACKS_NODE_VERSION)),
132+
);
133+
Self::new(server_version)
134+
}
135+
136+
/// Create an empty BlockProposalData
137+
pub fn empty() -> Self {
138+
Self::new(String::new())
139+
}
140+
141+
/// Serialize the "inner" block response data. Used to determine the bytes length of the serialized block response data
142+
fn inner_consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
143+
write_next(fd, &self.server_version.as_bytes().to_vec())?;
144+
// write_next(fd, &self.unknown_bytes)?;
145+
fd.write_all(&self.unknown_bytes)
146+
.map_err(CodecError::WriteError)?;
147+
Ok(())
148+
}
149+
}
150+
151+
impl StacksMessageCodec for BlockProposalData {
152+
/// Serialize the block response data.
153+
/// When creating a new version of the block response data, we are only ever
154+
/// appending new bytes to the end of the struct. When serializing, we use
155+
/// `bytes_len` to ensure that older versions of the code can read through the
156+
/// end of the serialized bytes.
157+
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
158+
write_next(fd, &self.version)?;
159+
let mut inner_bytes = vec![];
160+
self.inner_consensus_serialize(&mut inner_bytes)?;
161+
write_next(fd, &inner_bytes)?;
162+
Ok(())
163+
}
164+
165+
/// Deserialize the block response data in a backwards-compatible manner.
166+
/// When creating a new version of the block response data, we are only ever
167+
/// appending new bytes to the end of the struct. When deserializing, we use
168+
/// `bytes_len` to ensure that we read through the end of the serialized bytes.
169+
fn consensus_deserialize<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
170+
let Ok(version) = read_next(fd) else {
171+
return Ok(Self::empty());
172+
};
173+
let inner_bytes: Vec<u8> = read_next_at_most(fd, BLOCK_RESPONSE_DATA_MAX_SIZE)?;
174+
let mut inner_reader = inner_bytes.as_slice();
175+
let server_version: Vec<u8> = read_next(&mut inner_reader)?;
176+
let server_version = String::from_utf8(server_version).map_err(|e| {
177+
CodecError::DeserializeError(format!("Failed to decode server version: {:?}", &e))
178+
})?;
179+
Ok(Self {
180+
version,
181+
server_version,
182+
unknown_bytes: inner_reader.to_vec(),
90183
})
91184
}
92185
}

libsigner/src/libsigner.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ use stacks_common::versions::STACKS_SIGNER_VERSION;
5757

5858
pub use crate::error::{EventError, RPCError};
5959
pub use crate::events::{
60-
BlockProposal, EventReceiver, EventStopSignaler, SignerEvent, SignerEventReceiver,
61-
SignerEventTrait, SignerStopSignaler,
60+
BlockProposal, BlockProposalData, EventReceiver, EventStopSignaler, SignerEvent,
61+
SignerEventReceiver, SignerEventTrait, SignerStopSignaler,
6262
};
6363
pub use crate::runloop::{RunningSigner, Signer, SignerRunLoop};
6464
pub use crate::session::{SignerSession, StackerDBSession};

libsigner/src/tests/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use stacks_common::codec::{
4141
use stacks_common::util::secp256k1::Secp256k1PrivateKey;
4242
use stacks_common::util::sleep_ms;
4343

44-
use crate::events::{SignerEvent, SignerEventTrait};
44+
use crate::events::{BlockProposalData, SignerEvent, SignerEventTrait};
4545
use crate::v0::messages::{BlockRejection, SignerMessage};
4646
use crate::{BlockProposal, Signer, SignerEventReceiver, SignerRunLoop};
4747

@@ -126,6 +126,7 @@ fn test_simple_signer() {
126126
},
127127
burn_height: 2,
128128
reward_cycle: 1,
129+
block_proposal_data: BlockProposalData::empty(),
129130
};
130131
for i in 0..max_events {
131132
let privk = Secp256k1PrivateKey::random();

libsigner/src/v0/messages.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,7 @@ mod test {
11601160
use stacks_common::types::chainstate::StacksPrivateKey;
11611161

11621162
use super::{StacksMessageCodecExtensions, *};
1163+
use crate::events::BlockProposalData;
11631164

11641165
#[test]
11651166
fn signer_slots_count_is_sane() {
@@ -1276,6 +1277,7 @@ mod test {
12761277
block,
12771278
burn_height: thread_rng().next_u64(),
12781279
reward_cycle: thread_rng().next_u64(),
1280+
block_proposal_data: BlockProposalData::empty(),
12791281
};
12801282
let signer_message = SignerMessage::BlockProposal(block_proposal);
12811283
let serialized_signer_message = signer_message.serialize_to_vec();

stacks-signer/src/signerdb.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,7 @@ mod tests {
11811181
use clarity::types::chainstate::{StacksBlockId, StacksPrivateKey, StacksPublicKey};
11821182
use clarity::util::hash::Hash160;
11831183
use clarity::util::secp256k1::MessageSignature;
1184-
use libsigner::BlockProposal;
1184+
use libsigner::{BlockProposal, BlockProposalData};
11851185

11861186
use super::*;
11871187
use crate::signerdb::NakamotoBlockVote;
@@ -1204,6 +1204,7 @@ mod tests {
12041204
block,
12051205
burn_height: 7,
12061206
reward_cycle: 42,
1207+
block_proposal_data: BlockProposalData::empty(),
12071208
};
12081209
overrides(&mut block_proposal);
12091210
(BlockInfo::from(block_proposal.clone()), block_proposal)

stacks-signer/src/tests/chainstate.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use blockstack_lib::net::api::get_tenures_fork_info::TenureForkingInfo;
2929
use blockstack_lib::net::api::getsortition::SortitionInfo;
3030
use clarity::types::chainstate::{BurnchainHeaderHash, SortitionId};
3131
use clarity::util::vrf::VRFProof;
32-
use libsigner::BlockProposal;
32+
use libsigner::{BlockProposal, BlockProposalData};
3333
use slog::slog_info;
3434
use stacks_common::bitvec::BitVec;
3535
use stacks_common::consts::CHAIN_ID_TESTNET;
@@ -244,6 +244,7 @@ fn reorg_timing_testing(
244244
},
245245
burn_height: 2,
246246
reward_cycle: 1,
247+
block_proposal_data: BlockProposalData::empty(),
247248
};
248249
let mut header_clone = block_proposal_1.block.header.clone();
249250
let mut block_info_1 = BlockInfo::from(block_proposal_1);
@@ -511,6 +512,7 @@ fn check_sortition_timeout() {
511512
},
512513
burn_height: 2,
513514
reward_cycle: 1,
515+
block_proposal_data: BlockProposalData::empty(),
514516
};
515517

516518
let mut block_info = BlockInfo::from(block_proposal);

testnet/stacks-node/src/nakamoto_node/signer_coordinator.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::thread::JoinHandle;
2121
use std::time::{Duration, Instant};
2222

2323
use libsigner::v0::messages::{MinerSlotID, SignerMessage as SignerMessageV0};
24-
use libsigner::{BlockProposal, SignerSession, StackerDBSession};
24+
use libsigner::{BlockProposal, BlockProposalData, SignerSession, StackerDBSession};
2525
use stacks::burnchains::Burnchain;
2626
use stacks::chainstate::burn::db::sortdb::SortitionDB;
2727
use stacks::chainstate::burn::{BlockSnapshot, ConsensusHash};
@@ -250,6 +250,7 @@ impl SignerCoordinator {
250250
block: block.clone(),
251251
burn_height: election_sortition.block_height,
252252
reward_cycle: reward_cycle_id,
253+
block_proposal_data: BlockProposalData::from_current_version(),
253254
};
254255

255256
let block_proposal_message = SignerMessageV0::BlockProposal(block_proposal);

testnet/stacks-node/src/tests/signer/v0.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ use libsigner::v0::messages::{
2626
BlockAccepted, BlockRejection, BlockResponse, MessageSlotID, MinerSlotID, RejectCode,
2727
SignerMessage,
2828
};
29-
use libsigner::{BlockProposal, SignerSession, StackerDBSession, VERSION_STRING};
29+
use libsigner::{
30+
BlockProposal, BlockProposalData, SignerSession, StackerDBSession, VERSION_STRING,
31+
};
3032
use serde::Deserialize;
3133
use stacks::address::AddressHashMode;
3234
use stacks::burnchains::Txid;
@@ -405,6 +407,7 @@ impl SignerTest<SpawnedSigner> {
405407
block,
406408
burn_height,
407409
reward_cycle,
410+
block_proposal_data: BlockProposalData::empty(),
408411
});
409412
let miner_sk = self
410413
.running_nodes

0 commit comments

Comments
 (0)