Skip to main content

hotshot_new_protocol/
helpers.rs

1use std::{fmt::Display, ops::RangeInclusive};
2
3use committable::{Commitment, Committable};
4use hotshot_types::{
5    data::{EpochNumber, Leaf2, ViewNumber},
6    simple_certificate::{LightClientStateUpdateCertificateV2, check_qc_state_cert_correspondence},
7    traits::node_implementation::NodeType,
8};
9
10use crate::message::Proposal;
11
12/// A view and an epoch, ordered by view first.
13///
14/// Votes, proposals and certificates are counted once per epoch and view: at an
15/// epoch boundary the outgoing committee may vote on its last block again in the
16/// view the incoming committee proposes its first block in. With the view first,
17/// the keys of a range of views form a range.
18#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct ViewEpoch(pub ViewNumber, pub EpochNumber);
20
21impl ViewEpoch {
22    /// The first key at `view`.
23    pub const fn start(view: ViewNumber) -> Self {
24        Self(view, EpochNumber::new(0))
25    }
26
27    /// The keys at `view`, of every epoch.
28    pub fn at_view(view: ViewNumber) -> RangeInclusive<Self> {
29        Self::start(view)..=Self(view, EpochNumber::new(u64::MAX))
30    }
31}
32
33impl Display for ViewEpoch {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        write!(f, "{}@{}", self.1, self.0)
36    }
37}
38
39pub fn proposal_commitment<T: NodeType>(proposal: &Proposal<T>) -> Commitment<Leaf2<T>> {
40    let leaf: Leaf2<T> = proposal.clone().into();
41    leaf.commit()
42}
43
44/// The proposal's `state_cert`, if `Validator::state_cert` actually checked it.
45///
46/// The validator skips the field unless the parent QC sits at an epoch root, and
47/// the proposal signature does not cover it (`Leaf2` discards it), so anyone
48/// relaying a proposal can substitute it. Every store site must gate on this, or
49/// it files away a certificate nobody verified, under an epoch of the sender's
50/// choosing.
51///
52/// Re-running the correspondence check binds the certificate's epoch and view to
53/// the QC here rather than trusting that the validator did it, so the epoch the
54/// store sites key by cannot be chosen by the sender. Callers must still pass a
55/// validated proposal: the threshold signature check only happens there.
56pub fn validated_state_cert<T: NodeType>(
57    proposal: &Proposal<T>,
58    epoch_height: u64,
59) -> Option<&LightClientStateUpdateCertificateV2<T>> {
60    let state_cert = proposal.state_cert.as_ref()?;
61    check_qc_state_cert_correspondence(&proposal.justify_qc, state_cert, epoch_height)
62        .then_some(state_cert)
63}
64
65/// The protocol version a test runs at unless it chooses one.
66///
67/// [`NEW_PROTOCOL_VERSION`](versions::NEW_PROTOCOL_VERSION) by default.
68/// `NP_TEST_VERSION=0.7` runs the tests at
69/// [`TIMEOUT_EPOCH_VERSION`](versions::TIMEOUT_EPOCH_VERSION) instead, whose
70/// timeout votes and certificates sign their lock: the form the Lean
71/// specification covers. `just test-lean-check` records the tests that way.
72///
73/// A test of behaviour one version has and another lacks pins its version
74/// ([`pin_test_version`]), and runs at it whatever `NP_TEST_VERSION` says.
75#[cfg(test)]
76pub fn test_version() -> versions::Version {
77    if let Some(version) = PINNED_TEST_VERSION.get() {
78        return version;
79    }
80    match std::env::var("NP_TEST_VERSION").as_deref() {
81        Ok("0.7") => versions::TIMEOUT_EPOCH_VERSION,
82        _ => versions::NEW_PROTOCOL_VERSION,
83    }
84}
85
86#[cfg(test)]
87thread_local! {
88    static PINNED_TEST_VERSION: std::cell::Cell<Option<versions::Version>> =
89        const { std::cell::Cell::new(None) };
90}
91
92/// Run the calling test at `version` ([`test_version`]), for the rest of it.
93///
94/// The pin is per thread, and a `#[tokio::test]` runs on its own: it holds for
95/// everything the test builds and runs, and for no other test.
96#[cfg(test)]
97pub fn pin_test_version(version: versions::Version) {
98    PINNED_TEST_VERSION.set(Some(version));
99}
100
101#[cfg(test)]
102pub fn test_upgrade_lock<T: NodeType>() -> hotshot_types::message::UpgradeLock<T> {
103    hotshot_types::message::UpgradeLock::new(versions::Upgrade::trivial(test_version()))
104}
105
106/// An upgrade lock with [`TIMEOUT_EPOCH_VERSION`] already in effect, so
107/// timeout votes and certificates take the form that binds the epoch.
108#[cfg(test)]
109pub fn test_timeout_epoch_lock<T: NodeType>() -> hotshot_types::message::UpgradeLock<T> {
110    use versions::{TIMEOUT_EPOCH_VERSION, Upgrade};
111
112    hotshot_types::message::UpgradeLock::new(Upgrade::trivial(TIMEOUT_EPOCH_VERSION))
113}