1mod canonical_proposal;
4mod msg_type;
5mod sign_proposal;
6
7use bytes::BufMut;
8use cometbft_proto::types::v1::CanonicalProposal as RawCanonicalProposal;
9use cometbft_proto::{Error as ProtobufError, Protobuf};
10pub use msg_type::Type;
11pub use sign_proposal::{SignProposalRequest, SignedProposalResponse};
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
41cometbft_old_pb_modules! {
46 use super::Proposal;
47 use crate::{Signature, Error, block::Round};
48 use pb::types::Proposal as RawProposal;
49
50 impl Protobuf<RawProposal> for Proposal {}
51
52 impl TryFrom<RawProposal> for Proposal {
53 type Error = Error;
54
55 fn try_from(value: RawProposal) -> Result<Self, Self::Error> {
56 if value.pol_round < -1 {
57 return Err(Error::negative_pol_round());
58 }
59 let pol_round = match value.pol_round {
60 -1 => None,
61 n => Some(Round::try_from(n)?),
62 };
63 Ok(Proposal {
64 msg_type: value.r#type.try_into()?,
65 height: value.height.try_into()?,
66 round: value.round.try_into()?,
67 pol_round,
68 block_id: value.block_id.map(TryInto::try_into).transpose()?,
69 timestamp: value.timestamp.map(|t| t.try_into()).transpose()?,
70 signature: Signature::new(value.signature)?,
71 })
72 }
73 }
74
75 impl From<Proposal> for RawProposal {
76 fn from(value: Proposal) -> Self {
77 RawProposal {
78 r#type: value.msg_type.into(),
79 height: value.height.into(),
80 round: value.round.into(),
81 pol_round: value.pol_round.map_or(-1, Into::into),
82 block_id: value.block_id.map(Into::into),
83 timestamp: value.timestamp.map(Into::into),
84 signature: value.signature.map(|s| s.into_bytes()).unwrap_or_default(),
85 }
86 }
87 }
88}
89
90mod v1 {
91 use super::Proposal;
92 use crate::{block::Round, Error, Signature};
93 use cometbft_proto::types::v1::Proposal as RawProposal;
94
95 impl TryFrom<RawProposal> for Proposal {
96 type Error = Error;
97
98 fn try_from(value: RawProposal) -> Result<Self, Self::Error> {
99 if value.pol_round < -1 {
100 return Err(Error::negative_pol_round());
101 }
102 let pol_round = match value.pol_round {
103 -1 => None,
104 n => Some(Round::try_from(n)?),
105 };
106 Ok(Proposal {
107 msg_type: value.r#type.try_into()?,
108 height: value.height.try_into()?,
109 round: value.round.try_into()?,
110 pol_round,
111 block_id: value.block_id.map(TryInto::try_into).transpose()?,
112 timestamp: value.timestamp.map(|t| t.try_into()).transpose()?,
113 signature: Signature::new(value.signature)?,
114 })
115 }
116 }
117
118 impl From<Proposal> for RawProposal {
119 fn from(value: Proposal) -> Self {
120 RawProposal {
121 r#type: value.msg_type.into(),
122 height: value.height.into(),
123 round: value.round.into(),
124 pol_round: value.pol_round.map_or(-1, Into::into),
125 block_id: value.block_id.map(Into::into),
126 timestamp: value.timestamp.map(Into::into),
127 signature: value.signature.map(|s| s.into_bytes()).unwrap_or_default(),
128 }
129 }
130 }
131}
132
133mod v1beta1 {
134 use super::Proposal;
135 use crate::{block::Round, Error, Signature};
136 use cometbft_proto::types::v1beta1::Proposal as RawProposal;
137
138 impl TryFrom<RawProposal> for Proposal {
139 type Error = Error;
140
141 fn try_from(value: RawProposal) -> Result<Self, Self::Error> {
142 if value.pol_round < -1 {
143 return Err(Error::negative_pol_round());
144 }
145 let pol_round = match value.pol_round {
146 -1 => None,
147 n => Some(Round::try_from(n)?),
148 };
149 Ok(Proposal {
150 msg_type: value.r#type.try_into()?,
151 height: value.height.try_into()?,
152 round: value.round.try_into()?,
153 pol_round,
154 block_id: value.block_id.map(TryInto::try_into).transpose()?,
155 timestamp: value.timestamp.map(|t| t.try_into()).transpose()?,
156 signature: Signature::new(value.signature)?,
157 })
158 }
159 }
160
161 impl From<Proposal> for RawProposal {
162 fn from(value: Proposal) -> Self {
163 RawProposal {
164 r#type: value.msg_type.into(),
165 height: value.height.into(),
166 round: value.round.into(),
167 pol_round: value.pol_round.map_or(-1, Into::into),
168 block_id: value.block_id.map(Into::into),
169 timestamp: value.timestamp.map(Into::into),
170 signature: value.signature.map(|s| s.into_bytes()).unwrap_or_default(),
171 }
172 }
173 }
174}
175
176impl Proposal {
177 pub fn to_signable_bytes<B>(
179 &self,
180 chain_id: ChainId,
181 sign_bytes: &mut B,
182 ) -> Result<bool, ProtobufError>
183 where
184 B: BufMut,
185 {
186 let canonical = CanonicalProposal::new(self.clone(), chain_id);
187 Protobuf::<RawCanonicalProposal>::encode_length_delimited(canonical, sign_bytes)?;
188 Ok(true)
189 }
190
191 pub fn into_signable_vec(self, chain_id: ChainId) -> Vec<u8> {
193 let canonical = CanonicalProposal::new(self, chain_id);
194 Protobuf::<RawCanonicalProposal>::encode_length_delimited_vec(canonical)
195 }
196
197 #[deprecated(
199 since = "0.17.0",
200 note = "This seems unnecessary, please raise it to the team, if you need it."
201 )]
202 pub fn consensus_state(&self) -> State {
203 State {
204 height: self.height,
205 round: self.round,
206 step: 3,
207 block_id: self.block_id,
208 }
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use core::str::FromStr;
215
216 use time::macros::datetime;
217
218 use crate::{
219 block::{parts::Header, Height, Id as BlockId, Round},
220 chain::Id as ChainId,
221 hash::{Algorithm, Hash},
222 prelude::*,
223 proposal::{SignProposalRequest, Type},
224 test::dummy_signature,
225 Proposal,
226 };
227
228 #[test]
229 fn test_serialization() {
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(
238 Algorithm::Sha256,
239 "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFADEADBEEFDEADBEEFBAFBAFBAFBAFBAFA",
240 )
241 .unwrap(),
242 part_set_header: Header::new(
243 65535,
244 Hash::from_hex_upper(
245 Algorithm::Sha256,
246 "0022446688AACCEE1133557799BBDDFF0022446688AACCEE1133557799BBDDFF",
247 )
248 .unwrap(),
249 )
250 .unwrap(),
251 }),
252 timestamp: Some(dt.try_into().unwrap()),
253 signature: Some(dummy_signature()),
254 };
255
256 let mut got = vec![];
257
258 let request = SignProposalRequest {
259 proposal,
260 chain_id: ChainId::from_str("test_chain_id").unwrap(),
261 };
262
263 let _have = request.to_signable_bytes(&mut got);
264
265 let want = vec![
300 136, 1, 8, 32, 17, 57, 48, 0, 0, 0, 0, 0, 0, 25, 160, 91, 0, 0, 0, 0, 0, 0, 32, 255,
301 255, 255, 255, 255, 255, 255, 255, 255, 1, 42, 74, 10, 32, 222, 173, 190, 239, 222,
302 173, 190, 239, 186, 251, 175, 186, 251, 175, 186, 250, 222, 173, 190, 239, 222, 173,
303 190, 239, 186, 251, 175, 186, 251, 175, 186, 250, 18, 38, 8, 255, 255, 3, 18, 32, 0,
304 34, 68, 102, 136, 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 0, 34, 68, 102,
305 136, 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 50, 12, 8, 162, 216, 255, 211,
306 5, 16, 192, 242, 227, 236, 2, 58, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110,
307 95, 105, 100,
308 ];
309
310 assert_eq!(got, want)
311 }
312
313 #[test]
314 fn test_encoding_with_empty_block_id() {
316 let dt = datetime!(2018-02-11 07:09:22.765 UTC);
317 let proposal = Proposal {
318 msg_type: Type::Proposal,
319 height: Height::from(12345_u32),
320 round: Round::from(23456_u16),
321 pol_round: None,
322 block_id: Some(BlockId {
323 hash: Hash::from_hex_upper(Algorithm::Sha256, "").unwrap(),
324 part_set_header: Header::new(
325 65535,
326 Hash::from_hex_upper(
327 Algorithm::Sha256,
328 "0022446688AACCEE1133557799BBDDFF0022446688AACCEE1133557799BBDDFF",
329 )
330 .unwrap(),
331 )
332 .unwrap(),
333 }),
334 timestamp: Some(dt.try_into().unwrap()),
335 signature: Some(dummy_signature()),
336 };
337
338 let mut got = vec![];
339
340 let request = SignProposalRequest {
341 proposal,
342 chain_id: ChainId::from_str("test_chain_id").unwrap(),
343 };
344
345 let _have = request.to_signable_bytes(&mut got);
346
347 let want = vec![
381 102, 8, 32, 17, 57, 48, 0, 0, 0, 0, 0, 0, 25, 160, 91, 0, 0, 0, 0, 0, 0, 32, 255, 255,
382 255, 255, 255, 255, 255, 255, 255, 1, 42, 40, 18, 38, 8, 255, 255, 3, 18, 32, 0, 34,
383 68, 102, 136, 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 0, 34, 68, 102, 136,
384 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 50, 12, 8, 162, 216, 255, 211, 5,
385 16, 192, 242, 227, 236, 2, 58, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110, 95,
386 105, 100,
387 ];
388
389 assert_eq!(got, want)
390 }
391
392 cometbft_old_pb_modules! {
393 use super::*;
394
395 #[test]
396 fn test_deserialization() {
397 let dt = datetime!(2018-02-11 07:09:22.765 UTC);
398 let proposal = Proposal {
399 msg_type: Type::Proposal,
400 height: Height::from(12345_u32),
401 round: Round::from(23456_u16),
402 timestamp: Some(dt.try_into().unwrap()),
403
404 pol_round: None,
405 block_id: Some(BlockId {
406 hash: Hash::from_hex_upper(
407 Algorithm::Sha256,
408 "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFADEADBEEFDEADBEEFBAFBAFBAFBAFBAFA",
409 )
410 .unwrap(),
411 part_set_header: Header::new(
412 65535,
413 Hash::from_hex_upper(
414 Algorithm::Sha256,
415 "0022446688AACCEE1133557799BBDDFF0022446688AACCEE1133557799BBDDFF",
416 )
417 .unwrap(),
418 )
419 .unwrap(),
420 }),
421 signature: Some(dummy_signature()),
422 };
423 let want = SignProposalRequest {
424 proposal,
425 chain_id: ChainId::from_str("test_chain_id").unwrap(),
426 };
427
428 let data = vec![
429 10, 176, 1, 8, 32, 16, 185, 96, 24, 160, 183, 1, 32, 255, 255, 255, 255, 255, 255, 255,
430 255, 255, 1, 42, 74, 10, 32, 222, 173, 190, 239, 222, 173, 190, 239, 186, 251, 175,
431 186, 251, 175, 186, 250, 222, 173, 190, 239, 222, 173, 190, 239, 186, 251, 175, 186,
432 251, 175, 186, 250, 18, 38, 8, 255, 255, 3, 18, 32, 0, 34, 68, 102, 136, 170, 204, 238,
433 17, 51, 85, 119, 153, 187, 221, 255, 0, 34, 68, 102, 136, 170, 204, 238, 17, 51, 85,
434 119, 153, 187, 221, 255, 50, 12, 8, 162, 216, 255, 211, 5, 16, 192, 242, 227, 236, 2,
435 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,
436 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,
437 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110, 95,
438 105, 100,
439 ];
440
441 let have = <SignProposalRequest as Protobuf<pb::privval::SignProposalRequest>>::decode_vec(&data).unwrap();
442 assert_eq!(have, want);
443 }
444 }
445
446 mod v1 {
447 use super::*;
448 use cometbft_proto::privval::v1 as pb;
449 use cometbft_proto::Protobuf;
450
451 #[test]
452 fn test_deserialization() {
453 let dt = datetime!(2018-02-11 07:09:22.765 UTC);
454 let proposal = Proposal {
455 msg_type: Type::Proposal,
456 height: Height::from(12345_u32),
457 round: Round::from(23456_u16),
458 timestamp: Some(dt.try_into().unwrap()),
459
460 pol_round: None,
461 block_id: Some(BlockId {
462 hash: Hash::from_hex_upper(
463 Algorithm::Sha256,
464 "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFADEADBEEFDEADBEEFBAFBAFBAFBAFBAFA",
465 )
466 .unwrap(),
467 part_set_header: Header::new(
468 65535,
469 Hash::from_hex_upper(
470 Algorithm::Sha256,
471 "0022446688AACCEE1133557799BBDDFF0022446688AACCEE1133557799BBDDFF",
472 )
473 .unwrap(),
474 )
475 .unwrap(),
476 }),
477 signature: Some(dummy_signature()),
478 };
479 let want = SignProposalRequest {
480 proposal,
481 chain_id: ChainId::from_str("test_chain_id").unwrap(),
482 };
483
484 let data = vec![
485 10, 176, 1, 8, 32, 16, 185, 96, 24, 160, 183, 1, 32, 255, 255, 255, 255, 255, 255,
486 255, 255, 255, 1, 42, 74, 10, 32, 222, 173, 190, 239, 222, 173, 190, 239, 186, 251,
487 175, 186, 251, 175, 186, 250, 222, 173, 190, 239, 222, 173, 190, 239, 186, 251,
488 175, 186, 251, 175, 186, 250, 18, 38, 8, 255, 255, 3, 18, 32, 0, 34, 68, 102, 136,
489 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 0, 34, 68, 102, 136, 170, 204,
490 238, 17, 51, 85, 119, 153, 187, 221, 255, 50, 12, 8, 162, 216, 255, 211, 5, 16,
491 192, 242, 227, 236, 2, 58, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
492 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,
493 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 13, 116, 101, 115,
494 116, 95, 99, 104, 97, 105, 110, 95, 105, 100,
495 ];
496
497 let have =
498 <SignProposalRequest as Protobuf<pb::SignProposalRequest>>::decode_vec(&data)
499 .unwrap();
500 assert_eq!(have, want);
501 }
502 }
503
504 mod v1beta1 {
505 use super::*;
506 use cometbft_proto::privval::v1beta1 as pb;
507 use cometbft_proto::Protobuf;
508
509 #[test]
510 fn test_deserialization() {
511 let dt = datetime!(2018-02-11 07:09:22.765 UTC);
512 let proposal = Proposal {
513 msg_type: Type::Proposal,
514 height: Height::from(12345_u32),
515 round: Round::from(23456_u16),
516 timestamp: Some(dt.try_into().unwrap()),
517
518 pol_round: None,
519 block_id: Some(BlockId {
520 hash: Hash::from_hex_upper(
521 Algorithm::Sha256,
522 "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFADEADBEEFDEADBEEFBAFBAFBAFBAFBAFA",
523 )
524 .unwrap(),
525 part_set_header: Header::new(
526 65535,
527 Hash::from_hex_upper(
528 Algorithm::Sha256,
529 "0022446688AACCEE1133557799BBDDFF0022446688AACCEE1133557799BBDDFF",
530 )
531 .unwrap(),
532 )
533 .unwrap(),
534 }),
535 signature: Some(dummy_signature()),
536 };
537 let want = SignProposalRequest {
538 proposal,
539 chain_id: ChainId::from_str("test_chain_id").unwrap(),
540 };
541
542 let data = vec![
543 10, 176, 1, 8, 32, 16, 185, 96, 24, 160, 183, 1, 32, 255, 255, 255, 255, 255, 255,
544 255, 255, 255, 1, 42, 74, 10, 32, 222, 173, 190, 239, 222, 173, 190, 239, 186, 251,
545 175, 186, 251, 175, 186, 250, 222, 173, 190, 239, 222, 173, 190, 239, 186, 251,
546 175, 186, 251, 175, 186, 250, 18, 38, 8, 255, 255, 3, 18, 32, 0, 34, 68, 102, 136,
547 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 0, 34, 68, 102, 136, 170, 204,
548 238, 17, 51, 85, 119, 153, 187, 221, 255, 50, 12, 8, 162, 216, 255, 211, 5, 16,
549 192, 242, 227, 236, 2, 58, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
550 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,
551 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 13, 116, 101, 115,
552 116, 95, 99, 104, 97, 105, 110, 95, 105, 100,
553 ];
554
555 let have =
556 <SignProposalRequest as Protobuf<pb::SignProposalRequest>>::decode_vec(&data)
557 .unwrap();
558 assert_eq!(have, want);
559 }
560 }
561}