mod canonical_proposal;
mod msg_type;
mod sign_proposal;
use bytes::BufMut;
pub use msg_type::Type;
pub use sign_proposal::{SignProposalRequest, SignedProposalResponse};
use tendermint_proto::v0_37::types::CanonicalProposal as RawCanonicalProposal;
use tendermint_proto::{Error as ProtobufError, Protobuf};
pub use self::canonical_proposal::CanonicalProposal;
use crate::{
block::{Height, Id as BlockId, Round},
chain::Id as ChainId,
consensus::State,
prelude::*,
Signature, Time,
};
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Proposal {
pub msg_type: Type,
pub height: Height,
pub round: Round,
pub pol_round: Option<Round>,
pub block_id: Option<BlockId>,
pub timestamp: Option<Time>,
pub signature: Option<Signature>,
}
tendermint_pb_modules! {
use super::Proposal;
use crate::{Signature, Error, block::Round};
use pb::types::Proposal as RawProposal;
impl Protobuf<RawProposal> for Proposal {}
impl TryFrom<RawProposal> for Proposal {
type Error = Error;
fn try_from(value: RawProposal) -> Result<Self, Self::Error> {
if value.pol_round < -1 {
return Err(Error::negative_pol_round());
}
let pol_round = match value.pol_round {
-1 => None,
n => Some(Round::try_from(n)?),
};
Ok(Proposal {
msg_type: value.r#type.try_into()?,
height: value.height.try_into()?,
round: value.round.try_into()?,
pol_round,
block_id: value.block_id.map(TryInto::try_into).transpose()?,
timestamp: value.timestamp.map(|t| t.try_into()).transpose()?,
signature: Signature::new(value.signature)?,
})
}
}
impl From<Proposal> for RawProposal {
fn from(value: Proposal) -> Self {
RawProposal {
r#type: value.msg_type.into(),
height: value.height.into(),
round: value.round.into(),
pol_round: value.pol_round.map_or(-1, Into::into),
block_id: value.block_id.map(Into::into),
timestamp: value.timestamp.map(Into::into),
signature: value.signature.map(|s| s.into_bytes()).unwrap_or_default(),
}
}
}
}
impl Proposal {
pub fn to_signable_bytes<B>(
&self,
chain_id: ChainId,
sign_bytes: &mut B,
) -> Result<bool, ProtobufError>
where
B: BufMut,
{
let canonical = CanonicalProposal::new(self.clone(), chain_id);
Protobuf::<RawCanonicalProposal>::encode_length_delimited(canonical, sign_bytes)?;
Ok(true)
}
pub fn into_signable_vec(self, chain_id: ChainId) -> Vec<u8> {
let canonical = CanonicalProposal::new(self, chain_id);
Protobuf::<RawCanonicalProposal>::encode_length_delimited_vec(canonical)
}
#[deprecated(
since = "0.17.0",
note = "This seems unnecessary, please raise it to the team, if you need it."
)]
pub fn consensus_state(&self) -> State {
State {
height: self.height,
round: self.round,
step: 3,
block_id: self.block_id,
}
}
}
#[cfg(test)]
mod tests {
use core::str::FromStr;
use time::macros::datetime;
use crate::{
block::{parts::Header, Height, Id as BlockId, Round},
chain::Id as ChainId,
hash::{Algorithm, Hash},
prelude::*,
proposal::{SignProposalRequest, Type},
test::dummy_signature,
Proposal,
};
#[test]
fn test_serialization() {
let dt = datetime!(2018-02-11 07:09:22.765 UTC);
let proposal = Proposal {
msg_type: Type::Proposal,
height: Height::from(12345_u32),
round: Round::from(23456_u16),
pol_round: None,
block_id: Some(BlockId {
hash: Hash::from_hex_upper(
Algorithm::Sha256,
"DEADBEEFDEADBEEFBAFBAFBAFBAFBAFADEADBEEFDEADBEEFBAFBAFBAFBAFBAFA",
)
.unwrap(),
part_set_header: Header::new(
65535,
Hash::from_hex_upper(
Algorithm::Sha256,
"0022446688AACCEE1133557799BBDDFF0022446688AACCEE1133557799BBDDFF",
)
.unwrap(),
)
.unwrap(),
}),
timestamp: Some(dt.try_into().unwrap()),
signature: Some(dummy_signature()),
};
let mut got = vec![];
let request = SignProposalRequest {
proposal,
chain_id: ChainId::from_str("test_chain_id").unwrap(),
};
let _have = request.to_signable_bytes(&mut got);
let want = vec![
136, 1, 8, 32, 17, 57, 48, 0, 0, 0, 0, 0, 0, 25, 160, 91, 0, 0, 0, 0, 0, 0, 32, 255,
255, 255, 255, 255, 255, 255, 255, 255, 1, 42, 74, 10, 32, 222, 173, 190, 239, 222,
173, 190, 239, 186, 251, 175, 186, 251, 175, 186, 250, 222, 173, 190, 239, 222, 173,
190, 239, 186, 251, 175, 186, 251, 175, 186, 250, 18, 38, 8, 255, 255, 3, 18, 32, 0,
34, 68, 102, 136, 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 0, 34, 68, 102,
136, 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 50, 12, 8, 162, 216, 255, 211,
5, 16, 192, 242, 227, 236, 2, 58, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110,
95, 105, 100,
];
assert_eq!(got, want)
}
#[test]
fn test_encoding_with_empty_block_id() {
let dt = datetime!(2018-02-11 07:09:22.765 UTC);
let proposal = Proposal {
msg_type: Type::Proposal,
height: Height::from(12345_u32),
round: Round::from(23456_u16),
pol_round: None,
block_id: Some(BlockId {
hash: Hash::from_hex_upper(Algorithm::Sha256, "").unwrap(),
part_set_header: Header::new(
65535,
Hash::from_hex_upper(
Algorithm::Sha256,
"0022446688AACCEE1133557799BBDDFF0022446688AACCEE1133557799BBDDFF",
)
.unwrap(),
)
.unwrap(),
}),
timestamp: Some(dt.try_into().unwrap()),
signature: Some(dummy_signature()),
};
let mut got = vec![];
let request = SignProposalRequest {
proposal,
chain_id: ChainId::from_str("test_chain_id").unwrap(),
};
let _have = request.to_signable_bytes(&mut got);
let want = vec![
102, 8, 32, 17, 57, 48, 0, 0, 0, 0, 0, 0, 25, 160, 91, 0, 0, 0, 0, 0, 0, 32, 255, 255,
255, 255, 255, 255, 255, 255, 255, 1, 42, 40, 18, 38, 8, 255, 255, 3, 18, 32, 0, 34,
68, 102, 136, 170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 0, 34, 68, 102, 136,
170, 204, 238, 17, 51, 85, 119, 153, 187, 221, 255, 50, 12, 8, 162, 216, 255, 211, 5,
16, 192, 242, 227, 236, 2, 58, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110, 95,
105, 100,
];
assert_eq!(got, want)
}
tendermint_pb_modules! {
use super::*;
#[test]
fn test_deserialization() {
let dt = datetime!(2018-02-11 07:09:22.765 UTC);
let proposal = Proposal {
msg_type: Type::Proposal,
height: Height::from(12345_u32),
round: Round::from(23456_u16),
timestamp: Some(dt.try_into().unwrap()),
pol_round: None,
block_id: Some(BlockId {
hash: Hash::from_hex_upper(
Algorithm::Sha256,
"DEADBEEFDEADBEEFBAFBAFBAFBAFBAFADEADBEEFDEADBEEFBAFBAFBAFBAFBAFA",
)
.unwrap(),
part_set_header: Header::new(
65535,
Hash::from_hex_upper(
Algorithm::Sha256,
"0022446688AACCEE1133557799BBDDFF0022446688AACCEE1133557799BBDDFF",
)
.unwrap(),
)
.unwrap(),
}),
signature: Some(dummy_signature()),
};
let want = SignProposalRequest {
proposal,
chain_id: ChainId::from_str("test_chain_id").unwrap(),
};
let data = vec![
10, 176, 1, 8, 32, 16, 185, 96, 24, 160, 183, 1, 32, 255, 255, 255, 255, 255, 255, 255,
255, 255, 1, 42, 74, 10, 32, 222, 173, 190, 239, 222, 173, 190, 239, 186, 251, 175,
186, 251, 175, 186, 250, 222, 173, 190, 239, 222, 173, 190, 239, 186, 251, 175, 186,
251, 175, 186, 250, 18, 38, 8, 255, 255, 3, 18, 32, 0, 34, 68, 102, 136, 170, 204, 238,
17, 51, 85, 119, 153, 187, 221, 255, 0, 34, 68, 102, 136, 170, 204, 238, 17, 51, 85,
119, 153, 187, 221, 255, 50, 12, 8, 162, 216, 255, 211, 5, 16, 192, 242, 227, 236, 2,
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,
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,
0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110, 95,
105, 100,
];
let have = <SignProposalRequest as Protobuf<pb::privval::SignProposalRequest>>::decode_vec(&data).unwrap();
assert_eq!(have, want);
}
}
}