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    /// Aggregated vote extensions from the Vote2 round.
33    /// Contains (validator_id, extension_bytes) for each validator that
34    /// provided an extension in their Vote2 message.
35    #[serde(default)]
36    pub vote_extensions: Vec<(crate::ValidatorId, Vec<u8>)>,
37}
38
39/// TC_v: timeout certificate — 2f+1 validators want to leave view v
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct TimeoutCertificate {
42    pub view: ViewNumber,
43    pub aggregate_signature: AggregateSignature,
44    /// Each signer's highest known QC
45    pub highest_qcs: Vec<Option<QuorumCertificate>>,
46}
47
48impl TimeoutCertificate {
49    /// The highest QC carried in the TC
50    pub fn highest_qc(&self) -> Option<&QuorumCertificate> {
51        self.highest_qcs
52            .iter()
53            .filter_map(|qc| qc.as_ref())
54            .max_by_key(|qc| qc.view)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    fn make_qc(view: u64, hash: [u8; 32]) -> QuorumCertificate {
63        QuorumCertificate {
64            block_hash: BlockHash(hash),
65            view: ViewNumber(view),
66            aggregate_signature: AggregateSignature::new(4),
67            epoch: EpochNumber(0),
68        }
69    }
70
71    #[test]
72    fn test_qc_rank() {
73        let qc = make_qc(5, [1u8; 32]);
74        assert_eq!(qc.rank(), ViewNumber(5));
75    }
76
77    #[test]
78    fn test_tc_highest_qc_some() {
79        let qc1 = make_qc(3, [1u8; 32]);
80        let qc2 = make_qc(7, [2u8; 32]);
81        let tc = TimeoutCertificate {
82            view: ViewNumber(8),
83            aggregate_signature: AggregateSignature::new(4),
84            highest_qcs: vec![Some(qc1), None, Some(qc2), None],
85        };
86        let highest = tc.highest_qc().unwrap();
87        assert_eq!(highest.view, ViewNumber(7));
88    }
89
90    #[test]
91    fn test_tc_highest_qc_all_none() {
92        let tc = TimeoutCertificate {
93            view: ViewNumber(5),
94            aggregate_signature: AggregateSignature::new(4),
95            highest_qcs: vec![None, None, None, None],
96        };
97        assert!(tc.highest_qc().is_none());
98    }
99
100    #[test]
101    fn test_tc_highest_qc_empty() {
102        let tc = TimeoutCertificate {
103            view: ViewNumber(5),
104            aggregate_signature: AggregateSignature::new(4),
105            highest_qcs: vec![],
106        };
107        assert!(tc.highest_qc().is_none());
108    }
109}