hotshot_types/
data.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//! Provides types useful for representing `HotShot`'s data structures
8//!
9//! This module provides types for representing consensus internal state, such as leaves,
10//! `HotShot`'s version of a block, and proposals, messages upon which to reach the consensus.
11
12use std::{
13    fmt::{Debug, Display},
14    hash::Hash,
15    marker::PhantomData,
16    sync::Arc,
17    time::Duration,
18};
19
20use async_lock::RwLock;
21use bincode::Options;
22use committable::{Commitment, CommitmentBoundsArkless, Committable, RawCommitmentBuilder};
23use hotshot_utils::anytrace::*;
24use jf_vid::VidScheme;
25use rand::Rng;
26use serde::{Deserialize, Serialize};
27use tagged_base64::TaggedBase64;
28use thiserror::Error;
29use vbs::version::{StaticVersionType, Version};
30use vec1::Vec1;
31use vid_disperse::{ADVZDisperse, ADVZDisperseShare, AvidMDisperse, VidDisperseShare2};
32
33use crate::{
34    drb::DrbResult,
35    epoch_membership::EpochMembershipCoordinator,
36    impl_has_epoch, impl_has_none_epoch,
37    message::{convert_proposal, Proposal, UpgradeLock},
38    simple_certificate::{
39        LightClientStateUpdateCertificate, NextEpochQuorumCertificate2, QuorumCertificate,
40        QuorumCertificate2, TimeoutCertificate, TimeoutCertificate2, UpgradeCertificate,
41        ViewSyncFinalizeCertificate, ViewSyncFinalizeCertificate2,
42    },
43    simple_vote::{HasEpoch, QuorumData, QuorumData2, UpgradeProposalData, VersionedVoteData},
44    traits::{
45        block_contents::{BlockHeader, BuilderFee, EncodeBytes, TestableBlock},
46        node_implementation::{ConsensusTime, NodeType, Versions},
47        signature_key::SignatureKey,
48        states::TestableState,
49        BlockPayload,
50    },
51    utils::{
52        bincode_opts, genesis_epoch_from_version, option_epoch_from_block_number,
53        EpochTransitionIndicator,
54    },
55    vid::{
56        advz::{advz_scheme, ADVZCommitment, ADVZShare},
57        avidm::{init_avidm_param, AvidMCommitment, AvidMScheme, AvidMShare},
58    },
59    vote::{Certificate, HasViewNumber},
60};
61
62/// Implements `ConsensusTime`, `Display`, `Add`, `AddAssign`, `Deref` and `Sub`
63/// for the given thing wrapper type around u64.
64macro_rules! impl_u64_wrapper {
65    ($t:ty, $genesis_val:expr) => {
66        impl ConsensusTime for $t {
67            /// Create a genesis number
68            fn genesis() -> Self {
69                Self($genesis_val)
70            }
71            /// Create a new number with the given value.
72            fn new(n: u64) -> Self {
73                Self(n)
74            }
75            /// Return the u64 format
76            fn u64(&self) -> u64 {
77                self.0
78            }
79        }
80
81        impl Display for $t {
82            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83                write!(f, "{}", self.0)
84            }
85        }
86
87        impl std::ops::Add<u64> for $t {
88            type Output = $t;
89
90            fn add(self, rhs: u64) -> Self::Output {
91                Self(self.0 + rhs)
92            }
93        }
94
95        impl std::ops::AddAssign<u64> for $t {
96            fn add_assign(&mut self, rhs: u64) {
97                self.0 += rhs;
98            }
99        }
100
101        impl std::ops::Deref for $t {
102            type Target = u64;
103
104            fn deref(&self) -> &Self::Target {
105                &self.0
106            }
107        }
108
109        impl std::ops::Sub<u64> for $t {
110            type Output = $t;
111            fn sub(self, rhs: u64) -> Self::Output {
112                Self(self.0 - rhs)
113            }
114        }
115    };
116}
117
118/// Type-safe wrapper around `u64` so we know the thing we're talking about is a view number.
119#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
120pub struct ViewNumber(u64);
121
122impl Committable for ViewNumber {
123    fn commit(&self) -> Commitment<Self> {
124        let builder = RawCommitmentBuilder::new("View Number Commitment");
125        builder.u64(self.0).finalize()
126    }
127}
128
129impl_u64_wrapper!(ViewNumber, 0u64);
130
131/// Type-safe wrapper around `u64` so we know the thing we're talking about is a epoch number.
132#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
133pub struct EpochNumber(u64);
134
135impl Committable for EpochNumber {
136    fn commit(&self) -> Commitment<Self> {
137        let builder = RawCommitmentBuilder::new("Epoch Number Commitment");
138        builder.u64(self.0).finalize()
139    }
140}
141
142impl_u64_wrapper!(EpochNumber, 1u64);
143
144/// A proposal to start providing data availability for a block.
145#[derive(derive_more::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
146#[serde(bound = "TYPES: NodeType")]
147pub struct DaProposal<TYPES: NodeType> {
148    /// Encoded transactions in the block to be applied.
149    pub encoded_transactions: Arc<[u8]>,
150    /// Metadata of the block to be applied.
151    pub metadata: <TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
152    /// View this proposal applies to
153    pub view_number: TYPES::View,
154}
155
156/// A proposal to start providing data availability for a block.
157#[derive(derive_more::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
158#[serde(bound = "TYPES: NodeType")]
159pub struct DaProposal2<TYPES: NodeType> {
160    /// Encoded transactions in the block to be applied.
161    pub encoded_transactions: Arc<[u8]>,
162    /// Metadata of the block to be applied.
163    pub metadata: <TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
164    /// View this proposal applies to
165    pub view_number: TYPES::View,
166    /// Epoch this proposal applies to
167    pub epoch: Option<TYPES::Epoch>,
168    /// Indicates whether we are in epoch transition
169    /// In epoch transition the next epoch payload commit should be calculated additionally
170    pub epoch_transition_indicator: EpochTransitionIndicator,
171}
172
173impl<TYPES: NodeType> From<DaProposal<TYPES>> for DaProposal2<TYPES> {
174    fn from(da_proposal: DaProposal<TYPES>) -> Self {
175        Self {
176            encoded_transactions: da_proposal.encoded_transactions,
177            metadata: da_proposal.metadata,
178            view_number: da_proposal.view_number,
179            epoch: None,
180            epoch_transition_indicator: EpochTransitionIndicator::NotInTransition,
181        }
182    }
183}
184
185impl<TYPES: NodeType> From<DaProposal2<TYPES>> for DaProposal<TYPES> {
186    fn from(da_proposal2: DaProposal2<TYPES>) -> Self {
187        Self {
188            encoded_transactions: da_proposal2.encoded_transactions,
189            metadata: da_proposal2.metadata,
190            view_number: da_proposal2.view_number,
191        }
192    }
193}
194
195/// A proposal to upgrade the network
196#[derive(derive_more::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
197#[serde(bound = "TYPES: NodeType")]
198pub struct UpgradeProposal<TYPES>
199where
200    TYPES: NodeType,
201{
202    /// The information about which version we are upgrading to.
203    pub upgrade_proposal: UpgradeProposalData<TYPES>,
204    /// View this proposal applies to
205    pub view_number: TYPES::View,
206}
207
208/// VID Commitment type
209#[derive(Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize, Ord, PartialOrd)]
210#[serde(
211    try_from = "tagged_base64::TaggedBase64",
212    into = "tagged_base64::TaggedBase64"
213)]
214pub enum VidCommitment {
215    V0(ADVZCommitment),
216    V1(AvidMCommitment),
217}
218
219impl Default for VidCommitment {
220    fn default() -> Self {
221        Self::V0(Default::default())
222    }
223}
224
225impl Display for VidCommitment {
226    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227        std::write!(f, "{}", TaggedBase64::from(self))
228    }
229}
230
231impl Debug for VidCommitment {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        std::fmt::Display::fmt(self, f)
234    }
235}
236
237impl From<VidCommitment> for TaggedBase64 {
238    fn from(val: VidCommitment) -> Self {
239        match val {
240            VidCommitment::V0(comm) => comm.into(),
241            VidCommitment::V1(comm) => comm.into(),
242        }
243    }
244}
245
246impl From<&VidCommitment> for TaggedBase64 {
247    fn from(val: &VidCommitment) -> Self {
248        match val {
249            VidCommitment::V0(comm) => comm.into(),
250            VidCommitment::V1(comm) => comm.into(),
251        }
252    }
253}
254
255impl TryFrom<TaggedBase64> for VidCommitment {
256    type Error = tagged_base64::Tb64Error;
257
258    fn try_from(value: TaggedBase64) -> std::result::Result<Self, Self::Error> {
259        ADVZCommitment::try_from(&value)
260            .map(Self::V0)
261            .or(AvidMCommitment::try_from(value).map(Self::V1))
262    }
263}
264
265impl<'a> TryFrom<&'a TaggedBase64> for VidCommitment {
266    type Error = tagged_base64::Tb64Error;
267
268    fn try_from(value: &'a TaggedBase64) -> std::result::Result<Self, Self::Error> {
269        ADVZCommitment::try_from(value)
270            .map(Self::V0)
271            .or(AvidMCommitment::try_from(value).map(Self::V1))
272    }
273}
274
275impl std::str::FromStr for VidCommitment {
276    type Err = tagged_base64::Tb64Error;
277    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
278        use core::convert::TryFrom;
279        Self::try_from(TaggedBase64::from_str(s)?)
280            .map_err(|_| tagged_base64::Tb64Error::InvalidData)
281    }
282}
283
284// TODO(Chengyu): cannot have this because of `impl<H> From<Output<H>> for HasherNode<H>`.
285// impl From<ADVZCommitment> for VidCommitment {
286//     fn from(comm: ADVZCommitment) -> Self {
287//         Self::V0(comm)
288//     }
289// }
290
291impl From<AvidMCommitment> for VidCommitment {
292    fn from(comm: AvidMCommitment) -> Self {
293        Self::V1(comm)
294    }
295}
296
297impl AsRef<[u8]> for VidCommitment {
298    fn as_ref(&self) -> &[u8] {
299        match self {
300            Self::V0(comm) => comm.as_ref(),
301            Self::V1(comm) => comm.as_ref(),
302        }
303    }
304}
305
306impl AsRef<[u8; 32]> for VidCommitment {
307    fn as_ref(&self) -> &[u8; 32] {
308        match self {
309            Self::V0(comm) => comm.as_ref().as_ref(),
310            Self::V1(comm) => comm.as_ref(),
311        }
312    }
313}
314
315impl VidCommitment {
316    /// Unwrap an ADVZCommitment. Panic if incorrect version.
317    pub fn unwrap_v0(self) -> ADVZCommitment {
318        match self {
319            VidCommitment::V0(comm) => comm,
320            _ => panic!("Unexpected version for this commitment"),
321        }
322    }
323    /// Unwrap an AvidMCommitment. Panic if incorrect version.
324    pub fn unwrap_v1(self) -> AvidMCommitment {
325        match self {
326            VidCommitment::V1(comm) => comm,
327            _ => panic!("Unexpected version for this commitment"),
328        }
329    }
330}
331
332/// Compute the VID payload commitment.
333/// TODO(Gus) delete this function?
334/// # Panics
335/// If the VID computation fails.
336#[must_use]
337#[allow(clippy::panic)]
338pub fn vid_commitment<V: Versions>(
339    encoded_transactions: &[u8],
340    metadata: &[u8],
341    total_weight: usize,
342    version: Version,
343) -> VidCommitment {
344    if version < V::Epochs::VERSION {
345        let encoded_tx_len = encoded_transactions.len();
346        advz_scheme(total_weight).commit_only(encoded_transactions).map(VidCommitment::V0).unwrap_or_else(|err| panic!("VidScheme::commit_only failure:(total_weight,payload_byte_len)=({total_weight},{encoded_tx_len}) error: {err}"))
347    } else {
348        let param = init_avidm_param(total_weight).unwrap();
349        let encoded_tx_len = encoded_transactions.len();
350        AvidMScheme::commit(
351            &param,
352            encoded_transactions,
353            ns_table::parse_ns_table(encoded_tx_len, metadata),
354        )
355        .map(VidCommitment::V1)
356        .unwrap()
357    }
358}
359
360/// VID share type
361#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
362pub enum VidShare {
363    V0(ADVZShare),
364    V1(AvidMShare),
365}
366
367// TODO(Chengyu): cannot have this
368// impl From<ADVZShare> for VidShare {
369//     fn from(share: ADVZShare) -> Self {
370//         Self::V0(share)
371//     }
372// }
373
374impl From<AvidMShare> for VidShare {
375    fn from(share: AvidMShare) -> Self {
376        Self::V1(share)
377    }
378}
379
380pub mod ns_table;
381pub mod vid_disperse;
382
383/// A helper struct to hold the disperse data and the time it took to calculate the disperse
384pub struct VidDisperseAndDuration<TYPES: NodeType> {
385    /// The disperse data
386    pub disperse: VidDisperse<TYPES>,
387    /// The time it took to calculate the disperse
388    pub duration: Duration,
389}
390
391/// VID dispersal data
392///
393/// Like [`DaProposal`].
394///
395/// TODO move to vid.rs?
396#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
397#[serde(bound = "TYPES: NodeType")]
398pub enum VidDisperse<TYPES: NodeType> {
399    /// Disperse type for first VID version
400    V0(vid_disperse::ADVZDisperse<TYPES>),
401    /// Place holder for VID upgrade
402    V1(vid_disperse::AvidMDisperse<TYPES>),
403}
404
405impl<TYPES: NodeType> From<vid_disperse::ADVZDisperse<TYPES>> for VidDisperse<TYPES> {
406    fn from(disperse: vid_disperse::ADVZDisperse<TYPES>) -> Self {
407        Self::V0(disperse)
408    }
409}
410
411impl<TYPES: NodeType> From<vid_disperse::AvidMDisperse<TYPES>> for VidDisperse<TYPES> {
412    fn from(disperse: vid_disperse::AvidMDisperse<TYPES>) -> Self {
413        Self::V1(disperse)
414    }
415}
416
417impl<TYPES: NodeType> HasViewNumber<TYPES> for VidDisperse<TYPES> {
418    fn view_number(&self) -> TYPES::View {
419        match self {
420            Self::V0(disperse) => disperse.view_number(),
421            Self::V1(disperse) => disperse.view_number(),
422        }
423    }
424}
425
426impl<TYPES: NodeType> HasEpoch<TYPES> for VidDisperse<TYPES> {
427    fn epoch(&self) -> Option<TYPES::Epoch> {
428        match self {
429            Self::V0(disperse) => disperse.epoch(),
430            Self::V1(disperse) => disperse.epoch(),
431        }
432    }
433}
434
435impl<TYPES: NodeType> VidDisperse<TYPES> {
436    /// Calculate the vid disperse information from the payload given a view, epoch and membership,
437    /// If the sender epoch is missing, it means it's the same as the target epoch.
438    ///
439    /// # Errors
440    /// Returns an error if the disperse or commitment calculation fails
441    #[allow(clippy::panic)]
442    pub async fn calculate_vid_disperse<V: Versions>(
443        payload: &TYPES::BlockPayload,
444        membership: &EpochMembershipCoordinator<TYPES>,
445        view: TYPES::View,
446        target_epoch: Option<TYPES::Epoch>,
447        data_epoch: Option<TYPES::Epoch>,
448        metadata: &<TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
449        upgrade_lock: &UpgradeLock<TYPES, V>,
450    ) -> Result<VidDisperseAndDuration<TYPES>> {
451        let version = upgrade_lock.version_infallible(view).await;
452        if version < V::Epochs::VERSION {
453            ADVZDisperse::calculate_vid_disperse(
454                payload,
455                membership,
456                view,
457                target_epoch,
458                data_epoch,
459            )
460            .await
461            .map(|(disperse, duration)| VidDisperseAndDuration {
462                disperse: Self::V0(disperse),
463                duration,
464            })
465        } else {
466            AvidMDisperse::calculate_vid_disperse(
467                payload,
468                membership,
469                view,
470                target_epoch,
471                data_epoch,
472                metadata,
473            )
474            .await
475            .map(|(disperse, duration)| VidDisperseAndDuration {
476                disperse: Self::V1(disperse),
477                duration,
478            })
479        }
480    }
481
482    /// Return the internal payload commitment
483    pub fn payload_commitment(&self) -> VidCommitment {
484        match self {
485            Self::V0(disperse) => VidCommitment::V0(disperse.payload_commitment),
486            Self::V1(disperse) => disperse.payload_commitment.into(),
487        }
488    }
489
490    /// Return a slice reference to the payload commitment. Should be used for signature.
491    pub fn payload_commitment_ref(&self) -> &[u8] {
492        match self {
493            Self::V0(disperse) => disperse.payload_commitment.as_ref(),
494            Self::V1(disperse) => disperse.payload_commitment.as_ref(),
495        }
496    }
497
498    /// Set the view number
499    pub fn set_view_number(&mut self, view_number: <TYPES as NodeType>::View) {
500        match self {
501            Self::V0(share) => share.view_number = view_number,
502            Self::V1(share) => share.view_number = view_number,
503        }
504    }
505}
506
507/// VID share and associated metadata for a single node
508#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
509#[serde(bound = "TYPES: NodeType")]
510pub enum VidDisperseShare<TYPES: NodeType> {
511    /// VID disperse share type for first version VID
512    V0(vid_disperse::ADVZDisperseShare<TYPES>),
513    /// VID disperse share type after epoch upgrade and VID upgrade
514    V1(vid_disperse::VidDisperseShare2<TYPES>),
515}
516
517impl<TYPES: NodeType> VidDisperseShare<TYPES> {
518    /// Create a vector of `VidDisperseShare` from `VidDisperse`
519    pub fn from_vid_disperse(vid_disperse: VidDisperse<TYPES>) -> Vec<Self> {
520        match vid_disperse {
521            VidDisperse::V0(vid_disperse) => {
522                ADVZDisperseShare::<TYPES>::from_advz_disperse(vid_disperse)
523                    .into_iter()
524                    .map(|share| Self::V0(share))
525                    .collect()
526            },
527            VidDisperse::V1(vid_disperse) => {
528                VidDisperseShare2::<TYPES>::from_vid_disperse(vid_disperse)
529                    .into_iter()
530                    .map(|share| Self::V1(share))
531                    .collect()
532            },
533        }
534    }
535
536    /// Consume `self` and return a `Proposal`
537    pub fn to_proposal(
538        self,
539        private_key: &<TYPES::SignatureKey as SignatureKey>::PrivateKey,
540    ) -> Option<Proposal<TYPES, Self>> {
541        let payload_commitment_ref: &[u8] = match &self {
542            Self::V0(share) => share.payload_commitment.as_ref(),
543            Self::V1(share) => share.payload_commitment.as_ref(),
544        };
545        let Ok(signature) = TYPES::SignatureKey::sign(private_key, payload_commitment_ref) else {
546            tracing::error!("VID: failed to sign dispersal share payload");
547            return None;
548        };
549        Some(Proposal {
550            signature,
551            _pd: PhantomData,
552            data: self,
553        })
554    }
555
556    /// Split a VID share proposal into a proposal for each recipient.
557    pub fn to_vid_share_proposals(
558        vid_disperse_proposal: Proposal<TYPES, VidDisperse<TYPES>>,
559    ) -> Vec<Proposal<TYPES, Self>> {
560        match vid_disperse_proposal.data {
561            VidDisperse::V0(disperse) => ADVZDisperseShare::to_vid_share_proposals(
562                disperse,
563                &vid_disperse_proposal.signature,
564            )
565            .into_iter()
566            .map(|proposal| convert_proposal(proposal))
567            .collect(),
568            VidDisperse::V1(disperse) => VidDisperseShare2::to_vid_share_proposals(
569                disperse,
570                &vid_disperse_proposal.signature,
571            )
572            .into_iter()
573            .map(|proposal| convert_proposal(proposal))
574            .collect(),
575        }
576    }
577
578    /// Return the internal `recipient_key`
579    pub fn recipient_key(&self) -> &TYPES::SignatureKey {
580        match self {
581            Self::V0(share) => &share.recipient_key,
582            Self::V1(share) => &share.recipient_key,
583        }
584    }
585
586    /// Return the payload length in bytes.
587    pub fn payload_byte_len(&self) -> u32 {
588        match self {
589            Self::V0(share) => share.payload_byte_len(),
590            Self::V1(share) => share.payload_byte_len(),
591        }
592    }
593
594    /// Return a reference to the internal payload VID commitment
595    pub fn payload_commitment_ref(&self) -> &[u8] {
596        match self {
597            Self::V0(share) => share.payload_commitment.as_ref(),
598            Self::V1(share) => share.payload_commitment.as_ref(),
599        }
600    }
601
602    /// Return the internal payload VID commitment
603    pub fn payload_commitment(&self) -> VidCommitment {
604        match self {
605            Self::V0(share) => VidCommitment::V0(share.payload_commitment),
606            Self::V1(share) => share.payload_commitment.into(),
607        }
608    }
609
610    /// Return the target epoch
611    pub fn target_epoch(&self) -> Option<<TYPES as NodeType>::Epoch> {
612        match self {
613            Self::V0(_) => None,
614            Self::V1(share) => share.target_epoch,
615        }
616    }
617
618    /// Internally verify the share given necessary information
619    ///
620    /// # Errors
621    #[allow(clippy::result_unit_err)]
622    pub fn verify_share(&self, total_nodes: usize) -> std::result::Result<(), ()> {
623        match self {
624            Self::V0(share) => share.verify_share(total_nodes),
625            Self::V1(share) => share.verify_share(total_nodes),
626        }
627    }
628
629    /// Set the view number
630    pub fn set_view_number(&mut self, view_number: <TYPES as NodeType>::View) {
631        match self {
632            Self::V0(share) => share.view_number = view_number,
633            Self::V1(share) => share.view_number = view_number,
634        }
635    }
636}
637
638impl<TYPES: NodeType> HasViewNumber<TYPES> for VidDisperseShare<TYPES> {
639    fn view_number(&self) -> TYPES::View {
640        match self {
641            Self::V0(disperse) => disperse.view_number(),
642            Self::V1(disperse) => disperse.view_number(),
643        }
644    }
645}
646
647impl<TYPES: NodeType> HasEpoch<TYPES> for VidDisperseShare<TYPES> {
648    fn epoch(&self) -> Option<TYPES::Epoch> {
649        match self {
650            Self::V0(_) => None,
651            Self::V1(share) => share.epoch(),
652        }
653    }
654}
655
656impl<TYPES: NodeType> From<vid_disperse::ADVZDisperseShare<TYPES>> for VidDisperseShare<TYPES> {
657    fn from(share: vid_disperse::ADVZDisperseShare<TYPES>) -> Self {
658        Self::V0(share)
659    }
660}
661
662impl<TYPES: NodeType> From<vid_disperse::VidDisperseShare2<TYPES>> for VidDisperseShare<TYPES> {
663    fn from(share: vid_disperse::VidDisperseShare2<TYPES>) -> Self {
664        Self::V1(share)
665    }
666}
667
668/// Helper type to encapsulate the various ways that proposal certificates can be captured and
669/// stored.
670#[derive(derive_more::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
671#[serde(bound(deserialize = ""))]
672pub enum ViewChangeEvidence<TYPES: NodeType> {
673    /// Holds a timeout certificate.
674    Timeout(TimeoutCertificate<TYPES>),
675    /// Holds a view sync finalized certificate.
676    ViewSync(ViewSyncFinalizeCertificate<TYPES>),
677}
678
679impl<TYPES: NodeType> ViewChangeEvidence<TYPES> {
680    /// Check that the given ViewChangeEvidence is relevant to the current view.
681    pub fn is_valid_for_view(&self, view: &TYPES::View) -> bool {
682        match self {
683            ViewChangeEvidence::Timeout(timeout_cert) => timeout_cert.data().view == *view - 1,
684            ViewChangeEvidence::ViewSync(view_sync_cert) => view_sync_cert.view_number == *view,
685        }
686    }
687
688    /// Convert to ViewChangeEvidence2
689    pub fn to_evidence2(self) -> ViewChangeEvidence2<TYPES> {
690        match self {
691            ViewChangeEvidence::Timeout(timeout_cert) => {
692                ViewChangeEvidence2::Timeout(timeout_cert.to_tc2())
693            },
694            ViewChangeEvidence::ViewSync(view_sync_cert) => {
695                ViewChangeEvidence2::ViewSync(view_sync_cert.to_vsc2())
696            },
697        }
698    }
699}
700
701/// Helper type to encapsulate the various ways that proposal certificates can be captured and
702/// stored.
703#[derive(derive_more::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
704#[serde(bound(deserialize = ""))]
705pub enum ViewChangeEvidence2<TYPES: NodeType> {
706    /// Holds a timeout certificate.
707    Timeout(TimeoutCertificate2<TYPES>),
708    /// Holds a view sync finalized certificate.
709    ViewSync(ViewSyncFinalizeCertificate2<TYPES>),
710}
711
712impl<TYPES: NodeType> ViewChangeEvidence2<TYPES> {
713    /// Check that the given ViewChangeEvidence2 is relevant to the current view.
714    pub fn is_valid_for_view(&self, view: &TYPES::View) -> bool {
715        match self {
716            ViewChangeEvidence2::Timeout(timeout_cert) => timeout_cert.data().view == *view - 1,
717            ViewChangeEvidence2::ViewSync(view_sync_cert) => view_sync_cert.view_number == *view,
718        }
719    }
720
721    /// Convert to ViewChangeEvidence
722    pub fn to_evidence(self) -> ViewChangeEvidence<TYPES> {
723        match self {
724            ViewChangeEvidence2::Timeout(timeout_cert) => {
725                ViewChangeEvidence::Timeout(timeout_cert.to_tc())
726            },
727            ViewChangeEvidence2::ViewSync(view_sync_cert) => {
728                ViewChangeEvidence::ViewSync(view_sync_cert.to_vsc())
729            },
730        }
731    }
732}
733
734/// Proposal to append a block.
735#[derive(derive_more::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
736#[serde(bound(deserialize = ""))]
737pub struct QuorumProposal<TYPES: NodeType> {
738    /// The block header to append
739    pub block_header: TYPES::BlockHeader,
740
741    /// CurView from leader when proposing leaf
742    pub view_number: TYPES::View,
743
744    /// Per spec, justification
745    pub justify_qc: QuorumCertificate<TYPES>,
746
747    /// Possible upgrade certificate, which the leader may optionally attach.
748    pub upgrade_certificate: Option<UpgradeCertificate<TYPES>>,
749
750    /// Possible timeout or view sync certificate.
751    /// - A timeout certificate is only present if the justify_qc is not for the preceding view
752    /// - A view sync certificate is only present if the justify_qc and timeout_cert are not
753    ///   present.
754    pub proposal_certificate: Option<ViewChangeEvidence<TYPES>>,
755}
756
757/// Proposal to append a block.
758#[derive(derive_more::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
759#[serde(bound(deserialize = ""))]
760pub struct QuorumProposal2<TYPES: NodeType> {
761    /// The block header to append
762    pub block_header: TYPES::BlockHeader,
763
764    /// view number for the proposal
765    pub view_number: TYPES::View,
766
767    /// The epoch number corresponding to the block number. Can be `None` for pre-epoch version.
768    pub epoch: Option<TYPES::Epoch>,
769
770    /// certificate that the proposal is chaining from
771    pub justify_qc: QuorumCertificate2<TYPES>,
772
773    /// certificate that the proposal is chaining from formed by the next epoch nodes
774    pub next_epoch_justify_qc: Option<NextEpochQuorumCertificate2<TYPES>>,
775
776    /// Possible upgrade certificate, which the leader may optionally attach.
777    pub upgrade_certificate: Option<UpgradeCertificate<TYPES>>,
778
779    /// Possible timeout or view sync certificate. If the `justify_qc` is not for a proposal in the immediately preceding view, then either a timeout or view sync certificate must be attached.
780    pub view_change_evidence: Option<ViewChangeEvidence2<TYPES>>,
781
782    /// The DRB result for the next epoch.
783    ///
784    /// This is required only for the last block of the epoch. Nodes will verify that it's
785    /// consistent with the result from their computations.
786    #[serde(with = "serde_bytes")]
787    pub next_drb_result: Option<DrbResult>,
788
789    /// The light client state update certificate for the next epoch.
790    /// This is required for the epoch root.
791    pub state_cert: Option<LightClientStateUpdateCertificate<TYPES>>,
792}
793
794/// Wrapper around a proposal to append a block
795#[derive(derive_more::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
796#[serde(bound(deserialize = ""))]
797pub struct QuorumProposalWrapper<TYPES: NodeType> {
798    /// The wrapped proposal
799    pub proposal: QuorumProposal2<TYPES>,
800}
801
802impl<TYPES: NodeType> QuorumProposal2<TYPES> {
803    /// Validates whether the epoch is consistent with the version and the block number
804    /// # Errors
805    /// Returns an error if the epoch is inconsistent with the version or the block number
806    pub async fn validate_epoch<V: Versions>(
807        &self,
808        upgrade_lock: &UpgradeLock<TYPES, V>,
809        epoch_height: u64,
810    ) -> Result<()> {
811        let calculated_epoch = option_epoch_from_block_number::<TYPES>(
812            upgrade_lock.epochs_enabled(self.view_number()).await,
813            self.block_header.block_number(),
814            epoch_height,
815        );
816        ensure!(
817            calculated_epoch == self.epoch(),
818            "Quorum proposal invalid: inconsistent epoch."
819        );
820        Ok(())
821    }
822}
823
824impl<TYPES: NodeType> QuorumProposalWrapper<TYPES> {
825    /// Helper function to get the proposal's block_header
826    pub fn block_header(&self) -> &TYPES::BlockHeader {
827        &self.proposal.block_header
828    }
829
830    /// Helper function to get the proposal's view_number
831    pub fn view_number(&self) -> TYPES::View {
832        self.proposal.view_number
833    }
834
835    /// Helper function to get the proposal's justify_qc
836    pub fn justify_qc(&self) -> &QuorumCertificate2<TYPES> {
837        &self.proposal.justify_qc
838    }
839
840    /// Helper function to get the proposal's next_epoch_justify_qc
841    pub fn next_epoch_justify_qc(&self) -> &Option<NextEpochQuorumCertificate2<TYPES>> {
842        &self.proposal.next_epoch_justify_qc
843    }
844
845    /// Helper function to get the proposal's upgrade_certificate
846    pub fn upgrade_certificate(&self) -> &Option<UpgradeCertificate<TYPES>> {
847        &self.proposal.upgrade_certificate
848    }
849
850    /// Helper function to get the proposal's view_change_evidence
851    pub fn view_change_evidence(&self) -> &Option<ViewChangeEvidence2<TYPES>> {
852        &self.proposal.view_change_evidence
853    }
854
855    /// Helper function to get the proposal's next_drb_result
856    pub fn next_drb_result(&self) -> &Option<DrbResult> {
857        &self.proposal.next_drb_result
858    }
859
860    /// Validates whether the epoch is consistent with the version and the block number
861    /// # Errors
862    /// Returns an error if the epoch is inconsistent with the version or the block number
863    pub async fn validate_epoch<V: Versions>(
864        &self,
865        upgrade_lock: &UpgradeLock<TYPES, V>,
866        epoch_height: u64,
867    ) -> Result<()> {
868        self.proposal
869            .validate_epoch(upgrade_lock, epoch_height)
870            .await
871    }
872
873    /// Helper function to get the proposal's light client state update certificate
874    pub fn state_cert(&self) -> &Option<LightClientStateUpdateCertificate<TYPES>> {
875        &self.proposal.state_cert
876    }
877}
878
879impl<TYPES: NodeType> From<QuorumProposal<TYPES>> for QuorumProposalWrapper<TYPES> {
880    fn from(quorum_proposal: QuorumProposal<TYPES>) -> Self {
881        Self {
882            proposal: quorum_proposal.into(),
883        }
884    }
885}
886
887impl<TYPES: NodeType> From<QuorumProposal2<TYPES>> for QuorumProposalWrapper<TYPES> {
888    fn from(quorum_proposal2: QuorumProposal2<TYPES>) -> Self {
889        Self {
890            proposal: quorum_proposal2,
891        }
892    }
893}
894
895impl<TYPES: NodeType> From<QuorumProposalWrapper<TYPES>> for QuorumProposal<TYPES> {
896    fn from(quorum_proposal_wrapper: QuorumProposalWrapper<TYPES>) -> Self {
897        quorum_proposal_wrapper.proposal.into()
898    }
899}
900
901impl<TYPES: NodeType> From<QuorumProposalWrapper<TYPES>> for QuorumProposal2<TYPES> {
902    fn from(quorum_proposal_wrapper: QuorumProposalWrapper<TYPES>) -> Self {
903        quorum_proposal_wrapper.proposal
904    }
905}
906
907impl<TYPES: NodeType> From<QuorumProposal<TYPES>> for QuorumProposal2<TYPES> {
908    fn from(quorum_proposal: QuorumProposal<TYPES>) -> Self {
909        Self {
910            block_header: quorum_proposal.block_header,
911            view_number: quorum_proposal.view_number,
912            epoch: None,
913            justify_qc: quorum_proposal.justify_qc.to_qc2(),
914            next_epoch_justify_qc: None,
915            upgrade_certificate: quorum_proposal.upgrade_certificate,
916            view_change_evidence: quorum_proposal
917                .proposal_certificate
918                .map(ViewChangeEvidence::to_evidence2),
919            next_drb_result: None,
920            state_cert: None,
921        }
922    }
923}
924
925impl<TYPES: NodeType> From<QuorumProposal2<TYPES>> for QuorumProposal<TYPES> {
926    fn from(quorum_proposal2: QuorumProposal2<TYPES>) -> Self {
927        Self {
928            block_header: quorum_proposal2.block_header,
929            view_number: quorum_proposal2.view_number,
930            justify_qc: quorum_proposal2.justify_qc.to_qc(),
931            upgrade_certificate: quorum_proposal2.upgrade_certificate,
932            proposal_certificate: quorum_proposal2
933                .view_change_evidence
934                .map(ViewChangeEvidence2::to_evidence),
935        }
936    }
937}
938
939impl<TYPES: NodeType> From<Leaf<TYPES>> for Leaf2<TYPES> {
940    fn from(leaf: Leaf<TYPES>) -> Self {
941        let bytes: [u8; 32] = leaf.parent_commitment.into();
942
943        Self {
944            view_number: leaf.view_number,
945            justify_qc: leaf.justify_qc.to_qc2(),
946            next_epoch_justify_qc: None,
947            parent_commitment: Commitment::from_raw(bytes),
948            block_header: leaf.block_header,
949            upgrade_certificate: leaf.upgrade_certificate,
950            block_payload: leaf.block_payload,
951            view_change_evidence: None,
952            next_drb_result: None,
953            with_epoch: false,
954        }
955    }
956}
957
958impl<TYPES: NodeType> HasViewNumber<TYPES> for DaProposal<TYPES> {
959    fn view_number(&self) -> TYPES::View {
960        self.view_number
961    }
962}
963
964impl<TYPES: NodeType> HasViewNumber<TYPES> for DaProposal2<TYPES> {
965    fn view_number(&self) -> TYPES::View {
966        self.view_number
967    }
968}
969
970impl<TYPES: NodeType> HasViewNumber<TYPES> for QuorumProposal<TYPES> {
971    fn view_number(&self) -> TYPES::View {
972        self.view_number
973    }
974}
975
976impl<TYPES: NodeType> HasViewNumber<TYPES> for QuorumProposal2<TYPES> {
977    fn view_number(&self) -> TYPES::View {
978        self.view_number
979    }
980}
981
982impl<TYPES: NodeType> HasViewNumber<TYPES> for QuorumProposalWrapper<TYPES> {
983    fn view_number(&self) -> TYPES::View {
984        self.proposal.view_number
985    }
986}
987
988impl<TYPES: NodeType> HasViewNumber<TYPES> for UpgradeProposal<TYPES> {
989    fn view_number(&self) -> TYPES::View {
990        self.view_number
991    }
992}
993
994impl_has_epoch!(QuorumProposal2<TYPES>, DaProposal2<TYPES>);
995
996impl_has_none_epoch!(
997    QuorumProposal<TYPES>,
998    DaProposal<TYPES>,
999    UpgradeProposal<TYPES>,
1000    ADVZDisperseShare<TYPES>
1001);
1002
1003impl<TYPES: NodeType> HasEpoch<TYPES> for QuorumProposalWrapper<TYPES> {
1004    /// Return an underlying proposal's epoch
1005    #[allow(clippy::panic)]
1006    fn epoch(&self) -> Option<TYPES::Epoch> {
1007        self.proposal.epoch()
1008    }
1009}
1010
1011/// The error type for block and its transactions.
1012#[derive(Error, Debug, Serialize, Deserialize)]
1013pub enum BlockError {
1014    /// The block header is invalid
1015    #[error("Invalid block header: {0}")]
1016    InvalidBlockHeader(String),
1017
1018    /// The payload commitment does not match the block header's payload commitment
1019    #[error("Inconsistent payload commitment")]
1020    InconsistentPayloadCommitment,
1021
1022    /// The block header apply failed
1023    #[error("Failed to apply block header: {0}")]
1024    FailedHeaderApply(String),
1025}
1026
1027/// Additional functions required to use a [`Leaf`] with hotshot-testing.
1028pub trait TestableLeaf {
1029    /// Type of nodes participating in the network.
1030    type NodeType: NodeType;
1031
1032    /// Create a transaction that can be added to the block contained in this leaf.
1033    fn create_random_transaction(
1034        &self,
1035        rng: &mut dyn rand::RngCore,
1036        padding: u64,
1037    ) -> <<Self::NodeType as NodeType>::BlockPayload as BlockPayload<Self::NodeType>>::Transaction;
1038}
1039
1040/// This is the consensus-internal analogous concept to a block, and it contains the block proper,
1041/// as well as the hash of its parent `Leaf`.
1042/// NOTE: `State` is constrained to implementing `BlockContents`, is `TypeMap::BlockPayload`
1043#[derive(Serialize, Deserialize, Clone, Debug, Eq)]
1044#[serde(bound(deserialize = ""))]
1045pub struct Leaf<TYPES: NodeType> {
1046    /// CurView from leader when proposing leaf
1047    view_number: TYPES::View,
1048
1049    /// Per spec, justification
1050    justify_qc: QuorumCertificate<TYPES>,
1051
1052    /// The hash of the parent `Leaf`
1053    /// So we can ask if it extends
1054    parent_commitment: Commitment<Self>,
1055
1056    /// Block header.
1057    block_header: TYPES::BlockHeader,
1058
1059    /// Optional upgrade certificate, if one was attached to the quorum proposal for this view.
1060    upgrade_certificate: Option<UpgradeCertificate<TYPES>>,
1061
1062    /// Optional block payload.
1063    ///
1064    /// It may be empty for nodes not in the DA committee.
1065    block_payload: Option<TYPES::BlockPayload>,
1066}
1067
1068/// This is the consensus-internal analogous concept to a block, and it contains the block proper,
1069/// as well as the hash of its parent `Leaf`.
1070#[derive(Serialize, Deserialize, Clone, Debug, Eq)]
1071#[serde(bound(deserialize = ""))]
1072pub struct Leaf2<TYPES: NodeType> {
1073    /// CurView from leader when proposing leaf
1074    view_number: TYPES::View,
1075
1076    /// Per spec, justification
1077    justify_qc: QuorumCertificate2<TYPES>,
1078
1079    /// certificate that the proposal is chaining from formed by the next epoch nodes
1080    next_epoch_justify_qc: Option<NextEpochQuorumCertificate2<TYPES>>,
1081
1082    /// The hash of the parent `Leaf`
1083    /// So we can ask if it extends
1084    parent_commitment: Commitment<Self>,
1085
1086    /// Block header.
1087    block_header: TYPES::BlockHeader,
1088
1089    /// Optional upgrade certificate, if one was attached to the quorum proposal for this view.
1090    upgrade_certificate: Option<UpgradeCertificate<TYPES>>,
1091
1092    /// Optional block payload.
1093    ///
1094    /// It may be empty for nodes not in the DA committee.
1095    block_payload: Option<TYPES::BlockPayload>,
1096
1097    /// Possible timeout or view sync certificate. If the `justify_qc` is not for a proposal in the immediately preceding view, then either a timeout or view sync certificate must be attached.
1098    pub view_change_evidence: Option<ViewChangeEvidence2<TYPES>>,
1099
1100    /// The DRB result for the next epoch.
1101    ///
1102    /// This is required only for the last block of the epoch. Nodes will verify that it's
1103    /// consistent with the result from their computations.
1104    #[serde(with = "serde_bytes")]
1105    pub next_drb_result: Option<DrbResult>,
1106
1107    /// Indicates whether or not epochs were enabled.
1108    pub with_epoch: bool,
1109}
1110
1111impl<TYPES: NodeType> Leaf2<TYPES> {
1112    /// Create a new leaf from its components.
1113    ///
1114    /// # Panics
1115    ///
1116    /// Panics if the genesis payload (`TYPES::BlockPayload::genesis()`) is malformed (unable to be
1117    /// interpreted as bytes).
1118    #[must_use]
1119    pub async fn genesis<V: Versions>(
1120        validated_state: &TYPES::ValidatedState,
1121        instance_state: &TYPES::InstanceState,
1122    ) -> Self {
1123        let epoch = genesis_epoch_from_version::<V, TYPES>();
1124
1125        let (payload, metadata) =
1126            TYPES::BlockPayload::from_transactions([], validated_state, instance_state)
1127                .await
1128                .unwrap();
1129
1130        let genesis_view = TYPES::View::genesis();
1131
1132        let block_header =
1133            TYPES::BlockHeader::genesis::<V>(instance_state, payload.clone(), &metadata);
1134
1135        let block_number = if V::Base::VERSION < V::Epochs::VERSION {
1136            None
1137        } else {
1138            Some(0u64)
1139        };
1140
1141        let null_quorum_data = QuorumData2 {
1142            leaf_commit: Commitment::<Leaf2<TYPES>>::default_commitment_no_preimage(),
1143            epoch,
1144            block_number,
1145        };
1146
1147        let justify_qc = QuorumCertificate2::new(
1148            null_quorum_data.clone(),
1149            null_quorum_data.commit(),
1150            genesis_view,
1151            None,
1152            PhantomData,
1153        );
1154
1155        Self {
1156            view_number: genesis_view,
1157            justify_qc,
1158            next_epoch_justify_qc: None,
1159            parent_commitment: null_quorum_data.leaf_commit,
1160            upgrade_certificate: None,
1161            block_header: block_header.clone(),
1162            block_payload: Some(payload),
1163            view_change_evidence: None,
1164            next_drb_result: None,
1165            with_epoch: epoch.is_some(),
1166        }
1167    }
1168    /// Time when this leaf was created.
1169    pub fn view_number(&self) -> TYPES::View {
1170        self.view_number
1171    }
1172    /// Epoch in which this leaf was created.
1173    pub fn epoch(&self, epoch_height: u64) -> Option<TYPES::Epoch> {
1174        option_epoch_from_block_number::<TYPES>(
1175            self.with_epoch,
1176            self.block_header.block_number(),
1177            epoch_height,
1178        )
1179    }
1180    /// Height of this leaf in the chain.
1181    ///
1182    /// Equivalently, this is the number of leaves before this one in the chain.
1183    pub fn height(&self) -> u64 {
1184        self.block_header.block_number()
1185    }
1186    /// The QC linking this leaf to its parent in the chain.
1187    pub fn justify_qc(&self) -> QuorumCertificate2<TYPES> {
1188        self.justify_qc.clone()
1189    }
1190    /// The QC linking this leaf to its parent in the chain.
1191    pub fn upgrade_certificate(&self) -> Option<UpgradeCertificate<TYPES>> {
1192        self.upgrade_certificate.clone()
1193    }
1194    /// Commitment to this leaf's parent.
1195    pub fn parent_commitment(&self) -> Commitment<Self> {
1196        self.parent_commitment
1197    }
1198    /// The block header contained in this leaf.
1199    pub fn block_header(&self) -> &<TYPES as NodeType>::BlockHeader {
1200        &self.block_header
1201    }
1202
1203    /// Get a mutable reference to the block header contained in this leaf.
1204    pub fn block_header_mut(&mut self) -> &mut <TYPES as NodeType>::BlockHeader {
1205        &mut self.block_header
1206    }
1207    /// Fill this leaf with the block payload.
1208    ///
1209    /// # Errors
1210    ///
1211    /// Fails if the payload commitment doesn't match `self.block_header.payload_commitment()`
1212    /// or if the transactions are of invalid length
1213    pub fn fill_block_payload<V: Versions>(
1214        &mut self,
1215        block_payload: TYPES::BlockPayload,
1216        num_storage_nodes: usize,
1217        version: Version,
1218    ) -> std::result::Result<(), BlockError> {
1219        let encoded_txns = block_payload.encode();
1220        let commitment = vid_commitment::<V>(
1221            &encoded_txns,
1222            &self.block_header.metadata().encode(),
1223            num_storage_nodes,
1224            version,
1225        );
1226        if commitment != self.block_header.payload_commitment() {
1227            return Err(BlockError::InconsistentPayloadCommitment);
1228        }
1229        self.block_payload = Some(block_payload);
1230        Ok(())
1231    }
1232
1233    /// Take the block payload from the leaf and return it if it is present
1234    pub fn unfill_block_payload(&mut self) -> Option<TYPES::BlockPayload> {
1235        self.block_payload.take()
1236    }
1237
1238    /// Fill this leaf with the block payload, without checking
1239    /// header and payload consistency
1240    pub fn fill_block_payload_unchecked(&mut self, block_payload: TYPES::BlockPayload) {
1241        self.block_payload = Some(block_payload);
1242    }
1243
1244    /// Optional block payload.
1245    pub fn block_payload(&self) -> Option<TYPES::BlockPayload> {
1246        self.block_payload.clone()
1247    }
1248
1249    /// A commitment to the block payload contained in this leaf.
1250    pub fn payload_commitment(&self) -> VidCommitment {
1251        self.block_header().payload_commitment()
1252    }
1253
1254    /// Validate that a leaf has the right upgrade certificate to be the immediate child of another leaf
1255    ///
1256    /// This may not be a complete function. Please double-check that it performs the checks you expect before substituting validation logic with it.
1257    ///
1258    /// # Errors
1259    /// Returns an error if the certificates are not identical, or that when we no longer see a
1260    /// cert, it's for the right reason.
1261    pub async fn extends_upgrade(
1262        &self,
1263        parent: &Self,
1264        decided_upgrade_certificate: &Arc<RwLock<Option<UpgradeCertificate<TYPES>>>>,
1265    ) -> Result<()> {
1266        match (self.upgrade_certificate(), parent.upgrade_certificate()) {
1267            // Easiest cases are:
1268            //   - no upgrade certificate on either: this is the most common case, and is always fine.
1269            //   - if the parent didn't have a certificate, but we see one now, it just means that we have begun an upgrade: again, this is always fine.
1270            (None | Some(_), None) => {},
1271            // If we no longer see a cert, we have to make sure that we either:
1272            //    - no longer care because we have passed new_version_first_view, or
1273            //    - no longer care because we have passed `decide_by` without deciding the certificate.
1274            (None, Some(parent_cert)) => {
1275                let decided_upgrade_certificate_read = decided_upgrade_certificate.read().await;
1276                ensure!(self.view_number() > parent_cert.data.new_version_first_view
1277                    || (self.view_number() > parent_cert.data.decide_by && decided_upgrade_certificate_read.is_none()),
1278                       "The new leaf is missing an upgrade certificate that was present in its parent, and should still be live."
1279                );
1280            },
1281            // If we both have a certificate, they should be identical.
1282            // Technically, this prevents us from initiating a new upgrade in the view immediately following an upgrade.
1283            // I think this is a fairly lax restriction.
1284            (Some(cert), Some(parent_cert)) => {
1285                ensure!(cert == parent_cert, "The new leaf does not extend the parent leaf, because it has attached a different upgrade certificate.");
1286            },
1287        }
1288
1289        // This check should be added once we sort out the genesis leaf/justify_qc issue.
1290        // ensure!(self.parent_commitment() == parent_leaf.commit(), "The commitment of the parent leaf does not match the specified parent commitment.");
1291
1292        Ok(())
1293    }
1294
1295    /// Converts a `Leaf2` to a `Leaf`. This operation is fundamentally unsafe and should not be used.
1296    pub fn to_leaf_unsafe(self) -> Leaf<TYPES> {
1297        let bytes: [u8; 32] = self.parent_commitment.into();
1298
1299        Leaf {
1300            view_number: self.view_number,
1301            justify_qc: self.justify_qc.to_qc(),
1302            parent_commitment: Commitment::from_raw(bytes),
1303            block_header: self.block_header,
1304            upgrade_certificate: self.upgrade_certificate,
1305            block_payload: self.block_payload,
1306        }
1307    }
1308}
1309
1310impl<TYPES: NodeType> Committable for Leaf2<TYPES> {
1311    fn commit(&self) -> committable::Commitment<Self> {
1312        let Leaf2 {
1313            view_number,
1314            justify_qc,
1315            next_epoch_justify_qc,
1316            parent_commitment,
1317            block_header,
1318            upgrade_certificate,
1319            block_payload: _,
1320            view_change_evidence,
1321            next_drb_result,
1322            with_epoch,
1323        } = self;
1324
1325        let mut cb = RawCommitmentBuilder::new("leaf commitment")
1326            .u64_field("view number", **view_number)
1327            .field("parent leaf commitment", *parent_commitment)
1328            .field("block header", block_header.commit())
1329            .field("justify qc", justify_qc.commit())
1330            .optional("upgrade certificate", upgrade_certificate);
1331
1332        if *with_epoch {
1333            cb = cb
1334                .constant_str("with_epoch")
1335                .optional("next_epoch_justify_qc", next_epoch_justify_qc);
1336
1337            if let Some(next_drb_result) = next_drb_result {
1338                cb = cb
1339                    .constant_str("next_drb_result")
1340                    .fixed_size_bytes(next_drb_result);
1341            }
1342
1343            match view_change_evidence {
1344                Some(ViewChangeEvidence2::Timeout(cert)) => {
1345                    cb = cb.field("timeout cert", cert.commit());
1346                },
1347                Some(ViewChangeEvidence2::ViewSync(cert)) => {
1348                    cb = cb.field("viewsync cert", cert.commit());
1349                },
1350                None => {},
1351            }
1352        }
1353
1354        cb.finalize()
1355    }
1356}
1357
1358impl<TYPES: NodeType> Leaf<TYPES> {
1359    #[allow(clippy::unused_async)]
1360    /// Calculate the leaf commitment,
1361    /// which is gated on the version to include the block header.
1362    pub async fn commit<V: Versions>(
1363        &self,
1364        _upgrade_lock: &UpgradeLock<TYPES, V>,
1365    ) -> Commitment<Self> {
1366        <Self as Committable>::commit(self)
1367    }
1368}
1369
1370impl<TYPES: NodeType> PartialEq for Leaf<TYPES> {
1371    fn eq(&self, other: &Self) -> bool {
1372        self.view_number == other.view_number
1373            && self.justify_qc == other.justify_qc
1374            && self.parent_commitment == other.parent_commitment
1375            && self.block_header == other.block_header
1376    }
1377}
1378
1379impl<TYPES: NodeType> PartialEq for Leaf2<TYPES> {
1380    fn eq(&self, other: &Self) -> bool {
1381        let Leaf2 {
1382            view_number,
1383            justify_qc,
1384            next_epoch_justify_qc,
1385            parent_commitment,
1386            block_header,
1387            upgrade_certificate,
1388            block_payload: _,
1389            view_change_evidence,
1390            next_drb_result,
1391            with_epoch,
1392        } = self;
1393
1394        *view_number == other.view_number
1395            && *justify_qc == other.justify_qc
1396            && *next_epoch_justify_qc == other.next_epoch_justify_qc
1397            && *parent_commitment == other.parent_commitment
1398            && *block_header == other.block_header
1399            && *upgrade_certificate == other.upgrade_certificate
1400            && *view_change_evidence == other.view_change_evidence
1401            && *next_drb_result == other.next_drb_result
1402            && *with_epoch == other.with_epoch
1403    }
1404}
1405
1406impl<TYPES: NodeType> Hash for Leaf<TYPES> {
1407    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1408        self.view_number.hash(state);
1409        self.justify_qc.hash(state);
1410        self.parent_commitment.hash(state);
1411        self.block_header.hash(state);
1412    }
1413}
1414
1415impl<TYPES: NodeType> Hash for Leaf2<TYPES> {
1416    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1417        self.commit().hash(state);
1418        self.view_number.hash(state);
1419        self.justify_qc.hash(state);
1420        self.parent_commitment.hash(state);
1421        self.block_header.hash(state);
1422    }
1423}
1424
1425impl<TYPES: NodeType> Display for Leaf<TYPES> {
1426    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1427        write!(
1428            f,
1429            "view: {:?}, height: {:?}, justify: {}",
1430            self.view_number,
1431            self.height(),
1432            self.justify_qc
1433        )
1434    }
1435}
1436
1437impl<TYPES: NodeType> QuorumCertificate<TYPES> {
1438    #[must_use]
1439    /// Creat the Genesis certificate
1440    pub async fn genesis<V: Versions>(
1441        validated_state: &TYPES::ValidatedState,
1442        instance_state: &TYPES::InstanceState,
1443    ) -> Self {
1444        // since this is genesis, we should never have a decided upgrade certificate.
1445        let upgrade_lock = UpgradeLock::<TYPES, V>::new();
1446
1447        let genesis_view = <TYPES::View as ConsensusTime>::genesis();
1448
1449        let data = QuorumData {
1450            leaf_commit: Leaf::genesis::<V>(validated_state, instance_state)
1451                .await
1452                .commit(&upgrade_lock)
1453                .await,
1454        };
1455
1456        let versioned_data =
1457            VersionedVoteData::<_, _, V>::new_infallible(data.clone(), genesis_view, &upgrade_lock)
1458                .await;
1459
1460        let bytes: [u8; 32] = versioned_data.commit().into();
1461
1462        Self::new(
1463            data,
1464            Commitment::from_raw(bytes),
1465            genesis_view,
1466            None,
1467            PhantomData,
1468        )
1469    }
1470}
1471
1472impl<TYPES: NodeType> QuorumCertificate2<TYPES> {
1473    #[must_use]
1474    /// Create the Genesis certificate
1475    pub async fn genesis<V: Versions>(
1476        validated_state: &TYPES::ValidatedState,
1477        instance_state: &TYPES::InstanceState,
1478    ) -> Self {
1479        // since this is genesis, we should never have a decided upgrade certificate.
1480        let upgrade_lock = UpgradeLock::<TYPES, V>::new();
1481
1482        let genesis_view = <TYPES::View as ConsensusTime>::genesis();
1483
1484        let genesis_leaf = Leaf2::genesis::<V>(validated_state, instance_state).await;
1485        let block_number = if upgrade_lock.epochs_enabled(genesis_view).await {
1486            Some(genesis_leaf.height())
1487        } else {
1488            None
1489        };
1490        let data = QuorumData2 {
1491            leaf_commit: genesis_leaf.commit(),
1492            epoch: genesis_epoch_from_version::<V, TYPES>(), // #3967 make sure this is enough of a gate for epochs
1493            block_number,
1494        };
1495
1496        let versioned_data =
1497            VersionedVoteData::<_, _, V>::new_infallible(data.clone(), genesis_view, &upgrade_lock)
1498                .await;
1499
1500        let bytes: [u8; 32] = versioned_data.commit().into();
1501
1502        Self::new(
1503            data,
1504            Commitment::from_raw(bytes),
1505            genesis_view,
1506            None,
1507            PhantomData,
1508        )
1509    }
1510}
1511
1512impl<TYPES: NodeType> Leaf<TYPES> {
1513    /// Create a new leaf from its components.
1514    ///
1515    /// # Panics
1516    ///
1517    /// Panics if the genesis payload (`TYPES::BlockPayload::genesis()`) is malformed (unable to be
1518    /// interpreted as bytes).
1519    #[must_use]
1520    pub async fn genesis<V: Versions>(
1521        validated_state: &TYPES::ValidatedState,
1522        instance_state: &TYPES::InstanceState,
1523    ) -> Self {
1524        let (payload, metadata) =
1525            TYPES::BlockPayload::from_transactions([], validated_state, instance_state)
1526                .await
1527                .unwrap();
1528
1529        let genesis_view = TYPES::View::genesis();
1530
1531        let block_header =
1532            TYPES::BlockHeader::genesis::<V>(instance_state, payload.clone(), &metadata);
1533
1534        let null_quorum_data = QuorumData {
1535            leaf_commit: Commitment::<Leaf<TYPES>>::default_commitment_no_preimage(),
1536        };
1537
1538        let justify_qc = QuorumCertificate::new(
1539            null_quorum_data.clone(),
1540            null_quorum_data.commit(),
1541            genesis_view,
1542            None,
1543            PhantomData,
1544        );
1545
1546        Self {
1547            view_number: genesis_view,
1548            justify_qc,
1549            parent_commitment: null_quorum_data.leaf_commit,
1550            upgrade_certificate: None,
1551            block_header: block_header.clone(),
1552            block_payload: Some(payload),
1553        }
1554    }
1555
1556    /// Time when this leaf was created.
1557    pub fn view_number(&self) -> TYPES::View {
1558        self.view_number
1559    }
1560    /// Height of this leaf in the chain.
1561    ///
1562    /// Equivalently, this is the number of leaves before this one in the chain.
1563    pub fn height(&self) -> u64 {
1564        self.block_header.block_number()
1565    }
1566    /// The QC linking this leaf to its parent in the chain.
1567    pub fn justify_qc(&self) -> QuorumCertificate<TYPES> {
1568        self.justify_qc.clone()
1569    }
1570    /// The QC linking this leaf to its parent in the chain.
1571    pub fn upgrade_certificate(&self) -> Option<UpgradeCertificate<TYPES>> {
1572        self.upgrade_certificate.clone()
1573    }
1574    /// Commitment to this leaf's parent.
1575    pub fn parent_commitment(&self) -> Commitment<Self> {
1576        self.parent_commitment
1577    }
1578    /// The block header contained in this leaf.
1579    pub fn block_header(&self) -> &<TYPES as NodeType>::BlockHeader {
1580        &self.block_header
1581    }
1582
1583    /// Get a mutable reference to the block header contained in this leaf.
1584    pub fn block_header_mut(&mut self) -> &mut <TYPES as NodeType>::BlockHeader {
1585        &mut self.block_header
1586    }
1587    /// Fill this leaf with the block payload.
1588    ///
1589    /// # Errors
1590    ///
1591    /// Fails if the payload commitment doesn't match `self.block_header.payload_commitment()`
1592    /// or if the transactions are of invalid length
1593    pub fn fill_block_payload<V: Versions>(
1594        &mut self,
1595        block_payload: TYPES::BlockPayload,
1596        num_storage_nodes: usize,
1597        version: Version,
1598    ) -> std::result::Result<(), BlockError> {
1599        let encoded_txns = block_payload.encode();
1600        let commitment = vid_commitment::<V>(
1601            &encoded_txns,
1602            &self.block_header.metadata().encode(),
1603            num_storage_nodes,
1604            version,
1605        );
1606        if commitment != self.block_header.payload_commitment() {
1607            return Err(BlockError::InconsistentPayloadCommitment);
1608        }
1609        self.block_payload = Some(block_payload);
1610        Ok(())
1611    }
1612
1613    /// Take the block payload from the leaf and return it if it is present
1614    pub fn unfill_block_payload(&mut self) -> Option<TYPES::BlockPayload> {
1615        self.block_payload.take()
1616    }
1617
1618    /// Fill this leaf with the block payload, without checking
1619    /// header and payload consistency
1620    pub fn fill_block_payload_unchecked(&mut self, block_payload: TYPES::BlockPayload) {
1621        self.block_payload = Some(block_payload);
1622    }
1623
1624    /// Optional block payload.
1625    pub fn block_payload(&self) -> Option<TYPES::BlockPayload> {
1626        self.block_payload.clone()
1627    }
1628
1629    /// A commitment to the block payload contained in this leaf.
1630    pub fn payload_commitment(&self) -> VidCommitment {
1631        self.block_header().payload_commitment()
1632    }
1633
1634    /// Validate that a leaf has the right upgrade certificate to be the immediate child of another leaf
1635    ///
1636    /// This may not be a complete function. Please double-check that it performs the checks you expect before substituting validation logic with it.
1637    ///
1638    /// # Errors
1639    /// Returns an error if the certificates are not identical, or that when we no longer see a
1640    /// cert, it's for the right reason.
1641    pub async fn extends_upgrade(
1642        &self,
1643        parent: &Self,
1644        decided_upgrade_certificate: &Arc<RwLock<Option<UpgradeCertificate<TYPES>>>>,
1645    ) -> Result<()> {
1646        match (self.upgrade_certificate(), parent.upgrade_certificate()) {
1647            // Easiest cases are:
1648            //   - no upgrade certificate on either: this is the most common case, and is always fine.
1649            //   - if the parent didn't have a certificate, but we see one now, it just means that we have begun an upgrade: again, this is always fine.
1650            (None | Some(_), None) => {},
1651            // If we no longer see a cert, we have to make sure that we either:
1652            //    - no longer care because we have passed new_version_first_view, or
1653            //    - no longer care because we have passed `decide_by` without deciding the certificate.
1654            (None, Some(parent_cert)) => {
1655                let decided_upgrade_certificate_read = decided_upgrade_certificate.read().await;
1656                ensure!(self.view_number() > parent_cert.data.new_version_first_view
1657                    || (self.view_number() > parent_cert.data.decide_by && decided_upgrade_certificate_read.is_none()),
1658                       "The new leaf is missing an upgrade certificate that was present in its parent, and should still be live."
1659                );
1660            },
1661            // If we both have a certificate, they should be identical.
1662            // Technically, this prevents us from initiating a new upgrade in the view immediately following an upgrade.
1663            // I think this is a fairly lax restriction.
1664            (Some(cert), Some(parent_cert)) => {
1665                ensure!(cert == parent_cert, "The new leaf does not extend the parent leaf, because it has attached a different upgrade certificate.");
1666            },
1667        }
1668
1669        // This check should be added once we sort out the genesis leaf/justify_qc issue.
1670        // ensure!(self.parent_commitment() == parent_leaf.commit(), "The commitment of the parent leaf does not match the specified parent commitment.");
1671
1672        Ok(())
1673    }
1674}
1675
1676impl<TYPES: NodeType> TestableLeaf for Leaf<TYPES>
1677where
1678    TYPES::ValidatedState: TestableState<TYPES>,
1679    TYPES::BlockPayload: TestableBlock<TYPES>,
1680{
1681    type NodeType = TYPES;
1682
1683    fn create_random_transaction(
1684        &self,
1685        rng: &mut dyn rand::RngCore,
1686        padding: u64,
1687    ) -> <<Self::NodeType as NodeType>::BlockPayload as BlockPayload<Self::NodeType>>::Transaction
1688    {
1689        TYPES::ValidatedState::create_random_transaction(None, rng, padding)
1690    }
1691}
1692impl<TYPES: NodeType> TestableLeaf for Leaf2<TYPES>
1693where
1694    TYPES::ValidatedState: TestableState<TYPES>,
1695    TYPES::BlockPayload: TestableBlock<TYPES>,
1696{
1697    type NodeType = TYPES;
1698
1699    fn create_random_transaction(
1700        &self,
1701        rng: &mut dyn rand::RngCore,
1702        padding: u64,
1703    ) -> <<Self::NodeType as NodeType>::BlockPayload as BlockPayload<Self::NodeType>>::Transaction
1704    {
1705        TYPES::ValidatedState::create_random_transaction(None, rng, padding)
1706    }
1707}
1708/// Fake the thing a genesis block points to. Needed to avoid infinite recursion
1709#[must_use]
1710pub fn fake_commitment<S: Committable>() -> Commitment<S> {
1711    RawCommitmentBuilder::new("Dummy commitment for arbitrary genesis").finalize()
1712}
1713
1714/// create a random commitment
1715#[must_use]
1716pub fn random_commitment<S: Committable>(rng: &mut dyn rand::RngCore) -> Commitment<S> {
1717    let random_array: Vec<u8> = (0u8..100u8).map(|_| rng.gen_range(0..255)).collect();
1718    RawCommitmentBuilder::new("Random Commitment")
1719        .constant_str("Random Field")
1720        .var_size_bytes(&random_array)
1721        .finalize()
1722}
1723
1724/// Serialization for the QC assembled signature
1725/// # Panics
1726/// if serialization fails
1727pub fn serialize_signature2<TYPES: NodeType>(
1728    signatures: &<TYPES::SignatureKey as SignatureKey>::QcType,
1729) -> Vec<u8> {
1730    let mut signatures_bytes = vec![];
1731    signatures_bytes.extend("Yes".as_bytes());
1732
1733    let (sig, proof) = TYPES::SignatureKey::sig_proof(signatures);
1734    let proof_bytes = bincode_opts()
1735        .serialize(&proof.as_bitslice())
1736        .expect("This serialization shouldn't be able to fail");
1737    signatures_bytes.extend("bitvec proof".as_bytes());
1738    signatures_bytes.extend(proof_bytes.as_slice());
1739    let sig_bytes = bincode_opts()
1740        .serialize(&sig)
1741        .expect("This serialization shouldn't be able to fail");
1742    signatures_bytes.extend("aggregated signature".as_bytes());
1743    signatures_bytes.extend(sig_bytes.as_slice());
1744    signatures_bytes
1745}
1746
1747impl<TYPES: NodeType> Committable for Leaf<TYPES> {
1748    fn commit(&self) -> committable::Commitment<Self> {
1749        RawCommitmentBuilder::new("leaf commitment")
1750            .u64_field("view number", *self.view_number)
1751            .field("parent leaf commitment", self.parent_commitment)
1752            .field("block header", self.block_header.commit())
1753            .field("justify qc", self.justify_qc.commit())
1754            .optional("upgrade certificate", &self.upgrade_certificate)
1755            .finalize()
1756    }
1757}
1758
1759impl<TYPES: NodeType> Leaf2<TYPES> {
1760    /// Constructs a leaf from a given quorum proposal.
1761    pub fn from_quorum_proposal(quorum_proposal: &QuorumProposalWrapper<TYPES>) -> Self {
1762        // WARNING: Do NOT change this to a wildcard match, or reference the fields directly in the construction of the leaf.
1763        // The point of this match is that we will get a compile-time error if we add a field without updating this.
1764        let QuorumProposalWrapper {
1765            proposal:
1766                QuorumProposal2 {
1767                    view_number,
1768                    epoch,
1769                    justify_qc,
1770                    next_epoch_justify_qc,
1771                    block_header,
1772                    upgrade_certificate,
1773                    view_change_evidence,
1774                    next_drb_result,
1775                    state_cert: _,
1776                },
1777        } = quorum_proposal;
1778
1779        Self {
1780            view_number: *view_number,
1781            justify_qc: justify_qc.clone(),
1782            next_epoch_justify_qc: next_epoch_justify_qc.clone(),
1783            parent_commitment: justify_qc.data().leaf_commit,
1784            block_header: block_header.clone(),
1785            upgrade_certificate: upgrade_certificate.clone(),
1786            block_payload: None,
1787            view_change_evidence: view_change_evidence.clone(),
1788            next_drb_result: *next_drb_result,
1789            with_epoch: epoch.is_some(),
1790        }
1791    }
1792}
1793
1794impl<TYPES: NodeType> Leaf<TYPES> {
1795    /// Constructs a leaf from a given quorum proposal.
1796    pub fn from_quorum_proposal(quorum_proposal: &QuorumProposal<TYPES>) -> Self {
1797        // WARNING: Do NOT change this to a wildcard match, or reference the fields directly in the construction of the leaf.
1798        // The point of this match is that we will get a compile-time error if we add a field without updating this.
1799        let QuorumProposal {
1800            view_number,
1801            justify_qc,
1802            block_header,
1803            upgrade_certificate,
1804            proposal_certificate: _,
1805        } = quorum_proposal;
1806
1807        Self {
1808            view_number: *view_number,
1809            justify_qc: justify_qc.clone(),
1810            parent_commitment: justify_qc.data().leaf_commit,
1811            block_header: block_header.clone(),
1812            upgrade_certificate: upgrade_certificate.clone(),
1813            block_payload: None,
1814        }
1815    }
1816}
1817
1818pub mod null_block {
1819    #![allow(missing_docs)]
1820
1821    use jf_vid::VidScheme;
1822    use vbs::version::StaticVersionType;
1823
1824    use crate::{
1825        data::VidCommitment,
1826        traits::{
1827            block_contents::BuilderFee,
1828            node_implementation::{NodeType, Versions},
1829            signature_key::BuilderSignatureKey,
1830            BlockPayload,
1831        },
1832        vid::advz::advz_scheme,
1833    };
1834
1835    /// The commitment for a null block payload.
1836    ///
1837    /// Note: the commitment depends on the network (via `num_storage_nodes`),
1838    /// and may change (albeit rarely) during execution.
1839    ///
1840    /// We memoize the result to avoid having to recalculate it.
1841    // TODO(Chengyu): fix it. Empty commitment must be computed at every upgrade.
1842    // #[memoize(SharedCache, Capacity: 10)]
1843    #[must_use]
1844    pub fn commitment<V: Versions>(num_storage_nodes: usize) -> Option<VidCommitment> {
1845        let vid_result = advz_scheme(num_storage_nodes).commit_only(Vec::new());
1846
1847        match vid_result {
1848            Ok(r) => Some(VidCommitment::V0(r)),
1849            Err(_) => None,
1850        }
1851    }
1852
1853    /// Builder fee data for a null block payload
1854    #[must_use]
1855    pub fn builder_fee<TYPES: NodeType, V: Versions>(
1856        num_storage_nodes: usize,
1857        version: vbs::version::Version,
1858    ) -> Option<BuilderFee<TYPES>> {
1859        /// Arbitrary fee amount, this block doesn't actually come from a builder
1860        const FEE_AMOUNT: u64 = 0;
1861
1862        let (pub_key, priv_key) =
1863            <TYPES::BuilderSignatureKey as BuilderSignatureKey>::generated_from_seed_indexed(
1864                [0_u8; 32], 0,
1865            );
1866
1867        if version >= V::Epochs::VERSION {
1868            let (_null_block, null_block_metadata) =
1869                <TYPES::BlockPayload as BlockPayload<TYPES>>::empty();
1870
1871            match TYPES::BuilderSignatureKey::sign_fee(&priv_key, FEE_AMOUNT, &null_block_metadata)
1872            {
1873                Ok(sig) => Some(BuilderFee {
1874                    fee_amount: FEE_AMOUNT,
1875                    fee_account: pub_key,
1876                    fee_signature: sig,
1877                }),
1878                Err(_) => None,
1879            }
1880        } else {
1881            let (_null_block, null_block_metadata) =
1882                <TYPES::BlockPayload as BlockPayload<TYPES>>::empty();
1883
1884            match TYPES::BuilderSignatureKey::sign_fee_with_vid_commitment(
1885                &priv_key,
1886                FEE_AMOUNT,
1887                &null_block_metadata,
1888                &commitment::<V>(num_storage_nodes)?,
1889            ) {
1890                Ok(sig) => Some(BuilderFee {
1891                    fee_amount: FEE_AMOUNT,
1892                    fee_account: pub_key,
1893                    fee_signature: sig,
1894                }),
1895                Err(_) => None,
1896            }
1897        }
1898    }
1899}
1900
1901/// A packed bundle constructed from a sequence of bundles.
1902#[derive(Debug, Eq, PartialEq, Clone)]
1903pub struct PackedBundle<TYPES: NodeType> {
1904    /// The combined transactions as bytes.
1905    pub encoded_transactions: Arc<[u8]>,
1906
1907    /// The metadata of the block.
1908    pub metadata: <TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
1909
1910    /// The view number that this block is associated with.
1911    pub view_number: TYPES::View,
1912
1913    /// The view number that this block is associated with.
1914    pub epoch_number: Option<TYPES::Epoch>,
1915
1916    /// The sequencing fee for submitting bundles.
1917    pub sequencing_fees: Vec1<BuilderFee<TYPES>>,
1918}
1919
1920impl<TYPES: NodeType> PackedBundle<TYPES> {
1921    /// Create a new [`PackedBundle`].
1922    pub fn new(
1923        encoded_transactions: Arc<[u8]>,
1924        metadata: <TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
1925        view_number: TYPES::View,
1926        epoch_number: Option<TYPES::Epoch>,
1927        sequencing_fees: Vec1<BuilderFee<TYPES>>,
1928    ) -> Self {
1929        Self {
1930            encoded_transactions,
1931            metadata,
1932            view_number,
1933            epoch_number,
1934            sequencing_fees,
1935        }
1936    }
1937}
1938
1939#[cfg(test)]
1940mod test {
1941    use super::*;
1942
1943    #[test]
1944    fn test_vid_commitment_display() {
1945        let vc = VidCommitment::V0(ADVZCommitment::default());
1946        assert_eq!(
1947            format!("{vc}"),
1948            "HASH~AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI"
1949        );
1950        assert_eq!(
1951            format!("{vc:?}"),
1952            "HASH~AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI"
1953        );
1954
1955        let vc = VidCommitment::V1(AvidMCommitment {
1956            commit: Default::default(),
1957        });
1958        assert_eq!(
1959            format!("{vc}"),
1960            "AvidMCommit~AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADr"
1961        );
1962        assert_eq!(
1963            format!("{vc:?}"),
1964            "AvidMCommit~AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADr"
1965        );
1966    }
1967}