iq_cometbft/block/
commit_sig.rs

1//! CommitSig within Commit
2
3use cometbft_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 CometBFT-specific one for 1-1-1 00:00:00 UTC
12///
13/// From the corresponding CometBFT `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
83cometbft_old_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}
209
210mod v1 {
211    use super::{CommitSig, ZERO_TIMESTAMP};
212    use crate::{error::Error, prelude::*, Signature};
213    use cometbft_proto::types::v1::{self as pb, BlockIdFlag};
214
215    impl TryFrom<pb::CommitSig> for CommitSig {
216        type Error = Error;
217
218        fn try_from(value: pb::CommitSig) -> Result<Self, Self::Error> {
219            if value.block_id_flag == BlockIdFlag::Absent as i32 {
220                if value.timestamp.is_some() {
221                    let timestamp = value.timestamp.unwrap();
222                    // 0001-01-01T00:00:00.000Z translates to EPOCH-62135596800 seconds
223                    if timestamp.nanos != 0 || timestamp.seconds != -62135596800 {
224                        return Err(Error::invalid_timestamp(
225                            "absent commitsig has non-zero timestamp".to_string(),
226                        ));
227                    }
228                }
229
230                if !value.signature.is_empty() {
231                    return Err(Error::invalid_signature(
232                        "expected empty signature for absent commitsig".to_string(),
233                    ));
234                }
235
236                return Ok(CommitSig::BlockIdFlagAbsent);
237            }
238
239            if value.block_id_flag == BlockIdFlag::Commit as i32 {
240                if value.signature.is_empty() {
241                    return Err(Error::invalid_signature(
242                        "expected non-empty signature for regular commitsig".to_string(),
243                    ));
244                }
245
246                if value.validator_address.is_empty() {
247                    return Err(Error::invalid_validator_address());
248                }
249
250                let timestamp = value
251                    .timestamp
252                    .ok_or_else(Error::missing_timestamp)?
253                    .try_into()?;
254
255                return Ok(CommitSig::BlockIdFlagCommit {
256                    validator_address: value.validator_address.try_into()?,
257                    timestamp,
258                    signature: Signature::new(value.signature)?,
259                });
260            }
261            if value.block_id_flag == BlockIdFlag::Nil as i32 {
262                if value.signature.is_empty() {
263                    return Err(Error::invalid_signature(
264                        "nil commitsig has no signature".to_string(),
265                    ));
266                }
267                if value.validator_address.is_empty() {
268                    return Err(Error::invalid_validator_address());
269                }
270                return Ok(CommitSig::BlockIdFlagNil {
271                    validator_address: value.validator_address.try_into()?,
272                    timestamp: value
273                        .timestamp
274                        .ok_or_else(Error::missing_timestamp)?
275                        .try_into()?,
276                    signature: Signature::new(value.signature)?,
277                });
278            }
279            Err(Error::block_id_flag())
280        }
281    }
282
283    impl From<CommitSig> for pb::CommitSig {
284        fn from(commit: CommitSig) -> pb::CommitSig {
285            match commit {
286                CommitSig::BlockIdFlagAbsent => pb::CommitSig {
287                    block_id_flag: BlockIdFlag::Absent as i32,
288                    validator_address: Vec::new(),
289                    timestamp: Some(ZERO_TIMESTAMP),
290                    signature: Vec::new(),
291                },
292                CommitSig::BlockIdFlagNil {
293                    validator_address,
294                    timestamp,
295                    signature,
296                } => pb::CommitSig {
297                    block_id_flag: BlockIdFlag::Nil as i32,
298                    validator_address: validator_address.into(),
299                    timestamp: Some(timestamp.into()),
300                    signature: signature.map(|s| s.into_bytes()).unwrap_or_default(),
301                },
302                CommitSig::BlockIdFlagCommit {
303                    validator_address,
304                    timestamp,
305                    signature,
306                } => pb::CommitSig {
307                    block_id_flag: BlockIdFlag::Commit as i32,
308                    validator_address: validator_address.into(),
309                    timestamp: Some(timestamp.into()),
310                    signature: signature.map(|s| s.into_bytes()).unwrap_or_default(),
311                },
312            }
313        }
314    }
315
316    #[test]
317    #[cfg(test)]
318    fn test_block_id_flag_absent_serialization() {
319        let absent = CommitSig::BlockIdFlagAbsent;
320        let raw_absent = pb::CommitSig::from(absent);
321        let expected = r#"{"block_id_flag":1,"validator_address":"","timestamp":"0001-01-01T00:00:00Z","signature":""}"#;
322        let output = serde_json::to_string(&raw_absent).unwrap();
323        assert_eq!(expected, &output);
324    }
325
326    #[test]
327    #[cfg(test)]
328    fn test_block_id_flag_absent_deserialization() {
329        let json = r#"{"block_id_flag":1,"validator_address":"","timestamp":"0001-01-01T00:00:00Z","signature":""}"#;
330        let raw_commit_sg = serde_json::from_str::<pb::CommitSig>(json).unwrap();
331        let commit_sig = CommitSig::try_from(raw_commit_sg).unwrap();
332        assert_eq!(commit_sig, CommitSig::BlockIdFlagAbsent);
333    }
334}
335
336mod v1beta1 {
337    use super::{CommitSig, ZERO_TIMESTAMP};
338    use crate::{error::Error, prelude::*, Signature};
339    use cometbft_proto::types::v1beta1::{self as pb, BlockIdFlag};
340
341    impl TryFrom<pb::CommitSig> for CommitSig {
342        type Error = Error;
343
344        fn try_from(value: pb::CommitSig) -> Result<Self, Self::Error> {
345            if value.block_id_flag == BlockIdFlag::Absent as i32 {
346                if value.timestamp.is_some() {
347                    let timestamp = value.timestamp.unwrap();
348                    // 0001-01-01T00:00:00.000Z translates to EPOCH-62135596800 seconds
349                    if timestamp.nanos != 0 || timestamp.seconds != -62135596800 {
350                        return Err(Error::invalid_timestamp(
351                            "absent commitsig has non-zero timestamp".to_string(),
352                        ));
353                    }
354                }
355
356                if !value.signature.is_empty() {
357                    return Err(Error::invalid_signature(
358                        "expected empty signature for absent commitsig".to_string(),
359                    ));
360                }
361
362                return Ok(CommitSig::BlockIdFlagAbsent);
363            }
364
365            if value.block_id_flag == BlockIdFlag::Commit as i32 {
366                if value.signature.is_empty() {
367                    return Err(Error::invalid_signature(
368                        "expected non-empty signature for regular commitsig".to_string(),
369                    ));
370                }
371
372                if value.validator_address.is_empty() {
373                    return Err(Error::invalid_validator_address());
374                }
375
376                let timestamp = value
377                    .timestamp
378                    .ok_or_else(Error::missing_timestamp)?
379                    .try_into()?;
380
381                return Ok(CommitSig::BlockIdFlagCommit {
382                    validator_address: value.validator_address.try_into()?,
383                    timestamp,
384                    signature: Signature::new(value.signature)?,
385                });
386            }
387            if value.block_id_flag == BlockIdFlag::Nil as i32 {
388                if value.signature.is_empty() {
389                    return Err(Error::invalid_signature(
390                        "nil commitsig has no signature".to_string(),
391                    ));
392                }
393                if value.validator_address.is_empty() {
394                    return Err(Error::invalid_validator_address());
395                }
396                return Ok(CommitSig::BlockIdFlagNil {
397                    validator_address: value.validator_address.try_into()?,
398                    timestamp: value
399                        .timestamp
400                        .ok_or_else(Error::missing_timestamp)?
401                        .try_into()?,
402                    signature: Signature::new(value.signature)?,
403                });
404            }
405            Err(Error::block_id_flag())
406        }
407    }
408
409    impl From<CommitSig> for pb::CommitSig {
410        fn from(commit: CommitSig) -> pb::CommitSig {
411            match commit {
412                CommitSig::BlockIdFlagAbsent => pb::CommitSig {
413                    block_id_flag: BlockIdFlag::Absent as i32,
414                    validator_address: Vec::new(),
415                    timestamp: Some(ZERO_TIMESTAMP),
416                    signature: Vec::new(),
417                },
418                CommitSig::BlockIdFlagNil {
419                    validator_address,
420                    timestamp,
421                    signature,
422                } => pb::CommitSig {
423                    block_id_flag: BlockIdFlag::Nil as i32,
424                    validator_address: validator_address.into(),
425                    timestamp: Some(timestamp.into()),
426                    signature: signature.map(|s| s.into_bytes()).unwrap_or_default(),
427                },
428                CommitSig::BlockIdFlagCommit {
429                    validator_address,
430                    timestamp,
431                    signature,
432                } => pb::CommitSig {
433                    block_id_flag: BlockIdFlag::Commit as i32,
434                    validator_address: validator_address.into(),
435                    timestamp: Some(timestamp.into()),
436                    signature: signature.map(|s| s.into_bytes()).unwrap_or_default(),
437                },
438            }
439        }
440    }
441
442    #[test]
443    #[cfg(test)]
444    fn test_block_id_flag_absent_serialization() {
445        let absent = CommitSig::BlockIdFlagAbsent;
446        let raw_absent = pb::CommitSig::from(absent);
447        let expected = r#"{"block_id_flag":1,"validator_address":"","timestamp":"0001-01-01T00:00:00Z","signature":""}"#;
448        let output = serde_json::to_string(&raw_absent).unwrap();
449        assert_eq!(expected, &output);
450    }
451
452    #[test]
453    #[cfg(test)]
454    fn test_block_id_flag_absent_deserialization() {
455        let json = r#"{"block_id_flag":1,"validator_address":"","timestamp":"0001-01-01T00:00:00Z","signature":""}"#;
456        let raw_commit_sg = serde_json::from_str::<pb::CommitSig>(json).unwrap();
457        let commit_sig = CommitSig::try_from(raw_commit_sg).unwrap();
458        assert_eq!(commit_sig, CommitSig::BlockIdFlagAbsent);
459    }
460}