Skip to main content

hotmint_types/
certificate.rs

1use serde::{Deserialize, Serialize};
2
3use crate::block::BlockHash;
4use crate::crypto::AggregateSignature;
5use crate::epoch::EpochNumber;
6use crate::view::ViewNumber;
7
8/// C_v(B_k): quorum certificate — 2f+1 validators signed the block hash in view v
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct QuorumCertificate {
11    pub block_hash: BlockHash,
12    pub view: ViewNumber,
13    pub aggregate_signature: AggregateSignature,
14    /// Epoch in which this QC was formed. Included in signing bytes (V2 protocol)
15    /// so cross-epoch QCs can be verified without ambiguity.
16    #[serde(default)]
17    pub epoch: EpochNumber,
18}
19
20impl QuorumCertificate {
21    /// Rank of a QC is its view number (used for comparison in safety rules)
22    pub fn rank(&self) -> ViewNumber {
23        self.view
24    }
25}
26
27/// C_v(C_v(B_k)): double certificate — QC of QC, triggers commit
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct DoubleCertificate {
30    pub inner_qc: QuorumCertificate,
31    pub outer_qc: QuorumCertificate,
32}
33
34/// TC_v: timeout certificate — 2f+1 validators want to leave view v
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct TimeoutCertificate {
37    pub view: ViewNumber,
38    pub aggregate_signature: AggregateSignature,
39    /// Each signer's highest known QC
40    pub highest_qcs: Vec<Option<QuorumCertificate>>,
41}
42
43impl TimeoutCertificate {
44    /// The highest QC carried in the TC
45    pub fn highest_qc(&self) -> Option<&QuorumCertificate> {
46        self.highest_qcs
47            .iter()
48            .filter_map(|qc| qc.as_ref())
49            .max_by_key(|qc| qc.view)
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    fn make_qc(view: u64, hash: [u8; 32]) -> QuorumCertificate {
58        QuorumCertificate {
59            block_hash: BlockHash(hash),
60            view: ViewNumber(view),
61            aggregate_signature: AggregateSignature::new(4),
62            epoch: EpochNumber(0),
63        }
64    }
65
66    #[test]
67    fn test_qc_rank() {
68        let qc = make_qc(5, [1u8; 32]);
69        assert_eq!(qc.rank(), ViewNumber(5));
70    }
71
72    #[test]
73    fn test_tc_highest_qc_some() {
74        let qc1 = make_qc(3, [1u8; 32]);
75        let qc2 = make_qc(7, [2u8; 32]);
76        let tc = TimeoutCertificate {
77            view: ViewNumber(8),
78            aggregate_signature: AggregateSignature::new(4),
79            highest_qcs: vec![Some(qc1), None, Some(qc2), None],
80        };
81        let highest = tc.highest_qc().unwrap();
82        assert_eq!(highest.view, ViewNumber(7));
83    }
84
85    #[test]
86    fn test_tc_highest_qc_all_none() {
87        let tc = TimeoutCertificate {
88            view: ViewNumber(5),
89            aggregate_signature: AggregateSignature::new(4),
90            highest_qcs: vec![None, None, None, None],
91        };
92        assert!(tc.highest_qc().is_none());
93    }
94
95    #[test]
96    fn test_tc_highest_qc_empty() {
97        let tc = TimeoutCertificate {
98            view: ViewNumber(5),
99            aggregate_signature: AggregateSignature::new(4),
100            highest_qcs: vec![],
101        };
102        assert!(tc.highest_qc().is_none());
103    }
104}