guts_consensus/
message.rs

1//! Consensus message types for P2P communication.
2//!
3//! These messages are exchanged between validators during consensus rounds.
4//! The protocol follows Simplex BFT with:
5//! - 2 network hops for block proposal
6//! - 3 network hops for finalization
7
8use crate::block::{Block, BlockId, FinalizedBlock};
9use crate::transaction::{SerializablePublicKey, SerializableSignature, Transaction};
10use bytes::Bytes;
11use serde::{Deserialize, Serialize};
12
13/// Channel ID for consensus messages (high priority).
14pub const CONSENSUS_CHANNEL: u64 = 0;
15
16/// Channel ID for transaction broadcast.
17pub const TRANSACTION_CHANNEL: u64 = 2;
18
19/// Channel ID for block sync requests.
20pub const SYNC_CHANNEL: u64 = 3;
21
22/// Consensus message types exchanged between validators.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub enum ConsensusMessage {
25    /// Block proposal from the leader.
26    Propose(ProposeMessage),
27
28    /// Vote to notarize a block (step 1 of finalization).
29    Notarize(NotarizeMessage),
30
31    /// Vote when leader is unresponsive (timeout).
32    Nullify(NullifyMessage),
33
34    /// Vote to finalize a block (step 2 of finalization).
35    Finalize(FinalizeMessage),
36
37    /// Broadcast a new transaction.
38    Transaction(TransactionMessage),
39
40    /// Request missing blocks.
41    SyncRequest(SyncRequestMessage),
42
43    /// Response with requested blocks.
44    SyncResponse(SyncResponseMessage),
45}
46
47impl ConsensusMessage {
48    /// Encodes the message to bytes.
49    pub fn encode(&self) -> Bytes {
50        let json = serde_json::to_vec(self).expect("message serialization should not fail");
51        Bytes::from(json)
52    }
53
54    /// Decodes a message from bytes.
55    pub fn decode(data: &[u8]) -> Result<Self, serde_json::Error> {
56        serde_json::from_slice(data)
57    }
58
59    /// Returns the message type as a string for logging.
60    pub fn kind(&self) -> &'static str {
61        match self {
62            ConsensusMessage::Propose(_) => "propose",
63            ConsensusMessage::Notarize(_) => "notarize",
64            ConsensusMessage::Nullify(_) => "nullify",
65            ConsensusMessage::Finalize(_) => "finalize",
66            ConsensusMessage::Transaction(_) => "transaction",
67            ConsensusMessage::SyncRequest(_) => "sync_request",
68            ConsensusMessage::SyncResponse(_) => "sync_response",
69        }
70    }
71}
72
73/// Block proposal message from the leader.
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct ProposeMessage {
76    /// The view number for this proposal.
77    pub view: u64,
78
79    /// The proposed block.
80    pub block: Block,
81
82    /// Producer's public key.
83    pub producer: SerializablePublicKey,
84
85    /// Producer's signature over the block hash.
86    pub signature: SerializableSignature,
87}
88
89impl ProposeMessage {
90    /// Creates a new propose message.
91    pub fn new(
92        view: u64,
93        block: Block,
94        producer: SerializablePublicKey,
95        signature: SerializableSignature,
96    ) -> Self {
97        Self {
98            view,
99            block,
100            producer,
101            signature,
102        }
103    }
104
105    /// Returns the block ID.
106    pub fn block_id(&self) -> BlockId {
107        self.block.id()
108    }
109}
110
111/// Vote to notarize a block.
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct NotarizeMessage {
114    /// The view number.
115    pub view: u64,
116
117    /// Block ID being voted for.
118    pub block_id: BlockId,
119
120    /// Voter's public key.
121    pub voter: SerializablePublicKey,
122
123    /// Voter's signature over (view, block_id).
124    pub signature: SerializableSignature,
125}
126
127impl NotarizeMessage {
128    /// Creates a new notarize message.
129    pub fn new(
130        view: u64,
131        block_id: BlockId,
132        voter: SerializablePublicKey,
133        signature: SerializableSignature,
134    ) -> Self {
135        Self {
136            view,
137            block_id,
138            voter,
139            signature,
140        }
141    }
142
143    /// Returns the data that should be signed for this vote.
144    pub fn signing_data(&self) -> Vec<u8> {
145        let mut data = Vec::new();
146        data.extend_from_slice(b"NOTARIZE:");
147        data.extend_from_slice(&self.view.to_le_bytes());
148        data.extend_from_slice(self.block_id.as_bytes());
149        data
150    }
151}
152
153/// Nullify vote when leader is unresponsive.
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct NullifyMessage {
156    /// The view number being nullified.
157    pub view: u64,
158
159    /// Voter's public key.
160    pub voter: SerializablePublicKey,
161
162    /// Voter's signature over (view, "NULLIFY").
163    pub signature: SerializableSignature,
164}
165
166impl NullifyMessage {
167    /// Creates a new nullify message.
168    pub fn new(view: u64, voter: SerializablePublicKey, signature: SerializableSignature) -> Self {
169        Self {
170            view,
171            voter,
172            signature,
173        }
174    }
175
176    /// Returns the data that should be signed for this vote.
177    pub fn signing_data(&self) -> Vec<u8> {
178        let mut data = Vec::new();
179        data.extend_from_slice(b"NULLIFY:");
180        data.extend_from_slice(&self.view.to_le_bytes());
181        data
182    }
183}
184
185/// Vote to finalize a block.
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct FinalizeMessage {
188    /// The view number.
189    pub view: u64,
190
191    /// Block ID being finalized.
192    pub block_id: BlockId,
193
194    /// Voter's public key.
195    pub voter: SerializablePublicKey,
196
197    /// Voter's signature over (view, block_id, "FINALIZE").
198    pub signature: SerializableSignature,
199}
200
201impl FinalizeMessage {
202    /// Creates a new finalize message.
203    pub fn new(
204        view: u64,
205        block_id: BlockId,
206        voter: SerializablePublicKey,
207        signature: SerializableSignature,
208    ) -> Self {
209        Self {
210            view,
211            block_id,
212            voter,
213            signature,
214        }
215    }
216
217    /// Returns the data that should be signed for this vote.
218    pub fn signing_data(&self) -> Vec<u8> {
219        let mut data = Vec::new();
220        data.extend_from_slice(b"FINALIZE:");
221        data.extend_from_slice(&self.view.to_le_bytes());
222        data.extend_from_slice(self.block_id.as_bytes());
223        data
224    }
225}
226
227/// Broadcast a new transaction to the network.
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct TransactionMessage {
230    /// The transaction.
231    pub transaction: Transaction,
232}
233
234impl TransactionMessage {
235    /// Creates a new transaction message.
236    pub fn new(transaction: Transaction) -> Self {
237        Self { transaction }
238    }
239}
240
241/// Request missing blocks from a peer.
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct SyncRequestMessage {
244    /// Starting height (exclusive).
245    pub from_height: u64,
246
247    /// Ending height (inclusive).
248    pub to_height: u64,
249
250    /// Requestor's public key.
251    pub requestor: SerializablePublicKey,
252}
253
254impl SyncRequestMessage {
255    /// Creates a new sync request.
256    pub fn new(from_height: u64, to_height: u64, requestor: SerializablePublicKey) -> Self {
257        Self {
258            from_height,
259            to_height,
260            requestor,
261        }
262    }
263}
264
265/// Response with requested blocks.
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct SyncResponseMessage {
268    /// The finalized blocks.
269    pub blocks: Vec<FinalizedBlock>,
270
271    /// Responder's public key.
272    pub responder: SerializablePublicKey,
273}
274
275impl SyncResponseMessage {
276    /// Creates a new sync response.
277    pub fn new(blocks: Vec<FinalizedBlock>, responder: SerializablePublicKey) -> Self {
278        Self { blocks, responder }
279    }
280}
281
282/// Vote collection for tracking quorum.
283#[derive(Debug, Clone, Default)]
284pub struct VoteCollector {
285    /// Notarize votes by block ID.
286    notarize_votes: std::collections::HashMap<BlockId, Vec<NotarizeMessage>>,
287
288    /// Finalize votes by block ID.
289    finalize_votes: std::collections::HashMap<BlockId, Vec<FinalizeMessage>>,
290
291    /// Nullify votes by view.
292    nullify_votes: std::collections::HashMap<u64, Vec<NullifyMessage>>,
293}
294
295impl VoteCollector {
296    /// Creates a new vote collector.
297    pub fn new() -> Self {
298        Self::default()
299    }
300
301    /// Adds a notarize vote.
302    pub fn add_notarize(&mut self, vote: NotarizeMessage) {
303        self.notarize_votes
304            .entry(vote.block_id)
305            .or_default()
306            .push(vote);
307    }
308
309    /// Adds a finalize vote.
310    pub fn add_finalize(&mut self, vote: FinalizeMessage) {
311        self.finalize_votes
312            .entry(vote.block_id)
313            .or_default()
314            .push(vote);
315    }
316
317    /// Adds a nullify vote.
318    pub fn add_nullify(&mut self, vote: NullifyMessage) {
319        self.nullify_votes.entry(vote.view).or_default().push(vote);
320    }
321
322    /// Gets notarize votes for a block.
323    pub fn get_notarize_votes(&self, block_id: &BlockId) -> &[NotarizeMessage] {
324        self.notarize_votes
325            .get(block_id)
326            .map(|v| v.as_slice())
327            .unwrap_or(&[])
328    }
329
330    /// Gets finalize votes for a block.
331    pub fn get_finalize_votes(&self, block_id: &BlockId) -> &[FinalizeMessage] {
332        self.finalize_votes
333            .get(block_id)
334            .map(|v| v.as_slice())
335            .unwrap_or(&[])
336    }
337
338    /// Gets nullify votes for a view.
339    pub fn get_nullify_votes(&self, view: u64) -> &[NullifyMessage] {
340        self.nullify_votes
341            .get(&view)
342            .map(|v| v.as_slice())
343            .unwrap_or(&[])
344    }
345
346    /// Returns the number of notarize votes for a block.
347    pub fn notarize_count(&self, block_id: &BlockId) -> usize {
348        self.notarize_votes
349            .get(block_id)
350            .map(|v| v.len())
351            .unwrap_or(0)
352    }
353
354    /// Returns the number of finalize votes for a block.
355    pub fn finalize_count(&self, block_id: &BlockId) -> usize {
356        self.finalize_votes
357            .get(block_id)
358            .map(|v| v.len())
359            .unwrap_or(0)
360    }
361
362    /// Returns the number of nullify votes for a view.
363    pub fn nullify_count(&self, view: u64) -> usize {
364        self.nullify_votes.get(&view).map(|v| v.len()).unwrap_or(0)
365    }
366
367    /// Clears votes for a given view (after finalization or nullification).
368    pub fn clear_view(&mut self, view: u64) {
369        self.nullify_votes.remove(&view);
370        // Note: block votes are kept for proof of finalization
371    }
372
373    /// Clears all votes (used when syncing).
374    pub fn clear_all(&mut self) {
375        self.notarize_votes.clear();
376        self.finalize_votes.clear();
377        self.nullify_votes.clear();
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384    use commonware_cryptography::{ed25519, PrivateKeyExt, Signer};
385
386    fn test_keypair() -> (SerializablePublicKey, SerializableSignature) {
387        let key = ed25519::PrivateKey::from_seed(42);
388        let sig = key.sign(Some(b"_GUTS"), b"test");
389        (
390            SerializablePublicKey::from_pubkey(&key.public_key()),
391            SerializableSignature::from_signature(&sig),
392        )
393    }
394
395    #[test]
396    fn test_consensus_message_roundtrip() {
397        let (pk, sig) = test_keypair();
398        let block_id = BlockId::from_bytes([1u8; 32]);
399
400        let msg = ConsensusMessage::Notarize(NotarizeMessage {
401            view: 10,
402            block_id,
403            voter: pk,
404            signature: sig,
405        });
406
407        let encoded = msg.encode();
408        let decoded = ConsensusMessage::decode(&encoded).unwrap();
409
410        assert_eq!(decoded.kind(), "notarize");
411        if let ConsensusMessage::Notarize(n) = decoded {
412            assert_eq!(n.view, 10);
413            assert_eq!(n.block_id, block_id);
414        } else {
415            panic!("unexpected message type");
416        }
417    }
418
419    #[test]
420    fn test_vote_collector() {
421        let (pk, sig) = test_keypair();
422        let block_id = BlockId::from_bytes([1u8; 32]);
423
424        let mut collector = VoteCollector::new();
425
426        let vote = NotarizeMessage {
427            view: 1,
428            block_id,
429            voter: pk.clone(),
430            signature: sig.clone(),
431        };
432
433        collector.add_notarize(vote);
434        assert_eq!(collector.notarize_count(&block_id), 1);
435
436        let finalize_vote = FinalizeMessage {
437            view: 1,
438            block_id,
439            voter: pk,
440            signature: sig,
441        };
442
443        collector.add_finalize(finalize_vote);
444        assert_eq!(collector.finalize_count(&block_id), 1);
445    }
446
447    #[test]
448    fn test_signing_data() {
449        let (pk, sig) = test_keypair();
450        let block_id = BlockId::from_bytes([1u8; 32]);
451
452        let notarize = NotarizeMessage::new(5, block_id, pk.clone(), sig.clone());
453        let data = notarize.signing_data();
454        assert!(data.starts_with(b"NOTARIZE:"));
455
456        let nullify = NullifyMessage::new(5, pk.clone(), sig.clone());
457        let data = nullify.signing_data();
458        assert!(data.starts_with(b"NULLIFY:"));
459
460        let finalize = FinalizeMessage::new(5, block_id, pk, sig);
461        let data = finalize.signing_data();
462        assert!(data.starts_with(b"FINALIZE:"));
463    }
464}