Skip to main content

hotshot_types/
simple_certificate.rs

1// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
2// This file is part of the HotShot repository.
3
4// You should have received a copy of the MIT License
5// along with the HotShot repository. If not, see <https://mit-license.org/>.
6
7//! Implementations of the simple certificate type.  Used for Quorum, DA, and Timeout Certificates
8
9use std::{
10    borrow::Cow,
11    fmt::{self, Debug, Display, Formatter},
12    hash::Hash,
13    marker::PhantomData,
14};
15
16use alloy_primitives::{FixedBytes, U256};
17use committable::{Commitment, Committable};
18use hotshot_utils::anytrace::*;
19use serde::{
20    Deserialize, Deserializer, Serialize, Serializer,
21    de::{self, SeqAccess, Visitor},
22    ser::SerializeTuple,
23};
24
25use crate::{
26    PeerConfig,
27    data::{EpochNumber, Leaf2, ViewNumber, serialize_signature2},
28    epoch_membership::EpochMembership,
29    light_client::{LightClientState, StakeTableState},
30    message::UpgradeLock,
31    simple_vote::{
32        DaData, DaData2, HasEpoch, LockView, NextEpochQuorumData2, QuorumData, QuorumData2,
33        QuorumMarker, TimeoutData, TimeoutData2, TimeoutLock, UpgradeProposalData,
34        UpgradeProposalData2, VersionedVoteData, ViewSyncCommitData, ViewSyncCommitData2,
35        ViewSyncFinalizeData, ViewSyncFinalizeData2, ViewSyncPreCommitData, ViewSyncPreCommitData2,
36        Vote2Data, Voteable,
37    },
38    stake_table::{HSStakeTable, StakeTableEntries},
39    traits::{
40        node_implementation::NodeType,
41        signature_key::{SignatureKey, StateSignatureKey},
42    },
43    utils::{is_epoch_root, is_epoch_transition},
44    vote::{Certificate, HasViewNumber},
45};
46
47mod timeout3;
48
49pub use timeout3::{LatestTimeoutSignatures, TimeoutCertificate3, TimeoutSignatures};
50
51/// Trait which allows use to inject different threshold calculations into a Certificate type
52pub trait Threshold<TYPES: NodeType> {
53    /// Calculate a threshold based on the membership
54    fn threshold(membership: &EpochMembership<TYPES>) -> U256;
55}
56
57/// Defines a threshold which is 2f + 1 (Amount needed for Quorum)
58#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)]
59pub struct SuccessThreshold {}
60
61impl<TYPES: NodeType> Threshold<TYPES> for SuccessThreshold {
62    fn threshold(membership: &EpochMembership<TYPES>) -> U256 {
63        membership.success_threshold()
64    }
65}
66
67/// Defines a threshold which is f + 1 (i.e at least one of the stake is honest)
68#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)]
69pub struct OneHonestThreshold {}
70
71impl<TYPES: NodeType> Threshold<TYPES> for OneHonestThreshold {
72    fn threshold(membership: &EpochMembership<TYPES>) -> U256 {
73        membership.failure_threshold()
74    }
75}
76
77/// Defines a threshold which is 0.9n + 1 (i.e. over 90% of the nodes with stake)
78#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)]
79pub struct UpgradeThreshold {}
80
81impl<TYPES: NodeType> Threshold<TYPES> for UpgradeThreshold {
82    fn threshold(membership: &EpochMembership<TYPES>) -> U256 {
83        membership.upgrade_threshold()
84    }
85}
86
87/// A certificate which can be created by aggregating many simple votes on the commitment.
88#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)]
89pub struct SimpleCertificate<
90    TYPES: NodeType,
91    VOTEABLE: Voteable<TYPES>,
92    THRESHOLD: Threshold<TYPES>,
93> {
94    /// The data this certificate is for.  I.e the thing that was voted on to create this Certificate
95    pub data: VOTEABLE,
96    /// commitment of all the votes this cert should be signed over
97    vote_commitment: Commitment<VOTEABLE>,
98    /// Which view this QC relates to
99    pub view_number: ViewNumber,
100    /// assembled signature for certificate aggregation
101    pub signatures: Option<<TYPES::SignatureKey as SignatureKey>::QcType>,
102    /// phantom data for `THRESHOLD` and `TYPES`
103    pub _pd: PhantomData<(TYPES, THRESHOLD)>,
104}
105
106impl<TYPES: NodeType, VOTEABLE: Voteable<TYPES>, THRESHOLD: Threshold<TYPES>>
107    SimpleCertificate<TYPES, VOTEABLE, THRESHOLD>
108{
109    /// The commitment the signatures are over, which a client cannot recompute without the vote's
110    /// versioned encoding.
111    pub fn vote_commitment(&self) -> Commitment<VOTEABLE> {
112        self.vote_commitment
113    }
114
115    /// Creates a new instance of `SimpleCertificate`
116    pub fn new(
117        data: VOTEABLE,
118        vote_commitment: Commitment<VOTEABLE>,
119        view_number: ViewNumber,
120        signatures: Option<<TYPES::SignatureKey as SignatureKey>::QcType>,
121        pd: PhantomData<(TYPES, THRESHOLD)>,
122    ) -> Self {
123        Self {
124            data,
125            vote_commitment,
126            view_number,
127            signatures,
128            _pd: pd,
129        }
130    }
131
132    fn signers(
133        &self,
134        stake_table: &[<TYPES::SignatureKey as SignatureKey>::StakeTableEntry],
135        threshold: U256,
136    ) -> Result<Vec<<TYPES::SignatureKey as SignatureKey>::VerificationKeyType>> {
137        if self.view_number == ViewNumber::genesis() {
138            return Ok(vec![]);
139        }
140        let real_qc_pp =
141            <TYPES::SignatureKey as SignatureKey>::public_parameter(stake_table, threshold);
142
143        let Some(ref signatures) = self.signatures else {
144            bail!("No signatures found while retrieving signers");
145        };
146
147        <TYPES::SignatureKey as SignatureKey>::signers(&real_qc_pp, signatures)
148            .wrap()
149            .context(|e| warn!("Tracing signers: {e}"))
150    }
151}
152
153impl<TYPES: NodeType, VOTEABLE: Voteable<TYPES> + Committable, THRESHOLD: Threshold<TYPES>>
154    Committable for SimpleCertificate<TYPES, VOTEABLE, THRESHOLD>
155{
156    fn commit(&self) -> Commitment<Self> {
157        let signature_bytes = match self.signatures.as_ref() {
158            Some(sigs) => serialize_signature2::<TYPES>(sigs),
159            None => vec![],
160        };
161        committable::RawCommitmentBuilder::new("Certificate")
162            .field("data", self.data.commit())
163            .field("vote_commitment", self.vote_commitment)
164            .field("view number", self.view_number.commit())
165            .var_size_field("signatures", &signature_bytes)
166            .finalize()
167    }
168}
169
170impl<TYPES: NodeType, THRESHOLD: Threshold<TYPES>> Certificate<TYPES, DaData>
171    for SimpleCertificate<TYPES, DaData, THRESHOLD>
172{
173    type Voteable = DaData;
174    type Threshold = THRESHOLD;
175
176    fn create_signed_certificate(
177        vote_commitment: Commitment<VersionedVoteData<TYPES, DaData>>,
178        data: Self::Voteable,
179        sig: <TYPES::SignatureKey as SignatureKey>::QcType,
180        view: ViewNumber,
181    ) -> Self {
182        let vote_commitment_bytes: [u8; 32] = vote_commitment.into();
183
184        SimpleCertificate {
185            data,
186            vote_commitment: Commitment::from_raw(vote_commitment_bytes),
187            view_number: view,
188            signatures: Some(sig),
189            _pd: PhantomData,
190        }
191    }
192    fn is_valid_cert(
193        &self,
194        stake_table: &[<TYPES::SignatureKey as SignatureKey>::StakeTableEntry],
195        threshold: U256,
196        upgrade_lock: &UpgradeLock<TYPES>,
197    ) -> Result<()> {
198        if self.view_number == ViewNumber::genesis() {
199            return Ok(());
200        }
201        let real_qc_pp =
202            <TYPES::SignatureKey as SignatureKey>::public_parameter(stake_table, threshold);
203        let commit = self.data_commitment(upgrade_lock)?;
204
205        let Some(ref signatures) = self.signatures else {
206            bail!("No signatures found while validating certificate");
207        };
208
209        <TYPES::SignatureKey as SignatureKey>::check(&real_qc_pp, commit.as_ref(), signatures)
210            .wrap()
211            .context(|e| warn!("Signature check failed: {e}"))
212    }
213    fn signers(
214        &self,
215        stake_table: &[<TYPES::SignatureKey as SignatureKey>::StakeTableEntry],
216        threshold: U256,
217    ) -> Result<Vec<<TYPES::SignatureKey as SignatureKey>::VerificationKeyType>> {
218        self.signers(stake_table, threshold)
219    }
220    /// Proxy's to `Membership.stake`
221    fn stake_table_entry(
222        membership: &EpochMembership<TYPES>,
223        pub_key: &TYPES::SignatureKey,
224    ) -> Option<PeerConfig<TYPES>> {
225        membership.da_stake(pub_key)
226    }
227
228    /// Proxy's to `Membership.da_stake_table`
229    fn stake_table(membership: &EpochMembership<TYPES>) -> HSStakeTable<TYPES> {
230        membership.da_stake_table().collect()
231    }
232
233    /// Proxy's to `Membership.da_total_nodes`
234    fn total_nodes(membership: &EpochMembership<TYPES>) -> usize {
235        membership.da_total_nodes()
236    }
237
238    fn threshold(membership: &EpochMembership<TYPES>) -> U256 {
239        membership.da_success_threshold()
240    }
241
242    fn data(&self) -> &Self::Voteable {
243        &self.data
244    }
245
246    fn data_commitment(
247        &self,
248        upgrade_lock: &UpgradeLock<TYPES>,
249    ) -> Result<Commitment<VersionedVoteData<TYPES, DaData>>> {
250        Ok(VersionedVoteData::new(self.data.clone(), self.view_number, upgrade_lock)?.commit())
251    }
252}
253
254impl<TYPES: NodeType, THRESHOLD: Threshold<TYPES>> Certificate<TYPES, DaData2>
255    for SimpleCertificate<TYPES, DaData2, THRESHOLD>
256{
257    type Voteable = DaData2;
258    type Threshold = THRESHOLD;
259
260    fn create_signed_certificate(
261        vote_commitment: Commitment<VersionedVoteData<TYPES, DaData2>>,
262        data: Self::Voteable,
263        sig: <TYPES::SignatureKey as SignatureKey>::QcType,
264        view: ViewNumber,
265    ) -> Self {
266        let vote_commitment_bytes: [u8; 32] = vote_commitment.into();
267
268        SimpleCertificate {
269            data,
270            vote_commitment: Commitment::from_raw(vote_commitment_bytes),
271            view_number: view,
272            signatures: Some(sig),
273            _pd: PhantomData,
274        }
275    }
276    fn is_valid_cert(
277        &self,
278        stake_table: &[<TYPES::SignatureKey as SignatureKey>::StakeTableEntry],
279        threshold: U256,
280        upgrade_lock: &UpgradeLock<TYPES>,
281    ) -> Result<()> {
282        if self.view_number == ViewNumber::genesis() {
283            return Ok(());
284        }
285        let real_qc_pp =
286            <TYPES::SignatureKey as SignatureKey>::public_parameter(stake_table, threshold);
287        let commit = self.data_commitment(upgrade_lock)?;
288        let signatures = self
289            .signatures
290            .as_ref()
291            .ok_or_else(|| warn!("missing signatures"))?;
292
293        <TYPES::SignatureKey as SignatureKey>::check(&real_qc_pp, commit.as_ref(), signatures)
294            .wrap()
295            .context(|e| warn!("Signature check failed: {e}"))
296    }
297    fn signers(
298        &self,
299        stake_table: &[<TYPES::SignatureKey as SignatureKey>::StakeTableEntry],
300        threshold: U256,
301    ) -> Result<Vec<<TYPES::SignatureKey as SignatureKey>::VerificationKeyType>> {
302        self.signers(stake_table, threshold)
303    }
304    /// Proxy's to `Membership.stake`
305    fn stake_table_entry(
306        membership: &EpochMembership<TYPES>,
307        pub_key: &TYPES::SignatureKey,
308    ) -> Option<PeerConfig<TYPES>> {
309        membership.da_stake(pub_key)
310    }
311
312    /// Proxy's to `Membership.da_stake_table`
313    fn stake_table(membership: &EpochMembership<TYPES>) -> HSStakeTable<TYPES> {
314        membership.da_stake_table().collect()
315    }
316
317    /// Proxy's to `Membership.da_total_nodes`
318    fn total_nodes(membership: &EpochMembership<TYPES>) -> usize {
319        membership.da_total_nodes()
320    }
321
322    fn threshold(membership: &EpochMembership<TYPES>) -> U256 {
323        membership.da_success_threshold()
324    }
325
326    fn data(&self) -> &Self::Voteable {
327        &self.data
328    }
329
330    fn data_commitment(
331        &self,
332        upgrade_lock: &UpgradeLock<TYPES>,
333    ) -> Result<Commitment<VersionedVoteData<TYPES, DaData2>>> {
334        Ok(VersionedVoteData::new(self.data.clone(), self.view_number, upgrade_lock)?.commit())
335    }
336}
337
338impl<
339    TYPES: NodeType,
340    VOTEABLE: Voteable<TYPES> + 'static + QuorumMarker,
341    THRESHOLD: Threshold<TYPES>,
342> Certificate<TYPES, VOTEABLE> for SimpleCertificate<TYPES, VOTEABLE, THRESHOLD>
343{
344    type Voteable = VOTEABLE;
345    type Threshold = THRESHOLD;
346
347    fn create_signed_certificate(
348        vote_commitment: Commitment<VersionedVoteData<TYPES, VOTEABLE>>,
349        data: Self::Voteable,
350        sig: <TYPES::SignatureKey as SignatureKey>::QcType,
351        view: ViewNumber,
352    ) -> Self {
353        let vote_commitment_bytes: [u8; 32] = vote_commitment.into();
354
355        SimpleCertificate {
356            data,
357            vote_commitment: Commitment::from_raw(vote_commitment_bytes),
358            view_number: view,
359            signatures: Some(sig),
360            _pd: PhantomData,
361        }
362    }
363
364    fn is_valid_cert(
365        &self,
366        stake_table: &[<TYPES::SignatureKey as SignatureKey>::StakeTableEntry],
367        threshold: U256,
368        upgrade_lock: &UpgradeLock<TYPES>,
369    ) -> Result<()> {
370        if self.view_number == ViewNumber::genesis() {
371            return Ok(());
372        }
373        let real_qc_pp =
374            <TYPES::SignatureKey as SignatureKey>::public_parameter(stake_table, threshold);
375        let commit = self.data_commitment(upgrade_lock)?;
376        let signatures = self
377            .signatures
378            .as_ref()
379            .ok_or_else(|| warn!("missing signatures"))?;
380
381        <TYPES::SignatureKey as SignatureKey>::check(&real_qc_pp, commit.as_ref(), signatures)
382            .wrap()
383            .context(|e| warn!("Signature check failed: {e}"))
384    }
385
386    fn signers(
387        &self,
388        stake_table: &[<TYPES::SignatureKey as SignatureKey>::StakeTableEntry],
389        threshold: U256,
390    ) -> Result<Vec<<TYPES::SignatureKey as SignatureKey>::VerificationKeyType>> {
391        self.signers(stake_table, threshold)
392    }
393
394    fn threshold(membership: &EpochMembership<TYPES>) -> U256 {
395        THRESHOLD::threshold(membership)
396    }
397
398    fn stake_table_entry(
399        membership: &EpochMembership<TYPES>,
400        pub_key: &TYPES::SignatureKey,
401    ) -> Option<PeerConfig<TYPES>> {
402        membership.stake(pub_key)
403    }
404
405    fn stake_table(membership: &EpochMembership<TYPES>) -> HSStakeTable<TYPES> {
406        membership.stake_table().collect()
407    }
408
409    /// Proxy's to `Membership.total_nodes`
410    fn total_nodes(membership: &EpochMembership<TYPES>) -> usize {
411        membership.total_nodes()
412    }
413
414    fn data(&self) -> &Self::Voteable {
415        &self.data
416    }
417    fn data_commitment(
418        &self,
419        upgrade_lock: &UpgradeLock<TYPES>,
420    ) -> Result<Commitment<VersionedVoteData<TYPES, VOTEABLE>>> {
421        Ok(VersionedVoteData::new(self.data.clone(), self.view_number, upgrade_lock)?.commit())
422    }
423}
424
425impl<TYPES: NodeType, VOTEABLE: Voteable<TYPES> + 'static, THRESHOLD: Threshold<TYPES>>
426    HasViewNumber for SimpleCertificate<TYPES, VOTEABLE, THRESHOLD>
427{
428    fn view_number(&self) -> ViewNumber {
429        self.view_number
430    }
431}
432
433impl<TYPES: NodeType, VOTEABLE: Voteable<TYPES> + HasEpoch + 'static, THRESHOLD: Threshold<TYPES>>
434    HasEpoch for SimpleCertificate<TYPES, VOTEABLE, THRESHOLD>
435{
436    fn epoch(&self) -> Option<EpochNumber> {
437        self.data.epoch()
438    }
439}
440
441impl<TYPES: NodeType> Display for QuorumCertificate<TYPES> {
442    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
443        write!(f, "view: {:?}", self.view_number)
444    }
445}
446
447impl<TYPES: NodeType> UpgradeCertificate<TYPES> {
448    /// Determines whether or not a certificate is relevant (i.e. we still have time to reach a
449    /// decide)
450    ///
451    /// # Errors
452    /// Returns an error when the certificate is no longer relevant
453    pub async fn is_relevant(&self, view_number: ViewNumber) -> Result<()> {
454        ensure!(
455            self.data.new_version_first_view >= view_number,
456            "Upgrade certificate is no longer relevant."
457        );
458
459        Ok(())
460    }
461
462    /// Validate an upgrade certificate.
463    /// # Errors
464    /// Returns an error when the upgrade certificate is invalid.
465    pub async fn validate(
466        upgrade_certificate: &Option<Self>,
467        membership: &EpochMembership<TYPES>,
468        epoch: Option<EpochNumber>,
469        upgrade_lock: &UpgradeLock<TYPES>,
470    ) -> Result<()> {
471        ensure!(epoch == membership.epoch(), "Epochs don't match!");
472        if let Some(cert) = upgrade_certificate {
473            let membership_stake_table = membership.stake_table();
474            let membership_upgrade_threshold = membership.upgrade_threshold();
475
476            cert.is_valid_cert(
477                &StakeTableEntries::from_iter(membership_stake_table).0,
478                membership_upgrade_threshold,
479                upgrade_lock,
480            )
481            .context(|e| warn!("Invalid upgrade certificate: {e}"))?;
482        }
483
484        Ok(())
485    }
486
487    /// Given an upgrade certificate and a view, tests whether the view is in the period
488    /// where we are upgrading, which requires that we propose with null blocks.
489    pub fn upgrading_in(&self, view: ViewNumber) -> bool {
490        view > self.data.old_version_last_view && view < self.data.new_version_first_view
491    }
492}
493
494impl<TYPES: NodeType> QuorumCertificate<TYPES> {
495    /// Convert a `QuorumCertificate` into a `QuorumCertificate2`
496    pub fn to_qc2(self) -> QuorumCertificate2<TYPES> {
497        let bytes: [u8; 32] = self.data.leaf_commit.into();
498        let data = QuorumData2 {
499            leaf_commit: Commitment::from_raw(bytes),
500            epoch: None,
501            block_number: None,
502        };
503
504        let bytes: [u8; 32] = self.vote_commitment.into();
505        let vote_commitment = Commitment::from_raw(bytes);
506
507        SimpleCertificate {
508            data,
509            vote_commitment,
510            view_number: self.view_number,
511            signatures: self.signatures.clone(),
512            _pd: PhantomData,
513        }
514    }
515}
516
517impl<TYPES: NodeType> QuorumCertificate2<TYPES> {
518    /// Convert a `QuorumCertificate2` into a `QuorumCertificate`
519    pub fn to_qc(self) -> QuorumCertificate<TYPES> {
520        let bytes: [u8; 32] = self.data.leaf_commit.into();
521        let data = QuorumData {
522            leaf_commit: Commitment::from_raw(bytes),
523        };
524
525        let bytes: [u8; 32] = self.vote_commitment.into();
526        let vote_commitment = Commitment::from_raw(bytes);
527
528        SimpleCertificate {
529            data,
530            vote_commitment,
531            view_number: self.view_number,
532            signatures: self.signatures.clone(),
533            _pd: PhantomData,
534        }
535    }
536}
537
538impl<TYPES: NodeType> DaCertificate<TYPES> {
539    /// Convert a `DaCertificate` into a `DaCertificate2`
540    pub fn to_dac2(self) -> DaCertificate2<TYPES> {
541        let data = DaData2 {
542            payload_commit: self.data.payload_commit,
543            next_epoch_payload_commit: None,
544            epoch: None,
545        };
546
547        let bytes: [u8; 32] = self.vote_commitment.into();
548        let vote_commitment = Commitment::from_raw(bytes);
549
550        SimpleCertificate {
551            data,
552            vote_commitment,
553            view_number: self.view_number,
554            signatures: self.signatures.clone(),
555            _pd: PhantomData,
556        }
557    }
558}
559
560impl<TYPES: NodeType> DaCertificate2<TYPES> {
561    /// Convert a `DaCertificate` into a `DaCertificate2`
562    pub fn to_dac(self) -> DaCertificate<TYPES> {
563        let data = DaData {
564            payload_commit: self.data.payload_commit,
565        };
566
567        let bytes: [u8; 32] = self.vote_commitment.into();
568        let vote_commitment = Commitment::from_raw(bytes);
569
570        SimpleCertificate {
571            data,
572            vote_commitment,
573            view_number: self.view_number,
574            signatures: self.signatures.clone(),
575            _pd: PhantomData,
576        }
577    }
578}
579
580impl<TYPES: NodeType> ViewSyncPreCommitCertificate<TYPES> {
581    /// Convert a `DaCertificate` into a `DaCertificate2`
582    pub fn to_vsc2(self) -> ViewSyncPreCommitCertificate2<TYPES> {
583        let data = ViewSyncPreCommitData2 {
584            relay: self.data.relay,
585            round: self.data.round,
586            epoch: None,
587        };
588
589        let bytes: [u8; 32] = self.vote_commitment.into();
590        let vote_commitment = Commitment::from_raw(bytes);
591
592        SimpleCertificate {
593            data,
594            vote_commitment,
595            view_number: self.view_number,
596            signatures: self.signatures.clone(),
597            _pd: PhantomData,
598        }
599    }
600}
601
602impl<TYPES: NodeType> ViewSyncCommitCertificate<TYPES> {
603    /// Convert a `DaCertificate` into a `DaCertificate2`
604    pub fn to_vsc2(self) -> ViewSyncCommitCertificate2<TYPES> {
605        let data = ViewSyncCommitData2 {
606            relay: self.data.relay,
607            round: self.data.round,
608            epoch: None,
609        };
610
611        let bytes: [u8; 32] = self.vote_commitment.into();
612        let vote_commitment = Commitment::from_raw(bytes);
613
614        SimpleCertificate {
615            data,
616            vote_commitment,
617            view_number: self.view_number,
618            signatures: self.signatures.clone(),
619            _pd: PhantomData,
620        }
621    }
622}
623
624impl<TYPES: NodeType> ViewSyncFinalizeCertificate<TYPES> {
625    /// Convert a `DaCertificate` into a `DaCertificate2`
626    pub fn to_vsc2(self) -> ViewSyncFinalizeCertificate2<TYPES> {
627        let data = ViewSyncFinalizeData2 {
628            relay: self.data.relay,
629            round: self.data.round,
630            epoch: None,
631        };
632
633        let bytes: [u8; 32] = self.vote_commitment.into();
634        let vote_commitment = Commitment::from_raw(bytes);
635
636        SimpleCertificate {
637            data,
638            vote_commitment,
639            view_number: self.view_number,
640            signatures: self.signatures.clone(),
641            _pd: PhantomData,
642        }
643    }
644}
645
646impl<TYPES: NodeType> TimeoutCertificate<TYPES> {
647    /// Convert a `DaCertificate` into a `DaCertificate2`
648    pub fn to_tc2(self) -> TimeoutCertificate2<TYPES> {
649        let data = TimeoutData2 {
650            view: self.data.view,
651            epoch: None,
652        };
653
654        let bytes: [u8; 32] = self.vote_commitment.into();
655        let vote_commitment = Commitment::from_raw(bytes);
656
657        SimpleCertificate {
658            data,
659            vote_commitment,
660            view_number: self.view_number,
661            signatures: self.signatures.clone(),
662            _pd: PhantomData,
663        }
664    }
665}
666
667/// A timeout certificate, in one of the forms its signers may have used.
668#[derive(derive_more::Debug, Clone, Eq, PartialEq, Hash)]
669pub enum TimeoutEvidence<T: NodeType> {
670    V2(TimeoutCertificate2<T>),
671    V3(TimeoutCertificate3<T>),
672}
673
674impl<T: NodeType> TimeoutEvidence<T> {
675    pub fn binds_epoch(&self) -> bool {
676        matches!(self, Self::V3(_))
677    }
678
679    /// The certificate's lock, and the `Certificate1` that shows it.
680    ///
681    /// `None` for a certificate whose signers did not sign their locks, and
682    /// for one whose signers are all locked on nothing but genesis.
683    pub fn lock(&self) -> Option<(LockView, &QuorumCertificate2<T>)> {
684        match self {
685            Self::V2(_) => None,
686            Self::V3(cert) => cert.lock_cert().map(|cert| (LockView::of(cert), cert)),
687        }
688    }
689
690    /// The certificate's lock, by what is checked of it; [`TimeoutLock::None`]
691    /// for a certificate whose signers did not sign their locks.
692    pub fn timeout_lock(&self) -> TimeoutLock<'_, T> {
693        match self {
694            Self::V2(_) => TimeoutLock::None,
695            Self::V3(cert) => cert.timeout_lock(),
696        }
697    }
698
699    /// Check that this is the form its view requires, then check the threshold signature.
700    ///
701    /// For a [`Self::V3`] that is the signers' signatures only, not those of
702    /// the lock certificate it carries, which its own epoch's committee signs.
703    pub fn is_valid_cert(
704        &self,
705        stake_table: &[<T::SignatureKey as SignatureKey>::StakeTableEntry],
706        threshold: U256,
707        upgrade_lock: &UpgradeLock<T>,
708    ) -> Result<()> {
709        let view = self.view_number();
710        match self {
711            Self::V2(cert) => {
712                ensure! {
713                    !upgrade_lock.timeout_epoch_bound(view),
714                    "timeout certificate for view {} must not bind its epoch", view
715                }
716                ensure! {
717                    view == cert.data.view,
718                    "timeout certificate view {} != data view {}", view, cert.data.view
719                }
720                cert.is_valid_cert(stake_table, threshold, upgrade_lock)
721            },
722            // The lock certificate is signed by its own epoch's committee,
723            // which may not be the one given here, and is NOT checked: a V3
724            // certificate passing this is valid only once the caller has
725            // checked its lock certificate as well.
726            Self::V3(cert) => {
727                ensure! {
728                    upgrade_lock.timeout_epoch_bound(view),
729                    "timeout certificate for view {} must bind its epoch", view
730                }
731                cert.check_signatures(stake_table, threshold, upgrade_lock)
732            },
733        }
734    }
735}
736
737impl<T: NodeType> HasViewNumber for TimeoutEvidence<T> {
738    fn view_number(&self) -> ViewNumber {
739        match self {
740            Self::V2(c) => c.view_number,
741            Self::V3(c) => c.view_number,
742        }
743    }
744}
745
746impl<T: NodeType> HasEpoch for TimeoutEvidence<T> {
747    fn epoch(&self) -> Option<EpochNumber> {
748        match self {
749            Self::V2(cert) => cert.data.epoch,
750            Self::V3(cert) => Some(cert.epoch),
751        }
752    }
753}
754
755pub mod optional_timeout_evidence {
756    use super::*;
757
758    pub fn serialize<T: NodeType, S: Serializer>(
759        evidence: &Option<TimeoutEvidence<T>>,
760        s: S,
761    ) -> std::result::Result<S::Ok, S::Error> {
762        if s.is_human_readable() {
763            return match evidence {
764                None => s.serialize_none(),
765                Some(TimeoutEvidence::V2(cert)) => s.serialize_some(cert),
766                Some(TimeoutEvidence::V3(cert)) => s.serialize_some(&HumanReadableV3 {
767                    cert: Cow::Borrowed(cert),
768                }),
769            };
770        }
771
772        let mut t = s.serialize_tuple(2)?;
773        match evidence {
774            None => {
775                t.serialize_element(&0u8)?;
776                t.serialize_element(&())?;
777            },
778            Some(TimeoutEvidence::V2(cert)) => {
779                t.serialize_element(&1u8)?;
780                t.serialize_element(cert)?;
781            },
782            Some(TimeoutEvidence::V3(cert)) => {
783                t.serialize_element(&2u8)?;
784                t.serialize_element(cert)?;
785            },
786        }
787        t.end()
788    }
789
790    pub fn deserialize<'de, T: NodeType, D: Deserializer<'de>>(
791        d: D,
792    ) -> std::result::Result<Option<TimeoutEvidence<T>>, D::Error> {
793        if d.is_human_readable() {
794            return Ok(match Option::<HumanReadable<T>>::deserialize(d)? {
795                None => None,
796                Some(HumanReadable::V3(tagged)) => {
797                    Some(TimeoutEvidence::V3(tagged.cert.into_owned()))
798                },
799                Some(HumanReadable::V2(cert)) => Some(TimeoutEvidence::V2(cert)),
800            });
801        }
802
803        d.deserialize_tuple(2, EvidenceVisitor(PhantomData))
804    }
805
806    #[derive(Deserialize)]
807    #[serde(untagged, bound(deserialize = ""))]
808    enum HumanReadable<T: NodeType> {
809        V3(HumanReadableV3<'static, T>),
810        V2(TimeoutCertificate2<T>),
811    }
812
813    #[derive(Serialize, Deserialize)]
814    #[serde(bound(deserialize = ""))]
815    struct HumanReadableV3<'a, T: NodeType> {
816        #[serde(rename = "V3")]
817        cert: Cow<'a, TimeoutCertificate3<T>>,
818    }
819
820    struct EvidenceVisitor<T: NodeType>(PhantomData<T>);
821
822    impl<'de, T: NodeType> Visitor<'de> for EvidenceVisitor<T> {
823        type Value = Option<TimeoutEvidence<T>>;
824
825        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
826            write!(f, "a timeout evidence discriminant followed by its payload")
827        }
828
829        fn visit_seq<A: SeqAccess<'de>>(
830            self,
831            mut seq: A,
832        ) -> std::result::Result<Self::Value, A::Error> {
833            let missing = |field| de::Error::custom(format!("missing {field}"));
834            let tag: u8 = seq
835                .next_element()?
836                .ok_or_else(|| missing("timeout evidence discriminant"))?;
837            match tag {
838                0 => {
839                    seq.next_element::<()>()?;
840                    Ok(None)
841                },
842                1 => Ok(Some(TimeoutEvidence::V2(
843                    seq.next_element()?
844                        .ok_or_else(|| missing("timeout certificate"))?,
845                ))),
846                2 => Ok(Some(TimeoutEvidence::V3(
847                    seq.next_element()?
848                        .ok_or_else(|| missing("timeout certificate"))?,
849                ))),
850                other => Err(de::Error::custom(format!(
851                    "invalid timeout evidence discriminant {other}"
852                ))),
853            }
854        }
855    }
856}
857
858/// Type alias for a `QuorumCertificate`, which is a `SimpleCertificate` over `QuorumData`
859pub type QuorumCertificate<TYPES> = SimpleCertificate<TYPES, QuorumData<TYPES>, SuccessThreshold>;
860/// Type alias for a `QuorumCertificate2`, which is a `SimpleCertificate` over `QuorumData2`
861pub type QuorumCertificate2<TYPES> = SimpleCertificate<TYPES, QuorumData2<TYPES>, SuccessThreshold>;
862/// Type alias for a `QuorumCertificate2`, which is a `SimpleCertificate` over `QuorumData2`
863pub type NextEpochQuorumCertificate2<TYPES> =
864    SimpleCertificate<TYPES, NextEpochQuorumData2<TYPES>, SuccessThreshold>;
865/// Type alias for the new protocol's phase-1 certificate over `QuorumData2`
866pub type Certificate1<TYPES> = SimpleCertificate<TYPES, QuorumData2<TYPES>, SuccessThreshold>;
867/// Type alias for the new protocol's phase-2 certificate over `Vote2Data`
868pub type Certificate2<TYPES> = SimpleCertificate<TYPES, Vote2Data<TYPES>, SuccessThreshold>;
869/// Type alias for a `DaCertificate`, which is a `SimpleCertificate` over `DaData`
870pub type DaCertificate<TYPES> = SimpleCertificate<TYPES, DaData, SuccessThreshold>;
871/// Type alias for a `DaCertificate2`, which is a `SimpleCertificate` over `DaData2`
872pub type DaCertificate2<TYPES> = SimpleCertificate<TYPES, DaData2, SuccessThreshold>;
873/// Type alias for a Timeout certificate over a view number
874pub type TimeoutCertificate<TYPES> = SimpleCertificate<TYPES, TimeoutData, SuccessThreshold>;
875/// Type alias for a `TimeoutCertificate2`, which is a `SimpleCertificate` over `TimeoutData2`
876pub type TimeoutCertificate2<TYPES> = SimpleCertificate<TYPES, TimeoutData2, SuccessThreshold>;
877/// Type alias for a `ViewSyncPreCommit` certificate over a view number
878pub type ViewSyncPreCommitCertificate<TYPES> =
879    SimpleCertificate<TYPES, ViewSyncPreCommitData, OneHonestThreshold>;
880/// Type alias for a `ViewSyncPreCommitCertificate2`, which is a `SimpleCertificate` over `ViewSyncPreCommitData2`
881pub type ViewSyncPreCommitCertificate2<TYPES> =
882    SimpleCertificate<TYPES, ViewSyncPreCommitData2, OneHonestThreshold>;
883/// Type alias for a `ViewSyncCommit` certificate over a view number
884pub type ViewSyncCommitCertificate<TYPES> =
885    SimpleCertificate<TYPES, ViewSyncCommitData, SuccessThreshold>;
886/// Type alias for a `ViewSyncCommitCertificate2`, which is a `SimpleCertificate` over `ViewSyncCommitData2`
887pub type ViewSyncCommitCertificate2<TYPES> =
888    SimpleCertificate<TYPES, ViewSyncCommitData2, SuccessThreshold>;
889/// Type alias for a `ViewSyncFinalize` certificate over a view number
890pub type ViewSyncFinalizeCertificate<TYPES> =
891    SimpleCertificate<TYPES, ViewSyncFinalizeData, SuccessThreshold>;
892/// Type alias for a `ViewSyncFinalizeCertificate2`, which is a `SimpleCertificate` over `ViewSyncFinalizeData2`
893pub type ViewSyncFinalizeCertificate2<TYPES> =
894    SimpleCertificate<TYPES, ViewSyncFinalizeData2, SuccessThreshold>;
895/// Type alias for a `UpgradeCertificate`, which is a `SimpleCertificate` of `UpgradeProposalData`
896pub type UpgradeCertificate<TYPES> =
897    SimpleCertificate<TYPES, UpgradeProposalData, UpgradeThreshold>;
898/// Type alias for an upgrade certificate binding its epoch, a `SimpleCertificate` of
899/// `UpgradeProposalData2`
900pub type UpgradeCertificate2<TYPES> =
901    SimpleCertificate<TYPES, UpgradeProposalData2, UpgradeThreshold>;
902
903impl<TYPES: NodeType> UpgradeCertificate2<TYPES> {
904    /// The certificate as a `Leaf2` carries it, whose `UpgradeCertificate` field predates the
905    /// epoch. The epoch leaves `data` but stays bound in `vote_commitment` and the signatures,
906    /// which the leaf commitment covers. Only [`Self::restore_epoch`] with the carrier's epoch
907    /// makes the certificate verifiable again.
908    pub fn strip_epoch(self) -> UpgradeCertificate<TYPES> {
909        let vote_commitment: [u8; 32] = self.vote_commitment.into();
910        UpgradeCertificate::new(
911            self.data.strip_epoch(),
912            Commitment::from_raw(vote_commitment),
913            self.view_number,
914            self.signatures,
915            PhantomData,
916        )
917    }
918
919    /// Inverse of [`Self::strip_epoch`]: the certificate a `Leaf2` carries, restored with the
920    /// epoch its carrier was proposed in.
921    pub fn restore_epoch(cert: UpgradeCertificate<TYPES>, epoch: EpochNumber) -> Self {
922        let vote_commitment: [u8; 32] = cert.vote_commitment.into();
923        Self::new(
924            UpgradeProposalData2::restore_epoch(cert.data, epoch),
925            Commitment::from_raw(vote_commitment),
926            cert.view_number,
927            cert.signatures,
928            PhantomData,
929        )
930    }
931}
932
933/// Type for light client state update certificate
934#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)]
935pub struct LightClientStateUpdateCertificateV2<TYPES: NodeType> {
936    /// The epoch of the light client state
937    pub epoch: EpochNumber,
938    /// Light client state for epoch transition
939    pub light_client_state: LightClientState,
940    /// Next epoch stake table state
941    pub next_stake_table_state: StakeTableState,
942    /// Signatures to the light client state
943    #[allow(clippy::type_complexity)]
944    pub signatures: Vec<(
945        TYPES::StateSignatureKey,
946        <TYPES::StateSignatureKey as StateSignatureKey>::StateSignature, // LCV3 signature
947        <TYPES::StateSignatureKey as StateSignatureKey>::StateSignature, // LCV2 signature
948    )>,
949    /// Present in versions >= V4.
950    ///
951    /// This field stores the Keccak-256 hash of the concatenated Merkle roots.
952    /// Currently, it includes only the Espresso reward Merkle tree root.
953    pub auth_root: FixedBytes<32>,
954}
955
956/// Type for light client state update certificate
957#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)]
958pub struct LightClientStateUpdateCertificateV1<TYPES: NodeType> {
959    /// The epoch of the light client state
960    pub epoch: EpochNumber,
961    /// Light client state for epoch transition
962    pub light_client_state: LightClientState,
963    /// Next epoch stake table state
964    pub next_stake_table_state: StakeTableState,
965    /// Signatures to the light client state
966    pub signatures: Vec<(
967        TYPES::StateSignatureKey,
968        <TYPES::StateSignatureKey as StateSignatureKey>::StateSignature,
969    )>,
970}
971
972impl<TYPES: NodeType> From<LightClientStateUpdateCertificateV1<TYPES>>
973    for LightClientStateUpdateCertificateV2<TYPES>
974{
975    fn from(v1: LightClientStateUpdateCertificateV1<TYPES>) -> Self {
976        Self {
977            epoch: v1.epoch,
978            light_client_state: v1.light_client_state,
979            next_stake_table_state: v1.next_stake_table_state,
980            signatures: v1
981                .signatures
982                .into_iter()
983                .map(|(key, sig)| (key, sig.clone(), sig)) // Cloning the signatures here because we use it only for storage.
984                .collect(),
985            auth_root: Default::default(),
986        }
987    }
988}
989
990impl<TYPES: NodeType> From<LightClientStateUpdateCertificateV2<TYPES>>
991    for LightClientStateUpdateCertificateV1<TYPES>
992{
993    fn from(v2: LightClientStateUpdateCertificateV2<TYPES>) -> Self {
994        Self {
995            epoch: v2.epoch,
996            light_client_state: v2.light_client_state,
997            next_stake_table_state: v2.next_stake_table_state,
998            signatures: v2
999                .signatures
1000                .into_iter()
1001                .map(|(key, _, sig)| (key, sig))
1002                .collect(),
1003        }
1004    }
1005}
1006
1007impl<TYPES: NodeType> HasViewNumber for LightClientStateUpdateCertificateV2<TYPES> {
1008    fn view_number(&self) -> ViewNumber {
1009        ViewNumber::new(self.light_client_state.view_number)
1010    }
1011}
1012
1013impl<TYPES: NodeType> HasEpoch for LightClientStateUpdateCertificateV2<TYPES> {
1014    fn epoch(&self) -> Option<EpochNumber> {
1015        Some(self.epoch)
1016    }
1017}
1018
1019impl<TYPES: NodeType> LightClientStateUpdateCertificateV1<TYPES> {
1020    pub fn genesis() -> Self {
1021        Self {
1022            epoch: EpochNumber::genesis(),
1023            light_client_state: Default::default(),
1024            next_stake_table_state: Default::default(),
1025            signatures: vec![],
1026        }
1027    }
1028}
1029
1030impl<TYPES: NodeType> LightClientStateUpdateCertificateV2<TYPES> {
1031    pub fn genesis() -> Self {
1032        Self {
1033            epoch: EpochNumber::genesis(),
1034            light_client_state: Default::default(),
1035            next_stake_table_state: Default::default(),
1036            signatures: vec![],
1037            auth_root: Default::default(),
1038        }
1039    }
1040}
1041
1042#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)]
1043#[serde(bound(deserialize = "QuorumCertificate2<TYPES>:for<'a> Deserialize<'a>"))]
1044pub struct EpochRootQuorumCertificateV2<TYPES: NodeType> {
1045    pub qc: QuorumCertificate2<TYPES>,
1046    pub state_cert: LightClientStateUpdateCertificateV2<TYPES>,
1047}
1048
1049impl<TYPES: NodeType> HasViewNumber for EpochRootQuorumCertificateV2<TYPES> {
1050    fn view_number(&self) -> ViewNumber {
1051        self.qc.view_number()
1052    }
1053}
1054
1055impl<TYPES: NodeType> HasEpoch for EpochRootQuorumCertificateV2<TYPES> {
1056    fn epoch(&self) -> Option<EpochNumber> {
1057        self.qc.epoch()
1058    }
1059}
1060
1061#[derive(Serialize, Deserialize, Eq, Hash, PartialEq, Debug, Clone)]
1062#[serde(bound(deserialize = "QuorumCertificate2<TYPES>:for<'a> Deserialize<'a>"))]
1063pub struct EpochRootQuorumCertificateV1<TYPES: NodeType> {
1064    pub qc: QuorumCertificate2<TYPES>,
1065    pub state_cert: LightClientStateUpdateCertificateV1<TYPES>,
1066}
1067
1068impl<TYPES: NodeType> HasViewNumber for EpochRootQuorumCertificateV1<TYPES> {
1069    fn view_number(&self) -> ViewNumber {
1070        self.qc.view_number()
1071    }
1072}
1073
1074impl<TYPES: NodeType> HasEpoch for EpochRootQuorumCertificateV1<TYPES> {
1075    fn epoch(&self) -> Option<EpochNumber> {
1076        self.qc.epoch()
1077    }
1078}
1079
1080impl<TYPES: NodeType> From<EpochRootQuorumCertificateV1<TYPES>>
1081    for EpochRootQuorumCertificateV2<TYPES>
1082{
1083    fn from(root_qc: EpochRootQuorumCertificateV1<TYPES>) -> Self {
1084        Self {
1085            qc: root_qc.qc,
1086            state_cert: root_qc.state_cert.into(),
1087        }
1088    }
1089}
1090
1091impl<TYPES: NodeType> From<EpochRootQuorumCertificateV2<TYPES>>
1092    for EpochRootQuorumCertificateV1<TYPES>
1093{
1094    fn from(root_qc: EpochRootQuorumCertificateV2<TYPES>) -> Self {
1095        Self {
1096            qc: root_qc.qc,
1097            state_cert: root_qc.state_cert.into(),
1098        }
1099    }
1100}
1101
1102/// A pair of QCs (or a single QC) attesting to a leaf.
1103///
1104/// Generally we only need a single QC, but during an epoch transition, we require a pair: one
1105/// signed by the current membership and one signed by the next epoch's membership. This type
1106/// encapsulates both.
1107#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
1108#[serde(bound = "")]
1109pub struct CertificatePair<TYPES: NodeType> {
1110    /// The basic QC.
1111    qc: QuorumCertificate2<TYPES>,
1112
1113    /// A QC from the next epoch's membership, if this QC is part of an epoch transition.
1114    next_epoch_qc: Option<NextEpochQuorumCertificate2<TYPES>>,
1115}
1116
1117impl<TYPES: NodeType> CertificatePair<TYPES> {
1118    /// Create a certificate pair.
1119    pub fn new(
1120        qc: QuorumCertificate2<TYPES>,
1121        next_epoch_qc: Option<NextEpochQuorumCertificate2<TYPES>>,
1122    ) -> Self {
1123        Self { qc, next_epoch_qc }
1124    }
1125
1126    /// Create a certificate for a non-epoch-transitioning block.
1127    pub fn non_epoch_change(qc: QuorumCertificate2<TYPES>) -> Self {
1128        Self::new(qc, None)
1129    }
1130
1131    /// Create a certificate for the parent of a leaf, using the justifying QCs in the leaf.
1132    pub fn for_parent(leaf: &Leaf2<TYPES>) -> Self {
1133        Self {
1134            qc: leaf.justify_qc().clone(),
1135            next_epoch_qc: leaf.next_epoch_justify_qc().cloned(),
1136        }
1137    }
1138
1139    /// The raw QC.
1140    pub fn qc(&self) -> &QuorumCertificate2<TYPES> {
1141        &self.qc
1142    }
1143
1144    /// A raw QC from the subsequent epoch's quorum, if this certificate is part of an epoch change.
1145    pub fn next_epoch_qc(&self) -> Option<&NextEpochQuorumCertificate2<TYPES>> {
1146        self.next_epoch_qc.as_ref()
1147    }
1148
1149    /// The leaf commitment signed by this certificate.
1150    pub fn leaf_commit(&self) -> Commitment<Leaf2<TYPES>> {
1151        self.qc.data.leaf_commit
1152    }
1153
1154    /// The epoch number this certificate belongs to.
1155    ///
1156    /// [`None`] if this certificate originated before epochs were enabled.
1157    pub fn epoch(&self) -> Option<EpochNumber> {
1158        self.qc.data.epoch
1159    }
1160
1161    /// The block number attached to this certificate.
1162    ///
1163    /// [`None`] if this certificate originated before epochs were enabled.
1164    pub fn block_number(&self) -> Option<u64> {
1165        self.qc.data.block_number
1166    }
1167
1168    /// Verify that the next epoch QC is present and consistent if required.
1169    ///
1170    /// This checks that if required, the next epoch QC is present and is consistent with the
1171    /// primary QC. It does not check the signature on either QC, only that the data being signed
1172    /// over is consistent between the two.
1173    ///
1174    /// Returns the next epoch QC if it is present and invariants are satisfied. Returns an error if
1175    /// a required next epoch QC is missing or if it is inconsistent with the primary QC. Returns
1176    /// [`None`] if a next epoch QC is not required for this certificate.
1177    pub fn verify_next_epoch_qc(
1178        &self,
1179        epoch_height: u64,
1180    ) -> Result<Option<&NextEpochQuorumCertificate2<TYPES>>> {
1181        let block_number = self.qc.data.block_number.context(warn!(
1182            "QC for epoch {:?} has no block number",
1183            self.qc.data.epoch
1184        ))?;
1185        if !is_epoch_transition(block_number, epoch_height) {
1186            tracing::debug!(
1187                block_number,
1188                epoch_height,
1189                "QC is not in an epoch transition"
1190            );
1191            return Ok(None);
1192        }
1193
1194        let next_epoch_qc = self.next_epoch_qc.as_ref().context(warn!(
1195            "Received High QC for the transition block {block_number} but not the next epoch QC"
1196        ))?;
1197
1198        // The signature from the next epoch must be over the same data.
1199        ensure!(self.qc.view_number == next_epoch_qc.view_number);
1200        ensure!(self.qc.data == *next_epoch_qc.data);
1201
1202        Ok(Some(next_epoch_qc))
1203    }
1204}
1205
1206impl<TYPES: NodeType> HasViewNumber for CertificatePair<TYPES> {
1207    fn view_number(&self) -> ViewNumber {
1208        self.qc.view_number()
1209    }
1210}
1211
1212/// Check that a light client state update certificate corresponds to a given QC:
1213/// the QC is for an epoch root block, the epochs match, and the view numbers match.
1214pub fn check_qc_state_cert_correspondence<TYPES: NodeType>(
1215    qc: &QuorumCertificate2<TYPES>,
1216    state_cert: &LightClientStateUpdateCertificateV2<TYPES>,
1217    epoch_height: u64,
1218) -> bool {
1219    qc.data
1220        .block_number
1221        .is_some_and(|bn| is_epoch_root(bn, epoch_height))
1222        && Some(state_cert.epoch) == qc.data.epoch()
1223        && qc.view_number().u64() == state_cert.light_client_state.view_number
1224}