tendermint/block/
commit_sig.rs

1//! CommitSig within Commit
2
3use tendermint_proto::google::protobuf::Timestamp;
4
5use crate::{account, prelude::*, Signature, Time};
6
7/// The special zero timestamp is to be used for absent votes,
8/// where there is no timestamp to speak of.
9///
10/// It is not the standard UNIX epoch at 0 seconds, ie. 1970-01-01 00:00:00 UTC,
11/// but a custom Tendermint-specific one for 1-1-1 00:00:00 UTC
12///
13/// From the corresponding Tendermint `Time` struct:
14///
15/// The zero value for a Time is defined to be
16/// January 1, year 1, 00:00:00.000000000 UTC
17/// which (1) looks like a zero, or as close as you can get in a date
18/// (1-1-1 00:00:00 UTC), (2) is unlikely enough to arise in practice to
19/// be a suitable "not set" sentinel, unlike Jan 1 1970, and (3) has a
20/// non-negative year even in time zones west of UTC, unlike 1-1-0
21/// 00:00:00 UTC, which would be 12-31-(-1) 19:00:00 in New York.
22const ZERO_TIMESTAMP: Timestamp = Timestamp {
23    seconds: -62135596800,
24    nanos: 0,
25};
26
27/// CommitSig represents a signature of a validator.
28/// It's a part of the Commit and can be used to reconstruct the vote set given the validator set.
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub enum CommitSig {
31    /// no vote was received from a validator.
32    BlockIdFlagAbsent,
33    /// voted for the Commit.BlockID.
34    BlockIdFlagCommit {
35        /// Validator address
36        validator_address: account::Id,
37        /// Timestamp of vote
38        timestamp: Time,
39        /// Signature of vote
40        signature: Option<Signature>,
41    },
42    /// voted for nil.
43    BlockIdFlagNil {
44        /// Validator address
45        validator_address: account::Id,
46        /// Timestamp of vote
47        timestamp: Time,
48        /// Signature of vote
49        signature: Option<Signature>,
50    },
51}
52
53impl CommitSig {
54    /// Get the address of this validator if a vote was received.
55    pub fn validator_address(&self) -> Option<account::Id> {
56        match self {
57            Self::BlockIdFlagCommit {
58                validator_address, ..
59            } => Some(*validator_address),
60            Self::BlockIdFlagNil {
61                validator_address, ..
62            } => Some(*validator_address),
63            _ => None,
64        }
65    }
66
67    /// Whether this signature is absent (no vote was received from validator)
68    pub fn is_absent(&self) -> bool {
69        self == &Self::BlockIdFlagAbsent
70    }
71
72    /// Whether this signature is a commit  (validator voted for the Commit.BlockId)
73    pub fn is_commit(&self) -> bool {
74        matches!(self, Self::BlockIdFlagCommit { .. })
75    }
76
77    /// Whether this signature is nil (validator voted for nil)
78    pub fn is_nil(&self) -> bool {
79        matches!(self, Self::BlockIdFlagNil { .. })
80    }
81}
82
83tendermint_pb_modules! {
84    use super::{CommitSig, ZERO_TIMESTAMP};
85    use crate::{error::Error, prelude::*, Signature};
86
87    use pb::types::{BlockIdFlag, CommitSig as RawCommitSig};
88
89    impl TryFrom<RawCommitSig> for CommitSig {
90        type Error = Error;
91
92        fn try_from(value: RawCommitSig) -> Result<Self, Self::Error> {
93            if value.block_id_flag == BlockIdFlag::Absent as i32 {
94                if value.timestamp.is_some() {
95                    let timestamp = value.timestamp.unwrap();
96                    // 0001-01-01T00:00:00.000Z translates to EPOCH-62135596800 seconds
97                    if timestamp.nanos != 0 || timestamp.seconds != -62135596800 {
98                        return Err(Error::invalid_timestamp(
99                            "absent commitsig has non-zero timestamp".to_string(),
100                        ));
101                    }
102                }
103
104                if !value.signature.is_empty() {
105                    return Err(Error::invalid_signature(
106                        "expected empty signature for absent commitsig".to_string(),
107                    ));
108                }
109
110                return Ok(CommitSig::BlockIdFlagAbsent);
111            }
112
113            if value.block_id_flag == BlockIdFlag::Commit as i32 {
114                if value.signature.is_empty() {
115                    return Err(Error::invalid_signature(
116                        "expected non-empty signature for regular commitsig".to_string(),
117                    ));
118                }
119
120                if value.validator_address.is_empty() {
121                    return Err(Error::invalid_validator_address());
122                }
123
124                let timestamp = value
125                    .timestamp
126                    .ok_or_else(Error::missing_timestamp)?
127                    .try_into()?;
128
129                return Ok(CommitSig::BlockIdFlagCommit {
130                    validator_address: value.validator_address.try_into()?,
131                    timestamp,
132                    signature: Signature::new(value.signature)?,
133                });
134            }
135            if value.block_id_flag == BlockIdFlag::Nil as i32 {
136                if value.signature.is_empty() {
137                    return Err(Error::invalid_signature(
138                        "nil commitsig has no signature".to_string(),
139                    ));
140                }
141                if value.validator_address.is_empty() {
142                    return Err(Error::invalid_validator_address());
143                }
144                return Ok(CommitSig::BlockIdFlagNil {
145                    validator_address: value.validator_address.try_into()?,
146                    timestamp: value
147                        .timestamp
148                        .ok_or_else(Error::missing_timestamp)?
149                        .try_into()?,
150                    signature: Signature::new(value.signature)?,
151                });
152            }
153            Err(Error::block_id_flag())
154        }
155    }
156
157    impl From<CommitSig> for RawCommitSig {
158        fn from(commit: CommitSig) -> RawCommitSig {
159            match commit {
160                CommitSig::BlockIdFlagAbsent => RawCommitSig {
161                    block_id_flag: BlockIdFlag::Absent as i32,
162                    validator_address: Vec::new(),
163                    timestamp: Some(ZERO_TIMESTAMP),
164                    signature: Vec::new(),
165                },
166                CommitSig::BlockIdFlagNil {
167                    validator_address,
168                    timestamp,
169                    signature,
170                } => RawCommitSig {
171                    block_id_flag: BlockIdFlag::Nil as i32,
172                    validator_address: validator_address.into(),
173                    timestamp: Some(timestamp.into()),
174                    signature: signature.map(|s| s.into_bytes()).unwrap_or_default(),
175                },
176                CommitSig::BlockIdFlagCommit {
177                    validator_address,
178                    timestamp,
179                    signature,
180                } => RawCommitSig {
181                    block_id_flag: BlockIdFlag::Commit as i32,
182                    validator_address: validator_address.into(),
183                    timestamp: Some(timestamp.into()),
184                    signature: signature.map(|s| s.into_bytes()).unwrap_or_default(),
185                },
186            }
187        }
188    }
189
190    #[test]
191    #[cfg(test)]
192    fn test_block_id_flag_absent_serialization() {
193        let absent = CommitSig::BlockIdFlagAbsent;
194        let raw_absent = RawCommitSig::from(absent);
195        let expected = r#"{"block_id_flag":1,"validator_address":"","timestamp":"0001-01-01T00:00:00Z","signature":""}"#;
196        let output = serde_json::to_string(&raw_absent).unwrap();
197        assert_eq!(expected, &output);
198    }
199
200    #[test]
201    #[cfg(test)]
202    fn test_block_id_flag_absent_deserialization() {
203        let json = r#"{"block_id_flag":1,"validator_address":"","timestamp":"0001-01-01T00:00:00Z","signature":""}"#;
204        let raw_commit_sg = serde_json::from_str::<RawCommitSig>(json).unwrap();
205        let commit_sig = CommitSig::try_from(raw_commit_sg).unwrap();
206        assert_eq!(commit_sig, CommitSig::BlockIdFlagAbsent);
207    }
208}