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