tendermint/
vote.rs

1//! Votes from validators
2
3mod canonical_vote;
4mod power;
5mod sign_vote;
6mod validator_index;
7
8use core::{fmt, str::FromStr};
9
10use bytes::BufMut;
11use serde::{Deserialize, Serialize};
12use tendermint_proto::v0_38::types::{CanonicalVote as RawCanonicalVote, Vote as RawVote};
13use tendermint_proto::{Error as ProtobufError, Protobuf};
14
15pub use self::{
16    canonical_vote::CanonicalVote, power::Power, sign_vote::*, validator_index::ValidatorIndex,
17};
18use crate::{
19    account, block, chain::Id as ChainId, consensus::State, error::Error, hash, prelude::*,
20    Signature, Time,
21};
22
23/// Votes are signed messages from validators for a particular block which
24/// include information about the validator signing it.
25///
26/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#vote>
27#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
28#[serde(try_from = "RawVote", into = "RawVote")]
29pub struct Vote {
30    /// Type of vote (prevote or precommit)
31    pub vote_type: Type,
32
33    /// Block height
34    pub height: block::Height,
35
36    /// Round
37    pub round: block::Round,
38
39    /// Block ID
40    pub block_id: Option<block::Id>,
41
42    /// Timestamp
43    pub timestamp: Option<Time>,
44
45    /// Validator address
46    pub validator_address: account::Id,
47
48    /// Validator index
49    pub validator_index: ValidatorIndex,
50
51    /// Signature
52    pub signature: Option<Signature>,
53
54    /// Vote extension provided by the application.
55    /// Only valid for precommit messages.
56    ///
57    /// This field has been added in CometBFT 0.38 and will be ignored when
58    /// encoding into earlier protocol versions.
59    pub extension: Vec<u8>,
60
61    /// Vote extension signature by the validator
62    /// Only valid for precommit messages.
63    ///
64    /// This field has been added in CometBFT 0.38 and will be ignored when
65    /// encoding into earlier protocol versions.
66    pub extension_signature: Option<Signature>,
67}
68
69// =============================================================================
70// Protobuf conversions
71// =============================================================================
72
73mod v0_34 {
74    use super::Vote;
75    use crate::{block, prelude::*, Error, Signature};
76    use tendermint_proto::v0_34::types::Vote as RawVote;
77    use tendermint_proto::Protobuf;
78
79    impl Protobuf<RawVote> for Vote {}
80
81    impl TryFrom<RawVote> for Vote {
82        type Error = Error;
83
84        fn try_from(value: RawVote) -> Result<Self, Self::Error> {
85            if value.timestamp.is_none() {
86                return Err(Error::missing_timestamp());
87            }
88            Ok(Vote {
89                vote_type: value.r#type.try_into()?,
90                height: value.height.try_into()?,
91                round: value.round.try_into()?,
92                // block_id can be nil in the Go implementation
93                block_id: value
94                    .block_id
95                    .map(TryInto::try_into)
96                    .transpose()?
97                    .filter(|i| i != &block::Id::default()),
98                timestamp: value.timestamp.map(|t| t.try_into()).transpose()?,
99                validator_address: value.validator_address.try_into()?,
100                validator_index: value.validator_index.try_into()?,
101                signature: Signature::new(value.signature)?,
102                extension: Default::default(),
103                extension_signature: None,
104            })
105        }
106    }
107
108    impl From<Vote> for RawVote {
109        fn from(value: Vote) -> Self {
110            RawVote {
111                r#type: value.vote_type.into(),
112                height: value.height.into(),
113                round: value.round.into(),
114                block_id: value.block_id.map(Into::into),
115                timestamp: value.timestamp.map(Into::into),
116                validator_address: value.validator_address.into(),
117                validator_index: value.validator_index.into(),
118                signature: value.signature.map(|s| s.into_bytes()).unwrap_or_default(),
119            }
120        }
121    }
122}
123
124mod v0_37 {
125    use super::Vote;
126    use crate::{block, prelude::*, Error, Signature};
127    use tendermint_proto::v0_37::types::Vote as RawVote;
128    use tendermint_proto::Protobuf;
129
130    impl Protobuf<RawVote> for Vote {}
131
132    impl TryFrom<RawVote> for Vote {
133        type Error = Error;
134
135        fn try_from(value: RawVote) -> Result<Self, Self::Error> {
136            if value.timestamp.is_none() {
137                return Err(Error::missing_timestamp());
138            }
139            Ok(Vote {
140                vote_type: value.r#type.try_into()?,
141                height: value.height.try_into()?,
142                round: value.round.try_into()?,
143                // block_id can be nil in the Go implementation
144                block_id: value
145                    .block_id
146                    .map(TryInto::try_into)
147                    .transpose()?
148                    .filter(|i| i != &block::Id::default()),
149                timestamp: value.timestamp.map(|t| t.try_into()).transpose()?,
150                validator_address: value.validator_address.try_into()?,
151                validator_index: value.validator_index.try_into()?,
152                signature: Signature::new(value.signature)?,
153                extension: Default::default(),
154                extension_signature: None,
155            })
156        }
157    }
158
159    impl From<Vote> for RawVote {
160        fn from(value: Vote) -> Self {
161            RawVote {
162                r#type: value.vote_type.into(),
163                height: value.height.into(),
164                round: value.round.into(),
165                block_id: value.block_id.map(Into::into),
166                timestamp: value.timestamp.map(Into::into),
167                validator_address: value.validator_address.into(),
168                validator_index: value.validator_index.into(),
169                signature: value.signature.map(|s| s.into_bytes()).unwrap_or_default(),
170            }
171        }
172    }
173}
174
175mod v0_38 {
176    use super::Vote;
177    use crate::{block, prelude::*, Error, Signature};
178    use tendermint_proto::v0_38::types::Vote as RawVote;
179    use tendermint_proto::Protobuf;
180
181    impl Protobuf<RawVote> for Vote {}
182
183    impl TryFrom<RawVote> for Vote {
184        type Error = Error;
185
186        fn try_from(value: RawVote) -> Result<Self, Self::Error> {
187            if value.timestamp.is_none() {
188                return Err(Error::missing_timestamp());
189            }
190            Ok(Vote {
191                vote_type: value.r#type.try_into()?,
192                height: value.height.try_into()?,
193                round: value.round.try_into()?,
194                // block_id can be nil in the Go implementation
195                block_id: value
196                    .block_id
197                    .map(TryInto::try_into)
198                    .transpose()?
199                    .filter(|i| i != &block::Id::default()),
200                timestamp: value.timestamp.map(|t| t.try_into()).transpose()?,
201                validator_address: value.validator_address.try_into()?,
202                validator_index: value.validator_index.try_into()?,
203                signature: Signature::new(value.signature)?,
204                extension: value.extension,
205                extension_signature: Signature::new(value.extension_signature)?,
206            })
207        }
208    }
209
210    impl From<Vote> for RawVote {
211        fn from(value: Vote) -> Self {
212            RawVote {
213                r#type: value.vote_type.into(),
214                height: value.height.into(),
215                round: value.round.into(),
216                block_id: value.block_id.map(Into::into),
217                timestamp: value.timestamp.map(Into::into),
218                validator_address: value.validator_address.into(),
219                validator_index: value.validator_index.into(),
220                signature: value.signature.map(|s| s.into_bytes()).unwrap_or_default(),
221                extension: value.extension,
222                extension_signature: value
223                    .extension_signature
224                    .map(|s| s.into_bytes())
225                    .unwrap_or_default(),
226            }
227        }
228    }
229}
230
231impl Vote {
232    /// Is this vote a prevote?
233    pub fn is_prevote(&self) -> bool {
234        match self.vote_type {
235            Type::Prevote => true,
236            Type::Precommit => false,
237        }
238    }
239
240    /// Is this vote a precommit?
241    pub fn is_precommit(&self) -> bool {
242        match self.vote_type {
243            Type::Precommit => true,
244            Type::Prevote => false,
245        }
246    }
247
248    /// Returns block_id.hash
249    pub fn header_hash(&self) -> Option<hash::Hash> {
250        self.block_id.map(|b| b.hash)
251    }
252
253    /// Create signable bytes from Vote.
254    pub fn to_signable_bytes<B>(
255        &self,
256        chain_id: ChainId,
257        sign_bytes: &mut B,
258    ) -> Result<bool, ProtobufError>
259    where
260        B: BufMut,
261    {
262        let canonical = CanonicalVote::new(self.clone(), chain_id);
263        Protobuf::<RawCanonicalVote>::encode_length_delimited(canonical, sign_bytes)?;
264        Ok(true)
265    }
266
267    /// Create signable vector from Vote.
268    pub fn into_signable_vec(self, chain_id: ChainId) -> Vec<u8> {
269        let canonical = CanonicalVote::new(self, chain_id);
270        Protobuf::<RawCanonicalVote>::encode_length_delimited_vec(canonical)
271    }
272
273    /// Consensus state from this vote - This doesn't seem to be used anywhere.
274    #[deprecated(
275        since = "0.17.0",
276        note = "This seems unnecessary, please raise it to the team, if you need it."
277    )]
278    pub fn consensus_state(&self) -> State {
279        State {
280            height: self.height,
281            round: self.round,
282            step: 6,
283            block_id: self.block_id,
284        }
285    }
286}
287
288/// SignedVote is the union of a canonicalized vote, the signature on
289/// the sign bytes of that vote and the id of the validator who signed it.
290pub struct SignedVote {
291    vote: CanonicalVote,
292    validator_address: account::Id,
293    signature: Signature,
294}
295
296impl SignedVote {
297    /// Create new `SignedVote` from provided canonicalized vote, validator id, and
298    /// the signature of that validator.
299    pub fn new(
300        vote: Vote,
301        chain_id: ChainId,
302        validator_address: account::Id,
303        signature: Signature,
304    ) -> SignedVote {
305        let canonical_vote = CanonicalVote::new(vote, chain_id);
306        SignedVote {
307            vote: canonical_vote,
308            signature,
309            validator_address,
310        }
311    }
312
313    /// Create a new `SignedVote` from the provided `Vote`, which may or may not be signed.
314    /// If the vote is not signed, this function will return `None`.
315    pub fn from_vote(vote: Vote, chain_id: ChainId) -> Option<Self> {
316        let validator_address = vote.validator_address;
317        vote.signature
318            .clone()
319            .map(|signature| Self::new(vote, chain_id, validator_address, signature))
320    }
321
322    /// Return the id of the validator that signed this vote.
323    pub fn validator_id(&self) -> account::Id {
324        self.validator_address
325    }
326
327    /// Return the bytes (of the canonicalized vote) that were signed.
328    pub fn sign_bytes(&self) -> Vec<u8> {
329        Protobuf::<RawCanonicalVote>::encode_length_delimited_vec(self.vote.clone())
330    }
331
332    pub fn sign_bytes_into(&self, buf: &mut impl BufMut) -> Result<(), ProtobufError> {
333        Protobuf::<RawCanonicalVote>::encode_length_delimited(self.vote.clone(), buf)
334    }
335
336    /// Return the actual signature on the canonicalized vote.
337    pub fn signature(&self) -> &Signature {
338        &self.signature
339    }
340}
341
342/// Types of votes
343#[repr(u8)]
344#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
345pub enum Type {
346    /// Votes for blocks which validators observe are valid for a given round
347    Prevote = 1,
348
349    /// Votes to commit to a particular block for a given round
350    Precommit = 2,
351}
352
353impl Protobuf<i32> for Type {}
354
355impl TryFrom<i32> for Type {
356    type Error = Error;
357
358    fn try_from(value: i32) -> Result<Self, Self::Error> {
359        match value {
360            1 => Ok(Type::Prevote),
361            2 => Ok(Type::Precommit),
362            _ => Err(Error::invalid_message_type()),
363        }
364    }
365}
366
367impl From<Type> for i32 {
368    fn from(value: Type) -> Self {
369        value as i32
370    }
371}
372
373impl fmt::Display for Type {
374    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375        let id = match self {
376            Type::Prevote => "Prevote",
377            Type::Precommit => "Precommit",
378        };
379        write!(f, "{id}")
380    }
381}
382
383impl FromStr for Type {
384    type Err = Error;
385
386    fn from_str(s: &str) -> Result<Self, Self::Err> {
387        match s {
388            "Prevote" => Ok(Self::Prevote),
389            "Precommit" => Ok(Self::Precommit),
390            _ => Err(Error::invalid_message_type()),
391        }
392    }
393}