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#[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 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 pub fn vote(&self) -> &TimeoutVote3<T> {
85 &self.vote
86 }
87
88 pub fn lock(&self) -> Option<&Certificate1<T>> {
90 self.lock.as_ref()
91 }
92
93 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 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 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
187pub type VidShareMessage<T> = SignedProposal<T, VidDisperseShare2<T>>;
189
190pub 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 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
210pub type UpgradeProposalMessage<T> = SignedProposal<T, UpgradeProposal2>;
212
213pub 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#[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 pub lock: Option<Certificate1<T>>,
245 pub evidence: Option<CatchupEvidence<T>>,
249}
250
251impl<T: NodeType> TimeoutVoteMessage3<T> {
252 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 pub fn evidence(&self) -> Option<CatchupEvidence<T>> {
270 self.evidence
271 .clone()
272 .or_else(|| self.lock.clone().map(CatchupEvidence::Qc))
273 }
274
275 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#[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#[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 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 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 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#[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 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#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
494#[serde(bound(deserialize = ""))]
495pub struct ReVote<T: NodeType> {
496 pub view: ViewNumber,
498 pub epoch: EpochNumber,
500 pub cert1: Certificate1<T>,
502 pub timeout: Option<TimeoutCertificate3<T>>,
505}
506
507impl<T: NodeType> ReVote<T> {
508 pub fn vote_data(&self) -> QuorumData2<T> {
510 self.cert1.data
511 }
512
513 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#[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#[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 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 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 VidShareFragment(VidShareFragmentMessage<T>),
685 VidShareBroadcast(VidDisperseShare2<T>),
687 HighQc(Certificate1<T>),
688 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), MessageType::PayloadFetch(message) => message.view_number(),
850 }
851 }
852}
853
854pub struct OpaqueMessage<K> {
855 pub sender: K,
856 pub data: Vec<u8>,
857}