Skip to main content

hotshot_types/
utils.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//! Utility functions, type aliases, helper structs and enum definitions.
8
9use std::{
10    hash::{Hash, Hasher},
11    ops::Deref,
12    sync::Arc,
13};
14
15use anyhow::{anyhow, ensure};
16use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
17use bincode::{
18    DefaultOptions, Options,
19    config::{
20        FixintEncoding, LittleEndian, RejectTrailing, WithOtherEndian, WithOtherIntEncoding,
21        WithOtherLimit, WithOtherTrailing,
22    },
23};
24use committable::{Commitment, Committable};
25use digest::OutputSizeUser;
26use serde::{Deserialize, Serialize};
27use sha2::Digest;
28use tagged_base64::tagged;
29use typenum::Unsigned;
30use vbs::version::Version;
31use versions::EPOCH_VERSION;
32
33use crate::{
34    data::{EpochNumber, Leaf2, VidCommitment, ViewNumber},
35    epoch_membership::EpochMembershipCoordinator,
36    message::UpgradeLock,
37    simple_certificate::QuorumCertificate2,
38    simple_vote::HasEpoch,
39    stake_table::StakeTableEntries,
40    traits::{ValidatedState, election::Membership, node_implementation::NodeType},
41    vote::{Certificate, HasViewNumber},
42};
43
44/// A view's state
45#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
46#[serde(bound = "")]
47pub enum ViewInner<TYPES: NodeType> {
48    /// A pending view with an available block but not leaf proposal yet.
49    ///
50    /// Storing this state allows us to garbage collect blocks for views where a proposal is never
51    /// made. This saves memory when a leader fails and subverts a DoS attack where malicious
52    /// leaders repeatedly request availability for blocks that they never propose.
53    Da {
54        /// Payload commitment to the available block.
55        payload_commitment: VidCommitment,
56        /// An epoch to which the data belongs to. Relevant for validating against the correct stake table
57        epoch: Option<EpochNumber>,
58    },
59    /// Undecided view
60    Leaf {
61        /// Proposed leaf
62        leaf: LeafCommitment<TYPES>,
63        /// Validated state.
64        state: Arc<TYPES::ValidatedState>,
65        /// Optional state delta.
66        delta: Option<Arc<<TYPES::ValidatedState as ValidatedState<TYPES>>::Delta>>,
67        /// An epoch to which the data belongs to. Relevant for validating against the correct stake table
68        epoch: Option<EpochNumber>,
69    },
70    /// Leaf has failed
71    Failed,
72}
73impl<TYPES: NodeType> Clone for ViewInner<TYPES> {
74    fn clone(&self) -> Self {
75        match self {
76            Self::Da {
77                payload_commitment,
78                epoch,
79            } => Self::Da {
80                payload_commitment: *payload_commitment,
81                epoch: *epoch,
82            },
83            Self::Leaf {
84                leaf,
85                state,
86                delta,
87                epoch,
88            } => Self::Leaf {
89                leaf: *leaf,
90                state: Arc::clone(state),
91                delta: delta.clone(),
92                epoch: *epoch,
93            },
94            Self::Failed => Self::Failed,
95        }
96    }
97}
98/// The hash of a leaf.
99pub type LeafCommitment<TYPES> = Commitment<Leaf2<TYPES>>;
100
101/// Optional validated state and state delta.
102pub type StateAndDelta<TYPES> = (
103    Option<Arc<<TYPES as NodeType>::ValidatedState>>,
104    Option<Arc<<<TYPES as NodeType>::ValidatedState as ValidatedState<TYPES>>::Delta>>,
105);
106
107pub async fn verify_leaf_chain<T: NodeType>(
108    mut leaf_chain: Vec<Leaf2<T>>,
109    coordinator: &EpochMembershipCoordinator<T>,
110    expected_height: u64,
111    upgrade_lock: &UpgradeLock<T>,
112) -> anyhow::Result<Leaf2<T>> {
113    let epoch_height = *coordinator.epoch_height();
114    let first_epoch = coordinator.membership().first_epoch();
115    // Derive each QC's epoch from the height of the leaf it certifies; trusting
116    // the epoch claimed in the QC would let a stale epoch's quorum pick the
117    // stake table that verifies its own signatures.
118    let validate_qc = |qc: &QuorumCertificate2<T>, certified_height: u64| -> anyhow::Result<()> {
119        let epoch = EpochNumber::new(epoch_from_block_number(certified_height, epoch_height));
120        let membership = match qc.data.epoch() {
121            // QCs formed before epochs were enabled carry no epoch. Epochs start part way
122            // through the first epoch, which runs on the genesis stake table like the
123            // non-epoch chain before it, so no later height may be certified this way.
124            None => {
125                ensure!(
126                    epoch <= first_epoch,
127                    "QC claims no epoch but certifies the leaf at height {certified_height} in \
128                     epoch {epoch}, after the first epoch {first_epoch}"
129                );
130                coordinator.stake_table_for_epoch(None)
131            },
132            Some(claimed) => {
133                ensure!(
134                    claimed == epoch,
135                    "QC claims epoch {claimed} but certifies the leaf at height \
136                     {certified_height} in epoch {epoch}"
137                );
138                coordinator.stake_table_for_epoch(Some(epoch))
139            },
140        }
141        .map_err(|err| anyhow!("no stake table available for epoch {epoch}: {err:?}"))?;
142        if let Some(block_number) = qc.data.block_number {
143            ensure!(
144                block_number == certified_height,
145                "QC claims block number {block_number} but certifies the leaf at height \
146                 {certified_height}"
147            );
148        }
149        let entries = StakeTableEntries::<T>::from_iter(membership.stake_table()).0;
150        qc.is_valid_cert(&entries, membership.success_threshold(), upgrade_lock)?;
151        Ok(())
152    };
153
154    // Sort the leaf chain by view number
155    leaf_chain.sort_by_key(|l| l.view_number());
156    // Reverse it
157    leaf_chain.reverse();
158
159    // Check we actually have a chain long enough for deciding
160    if leaf_chain.len() < 3 {
161        return Err(anyhow!("Leaf chain is not long enough for a decide"));
162    }
163
164    let newest_leaf = leaf_chain.first().unwrap();
165    let parent = &leaf_chain[1];
166    let grand_parent = &leaf_chain[2];
167
168    // Check if the leaves form a decide
169    if newest_leaf.justify_qc().view_number() != parent.view_number()
170        || parent.justify_qc().view_number() != grand_parent.view_number()
171    {
172        return Err(anyhow!("Leaf views do not chain"));
173    }
174    if newest_leaf.justify_qc().data.leaf_commit != parent.commit()
175        || parent.justify_qc().data().leaf_commit != grand_parent.commit()
176    {
177        return Err(anyhow!("Leaf commits do not chain"));
178    }
179    if parent.view_number() != grand_parent.view_number() + 1 {
180        return Err(anyhow::anyhow!(
181            "Decide rule failed, parent does not directly extend grandparent"
182        ));
183    }
184
185    // Verify the root is in the chain of decided leaves
186    validate_qc(newest_leaf.justify_qc(), parent.height())?;
187    let mut last_leaf = parent;
188    for leaf in leaf_chain.iter().skip(2) {
189        ensure!(last_leaf.justify_qc().view_number() == leaf.view_number());
190        ensure!(last_leaf.justify_qc().data().leaf_commit == leaf.commit());
191        validate_qc(last_leaf.justify_qc(), leaf.height())?;
192        if leaf.height() == expected_height {
193            return Ok(leaf.clone());
194        }
195        last_leaf = leaf;
196    }
197    Err(anyhow!("Epoch Root was not found in the decided chain"))
198}
199
200impl<TYPES: NodeType> ViewInner<TYPES> {
201    /// Return the underlying undecide leaf commitment and validated state if they exist.
202    #[must_use]
203    pub fn leaf_and_state(&self) -> Option<(LeafCommitment<TYPES>, &Arc<TYPES::ValidatedState>)> {
204        if let Self::Leaf { leaf, state, .. } = self {
205            Some((*leaf, state))
206        } else {
207            None
208        }
209    }
210
211    /// return the underlying leaf hash if it exists
212    #[must_use]
213    pub fn leaf_commitment(&self) -> Option<LeafCommitment<TYPES>> {
214        if let Self::Leaf { leaf, .. } = self {
215            Some(*leaf)
216        } else {
217            None
218        }
219    }
220
221    /// return the underlying validated state if it exists
222    #[must_use]
223    pub fn state(&self) -> Option<&Arc<TYPES::ValidatedState>> {
224        if let Self::Leaf { state, .. } = self {
225            Some(state)
226        } else {
227            None
228        }
229    }
230
231    /// Return the underlying validated state and state delta if they exist.
232    #[must_use]
233    pub fn state_and_delta(&self) -> StateAndDelta<TYPES> {
234        if let Self::Leaf { state, delta, .. } = self {
235            (Some(Arc::clone(state)), delta.clone())
236        } else {
237            (None, None)
238        }
239    }
240
241    /// return the underlying block payload commitment if it exists
242    #[must_use]
243    pub fn payload_commitment(&self) -> Option<VidCommitment> {
244        if let Self::Da {
245            payload_commitment, ..
246        } = self
247        {
248            Some(*payload_commitment)
249        } else {
250            None
251        }
252    }
253
254    /// Returns `Epoch` if possible
255    // #3967 REVIEW NOTE: This type is kinda ugly, should we Result<Option<Epoch>> instead?
256    pub fn epoch(&self) -> Option<Option<EpochNumber>> {
257        match self {
258            Self::Da { epoch, .. } | Self::Leaf { epoch, .. } => Some(*epoch),
259            Self::Failed => None,
260        }
261    }
262}
263
264impl<TYPES: NodeType> Deref for View<TYPES> {
265    type Target = ViewInner<TYPES>;
266
267    fn deref(&self) -> &Self::Target {
268        &self.view_inner
269    }
270}
271
272/// This exists so we can perform state transitions mutably
273#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
274#[serde(bound = "")]
275pub struct View<TYPES: NodeType> {
276    /// The view data. Wrapped in a struct so we can mutate
277    pub view_inner: ViewInner<TYPES>,
278}
279
280/// A struct containing information about a finished round.
281#[derive(Debug, Clone)]
282pub struct RoundFinishedEvent {
283    /// The round that finished
284    pub view_number: ViewNumber,
285}
286
287/// Type alias for byte array of SHA256 digest length
288type Sha256Digest = [u8; <sha2::Sha256 as OutputSizeUser>::OutputSize::USIZE];
289
290#[tagged("BUILDER_COMMITMENT")]
291#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
292/// Commitment that builders use to sign block options.
293/// A thin wrapper around a Sha256 digest.
294pub struct BuilderCommitment(Sha256Digest);
295
296impl BuilderCommitment {
297    /// Create new commitment for `data`
298    pub fn from_bytes(data: impl AsRef<[u8]>) -> Self {
299        Self(sha2::Sha256::digest(data.as_ref()).into())
300    }
301
302    /// Create a new commitment from a raw Sha256 digest
303    pub fn from_raw_digest(digest: impl Into<Sha256Digest>) -> Self {
304        Self(digest.into())
305    }
306
307    /// The commitment to no bytes, for where one is required but nothing was
308    /// committed to. Not the all-zero digest `Default` gives.
309    pub fn empty() -> Self {
310        Self::from_bytes([])
311    }
312}
313
314impl AsRef<Sha256Digest> for BuilderCommitment {
315    fn as_ref(&self) -> &Sha256Digest {
316        &self.0
317    }
318}
319
320type BincodeOpts = WithOtherTrailing<
321    WithOtherIntEncoding<
322        WithOtherEndian<WithOtherLimit<DefaultOptions, bincode::config::Infinite>, LittleEndian>,
323        FixintEncoding,
324    >,
325    RejectTrailing,
326>;
327
328/// For the wire format, we use bincode with the following options:
329///   - No upper size limit
330///   - Little endian encoding
331///   - Varint encoding
332///   - Reject trailing bytes
333#[must_use]
334pub fn bincode_opts() -> BincodeOpts {
335    bincode::DefaultOptions::new()
336        .with_no_limit()
337        .with_little_endian()
338        .with_fixint_encoding()
339        .reject_trailing_bytes()
340}
341
342/// Returns an epoch number given a block number and an epoch height
343#[must_use]
344pub fn epoch_from_block_number(block_number: u64, epoch_height: u64) -> u64 {
345    if epoch_height == 0 {
346        0
347    } else if block_number == 0 {
348        1
349    } else if block_number.is_multiple_of(epoch_height) {
350        block_number / epoch_height
351    } else {
352        block_number / epoch_height + 1
353    }
354}
355
356/// Returns the block number of the epoch root in the given epoch
357///
358/// WARNING: This is NOT the root block for the given epoch.
359/// To find that root block number for epoch e, call `root_block_in_epoch(e-2,_)`.
360#[must_use]
361pub fn root_block_in_epoch(epoch: u64, epoch_height: u64) -> u64 {
362    if epoch_height == 0 || epoch < 1 {
363        0
364    } else {
365        epoch_height * epoch - 5
366    }
367}
368
369/// Get the block height of the transition block for the given epoch
370///
371/// This is the height at which we begin the transition to LEAVE the specified epoch
372#[must_use]
373pub fn transition_block_for_epoch(epoch: u64, epoch_height: u64) -> u64 {
374    if epoch_height == 0 || epoch < 1 {
375        0
376    } else {
377        epoch_height * epoch - 3
378    }
379}
380
381/// Returns an `Option<Epoch>` based on a boolean condition of whether or not epochs are enabled, a block number,
382/// and the epoch height. If epochs are disabled or the epoch height is zero, returns None.
383#[must_use]
384pub fn option_epoch_from_block_number(
385    with_epoch: bool,
386    block_number: u64,
387    epoch_height: u64,
388) -> Option<EpochNumber> {
389    if with_epoch {
390        if epoch_height == 0 {
391            None
392        } else if block_number == 0 {
393            Some(1u64)
394        } else if block_number.is_multiple_of(epoch_height) {
395            Some(block_number / epoch_height)
396        } else {
397            Some(block_number / epoch_height + 1)
398        }
399        .map(EpochNumber::new)
400    } else {
401        None
402    }
403}
404
405/// Returns Some(1) if epochs are enabled by `base`, otherwise returns None
406#[must_use]
407pub fn genesis_epoch_from_version(base: Version) -> Option<EpochNumber> {
408    (base >= EPOCH_VERSION).then(|| EpochNumber::new(1))
409}
410
411/// A function for generating a cute little user mnemonic from a hash
412#[must_use]
413pub fn mnemonic<H: Hash>(bytes: H) -> String {
414    let mut state = std::collections::hash_map::DefaultHasher::new();
415    bytes.hash(&mut state);
416    mnemonic::to_string(state.finish().to_le_bytes())
417}
418
419/// A helper enum to indicate whether a node is in the epoch transition
420/// A node is in epoch transition when its high QC is for the last block in an epoch
421#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
422pub enum EpochTransitionIndicator {
423    /// A node is currently in the epoch transition
424    InTransition,
425    /// A node is not in the epoch transition
426    NotInTransition,
427}
428
429/// Return true if the given block number is the final full block, the "transition block"
430#[must_use]
431pub fn is_transition_block(block_number: u64, epoch_height: u64) -> bool {
432    if block_number == 0 || epoch_height == 0 {
433        false
434    } else {
435        (block_number + 3).is_multiple_of(epoch_height)
436    }
437}
438/// returns true if it's the first transition block (epoch height - 2)
439#[must_use]
440pub fn is_first_transition_block(block_number: u64, epoch_height: u64) -> bool {
441    if block_number == 0 || epoch_height == 0 {
442        false
443    } else {
444        block_number % epoch_height == epoch_height - 2
445    }
446}
447/// Returns true if the block is part of the epoch transition (including the last non null block)
448#[must_use]
449pub fn is_epoch_transition(block_number: u64, epoch_height: u64) -> bool {
450    if block_number == 0 || epoch_height == 0 {
451        false
452    } else {
453        block_number % epoch_height >= epoch_height - 3 || block_number.is_multiple_of(epoch_height)
454    }
455}
456
457/// Returns true if the block is the last block in the epoch
458#[must_use]
459pub fn is_last_block(block_number: u64, epoch_height: u64) -> bool {
460    if block_number == 0 || epoch_height == 0 {
461        false
462    } else {
463        block_number.is_multiple_of(epoch_height)
464    }
465}
466
467/// Returns true if the block number is in trasntion but not the transition block
468/// or the last block in the epoch.
469///
470/// This function is useful for determining if a proposal extending this QC must follow
471/// the special rules for transition blocks.
472#[must_use]
473pub fn is_middle_transition_block(block_number: u64, epoch_height: u64) -> bool {
474    if block_number == 0 || epoch_height == 0 {
475        false
476    } else {
477        let blocks_left = epoch_height - (block_number % epoch_height);
478        blocks_left == 1 || blocks_left == 2
479    }
480}
481
482/// Returns true if the given block number is the third from the last in the epoch based on the
483/// given epoch height.
484#[must_use]
485pub fn is_epoch_root(block_number: u64, epoch_height: u64) -> bool {
486    if block_number == 0 || epoch_height == 0 {
487        false
488    } else {
489        (block_number + 5).is_multiple_of(epoch_height)
490    }
491}
492
493/// Returns true if the given block number is equal or greater than the epoch root block
494#[must_use]
495pub fn is_ge_epoch_root(block_number: u64, epoch_height: u64) -> bool {
496    if block_number == 0 || epoch_height == 0 {
497        false
498    } else {
499        block_number.is_multiple_of(epoch_height) || block_number % epoch_height >= epoch_height - 5
500    }
501}
502
503/// Returns true if the given block number is strictly greater than the epoch root block
504pub fn is_gt_epoch_root(block_number: u64, epoch_height: u64) -> bool {
505    if block_number == 0 || epoch_height == 0 {
506        false
507    } else {
508        block_number.is_multiple_of(epoch_height) || block_number % epoch_height > epoch_height - 5
509    }
510}
511
512#[cfg(test)]
513mod test {
514    use super::*;
515
516    #[test]
517    fn test_epoch_from_block_number() {
518        // block 0 is always epoch 1
519        let epoch = epoch_from_block_number(0, 10);
520        assert_eq!(1, epoch);
521
522        let epoch = epoch_from_block_number(1, 10);
523        assert_eq!(1, epoch);
524
525        let epoch = epoch_from_block_number(10, 10);
526        assert_eq!(1, epoch);
527
528        let epoch = epoch_from_block_number(11, 10);
529        assert_eq!(2, epoch);
530
531        let epoch = epoch_from_block_number(20, 10);
532        assert_eq!(2, epoch);
533
534        let epoch = epoch_from_block_number(21, 10);
535        assert_eq!(3, epoch);
536
537        let epoch = epoch_from_block_number(21, 0);
538        assert_eq!(0, epoch);
539    }
540
541    #[test]
542    fn test_is_last_block_in_epoch() {
543        assert!(!is_epoch_transition(5, 10));
544        assert!(!is_epoch_transition(6, 10));
545        assert!(is_epoch_transition(7, 10));
546        assert!(is_epoch_transition(8, 10));
547        assert!(is_epoch_transition(9, 10));
548        assert!(is_epoch_transition(10, 10));
549        assert!(!is_epoch_transition(11, 10));
550
551        assert!(!is_epoch_transition(10, 0));
552    }
553
554    #[test]
555    fn test_is_epoch_root() {
556        assert!(is_epoch_root(5, 10));
557        assert!(!is_epoch_root(6, 10));
558        assert!(!is_epoch_root(7, 10));
559        assert!(!is_epoch_root(8, 10));
560        assert!(!is_epoch_root(9, 10));
561        assert!(!is_epoch_root(10, 10));
562        assert!(!is_epoch_root(11, 10));
563
564        assert!(!is_epoch_transition(10, 0));
565    }
566
567    #[test]
568    fn test_root_block_in_epoch() {
569        // block 0 is always epoch 0
570        let epoch = 3;
571        let epoch_height = 10;
572        let epoch_root_block_number = root_block_in_epoch(3, epoch_height);
573
574        assert!(is_epoch_root(25, epoch_height));
575
576        assert_eq!(epoch_root_block_number, 25);
577
578        assert_eq!(
579            epoch,
580            epoch_from_block_number(epoch_root_block_number, epoch_height)
581        );
582    }
583}