Skip to main content

hotshot_new_protocol/
message.rs

1use std::marker::PhantomData;
2
3use committable::{Commitment, Committable};
4use hotshot_types::{
5    data::{
6        EpochNumber, VidDisperseShare2, ViewNumber, vid_disperse::AvidmGf2DisperseShareFragment,
7    },
8    message::Proposal as SignedProposal,
9    request_response::ProposalRequestPayload,
10    simple_certificate::{
11        OneHonestThreshold, SimpleCertificate, SuccessThreshold, TimeoutCertificate2,
12    },
13    simple_vote::{
14        HasEpoch, LightClientStateUpdateVote2, QuorumVote2, SimpleVote, TimeoutData2, TimeoutVote2,
15        Vote2Data,
16    },
17    traits::{node_implementation::NodeType, signature_key::SignatureKey},
18    vote::HasViewNumber,
19};
20pub use hotshot_types::{
21    new_protocol::Proposal,
22    simple_certificate::{Certificate1, Certificate2},
23};
24use serde::{Deserialize, Serialize};
25
26pub type Vote2<T> = SimpleVote<T, Vote2Data<T>>;
27pub type TimeoutCertificate<T> = SimpleCertificate<T, TimeoutData2, SuccessThreshold>;
28pub type TimeoutOneHonest<T> = SimpleCertificate<T, TimeoutData2, OneHonestThreshold>;
29
30#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Deserialize)]
31pub enum Unchecked {}
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize)]
34pub enum Validated {}
35
36#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
37#[serde(bound(deserialize = "S: Deserialize<'de>"))]
38pub struct ProposalMessage<T: NodeType, S> {
39    pub proposal: SignedProposal<T, Proposal<T>>,
40    #[serde(skip)]
41    _marker: PhantomData<fn() -> S>,
42}
43
44impl<T: NodeType> ProposalMessage<T, Validated> {
45    pub fn validated(p: SignedProposal<T, Proposal<T>>) -> Self {
46        Self {
47            proposal: p,
48            _marker: PhantomData,
49        }
50    }
51}
52
53impl<T: NodeType> ProposalMessage<T, Unchecked> {
54    /// Wrap a proposal that has not been validated yet
55    pub fn unchecked(p: SignedProposal<T, Proposal<T>>) -> Self {
56        Self {
57            proposal: p,
58            _marker: PhantomData,
59        }
60    }
61}
62
63impl<T: NodeType, S> ProposalMessage<T, S> {
64    #[cfg(test)]
65    pub fn into_unchecked(self) -> ProposalMessage<T, Unchecked> {
66        ProposalMessage {
67            proposal: self.proposal,
68            _marker: PhantomData,
69        }
70    }
71}
72
73impl<T: NodeType, S> HasViewNumber for ProposalMessage<T, S> {
74    fn view_number(&self) -> ViewNumber {
75        self.proposal.data.view_number
76    }
77}
78
79/// A reassembled, signed VID share.
80pub type VidShareMessage<T> = SignedProposal<T, VidDisperseShare2<T>>;
81
82/// A signed per-namespace VID share fragment.
83///
84/// Unicast by the leader to a replica. A replica collects all of a view's
85/// fragments and reassembles them into a [`VidShareMessage`].
86pub type VidShareFragmentMessage<T> = SignedProposal<T, AvidmGf2DisperseShareFragment<T>>;
87
88#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
89#[serde(bound(deserialize = ""))]
90pub struct Vote1<T: NodeType> {
91    pub vote: QuorumVote2<T>,
92    /// Populated only when voting on an epoch-root leaf. Required there; absent otherwise.
93    pub state_vote: Option<LightClientStateUpdateVote2<T>>,
94}
95
96impl<T: NodeType> HasViewNumber for Vote1<T> {
97    fn view_number(&self) -> ViewNumber {
98        self.vote.view_number()
99    }
100}
101
102#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
103#[serde(bound(deserialize = ""))]
104pub struct TimeoutVoteMessage<T: NodeType> {
105    pub vote: TimeoutVote2<T>,
106    pub evidence: Option<CatchupEvidence<T>>,
107}
108
109impl<T: NodeType> HasViewNumber for TimeoutVoteMessage<T> {
110    fn view_number(&self) -> ViewNumber {
111        self.vote.view_number()
112    }
113}
114
115/// The highest certificate a node holds: its locked QC or its latest timeout
116/// certificate, whichever has the higher view. Attached to timeout votes and
117/// sent to peers stuck on stale views, so divergent nodes re-converge on the
118/// highest justified view.
119#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
120#[serde(bound(deserialize = ""))]
121pub enum CatchupEvidence<T: NodeType> {
122    Qc(Certificate1<T>),
123    Tc(TimeoutCertificate2<T>),
124}
125
126impl<T: NodeType> HasViewNumber for CatchupEvidence<T> {
127    fn view_number(&self) -> ViewNumber {
128        match self {
129            Self::Qc(qc) => qc.view_number(),
130            Self::Tc(tc) => tc.view_number(),
131        }
132    }
133}
134
135/// Message sent at the end of an epoch by the current committee
136/// to the next committee.  Both certificates are on the last block of the epoch.
137/// The protocol spec only requires the second certificate, but for consistency
138/// in the code and with the existing Proposal and Leaf structures
139/// We include the Certificate1.  This allows us to use the Certificate1 as the
140/// Justify QC on the first proposal.  The Certificate2 also required on that proposal
141/// but as next_epoch_justify_qc on the Leaf.
142///
143/// We include the proposal because the new leader in the next epoch
144/// will need it to build a header for the first block of the next epoch.
145#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
146#[serde(bound(deserialize = "S: Deserialize<'de>"))]
147pub struct EpochChangeMessage<T: NodeType, S> {
148    pub cert1: Certificate1<T>,
149    pub cert2: Certificate2<T>,
150    pub proposal: Proposal<T>,
151    #[serde(skip)]
152    _marker: PhantomData<fn() -> S>,
153}
154
155impl<T: NodeType> EpochChangeMessage<T, Validated> {
156    /// Wrap certificates this node has verified (or formed itself).
157    pub fn validated(
158        cert1: Certificate1<T>,
159        cert2: Certificate2<T>,
160        proposal: Proposal<T>,
161    ) -> Self {
162        Self {
163            cert1,
164            cert2,
165            proposal,
166            _marker: PhantomData,
167        }
168    }
169}
170
171impl<T: NodeType> EpochChangeMessage<T, Unchecked> {
172    /// Mark this message's certificates as verified.
173    pub(crate) fn into_validated(self) -> EpochChangeMessage<T, Validated> {
174        EpochChangeMessage {
175            cert1: self.cert1,
176            cert2: self.cert2,
177            proposal: self.proposal,
178            _marker: PhantomData,
179        }
180    }
181}
182
183impl<T: NodeType, S> EpochChangeMessage<T, S> {
184    #[cfg(test)]
185    pub fn into_unchecked(self) -> EpochChangeMessage<T, Unchecked> {
186        EpochChangeMessage {
187            cert1: self.cert1,
188            cert2: self.cert2,
189            proposal: self.proposal,
190            _marker: PhantomData,
191        }
192    }
193}
194
195impl<T: NodeType, S> HasViewNumber for EpochChangeMessage<T, S> {
196    fn view_number(&self) -> ViewNumber {
197        self.cert1.view_number()
198    }
199}
200
201impl<T: NodeType, S> HasEpoch for EpochChangeMessage<T, S> {
202    fn epoch(&self) -> Option<EpochNumber> {
203        self.cert1.epoch()
204    }
205}
206
207#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
208#[serde(bound(deserialize = ""))]
209pub struct ProposalFetchRequest<T: NodeType> {
210    pub payload: ProposalRequestPayload<T>,
211    pub signature: <T::SignatureKey as SignatureKey>::PureAssembledSignatureType,
212}
213
214impl<T: NodeType> ProposalFetchRequest<T> {
215    pub fn new(
216        view_number: ViewNumber,
217        key: T::SignatureKey,
218        private_key: &<T::SignatureKey as SignatureKey>::PrivateKey,
219    ) -> Result<Self, <T::SignatureKey as SignatureKey>::SignError> {
220        let payload = ProposalRequestPayload { view_number, key };
221        let signature = T::SignatureKey::sign(private_key, payload.commit().as_ref())?;
222        Ok(Self { payload, signature })
223    }
224
225    pub fn validate_sender(&self, sender: &T::SignatureKey) -> bool {
226        &self.payload.key == sender
227            && self
228                .payload
229                .key
230                .validate(&self.signature, self.payload.commit().as_ref())
231    }
232}
233
234impl<T: NodeType> HasViewNumber for ProposalFetchRequest<T> {
235    fn view_number(&self) -> ViewNumber {
236        self.payload.view_number
237    }
238}
239
240#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
241#[serde(bound(deserialize = "S: Deserialize<'de>"))]
242#[allow(clippy::large_enum_variant)]
243pub enum ConsensusMessage<T: NodeType, S> {
244    Proposal(ProposalMessage<T, S>),
245    Vote1(Vote1<T>),
246    Vote2(Vote2<T>),
247    Certificate1(Certificate1<T>, T::SignatureKey),
248    Certificate2(Certificate2<T>, T::SignatureKey),
249    TimeoutVote(TimeoutVoteMessage<T>),
250    TimeoutCertificate(TimeoutCertificate2<T>),
251    EpochChange(EpochChangeMessage<T, S>),
252    /// The leader's unicast of a per-namespace VID share fragment.
253    VidShareFragment(VidShareFragmentMessage<T>),
254    /// A node's own VID share, broadcast independently of Vote1.
255    VidShareBroadcast(VidDisperseShare2<T>),
256    HighQc(Certificate1<T>),
257}
258
259impl<T: NodeType, S> ConsensusMessage<T, S> {
260    #[cfg(test)]
261    pub fn into_unchecked(self) -> ConsensusMessage<T, Unchecked> {
262        match self {
263            Self::Proposal(p) => ConsensusMessage::Proposal(p.into_unchecked()),
264            Self::Vote1(v) => ConsensusMessage::Vote1(v),
265            Self::Vote2(v) => ConsensusMessage::Vote2(v),
266            Self::Certificate1(c, k) => ConsensusMessage::Certificate1(c, k),
267            Self::Certificate2(c, k) => ConsensusMessage::Certificate2(c, k),
268            Self::TimeoutVote(v) => ConsensusMessage::TimeoutVote(v),
269            Self::TimeoutCertificate(c) => ConsensusMessage::TimeoutCertificate(c),
270            Self::EpochChange(c) => ConsensusMessage::EpochChange(c.into_unchecked()),
271            Self::VidShareFragment(v) => ConsensusMessage::VidShareFragment(v),
272            Self::VidShareBroadcast(v) => ConsensusMessage::VidShareBroadcast(v),
273            Self::HighQc(c) => ConsensusMessage::HighQc(c),
274        }
275    }
276}
277
278impl<T: NodeType, S> HasViewNumber for ConsensusMessage<T, S> {
279    fn view_number(&self) -> ViewNumber {
280        match self {
281            Self::Proposal(proposal) => proposal.view_number(),
282            Self::Vote1(vote) => vote.view_number(),
283            Self::Vote2(vote) => vote.view_number(),
284            Self::Certificate1(certificate, _) => certificate.view_number(),
285            Self::Certificate2(certificate, _) => certificate.view_number(),
286            Self::TimeoutVote(msg) => msg.view_number(),
287            Self::TimeoutCertificate(certificate) => certificate.view_number(),
288            Self::EpochChange(epoch_change) => epoch_change.cert1.view_number(),
289            Self::VidShareFragment(fragment) => fragment.data.view_number(),
290            Self::VidShareBroadcast(vid_share) => vid_share.view_number(),
291            Self::HighQc(certificate) => certificate.view_number(),
292        }
293    }
294}
295
296#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
297#[serde(bound(deserialize = ""))]
298pub enum ProposalFetchMessage<T: NodeType> {
299    Request(ProposalFetchRequest<T>),
300    Response(Box<SignedProposal<T, Proposal<T>>>),
301}
302
303impl<T: NodeType> HasViewNumber for ProposalFetchMessage<T> {
304    fn view_number(&self) -> ViewNumber {
305        match self {
306            Self::Request(request) => request.view_number(),
307            Self::Response(proposal) => proposal.data.view_number(),
308        }
309    }
310}
311
312#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
313#[serde(bound(deserialize = ""))]
314pub struct DedupManifest<T: NodeType> {
315    pub(crate) view: ViewNumber,
316    pub(crate) epoch: EpochNumber,
317    pub(crate) hashes: Vec<Commitment<T::Transaction>>,
318}
319
320#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
321#[serde(bound(deserialize = ""))]
322pub struct TransactionMessage<T: NodeType> {
323    pub(crate) view: ViewNumber,
324    pub(crate) transactions: Vec<T::Transaction>,
325}
326
327#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
328#[serde(bound(deserialize = ""))]
329pub enum BlockMessage<T: NodeType> {
330    Transactions(TransactionMessage<T>),
331    DedupManifest(DedupManifest<T>),
332}
333
334impl<T: NodeType> HasViewNumber for BlockMessage<T> {
335    fn view_number(&self) -> ViewNumber {
336        match self {
337            BlockMessage::Transactions(msg) => msg.view,
338            BlockMessage::DedupManifest(msg) => msg.view,
339        }
340    }
341}
342
343#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
344#[serde(bound(deserialize = "S: Deserialize<'de>"))]
345#[allow(clippy::large_enum_variant)]
346pub enum MessageType<T: NodeType, S> {
347    Consensus(ConsensusMessage<T, S>),
348    Block(BlockMessage<T>),
349    ProposalFetch(ProposalFetchMessage<T>),
350    External(#[serde(with = "serde_bytes")] Vec<u8>),
351}
352
353impl<T: NodeType, S> MessageType<T, S> {
354    #[cfg(test)]
355    pub fn into_unchecked(self) -> MessageType<T, Unchecked> {
356        match self {
357            Self::Consensus(c) => MessageType::Consensus(c.into_unchecked()),
358            Self::Block(b) => MessageType::Block(b),
359            Self::ProposalFetch(r) => MessageType::ProposalFetch(r),
360            Self::External(v) => MessageType::External(v),
361        }
362    }
363}
364
365#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
366#[serde(bound(deserialize = "S: Deserialize<'de>"))]
367pub struct Message<T: NodeType, S> {
368    pub sender: T::SignatureKey,
369    pub message_type: MessageType<T, S>,
370}
371
372impl<T: NodeType, S> Message<T, S> {
373    pub fn is_external(&self) -> bool {
374        matches!(self.message_type, MessageType::External(_))
375    }
376
377    #[cfg(test)]
378    pub fn into_unchecked(self) -> Message<T, Unchecked> {
379        Message {
380            sender: self.sender,
381            message_type: self.message_type.into_unchecked(),
382        }
383    }
384}
385
386impl<T: NodeType, S> HasViewNumber for Message<T, S> {
387    fn view_number(&self) -> ViewNumber {
388        match &self.message_type {
389            MessageType::Consensus(consensus_message) => consensus_message.view_number(),
390            MessageType::Block(block_message) => block_message.view_number(),
391            MessageType::ProposalFetch(message) => message.view_number(),
392            MessageType::External(_) => ViewNumber::new(1), // TODO: This can become a problem
393        }
394    }
395}
396
397pub struct OpaqueMessage<K> {
398    pub sender: K,
399    pub data: Vec<u8>,
400}