1mod canonical_proposal;
4mod msg_type;
5mod sign_proposal;
6
7use core::convert::Infallible;
8
9use bytes::BufMut;
10use celestia_tendermint_proto::v0_37::types::CanonicalProposal as RawCanonicalProposal;
11use celestia_tendermint_proto::{Error as ProtobufError, Protobuf};
12pub use msg_type::Type;
13pub use sign_proposal::{SignProposalRequest, SignedProposalResponse};
14
15pub use self::canonical_proposal::CanonicalProposal;
16use crate::{
17 block::{Height, Id as BlockId, Round},
18 chain::Id as ChainId,
19 consensus::State,
20 prelude::*,
21 Signature, Time,
22};
23
24#[derive(Clone, PartialEq, Eq, Debug)]
26pub struct Proposal {
27 pub msg_type: Type,
29 pub height: Height,
31 pub round: Round,
33 pub pol_round: Option<Round>,
35 pub block_id: Option<BlockId>,
37 pub timestamp: Option<Time>,
39 pub signature: Option<Signature>,
41}
42
43tendermint_pb_modules! {
44 use super::Proposal;
45 use crate::{Signature, Error, block::Round};
46 use pb::types::Proposal as RawProposal;
47
48 impl Protobuf<RawProposal> for Proposal {}
49
50 impl TryFrom<RawProposal> for Proposal {
51 type Error = Error;
52
53 fn try_from(value: RawProposal) -> Result<Self, Self::Error> {
54 if value.pol_round < -1 {
55 return Err(Error::negative_pol_round());
56 }
57 let pol_round = match value.pol_round {
58 -1 => None,
59 n => Some(Round::try_from(n)?),
60 };
61 Ok(Proposal {
62 msg_type: value.r#type.try_into()?,
63 height: value.height.try_into()?,
64 round: value.round.try_into()?,
65 pol_round,
66 block_id: value.block_id.map(TryInto::try_into).transpose()?,
67 timestamp: value.timestamp.map(|t| t.try_into()).transpose()?,
68 signature: Signature::new(value.signature)?,
69 })
70 }
71 }
72
73 impl From<Proposal> for RawProposal {
74 fn from(value: Proposal) -> Self {
75 RawProposal {
76 r#type: value.msg_type.into(),
77 height: value.height.into(),
78 round: value.round.into(),
79 pol_round: value.pol_round.map_or(-1, Into::into),
80 block_id: value.block_id.map(Into::into),
81 timestamp: value.timestamp.map(Into::into),
82 signature: value.signature.map(|s| s.to_bytes()).unwrap_or_default(),
83 }
84 }
85 }
86}
87
88impl Proposal {
89 pub fn to_signable_bytes<B>(
91 &self,
92 chain_id: ChainId,
93 sign_bytes: &mut B,
94 ) -> Result<bool, ProtobufError>
95 where
96 B: BufMut,
97 {
98 let canonical = CanonicalProposal::new(self.clone(), chain_id);
99 Protobuf::<RawCanonicalProposal>::encode_length_delimited(&canonical, sign_bytes)?;
100 Ok(true)
101 }
102
103 pub fn to_signable_vec(&self, chain_id: ChainId) -> Result<Vec<u8>, ProtobufError> {
105 let canonical = CanonicalProposal::new(self.clone(), chain_id);
106 let encoded: Result<_, Infallible> =
107 Protobuf::<RawCanonicalProposal>::encode_length_delimited_vec(&canonical);
108 Ok(encoded.unwrap())
109 }
110
111 #[deprecated(
113 since = "0.17.0",
114 note = "This seems unnecessary, please raise it to the team, if you need it."
115 )]
116 pub fn consensus_state(&self) -> State {
117 State {
118 height: self.height,
119 round: self.round,
120 step: 3,
121 block_id: self.block_id,
122 }
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use core::str::FromStr;
129
130 use time::macros::datetime;
131
132 use crate::{
133 block::{parts::Header, Height, Id as BlockId, Round},
134 chain::Id as ChainId,
135 hash::{Algorithm, Hash},
136 prelude::*,
137 proposal::{SignProposalRequest, Type},
138 test::dummy_signature,
139 Proposal,
140 };
141
142 #[test]
143 fn test_serialization() {
144 let dt = datetime!(2018-02-11 07:09:22.765 UTC);
145 let proposal = Proposal {
146 msg_type: Type::Proposal,
147 height: Height::from(12345_u32),
148 round: Round::from(23456_u16),
149 pol_round: None,
150 block_id: Some(BlockId {
151 hash: Hash::from_hex_upper(
152 Algorithm::Sha256,
153 "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFADEADBEEFDEADBEEFBAFBAFBAFBAFBAFA",
154 )
155 .unwrap(),
156 part_set_header: Header::new(
157 65535,
158 Hash::from_hex_upper(
159 Algorithm::Sha256,
160 "0022446688AACCEE1133557799BBDDFF0022446688AACCEE1133557799BBDDFF",
161 )
162 .unwrap(),
163 )
164 .unwrap(),
165 }),
166 timestamp: Some(dt.try_into().unwrap()),
167 signature: Some(dummy_signature()),
168 };
169
170 let mut got = vec![];
171
172 let request = SignProposalRequest {
173 proposal,
174 chain_id: ChainId::from_str("test_chain_id").unwrap(),
175 };
176
177 let _have = request.to_signable_bytes(&mut got);
178
179 let want = vec![
214 136, 1, 8, 32, 17, 57, 48, 0, 0, 0, 0, 0, 0, 25, 160, 91, 0, 0, 0, 0, 0, 0, 32, 255,
215 255, 255, 255, 255, 255, 255, 255, 255, 1, 42, 74, 10, 32, 222, 173, 190, 239, 222,
216 173, 190, 239, 186, 251, 175, 186, 251, 175, 186, 250, 222, 173, 190, 239, 222, 173,
217 190, 239, 186, 251, 175, 186, 251, 175, 186, 250, 18, 38, 8, 255, 255, 3, 18, 32, 0,
218 34, 68, 102, 136, 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 0, 34, 68, 102,
219 136, 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 50, 12, 8, 162, 216, 255, 211,
220 5, 16, 192, 242, 227, 236, 2, 58, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110,
221 95, 105, 100,
222 ];
223
224 assert_eq!(got, want)
225 }
226
227 #[test]
228 fn test_encoding_with_empty_block_id() {
230 let dt = datetime!(2018-02-11 07:09:22.765 UTC);
231 let proposal = Proposal {
232 msg_type: Type::Proposal,
233 height: Height::from(12345_u32),
234 round: Round::from(23456_u16),
235 pol_round: None,
236 block_id: Some(BlockId {
237 hash: Hash::from_hex_upper(Algorithm::Sha256, "").unwrap(),
238 part_set_header: Header::new(
239 65535,
240 Hash::from_hex_upper(
241 Algorithm::Sha256,
242 "0022446688AACCEE1133557799BBDDFF0022446688AACCEE1133557799BBDDFF",
243 )
244 .unwrap(),
245 )
246 .unwrap(),
247 }),
248 timestamp: Some(dt.try_into().unwrap()),
249 signature: Some(dummy_signature()),
250 };
251
252 let mut got = vec![];
253
254 let request = SignProposalRequest {
255 proposal,
256 chain_id: ChainId::from_str("test_chain_id").unwrap(),
257 };
258
259 let _have = request.to_signable_bytes(&mut got);
260
261 let want = vec![
295 102, 8, 32, 17, 57, 48, 0, 0, 0, 0, 0, 0, 25, 160, 91, 0, 0, 0, 0, 0, 0, 32, 255, 255,
296 255, 255, 255, 255, 255, 255, 255, 1, 42, 40, 18, 38, 8, 255, 255, 3, 18, 32, 0, 34,
297 68, 102, 136, 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 0, 34, 68, 102, 136,
298 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 50, 12, 8, 162, 216, 255, 211, 5,
299 16, 192, 242, 227, 236, 2, 58, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110, 95,
300 105, 100,
301 ];
302
303 assert_eq!(got, want)
304 }
305
306 tendermint_pb_modules! {
307 use super::*;
308
309 #[test]
310 fn test_deserialization() {
311 let dt = datetime!(2018-02-11 07:09:22.765 UTC);
312 let proposal = Proposal {
313 msg_type: Type::Proposal,
314 height: Height::from(12345_u32),
315 round: Round::from(23456_u16),
316 timestamp: Some(dt.try_into().unwrap()),
317
318 pol_round: None,
319 block_id: Some(BlockId {
320 hash: Hash::from_hex_upper(
321 Algorithm::Sha256,
322 "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFADEADBEEFDEADBEEFBAFBAFBAFBAFBAFA",
323 )
324 .unwrap(),
325 part_set_header: Header::new(
326 65535,
327 Hash::from_hex_upper(
328 Algorithm::Sha256,
329 "0022446688AACCEE1133557799BBDDFF0022446688AACCEE1133557799BBDDFF",
330 )
331 .unwrap(),
332 )
333 .unwrap(),
334 }),
335 signature: Some(dummy_signature()),
336 };
337 let want = SignProposalRequest {
338 proposal,
339 chain_id: ChainId::from_str("test_chain_id").unwrap(),
340 };
341
342 let data = vec![
343 10, 176, 1, 8, 32, 16, 185, 96, 24, 160, 183, 1, 32, 255, 255, 255, 255, 255, 255, 255,
344 255, 255, 1, 42, 74, 10, 32, 222, 173, 190, 239, 222, 173, 190, 239, 186, 251, 175,
345 186, 251, 175, 186, 250, 222, 173, 190, 239, 222, 173, 190, 239, 186, 251, 175, 186,
346 251, 175, 186, 250, 18, 38, 8, 255, 255, 3, 18, 32, 0, 34, 68, 102, 136, 170, 204, 238,
347 17, 51, 85, 119, 153, 187, 221, 255, 0, 34, 68, 102, 136, 170, 204, 238, 17, 51, 85,
348 119, 153, 187, 221, 255, 50, 12, 8, 162, 216, 255, 211, 5, 16, 192, 242, 227, 236, 2,
349 58, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
350 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
351 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110, 95,
352 105, 100,
353 ];
354
355 let have = <SignProposalRequest as Protobuf<pb::privval::SignProposalRequest>>::decode_vec(&data).unwrap();
356 assert_eq!(have, want);
357 }
358 }
359}