Skip to main content

hotshot_new_protocol/
message.rs

1use std::marker::PhantomData;
2
3use committable::{Commitment, Committable};
4pub use hotshot_types::new_protocol::Proposal;
5use hotshot_types::{
6    data::{EpochNumber, VidDisperseShare2, ViewNumber},
7    message::Proposal as SignedProposal,
8    request_response::ProposalRequestPayload,
9    simple_certificate::{
10        OneHonestThreshold, SimpleCertificate, SuccessThreshold, TimeoutCertificate2,
11    },
12    simple_vote::{
13        CheckpointData, LightClientStateUpdateVote2, QuorumData2, QuorumVote2, SimpleVote,
14        TimeoutData2, TimeoutVote2, Vote2Data,
15    },
16    traits::{node_implementation::NodeType, signature_key::SignatureKey},
17    vote::HasViewNumber,
18};
19use serde::{Deserialize, Serialize};
20
21pub type Vote2<T> = SimpleVote<T, Vote2Data<T>>;
22pub type CheckpointVote<T> = SimpleVote<T, CheckpointData>;
23pub type CheckpointCertificate<T> = SimpleCertificate<T, CheckpointData, SuccessThreshold>;
24pub type Certificate1<T> = SimpleCertificate<T, QuorumData2<T>, SuccessThreshold>;
25pub type Certificate2<T> = SimpleCertificate<T, Vote2Data<T>, SuccessThreshold>;
26pub type TimeoutCertificate<T> = SimpleCertificate<T, TimeoutData2, SuccessThreshold>;
27pub type TimeoutOneHonest<T> = SimpleCertificate<T, TimeoutData2, OneHonestThreshold>;
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Deserialize)]
30pub enum Unchecked {}
31
32#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize)]
33pub enum Validated {}
34
35#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
36#[serde(bound(deserialize = "S: Deserialize<'de>"))]
37pub struct ProposalMessage<T: NodeType, S> {
38    pub proposal: SignedProposal<T, Proposal<T>>,
39    #[serde(skip)]
40    _marker: PhantomData<fn() -> S>,
41}
42
43impl<T: NodeType> ProposalMessage<T, Validated> {
44    pub fn validated(p: SignedProposal<T, Proposal<T>>) -> Self {
45        Self {
46            proposal: p,
47            _marker: PhantomData,
48        }
49    }
50}
51
52impl<T: NodeType, S> ProposalMessage<T, S> {
53    #[cfg(test)]
54    pub fn into_unchecked(self) -> ProposalMessage<T, Unchecked> {
55        ProposalMessage {
56            proposal: self.proposal,
57            _marker: PhantomData,
58        }
59    }
60}
61
62impl<T: NodeType, S> HasViewNumber for ProposalMessage<T, S> {
63    fn view_number(&self) -> ViewNumber {
64        self.proposal.data.view_number
65    }
66}
67
68/// A signed VidShare to be sent to the replicas.
69pub type VidShareMessage<T> = SignedProposal<T, VidDisperseShare2<T>>;
70
71#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
72#[serde(bound(deserialize = ""))]
73pub struct Vote1<T: NodeType> {
74    pub vote: QuorumVote2<T>,
75    pub vid_share: VidDisperseShare2<T>,
76    /// Populated only when voting on an epoch-root leaf. Required there; absent otherwise.
77    pub state_vote: Option<LightClientStateUpdateVote2<T>>,
78}
79
80impl<T: NodeType> HasViewNumber for Vote1<T> {
81    fn view_number(&self) -> ViewNumber {
82        self.vote.view_number()
83    }
84}
85
86#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
87#[serde(bound(deserialize = ""))]
88pub struct TimeoutVoteMessage<T: NodeType> {
89    pub vote: TimeoutVote2<T>,
90    pub lock: Option<Certificate1<T>>,
91}
92
93impl<T: NodeType> HasViewNumber for TimeoutVoteMessage<T> {
94    fn view_number(&self) -> ViewNumber {
95        self.vote.view_number()
96    }
97}
98
99/// Message sent at the end of an epoch by the current committee
100/// to the next committee.  Both certificates are on the last block of the epoch.
101/// The protocol spec only requires the second certificate, but for consistency
102/// in the code and with the existing Proposal and Leaf structures
103/// We include the Certificate1.  This allows us to use the Certificate1 as the
104/// Justify QC on the first proposal.  The Certificate2 also required on that proposal
105/// but as next_epoch_justify_qc on the Leaf.
106///
107/// We include the proposal because the new leader in the next epoch
108/// will need it to build a header for the first block of the next epoch.
109#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
110#[serde(bound(deserialize = ""))]
111pub struct EpochChangeMessage<T: NodeType> {
112    pub cert1: Certificate1<T>,
113    pub cert2: Certificate2<T>,
114    pub proposal: Proposal<T>,
115}
116
117#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
118#[serde(bound(deserialize = ""))]
119pub struct ProposalFetchRequest<T: NodeType> {
120    pub payload: ProposalRequestPayload<T>,
121    pub signature: <T::SignatureKey as SignatureKey>::PureAssembledSignatureType,
122}
123
124impl<T: NodeType> ProposalFetchRequest<T> {
125    pub fn new(
126        view_number: ViewNumber,
127        key: T::SignatureKey,
128        private_key: &<T::SignatureKey as SignatureKey>::PrivateKey,
129    ) -> Result<Self, <T::SignatureKey as SignatureKey>::SignError> {
130        let payload = ProposalRequestPayload { view_number, key };
131        let signature = T::SignatureKey::sign(private_key, payload.commit().as_ref())?;
132        Ok(Self { payload, signature })
133    }
134
135    pub fn validate_sender(&self, sender: &T::SignatureKey) -> bool {
136        &self.payload.key == sender
137            && self
138                .payload
139                .key
140                .validate(&self.signature, self.payload.commit().as_ref())
141    }
142}
143
144impl<T: NodeType> HasViewNumber for ProposalFetchRequest<T> {
145    fn view_number(&self) -> ViewNumber {
146        self.payload.view_number
147    }
148}
149
150#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
151#[serde(bound(deserialize = "S: Deserialize<'de>"))]
152#[allow(clippy::large_enum_variant)]
153pub enum ConsensusMessage<T: NodeType, S> {
154    Proposal(ProposalMessage<T, S>),
155    Vote1(Vote1<T>),
156    Vote2(Vote2<T>),
157    Certificate1(Certificate1<T>, T::SignatureKey),
158    Certificate2(Certificate2<T>, T::SignatureKey),
159    TimeoutVote(TimeoutVoteMessage<T>),
160    TimeoutCertificate(TimeoutCertificate2<T>),
161    EpochChange(EpochChangeMessage<T>),
162    Checkpoint(CheckpointVote<T>),
163    VidShare(VidShareMessage<T>),
164}
165
166impl<T: NodeType, S> ConsensusMessage<T, S> {
167    #[cfg(test)]
168    pub fn into_unchecked(self) -> ConsensusMessage<T, Unchecked> {
169        match self {
170            Self::Proposal(p) => ConsensusMessage::Proposal(p.into_unchecked()),
171            Self::Vote1(v) => ConsensusMessage::Vote1(v),
172            Self::Vote2(v) => ConsensusMessage::Vote2(v),
173            Self::Certificate1(c, k) => ConsensusMessage::Certificate1(c, k),
174            Self::Certificate2(c, k) => ConsensusMessage::Certificate2(c, k),
175            Self::TimeoutVote(v) => ConsensusMessage::TimeoutVote(v),
176            Self::TimeoutCertificate(c) => ConsensusMessage::TimeoutCertificate(c),
177            Self::Checkpoint(v) => ConsensusMessage::Checkpoint(v),
178            Self::EpochChange(c) => ConsensusMessage::EpochChange(c),
179            Self::VidShare(v) => ConsensusMessage::VidShare(v),
180        }
181    }
182}
183
184impl<T: NodeType, S> HasViewNumber for ConsensusMessage<T, S> {
185    fn view_number(&self) -> ViewNumber {
186        match self {
187            Self::Proposal(proposal) => proposal.view_number(),
188            Self::Vote1(vote) => vote.view_number(),
189            Self::Vote2(vote) => vote.view_number(),
190            Self::Certificate1(certificate, _) => certificate.view_number(),
191            Self::Certificate2(certificate, _) => certificate.view_number(),
192            Self::TimeoutVote(msg) => msg.view_number(),
193            Self::TimeoutCertificate(certificate) => certificate.view_number(),
194            Self::Checkpoint(vote) => vote.view_number(),
195            Self::EpochChange(epoch_change) => epoch_change.cert1.view_number(),
196            Self::VidShare(vid_share) => vid_share.data.view_number(),
197        }
198    }
199}
200
201#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
202#[serde(bound(deserialize = ""))]
203pub enum ProposalFetchMessage<T: NodeType> {
204    Request(ProposalFetchRequest<T>),
205    Response(Box<SignedProposal<T, Proposal<T>>>),
206}
207
208impl<T: NodeType> HasViewNumber for ProposalFetchMessage<T> {
209    fn view_number(&self) -> ViewNumber {
210        match self {
211            Self::Request(request) => request.view_number(),
212            Self::Response(proposal) => proposal.data.view_number(),
213        }
214    }
215}
216
217#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
218#[serde(bound(deserialize = ""))]
219pub struct DedupManifest<T: NodeType> {
220    pub(crate) view: ViewNumber,
221    pub(crate) epoch: EpochNumber,
222    pub(crate) hashes: Vec<Commitment<T::Transaction>>,
223}
224
225#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
226#[serde(bound(deserialize = ""))]
227pub struct TransactionMessage<T: NodeType> {
228    pub(crate) view: ViewNumber,
229    pub(crate) transactions: Vec<T::Transaction>,
230}
231
232#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
233#[serde(bound(deserialize = ""))]
234pub enum BlockMessage<T: NodeType> {
235    Transactions(TransactionMessage<T>),
236    DedupManifest(DedupManifest<T>),
237}
238
239impl<T: NodeType> HasViewNumber for BlockMessage<T> {
240    fn view_number(&self) -> ViewNumber {
241        match self {
242            BlockMessage::Transactions(msg) => msg.view,
243            BlockMessage::DedupManifest(msg) => msg.view,
244        }
245    }
246}
247
248#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
249#[serde(bound(deserialize = "S: Deserialize<'de>"))]
250#[allow(clippy::large_enum_variant)]
251pub enum MessageType<T: NodeType, S> {
252    Consensus(ConsensusMessage<T, S>),
253    Block(BlockMessage<T>),
254    ProposalFetch(ProposalFetchMessage<T>),
255    External(Vec<u8>),
256}
257
258impl<T: NodeType, S> MessageType<T, S> {
259    #[cfg(test)]
260    pub fn into_unchecked(self) -> MessageType<T, Unchecked> {
261        match self {
262            Self::Consensus(c) => MessageType::Consensus(c.into_unchecked()),
263            Self::Block(b) => MessageType::Block(b),
264            Self::ProposalFetch(r) => MessageType::ProposalFetch(r),
265            Self::External(v) => MessageType::External(v),
266        }
267    }
268}
269
270#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Hash, Eq)]
271#[serde(bound(deserialize = "S: Deserialize<'de>"))]
272pub struct Message<T: NodeType, S> {
273    pub sender: T::SignatureKey,
274    pub message_type: MessageType<T, S>,
275}
276
277impl<T: NodeType, S> Message<T, S> {
278    pub fn is_external(&self) -> bool {
279        matches!(self.message_type, MessageType::External(_))
280    }
281
282    #[cfg(test)]
283    pub fn into_unchecked(self) -> Message<T, Unchecked> {
284        Message {
285            sender: self.sender,
286            message_type: self.message_type.into_unchecked(),
287        }
288    }
289}
290
291impl<T: NodeType, S> HasViewNumber for Message<T, S> {
292    fn view_number(&self) -> ViewNumber {
293        match &self.message_type {
294            MessageType::Consensus(consensus_message) => consensus_message.view_number(),
295            MessageType::Block(block_message) => block_message.view_number(),
296            MessageType::ProposalFetch(message) => message.view_number(),
297            MessageType::External(_) => ViewNumber::new(0), // TODO: This can become a problem
298        }
299    }
300}