Skip to main content

hotshot_new_protocol/
message.rs

1pub mod fetch;
2pub mod payload;
3
4use std::marker::PhantomData;
5
6use committable::{Commitment, Committable};
7use hotshot_types::{
8    data::{
9        EpochNumber, UpgradeProposal2, VidDisperseShare2, ViewNumber,
10        vid_disperse::AvidmGf2DisperseShareFragment,
11    },
12    message::{Proposal as SignedProposal, UpgradeLock},
13    request_response::ProposalRequestPayload,
14    simple_certificate::{TimeoutCertificate2, TimeoutCertificate3, TimeoutEvidence},
15    simple_vote::{
16        HasEpoch, LightClientStateUpdateVote2, LockView, QuorumData2, QuorumVote2, SimpleVote,
17        TimeoutData3, TimeoutLock, TimeoutVote2, TimeoutVote3, UpgradeVote2, Vote2Data,
18    },
19    traits::{
20        block_contents::BlockHeader, node_implementation::NodeType, signature_key::SignatureKey,
21    },
22    utils::{epoch_from_block_number, is_last_block},
23    vote::{HasViewNumber, Vote},
24};
25pub use hotshot_types::{
26    new_protocol::Proposal,
27    simple_certificate::{Certificate1, Certificate2},
28};
29use serde::{Deserialize, Serialize};
30
31use crate::{
32    helpers::proposal_commitment,
33    message::payload::PayloadFetchMessage,
34    proposal::{
35        MalformedProposal, epoch_matches_height, justify_qc_matches_parent,
36        state_cert_matches_parent, view_change_evidence_matches_parent,
37    },
38};
39
40pub type Vote2<T> = SimpleVote<T, Vote2Data<T>>;
41
42#[derive(Clone, Debug, PartialEq, Hash, Eq)]
43pub enum TimeoutVote<T: NodeType> {
44    V2(TimeoutVote2<T>),
45    V3(TimeoutBallot<T>),
46}
47
48/// A timeout vote that signs its signer's lock, with the certificate of that
49/// lock.
50///
51/// The lock the vote signs is the certificate's, so the two always agree;
52/// `None` is a signer locked on nothing but genesis. A ballot can only be
53/// signed by its own signer ([`Self::sign`]) or read from a message
54/// ([`TimeoutVoteMessage3::ballot`]), and neither checks the signatures.
55#[derive(Clone, Debug, PartialEq, Hash, Eq)]
56pub struct TimeoutBallot<T: NodeType> {
57    vote: TimeoutVote3<T>,
58    lock: Option<Certificate1<T>>,
59}
60
61impl<T: NodeType> TimeoutBallot<T> {
62    /// Sign a timeout vote for `view` in `epoch` naming the lock `lock`
63    /// certifies.
64    pub fn sign(
65        view: ViewNumber,
66        epoch: EpochNumber,
67        lock: Option<Certificate1<T>>,
68        public_key: &T::SignatureKey,
69        private_key: &<T::SignatureKey as SignatureKey>::PrivateKey,
70        upgrade_lock: &UpgradeLock<T>,
71    ) -> anyhow::Result<Self> {
72        let data = TimeoutData3 {
73            view,
74            epoch,
75            lock: lock.as_ref().map(LockView::of),
76        };
77        let vote =
78            SimpleVote::create_signed_vote(data, view, public_key, private_key, upgrade_lock)
79                .map_err(|err| anyhow::anyhow!("failed to sign timeout vote: {err}"))?;
80        Ok(Self { vote, lock })
81    }
82
83    /// The signed vote.
84    pub fn vote(&self) -> &TimeoutVote3<T> {
85        &self.vote
86    }
87
88    /// The certificate of the lock the vote signs.
89    pub fn lock(&self) -> Option<&Certificate1<T>> {
90        self.lock.as_ref()
91    }
92
93    /// The lock the vote signs, by what is checked of it.
94    pub fn timeout_lock(&self) -> TimeoutLock<'_, T> {
95        TimeoutLock::of(self.lock.as_ref(), self.vote.data.epoch)
96    }
97}
98
99impl<T: NodeType> TimeoutVote<T> {
100    pub fn binds_epoch(&self) -> bool {
101        matches!(self, Self::V3(_))
102    }
103
104    pub fn signing_key(&self) -> T::SignatureKey {
105        match self {
106            Self::V2(vote) => vote.signing_key(),
107            Self::V3(ballot) => ballot.vote.signing_key(),
108        }
109    }
110
111    pub fn is_well_formed(&self) -> bool {
112        match self {
113            Self::V2(v) => v.view_number() == v.data.view,
114            // Its data is built from its view.
115            Self::V3(_) => true,
116        }
117    }
118}
119
120impl<T: NodeType> HasViewNumber for TimeoutVote<T> {
121    fn view_number(&self) -> ViewNumber {
122        match self {
123            Self::V2(vote) => vote.view_number(),
124            Self::V3(ballot) => ballot.vote.view_number(),
125        }
126    }
127}
128
129impl<T: NodeType> HasEpoch for TimeoutVote<T> {
130    fn epoch(&self) -> Option<EpochNumber> {
131        match self {
132            Self::V2(vote) => vote.data.epoch,
133            Self::V3(ballot) => Some(ballot.vote.data.epoch),
134        }
135    }
136}
137
138#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Deserialize)]
139pub enum Unchecked {}
140
141#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize)]
142pub enum Validated {}
143
144#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
145#[serde(bound(deserialize = "S: Deserialize<'de>"))]
146pub struct ProposalMessage<T: NodeType, S> {
147    pub proposal: SignedProposal<T, Proposal<T>>,
148    #[serde(skip)]
149    _marker: PhantomData<fn() -> S>,
150}
151
152impl<T: NodeType> ProposalMessage<T, Validated> {
153    pub fn validated(p: SignedProposal<T, Proposal<T>>) -> Self {
154        Self {
155            proposal: p,
156            _marker: PhantomData,
157        }
158    }
159}
160
161impl<T: NodeType> ProposalMessage<T, Unchecked> {
162    /// Wrap a proposal that has not been validated yet
163    pub fn unchecked(p: SignedProposal<T, Proposal<T>>) -> Self {
164        Self {
165            proposal: p,
166            _marker: PhantomData,
167        }
168    }
169}
170
171impl<T: NodeType, S> ProposalMessage<T, S> {
172    #[cfg(any(test, feature = "testing"))]
173    pub fn into_unchecked(self) -> ProposalMessage<T, Unchecked> {
174        ProposalMessage {
175            proposal: self.proposal,
176            _marker: PhantomData,
177        }
178    }
179}
180
181impl<T: NodeType, S> HasViewNumber for ProposalMessage<T, S> {
182    fn view_number(&self) -> ViewNumber {
183        self.proposal.data.view_number
184    }
185}
186
187/// A reassembled, signed VID share.
188pub type VidShareMessage<T> = SignedProposal<T, VidDisperseShare2<T>>;
189
190/// A signed per-namespace VID share fragment.
191///
192/// Unicast by the leader to a replica. A replica collects all of a view's
193/// fragments and reassembles them into a [`VidShareMessage`].
194pub type VidShareFragmentMessage<T> = SignedProposal<T, AvidmGf2DisperseShareFragment<T>>;
195
196#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
197#[serde(bound(deserialize = ""))]
198pub struct Vote1<T: NodeType> {
199    pub vote: QuorumVote2<T>,
200    /// Populated only when voting on an epoch-root leaf. Required there; absent otherwise.
201    pub state_vote: Option<LightClientStateUpdateVote2<T>>,
202}
203
204impl<T: NodeType> HasViewNumber for Vote1<T> {
205    fn view_number(&self) -> ViewNumber {
206        self.vote.view_number()
207    }
208}
209
210/// The leader's broadcast of an upgrade proposal for the network to vote on.
211pub type UpgradeProposalMessage<T> = SignedProposal<T, UpgradeProposal2>;
212
213/// An upgrade vote, broadcast all-to-all. Its signed data binds the voter's
214/// epoch, which selects the stake table under which the vote is tallied.
215pub type UpgradeVoteMessage<T> = UpgradeVote2<T>;
216
217#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
218#[serde(bound(deserialize = ""))]
219pub struct TimeoutVoteMessage<T: NodeType> {
220    pub vote: TimeoutVote2<T>,
221    pub evidence: Option<CatchupEvidence<T>>,
222}
223
224impl<T: NodeType> HasViewNumber for TimeoutVoteMessage<T> {
225    fn view_number(&self) -> ViewNumber {
226        self.vote.view_number()
227    }
228}
229
230/// A timeout vote that signs its signer's lock, as it travels.
231///
232/// The signed data is not sent: it is the view, the epoch and the lock of
233/// `lock`, so the lock a vote signs and the certificate it comes with cannot
234/// disagree, and neither can its view and the view its data names.
235#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
236#[serde(bound(deserialize = ""))]
237pub struct TimeoutVoteMessage3<T: NodeType> {
238    pub signer: T::SignatureKey,
239    pub signature: <T::SignatureKey as SignatureKey>::PureAssembledSignatureType,
240    pub view: ViewNumber,
241    pub epoch: EpochNumber,
242    /// The certificate of the signer's lock; `None` if it is locked on nothing
243    /// but genesis. A lock no quorum certified cannot be counted.
244    pub lock: Option<Certificate1<T>>,
245    /// The signer's catchup evidence.
246    ///
247    /// If the `lock` serves as catchup evidence it is `None`.
248    pub evidence: Option<CatchupEvidence<T>>,
249}
250
251impl<T: NodeType> TimeoutVoteMessage3<T> {
252    /// The message for `ballot`, with `evidence` left out if it is the lock.
253    pub fn new(ballot: TimeoutBallot<T>, evidence: Option<CatchupEvidence<T>>) -> Self {
254        let TimeoutBallot { vote, lock } = ballot;
255        let evidence =
256            evidence.filter(|e| !matches!(e, CatchupEvidence::Qc(qc) if Some(qc) == lock.as_ref()));
257        Self {
258            signer: vote.signature.0,
259            signature: vote.signature.1,
260            view: vote.view_number,
261            epoch: vote.data.epoch,
262            lock,
263            evidence,
264        }
265    }
266
267    /// The signer's catchup evidence: the one the message carries, or else
268    /// its lock.
269    pub fn evidence(&self) -> Option<CatchupEvidence<T>> {
270        self.evidence
271            .clone()
272            .or_else(|| self.lock.clone().map(CatchupEvidence::Qc))
273    }
274
275    /// The vote, if its lock is a signed certificate no later than the view
276    /// it times out and its epoch.
277    pub fn ballot(&self) -> Result<TimeoutBallot<T>, &'static str> {
278        if let Some(cert) = &self.lock {
279            if cert.view_number() == ViewNumber::genesis() {
280                return Err("the genesis certificate is no lock");
281            }
282            if cert.view_number() > self.view {
283                return Err("the lock is later than the view timed out");
284            }
285            if cert.data.epoch.is_some_and(|epoch| epoch > self.epoch) {
286                return Err("the lock is later than the vote's epoch");
287            }
288        }
289        let data = TimeoutData3 {
290            view: self.view,
291            epoch: self.epoch,
292            lock: self.lock.as_ref().map(LockView::of),
293        };
294        Ok(TimeoutBallot {
295            vote: SimpleVote {
296                signature: (self.signer.clone(), self.signature.clone()),
297                data,
298                view_number: self.view,
299            },
300            lock: self.lock.clone(),
301        })
302    }
303}
304
305impl<T: NodeType> HasViewNumber for TimeoutVoteMessage3<T> {
306    fn view_number(&self) -> ViewNumber {
307        self.view
308    }
309}
310
311/// The highest certificate a node holds: its locked QC or its latest timeout
312/// certificate, whichever has the higher view. Attached to timeout votes and
313/// sent to peers stuck on stale views, so divergent nodes re-converge on the
314/// highest justified view.
315#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
316#[serde(bound(deserialize = ""))]
317pub enum CatchupEvidence<T: NodeType> {
318    Qc(Certificate1<T>),
319    Tc(TimeoutCertificate2<T>),
320    Tc3(TimeoutCertificate3<T>),
321}
322
323impl<T: NodeType> From<&TimeoutEvidence<T>> for CatchupEvidence<T> {
324    fn from(evidence: &TimeoutEvidence<T>) -> Self {
325        match evidence {
326            TimeoutEvidence::V2(cert) => Self::Tc(cert.clone()),
327            TimeoutEvidence::V3(cert) => Self::Tc3(cert.clone()),
328        }
329    }
330}
331
332impl<T: NodeType> HasViewNumber for CatchupEvidence<T> {
333    fn view_number(&self) -> ViewNumber {
334        match self {
335            Self::Qc(qc) => qc.view_number(),
336            Self::Tc(tc) => tc.view_number(),
337            Self::Tc3(tc) => tc.view_number(),
338        }
339    }
340}
341
342/// Message sent at the end of an epoch by the current committee
343/// to the next committee.  Both certificates are on the last block of the epoch.
344/// The protocol spec only requires the second certificate, but for consistency
345/// in the code and with the existing Proposal and Leaf structures
346/// We include the Certificate1.  This allows us to use the Certificate1 as the
347/// Justify QC on the first proposal.  The Certificate2 also required on that proposal
348/// but as next_epoch_justify_qc on the Leaf.
349///
350/// We include the proposal because the new leader in the next epoch
351/// will need it to build a header for the first block of the next epoch.
352#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
353#[serde(bound(deserialize = "S: Deserialize<'de>"))]
354pub struct EpochChangeMessage<T: NodeType, S> {
355    pub cert1: Certificate1<T>,
356    pub cert2: Certificate2<T>,
357    pub proposal: Proposal<T>,
358    #[serde(skip)]
359    _marker: PhantomData<fn() -> S>,
360}
361
362impl<T: NodeType> EpochChangeMessage<T, Validated> {
363    /// Wrap certificates this node has verified (or formed itself).
364    pub fn validated(
365        cert1: Certificate1<T>,
366        cert2: Certificate2<T>,
367        proposal: Proposal<T>,
368    ) -> Self {
369        Self {
370            cert1,
371            cert2,
372            proposal,
373            _marker: PhantomData,
374        }
375    }
376}
377
378impl<T: NodeType> EpochChangeMessage<T, Unchecked> {
379    /// Mark this message's certificates as verified.
380    pub(crate) fn into_validated(self) -> EpochChangeMessage<T, Validated> {
381        EpochChangeMessage {
382            cert1: self.cert1,
383            cert2: self.cert2,
384            proposal: self.proposal,
385            _marker: PhantomData,
386        }
387    }
388}
389
390impl<T: NodeType, S> EpochChangeMessage<T, S> {
391    /// Structural validity of the message, independent of signatures.
392    ///
393    /// Every part must name the same block, and `cert1` and the proposal the
394    /// same view. `cert2` may be at a later view: a re-vote commits the last
395    /// block of an epoch again at a view of its own, and the first block of
396    /// the next epoch still names the block's own `cert1`. A certificate's
397    /// own view and block number are covered by the signatures over it, and
398    /// the proposal's are covered by the leaf commitment, so agreement between
399    /// them otherwise rests on an honest signer being in the quorum. Comparing
400    /// them makes the message self-checking. `Proposal::epoch` is not covered
401    /// by the commitment at all and has no other check.
402    ///
403    /// What the embedded proposal claims about itself and its parent is checked
404    /// by the same functions the proposal path uses, except for the boundary
405    /// Cert2 only the first proposal of an epoch carries.
406    pub fn well_formed(&self, epoch_height: u64) -> Result<(), EpochChangeError> {
407        let block_number = self.cert2.data.block_number;
408        if self.cert1.view_number() > self.cert2.view_number()
409            || self.cert1.epoch() != self.cert2.epoch()
410            || self.cert1.data.leaf_commit != self.cert2.data.leaf_commit
411            || self.cert1.data.block_number != Some(block_number)
412        {
413            return Err(EpochChangeError::CertificateMismatch);
414        }
415        if !is_last_block(block_number, epoch_height) {
416            return Err(EpochChangeError::NotLastBlock);
417        }
418        if self.cert2.data.epoch != epoch_from_block_number(block_number, epoch_height).into() {
419            return Err(EpochChangeError::WrongEpoch);
420        }
421        if proposal_commitment(&self.proposal) != self.cert1.data.leaf_commit {
422            return Err(EpochChangeError::ProposalMismatch);
423        }
424        if self.proposal.view_number() != self.cert1.view_number()
425            || self.proposal.block_header.block_number() != block_number
426        {
427            return Err(EpochChangeError::ProposalCertificateMismatch);
428        }
429        epoch_matches_height(&self.proposal, epoch_height)?;
430        justify_qc_matches_parent(&self.proposal, epoch_height)?;
431        state_cert_matches_parent(&self.proposal, epoch_height)?;
432        view_change_evidence_matches_parent(&self.proposal)?;
433        Ok(())
434    }
435
436    #[cfg(any(test, feature = "testing"))]
437    pub fn into_unchecked(self) -> EpochChangeMessage<T, Unchecked> {
438        EpochChangeMessage {
439            cert1: self.cert1,
440            cert2: self.cert2,
441            proposal: self.proposal,
442            _marker: PhantomData,
443        }
444    }
445}
446
447/// Reason an [`EpochChangeMessage`] is not [well-formed](EpochChangeMessage::well_formed).
448#[derive(Copy, Clone, Debug, thiserror::Error)]
449pub enum EpochChangeError {
450    #[error(
451        "certificates differ in epoch, block number or leaf commitment, or certificate2 is \
452         earlier than certificate1"
453    )]
454    CertificateMismatch,
455    #[error("certificate2 is not for the last block of an epoch")]
456    NotLastBlock,
457    #[error("certificate2's block number does not match its epoch")]
458    WrongEpoch,
459    #[error("proposal commitment does not match certificate1's leaf commitment")]
460    ProposalMismatch,
461    #[error("the embedded proposal names a different view or block than the certificates")]
462    ProposalCertificateMismatch,
463    #[error("the embedded proposal is malformed: {0}")]
464    Proposal(#[from] MalformedProposal),
465}
466
467impl<T: NodeType, S> HasViewNumber for EpochChangeMessage<T, S> {
468    /// The view of the commit: the next epoch starts after it.
469    fn view_number(&self) -> ViewNumber {
470        self.cert2.view_number()
471    }
472}
473
474impl<T: NodeType, S> HasEpoch for EpochChangeMessage<T, S> {
475    fn epoch(&self) -> Option<EpochNumber> {
476        self.cert1.epoch()
477    }
478}
479
480/// A request by the leader of `view` that the committee of `epoch` vote on
481/// the epoch's last block again.
482///
483/// The last block of an epoch must be committed before the next epoch can
484/// start, and its first block needs the commit certificate. If the block has
485/// a `Certificate1` but its `Certificate2` can no longer form, because the
486/// nodes that did not vote2 for it have timed its view out, the committee
487/// votes on the block itself again: a vote1 and then a vote2 over the block's
488/// existing vote data, at `view`. No block is built or dispersed.
489///
490/// The leader asks as soon as it can lock on the block, without waiting for
491/// the view to time out: in the view after the block's, or after a timeout
492/// certificate for the view before.
493#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
494#[serde(bound(deserialize = ""))]
495pub struct ReVote<T: NodeType> {
496    /// The view of the re-vote.
497    pub view: ViewNumber,
498    /// The epoch whose last block is voted on.
499    pub epoch: EpochNumber,
500    /// The block's own `Certificate1`, at the block's view.
501    pub cert1: Certificate1<T>,
502    /// The timeout certificate for the view before `view`, unless that is the
503    /// block's view.
504    pub timeout: Option<TimeoutCertificate3<T>>,
505}
506
507impl<T: NodeType> ReVote<T> {
508    /// The data every vote of the re-vote signs: the block's own.
509    pub fn vote_data(&self) -> QuorumData2<T> {
510        self.cert1.data
511    }
512
513    /// Structural validity, independent of signatures.
514    pub fn well_formed(&self, epoch_height: u64) -> Result<(), ReVoteError> {
515        let data = &self.cert1.data;
516        if data.epoch != Some(self.epoch) {
517            return Err(ReVoteError::Epoch);
518        }
519        let Some(block) = data.block_number else {
520            return Err(ReVoteError::NotLastBlock);
521        };
522        if !is_last_block(block, epoch_height)
523            || EpochNumber::new(epoch_from_block_number(block, epoch_height)) != self.epoch
524        {
525            return Err(ReVoteError::NotLastBlock);
526        }
527        let parent = self.cert1.view_number();
528        if parent == ViewNumber::genesis() || parent >= self.view {
529            return Err(ReVoteError::ParentNotEarlier);
530        }
531        match &self.timeout {
532            None if parent + 1 != self.view => return Err(ReVoteError::Timeout),
533            Some(tc)
534                if tc.view_number.u64().checked_add(1) != Some(self.view.u64())
535                    || tc.epoch != self.epoch =>
536            {
537                return Err(ReVoteError::Timeout);
538            },
539            Some(tc) if tc.check_lock().is_err() => return Err(ReVoteError::TimeoutLock),
540            _ => {},
541        }
542        Ok(())
543    }
544}
545
546impl<T: NodeType> Committable for ReVote<T> {
547    fn commit(&self) -> Commitment<Self> {
548        committable::RawCommitmentBuilder::new("Re-vote request")
549            .u64_field("view number", *self.view)
550            .u64_field("epoch number", *self.epoch)
551            .field("certificate1", self.cert1.commit())
552            .optional("timeout certificate", &self.timeout)
553            .finalize()
554    }
555}
556
557/// Reason a [`ReVote`] is not [well-formed](ReVote::well_formed).
558#[derive(Copy, Clone, Debug, thiserror::Error)]
559pub enum ReVoteError {
560    #[error("re-vote certificate is not of the re-vote's epoch")]
561    Epoch,
562    #[error("re-vote certificate is not over the last block of its epoch")]
563    NotLastBlock,
564    #[error("re-vote certificate is not earlier than the re-vote")]
565    ParentNotEarlier,
566    #[error(
567        "re-vote is neither in the view after its certificate's nor behind a timeout certificate \
568         for the view before it, in its epoch"
569    )]
570    Timeout,
571    #[error("re-vote's timeout certificate has a lock later than its view or epoch")]
572    TimeoutLock,
573}
574
575/// A [`ReVote`] signed by its view's leader.
576#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
577#[serde(bound(deserialize = "S: Deserialize<'de>"))]
578pub struct ReVoteMessage<T: NodeType, S> {
579    pub revote: ReVote<T>,
580    pub signature: <T::SignatureKey as SignatureKey>::PureAssembledSignatureType,
581    #[serde(skip)]
582    _marker: PhantomData<fn() -> S>,
583}
584
585impl<T: NodeType> ReVoteMessage<T, Validated> {
586    /// Sign a re-vote request.
587    pub fn new(
588        revote: ReVote<T>,
589        private_key: &<T::SignatureKey as SignatureKey>::PrivateKey,
590    ) -> Result<Self, <T::SignatureKey as SignatureKey>::SignError> {
591        let signature = T::SignatureKey::sign(private_key, revote.commit().as_ref())?;
592        Ok(Self {
593            revote,
594            signature,
595            _marker: PhantomData,
596        })
597    }
598}
599
600impl<T: NodeType> ReVoteMessage<T, Unchecked> {
601    pub(crate) fn into_validated(self) -> ReVoteMessage<T, Validated> {
602        ReVoteMessage {
603            revote: self.revote,
604            signature: self.signature,
605            _marker: PhantomData,
606        }
607    }
608}
609
610impl<T: NodeType, S> ReVoteMessage<T, S> {
611    /// Whether `leader` signed this request.
612    pub fn signed_by(&self, leader: &T::SignatureKey) -> bool {
613        leader.validate(&self.signature, self.revote.commit().as_ref())
614    }
615
616    #[cfg(any(test, feature = "testing"))]
617    pub fn into_unchecked(self) -> ReVoteMessage<T, Unchecked> {
618        ReVoteMessage {
619            revote: self.revote,
620            signature: self.signature,
621            _marker: PhantomData,
622        }
623    }
624}
625
626impl<T: NodeType, S> HasViewNumber for ReVoteMessage<T, S> {
627    fn view_number(&self) -> ViewNumber {
628        self.revote.view
629    }
630}
631
632impl<T: NodeType, S> HasEpoch for ReVoteMessage<T, S> {
633    fn epoch(&self) -> Option<EpochNumber> {
634        Some(self.revote.epoch)
635    }
636}
637
638#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
639#[serde(bound(deserialize = ""))]
640pub struct ProposalFetchRequest<T: NodeType> {
641    pub payload: ProposalRequestPayload<T>,
642    pub signature: <T::SignatureKey as SignatureKey>::PureAssembledSignatureType,
643}
644
645impl<T: NodeType> ProposalFetchRequest<T> {
646    pub fn new(
647        view_number: ViewNumber,
648        key: T::SignatureKey,
649        private_key: &<T::SignatureKey as SignatureKey>::PrivateKey,
650    ) -> Result<Self, <T::SignatureKey as SignatureKey>::SignError> {
651        let payload = ProposalRequestPayload { view_number, key };
652        let signature = T::SignatureKey::sign(private_key, payload.commit().as_ref())?;
653        Ok(Self { payload, signature })
654    }
655
656    pub fn validate_sender(&self, sender: &T::SignatureKey) -> bool {
657        &self.payload.key == sender
658            && self
659                .payload
660                .key
661                .validate(&self.signature, self.payload.commit().as_ref())
662    }
663}
664
665impl<T: NodeType> HasViewNumber for ProposalFetchRequest<T> {
666    fn view_number(&self) -> ViewNumber {
667        self.payload.view_number
668    }
669}
670
671#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
672#[serde(bound(deserialize = "S: Deserialize<'de>"))]
673#[allow(clippy::large_enum_variant)]
674pub enum ConsensusMessage<T: NodeType, S> {
675    Proposal(ProposalMessage<T, S>),
676    Vote1(Vote1<T>),
677    Vote2(Vote2<T>),
678    Certificate1(Certificate1<T>, T::SignatureKey),
679    Certificate2(Certificate2<T>, T::SignatureKey),
680    TimeoutVote(TimeoutVoteMessage<T>),
681    TimeoutCertificate(TimeoutCertificate2<T>),
682    EpochChange(EpochChangeMessage<T, S>),
683    /// The leader's unicast of a per-namespace VID share fragment.
684    VidShareFragment(VidShareFragmentMessage<T>),
685    /// A node's own VID share, broadcast independently of Vote1.
686    VidShareBroadcast(VidDisperseShare2<T>),
687    HighQc(Certificate1<T>),
688    // Only append new variants: bincode tags variants by index, so reordering
689    // or inserting breaks wire compatibility within a protocol version.
690    TimeoutVote3(TimeoutVoteMessage3<T>),
691    TimeoutCertificate3(TimeoutCertificate3<T>),
692    UpgradeProposal(UpgradeProposalMessage<T>),
693    UpgradeVote(UpgradeVoteMessage<T>),
694    ReVote(ReVoteMessage<T, S>),
695}
696
697impl<T: NodeType, S> ConsensusMessage<T, S> {
698    #[cfg(any(test, feature = "testing"))]
699    pub fn into_unchecked(self) -> ConsensusMessage<T, Unchecked> {
700        match self {
701            Self::Proposal(p) => ConsensusMessage::Proposal(p.into_unchecked()),
702            Self::Vote1(v) => ConsensusMessage::Vote1(v),
703            Self::Vote2(v) => ConsensusMessage::Vote2(v),
704            Self::Certificate1(c, k) => ConsensusMessage::Certificate1(c, k),
705            Self::Certificate2(c, k) => ConsensusMessage::Certificate2(c, k),
706            Self::TimeoutVote(v) => ConsensusMessage::TimeoutVote(v),
707            Self::TimeoutCertificate(c) => ConsensusMessage::TimeoutCertificate(c),
708            Self::EpochChange(c) => ConsensusMessage::EpochChange(c.into_unchecked()),
709            Self::VidShareFragment(v) => ConsensusMessage::VidShareFragment(v),
710            Self::VidShareBroadcast(v) => ConsensusMessage::VidShareBroadcast(v),
711            Self::HighQc(c) => ConsensusMessage::HighQc(c),
712            Self::TimeoutVote3(v) => ConsensusMessage::TimeoutVote3(v),
713            Self::TimeoutCertificate3(c) => ConsensusMessage::TimeoutCertificate3(c),
714            Self::UpgradeProposal(p) => ConsensusMessage::UpgradeProposal(p),
715            Self::UpgradeVote(v) => ConsensusMessage::UpgradeVote(v),
716            Self::ReVote(r) => ConsensusMessage::ReVote(r.into_unchecked()),
717        }
718    }
719}
720
721impl<T: NodeType, S> HasViewNumber for ConsensusMessage<T, S> {
722    fn view_number(&self) -> ViewNumber {
723        match self {
724            Self::Proposal(proposal) => proposal.view_number(),
725            Self::Vote1(vote) => vote.view_number(),
726            Self::Vote2(vote) => vote.view_number(),
727            Self::Certificate1(certificate, _) => certificate.view_number(),
728            Self::Certificate2(certificate, _) => certificate.view_number(),
729            Self::TimeoutVote(msg) => msg.view_number(),
730            Self::TimeoutCertificate(certificate) => certificate.view_number(),
731            Self::EpochChange(epoch_change) => epoch_change.view_number(),
732            Self::VidShareFragment(fragment) => fragment.data.view_number(),
733            Self::VidShareBroadcast(vid_share) => vid_share.view_number(),
734            Self::HighQc(certificate) => certificate.view_number(),
735            Self::TimeoutVote3(msg) => msg.view_number(),
736            Self::TimeoutCertificate3(certificate) => certificate.view_number(),
737            Self::UpgradeProposal(proposal) => proposal.data.view_number(),
738            Self::UpgradeVote(vote) => vote.view_number(),
739            Self::ReVote(revote) => revote.view_number(),
740        }
741    }
742}
743
744#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
745#[serde(bound(deserialize = ""))]
746pub enum ProposalFetchMessage<T: NodeType> {
747    Request(ProposalFetchRequest<T>),
748    Response(Box<SignedProposal<T, Proposal<T>>>),
749}
750
751impl<T: NodeType> HasViewNumber for ProposalFetchMessage<T> {
752    fn view_number(&self) -> ViewNumber {
753        match self {
754            Self::Request(request) => request.view_number(),
755            Self::Response(proposal) => proposal.data.view_number(),
756        }
757    }
758}
759
760#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
761#[serde(bound(deserialize = ""))]
762pub struct DedupManifest<T: NodeType> {
763    pub view: ViewNumber,
764    pub epoch: EpochNumber,
765    pub hashes: Vec<Commitment<T::Transaction>>,
766}
767
768#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
769#[serde(bound(deserialize = ""))]
770pub struct TransactionMessage<T: NodeType> {
771    pub view: ViewNumber,
772    pub transactions: Vec<T::Transaction>,
773}
774
775#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
776#[serde(bound(deserialize = ""))]
777pub enum BlockMessage<T: NodeType> {
778    Transactions(TransactionMessage<T>),
779    DedupManifest(DedupManifest<T>),
780}
781
782impl<T: NodeType> HasViewNumber for BlockMessage<T> {
783    fn view_number(&self) -> ViewNumber {
784        match self {
785            BlockMessage::Transactions(msg) => msg.view,
786            BlockMessage::DedupManifest(msg) => msg.view,
787        }
788    }
789}
790
791#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
792#[serde(bound(deserialize = "S: Deserialize<'de>"))]
793#[allow(clippy::large_enum_variant)]
794pub enum MessageType<T: NodeType, S> {
795    Consensus(ConsensusMessage<T, S>),
796    Block(BlockMessage<T>),
797    ProposalFetch(ProposalFetchMessage<T>),
798    External(#[serde(with = "serde_bytes")] Vec<u8>),
799    PayloadFetch(PayloadFetchMessage),
800}
801
802impl<T: NodeType, S> MessageType<T, S> {
803    #[cfg(any(test, feature = "testing"))]
804    pub fn into_unchecked(self) -> MessageType<T, Unchecked> {
805        match self {
806            Self::Consensus(c) => MessageType::Consensus(c.into_unchecked()),
807            Self::Block(b) => MessageType::Block(b),
808            Self::ProposalFetch(r) => MessageType::ProposalFetch(r),
809            Self::External(v) => MessageType::External(v),
810            Self::PayloadFetch(r) => MessageType::PayloadFetch(r),
811        }
812    }
813}
814
815impl<T: NodeType, S> From<ConsensusMessage<T, S>> for MessageType<T, S> {
816    fn from(message: ConsensusMessage<T, S>) -> Self {
817        Self::Consensus(message)
818    }
819}
820
821#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
822#[serde(bound(deserialize = "S: Deserialize<'de>"))]
823pub struct Message<T: NodeType, S> {
824    pub sender: T::SignatureKey,
825    pub message_type: MessageType<T, S>,
826}
827
828impl<T: NodeType, S> Message<T, S> {
829    pub fn is_external(&self) -> bool {
830        matches!(self.message_type, MessageType::External(_))
831    }
832
833    #[cfg(any(test, feature = "testing"))]
834    pub fn into_unchecked(self) -> Message<T, Unchecked> {
835        Message {
836            sender: self.sender,
837            message_type: self.message_type.into_unchecked(),
838        }
839    }
840}
841
842impl<T: NodeType, S> HasViewNumber for Message<T, S> {
843    fn view_number(&self) -> ViewNumber {
844        match &self.message_type {
845            MessageType::Consensus(consensus_message) => consensus_message.view_number(),
846            MessageType::Block(block_message) => block_message.view_number(),
847            MessageType::ProposalFetch(message) => message.view_number(),
848            MessageType::External(_) => ViewNumber::new(1), // TODO: This can become a problem
849            MessageType::PayloadFetch(message) => message.view_number(),
850        }
851    }
852}
853
854pub struct OpaqueMessage<K> {
855    pub sender: K,
856    pub data: Vec<u8>,
857}