Skip to main content

solana_entry/
block_component.rs

1/// Block components using wincode serialization.
2///
3/// A `BlockComponent` represents either an entry batch or a special block marker.
4/// Most of the time, a block component contains a vector of entries. However, periodically,
5/// there are special messages that a block needs to contain. To accommodate these special
6/// messages, `BlockComponent` allows for the inclusion of special data via `VersionedBlockMarker`.
7///
8/// ## Serialization Layouts
9///
10/// All numeric fields use little-endian encoding.
11///
12/// ### BlockComponent with EntryBatch
13/// ```text
14/// ┌─────────────────────────────────────────┐
15/// │ Entry Count                  (8 bytes)  │
16/// ├─────────────────────────────────────────┤
17/// │ bincode Entry 0           (variable)    │
18/// ├─────────────────────────────────────────┤
19/// │ bincode Entry 1           (variable)    │
20/// ├─────────────────────────────────────────┤
21/// │ ...                                     │
22/// ├─────────────────────────────────────────┤
23/// │ bincode Entry N-1         (variable)    │
24/// └─────────────────────────────────────────┘
25/// ```
26///
27/// ### BlockComponent with BlockMarker
28/// ```text
29/// ┌─────────────────────────────────────────┐
30/// │ Entry Count = 0              (8 bytes)  │
31/// ├─────────────────────────────────────────┤
32/// │ Marker Version               (2 bytes)  │
33/// ├─────────────────────────────────────────┤
34/// │ Marker Data               (variable)    │
35/// └─────────────────────────────────────────┘
36/// ```
37///
38/// ### BlockMarkerV1 Layout
39/// ```text
40/// ┌─────────────────────────────────────────┐
41/// │ Variant ID                   (1 byte)   │
42/// ├─────────────────────────────────────────┤
43/// │ Byte Length                  (2 bytes)  │
44/// ├─────────────────────────────────────────┤
45/// │ Variant Data              (variable)    │
46/// └─────────────────────────────────────────┘
47/// ```
48///
49/// ### BlockHeaderV1 Layout
50/// ```text
51/// ┌─────────────────────────────────────────┐
52/// │ Parent Slot                  (8 bytes)  │
53/// ├─────────────────────────────────────────┤
54/// │ Parent Block ID             (32 bytes)  │
55/// └─────────────────────────────────────────┘
56/// ```
57///
58/// ### UpdateParentV1 Layout
59/// ```text
60/// ┌─────────────────────────────────────────┐
61/// │ Parent Slot                  (8 bytes)  │
62/// ├─────────────────────────────────────────┤
63/// │ Parent Block ID             (32 bytes)  │
64/// └─────────────────────────────────────────┘
65/// ```
66///
67/// ### BlockFooterV1 Layout
68/// ```text
69/// ┌──────────────────────────────────────────────┐
70/// │ Bank Hash                        (32 bytes)  │
71/// ├──────────────────────────────────────────────┤
72/// │ Producer Time Nanos               (8 bytes)  │
73/// ├──────────────────────────────────────────────┤
74/// │ User Agent Length                 (1 byte)   │
75/// ├──────────────────────────────────────────────┤
76/// │ User Agent Bytes               (0-255 bytes) │
77/// ├──────────────────────────────────────────────┤
78/// │ Block Final Cert Present          (1 byte)   │
79/// ├──────────────────────────────────────────────┤
80/// │ BlockFinalizationCert (if present, variable) │
81/// ├──────────────────────────────────────────────┤
82/// │ Skip reward cert Present     (1 byte)        │
83/// ├──────────────────────────────────────────────┤
84/// │ SkipRewardCert (if present, variable)        │
85/// ├──────────────────────────────────────────────┤
86/// │ Notar reward cert Present    (1 byte)        │
87/// ├──────────────────────────────────────────────┤
88/// │ NotarRewardCert (if present, variable)       │
89/// └──────────────────────────────────────────────┘
90/// ```
91///
92/// ### BlockFinalizationCert Layout
93/// ```text
94/// ┌─────────────────────────────────────────┐
95/// │ Slot                         (8 bytes)  │
96/// ├─────────────────────────────────────────┤
97/// │ Block ID                    (32 bytes)  │
98/// ├─────────────────────────────────────────┤
99/// │ Final Aggregate (VotesAggregate)        │
100/// ├─────────────────────────────────────────┤
101/// │ Notar Aggregate Present      (1 byte)   │
102/// ├─────────────────────────────────────────┤
103/// │ Notar Aggregate (if present)            │
104/// └─────────────────────────────────────────┘
105/// ```
106///
107/// ### VotesAggregate Layout
108/// ```text
109/// ┌─────────────────────────────────────────┐
110/// │ BLS Signature Compressed    (96 bytes)  │
111/// ├─────────────────────────────────────────┤
112/// │ Bitmap Length                (2 bytes)  │
113/// ├─────────────────────────────────────────┤
114/// │ Bitmap                    (variable)    │
115/// └─────────────────────────────────────────┘
116/// ```
117///
118/// ### GenesisCertificate Layout
119/// ```text
120/// ┌─────────────────────────────────────────┐
121/// │ Genesis Slot                  (8 bytes) │
122/// ├─────────────────────────────────────────┤
123/// │ Genesis Block ID             (32 bytes) │
124/// ├─────────────────────────────────────────┤
125/// │ BLS Signature               (192 bytes) │
126/// ├─────────────────────────────────────────┤
127/// │ Bitmap length (max 512)       (8 bytes) │
128/// ├─────────────────────────────────────────┤
129/// │ Bitmap                (up to 512 bytes) │
130/// └─────────────────────────────────────────┘
131/// ```
132use {
133    crate::entry::{Entry, EntryView, MaxDataShredsLen},
134    agave_votor_messages::{
135        certificate::{CertSignature, CertificateType, GenesisCert},
136        consensus_message::Block,
137        reward_certificate::{NotarRewardCertificate, SkipRewardCertificate},
138        unverified_vote_message::UnverifiedCertificate,
139    },
140    bytes::Bytes,
141    solana_bls_signatures::{
142        BlsError, Signature as BLSSignature, SignatureCompressed as BLSSignatureCompressed,
143        signature::AsSignatureAffine,
144    },
145    solana_clock::Slot,
146    solana_hash::Hash,
147    solana_perf::packet::packet_config,
148    std::mem::MaybeUninit,
149    wincode::{
150        ReadResult, SchemaRead, SchemaWrite, TypeMeta, WriteResult,
151        config::{Config, ConfigCore, DefaultConfig},
152        containers::Vec as WincodeVec,
153        error::write_length_encoding_overflow,
154        io::{Reader, Writer},
155        len::{BincodeLen, FixIntLen},
156        pod_wrapper,
157    },
158};
159
160pod_wrapper! {
161    // Use `BLSSignature` directly once `BLSSignature` wincode support
162    // is released in solana-sdk.
163    unsafe struct PodBLSSignature(BLSSignature);
164    // Use `BLSSignatureCompressed` directly once `BLSSignature` wincode support
165    // is released in solana-sdk.
166    unsafe struct PodBLSSignatureCompressed(BLSSignatureCompressed);
167}
168
169/// Wraps a value with a u16 length prefix for TLV-style serialization.
170///
171/// The length prefix represents the serialized byte size of the inner value.
172#[derive(Debug, Clone, PartialEq, Eq, SchemaWrite)]
173pub struct LengthPrefixed<T> {
174    len: u16,
175    inner: T,
176}
177
178unsafe impl<'de, T, C: ConfigCore> SchemaRead<'de, C> for LengthPrefixed<T>
179where
180    T: SchemaRead<'de, C, Dst = T> + SchemaWrite<C, Src = T>,
181{
182    type Dst = Self;
183
184    const TYPE_META: TypeMeta = TypeMeta::join_types([
185        <u16 as SchemaRead<'de, C>>::TYPE_META,
186        <T as SchemaRead<'de, C>>::TYPE_META,
187    ])
188    .keep_zero_copy(false);
189
190    fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
191        let len = <u16 as SchemaRead<'de, C>>::get(reader.by_ref())?;
192        let inner = T::get(reader)?;
193        let inner_size = T::size_of(&inner)
194            .map_err(|_| wincode::ReadError::Custom("LengthPrefixed: inner size_of overflow"))?;
195
196        if inner_size != usize::from(len) {
197            return Err(wincode::ReadError::Custom(
198                "LengthPrefixed: inner serialized size does not match length prefix",
199            ));
200        }
201
202        dst.write(Self { len, inner });
203        Ok(())
204    }
205}
206
207impl<T> LengthPrefixed<T>
208where
209    T: SchemaWrite<DefaultConfig, Src = T>,
210{
211    pub fn new(inner: T) -> Self {
212        let inner_size = T::size_of(&inner).unwrap();
213        let len = inner_size
214            .try_into()
215            .map_err(|_| write_length_encoding_overflow("u16::MAX"))
216            .unwrap();
217        Self { len, inner }
218    }
219}
220
221impl<T> LengthPrefixed<T> {
222    pub fn inner(&self) -> &T {
223        &self.inner
224    }
225
226    pub fn into_inner(self) -> T {
227        self.inner
228    }
229}
230
231#[derive(Debug, thiserror::Error)]
232pub enum BlockComponentError {
233    #[error("Entry count {count} exceeds max {max}")]
234    TooManyEntries { count: usize, max: usize },
235    #[error("Entry batch cannot be empty")]
236    EmptyEntryBatch,
237}
238
239/// Block production metadata. User agent is capped at 255 bytes.
240#[derive(Clone, PartialEq, Eq, Debug, SchemaWrite, SchemaRead)]
241pub struct BlockFooterV1 {
242    pub bank_hash: Hash,
243    pub block_producer_time_nanos: u64,
244    #[wincode(with = "WincodeVec<u8, FixIntLen<u8>>")]
245    pub block_user_agent: Vec<u8>,
246    pub block_final_cert: Option<BlockFinalizationCert>,
247    pub skip_reward_cert: Option<SkipRewardCertificate>,
248    pub notar_reward_cert: Option<NotarRewardCertificate>,
249}
250
251#[derive(Clone, PartialEq, Eq, Debug, SchemaWrite, SchemaRead)]
252pub struct BlockHeaderV1 {
253    pub parent_slot: Slot,
254    pub parent_block_id: Hash,
255}
256
257#[derive(Clone, PartialEq, Eq, Debug, SchemaWrite, SchemaRead)]
258pub struct UpdateParentV1 {
259    pub new_parent_slot: Slot,
260    pub new_parent_block_id: Hash,
261}
262
263/// Attests to genesis block finalization with a BLS aggregate signature.
264#[derive(Clone, PartialEq, Eq, Debug, SchemaWrite, SchemaRead)]
265pub struct GenesisCertBlockMarker {
266    pub slot: Slot,
267    pub block_id: Hash,
268    #[wincode(with = "PodBLSSignature")]
269    pub bls_signature: BLSSignature,
270    #[wincode(with = "WincodeVec<u8, BincodeLen>")]
271    pub bitmap: Vec<u8>,
272}
273
274impl GenesisCertBlockMarker {
275    /// Max bitmap size in bytes (supports up to 4096 validators).
276    pub const MAX_BITMAP_SIZE: usize = 512;
277}
278
279impl TryFrom<GenesisCert> for GenesisCertBlockMarker {
280    type Error = String;
281
282    fn try_from(cert: GenesisCert) -> Result<Self, Self::Error> {
283        if cert.signature.bitmap.len() > Self::MAX_BITMAP_SIZE {
284            return Err(format!(
285                "bitmap size {} exceeds max {}",
286                cert.signature.bitmap.len(),
287                Self::MAX_BITMAP_SIZE
288            ));
289        }
290        Ok(Self {
291            slot: cert.block.slot,
292            block_id: cert.block.block_id,
293            bls_signature: cert.signature.signature,
294            bitmap: cert.signature.bitmap,
295        })
296    }
297}
298
299#[derive(Clone, PartialEq, Eq, Debug, SchemaWrite, SchemaRead)]
300pub struct BlockFinalizationCert {
301    pub slot: Slot,
302    pub block_id: Hash,
303    pub final_aggregate: VotesAggregate,
304    pub notar_aggregate: Option<VotesAggregate>,
305}
306
307impl BlockFinalizationCert {
308    #[cfg(feature = "dev-context-only-utils")]
309    pub fn new_for_tests() -> BlockFinalizationCert {
310        BlockFinalizationCert {
311            slot: 1234567890,
312            block_id: Hash::new_from_array([1u8; 32]),
313            final_aggregate: VotesAggregate {
314                signature: BLSSignatureCompressed(
315                    [0; solana_bls_signatures::BLS_SIGNATURE_COMPRESSED_SIZE],
316                ),
317                bitmap: vec![42; 64],
318            },
319            notar_aggregate: None,
320        }
321    }
322}
323
324#[derive(Clone, PartialEq, Eq, Debug, SchemaRead, SchemaWrite)]
325pub struct VotesAggregate {
326    #[wincode(with = "PodBLSSignatureCompressed")]
327    signature: BLSSignatureCompressed,
328    #[wincode(with = "WincodeVec<u8, FixIntLen<u16>>")]
329    bitmap: Vec<u8>,
330}
331
332impl VotesAggregate {
333    /// Creates a new `VotesAggregate` from a compressed signature and bitmap.
334    #[cfg(feature = "dev-context-only-utils")]
335    pub fn new(signature: BLSSignatureCompressed, bitmap: Vec<u8>) -> Self {
336        Self { signature, bitmap }
337    }
338
339    /// Returns a reference to the compressed signature.
340    pub fn signature(&self) -> &BLSSignatureCompressed {
341        &self.signature
342    }
343
344    /// Returns a reference to the bitmap.
345    pub fn bitmap(&self) -> &[u8] {
346        &self.bitmap
347    }
348
349    /// Returns every field of the aggregate. Callers that must not silently
350    /// ignore new fields (e.g. the geyser boundary conversion) bind this
351    /// tuple exhaustively, so growing it breaks them at compile time.
352    pub fn as_parts(&self) -> (&BLSSignatureCompressed, &[u8]) {
353        let Self { signature, bitmap } = self;
354        (signature, bitmap)
355    }
356
357    /// Creates a VotesAggregate from a Certificate's signature and bitmap.
358    ///
359    /// # Panics
360    /// Panics if the signature cannot be converted to compressed format.
361    /// This should never happen for valid certificates from the consensus pool.
362    pub fn from_cert_signature(signature: CertSignature) -> Self {
363        Self {
364            signature: BLSSignatureCompressed::try_from(&signature.signature)
365                .expect("valid certificate signature should convert to compressed format"),
366            bitmap: signature.bitmap,
367        }
368    }
369
370    /// Uncompresses the signature.
371    pub fn uncompress_signature(&self) -> Result<BLSSignature, BlsError> {
372        Ok(BLSSignature::from(self.signature.try_as_affine()?))
373    }
374
375    /// Consumes self and returns the bitmap.
376    pub fn into_bitmap(self) -> Vec<u8> {
377        self.bitmap
378    }
379}
380
381#[derive(Debug, Clone, PartialEq, Eq, SchemaWrite, SchemaRead)]
382#[wincode(tag_encoding = "u8")]
383pub enum VersionedBlockFooter {
384    #[wincode(tag = 1)]
385    V1(BlockFooterV1),
386}
387
388#[derive(Debug, Clone, PartialEq, Eq, SchemaWrite, SchemaRead)]
389#[wincode(tag_encoding = "u8")]
390pub enum VersionedBlockHeader {
391    #[wincode(tag = 1)]
392    V1(BlockHeaderV1),
393}
394
395#[derive(Debug, Clone, PartialEq, Eq, SchemaWrite, SchemaRead)]
396#[wincode(tag_encoding = "u8")]
397pub enum VersionedUpdateParent {
398    #[wincode(tag = 1)]
399    V1(UpdateParentV1),
400}
401
402/// TLV-encoded marker variants.
403#[allow(clippy::large_enum_variant)]
404#[derive(Debug, Clone, PartialEq, Eq, SchemaWrite, SchemaRead)]
405#[wincode(tag_encoding = "u8")]
406pub enum BlockMarkerV1 {
407    BlockFooter(LengthPrefixed<VersionedBlockFooter>),
408    BlockHeader(LengthPrefixed<VersionedBlockHeader>),
409    UpdateParent(LengthPrefixed<VersionedUpdateParent>),
410    GenesisCertificate(LengthPrefixed<GenesisCertBlockMarker>),
411}
412
413impl BlockMarkerV1 {
414    pub fn new_block_footer(f: VersionedBlockFooter) -> Self {
415        Self::BlockFooter(LengthPrefixed::new(f))
416    }
417
418    pub fn new_block_header(h: VersionedBlockHeader) -> Self {
419        Self::BlockHeader(LengthPrefixed::new(h))
420    }
421
422    pub fn new_update_parent(u: VersionedUpdateParent) -> Self {
423        Self::UpdateParent(LengthPrefixed::new(u))
424    }
425
426    pub fn new_genesis_certificate(c: GenesisCertBlockMarker) -> Self {
427        Self::GenesisCertificate(LengthPrefixed::new(c))
428    }
429
430    pub fn as_block_footer(&self) -> Option<&VersionedBlockFooter> {
431        match self {
432            Self::BlockFooter(lp) => Some(lp.inner()),
433            _ => None,
434        }
435    }
436
437    pub fn as_block_header(&self) -> Option<&VersionedBlockHeader> {
438        match self {
439            Self::BlockHeader(lp) => Some(lp.inner()),
440            _ => None,
441        }
442    }
443
444    pub fn as_update_parent(&self) -> Option<&VersionedUpdateParent> {
445        match self {
446            Self::UpdateParent(lp) => Some(lp.inner()),
447            _ => None,
448        }
449    }
450
451    pub fn as_genesis_certificate(&self) -> Option<&GenesisCertBlockMarker> {
452        match self {
453            Self::GenesisCertificate(lp) => Some(lp.inner()),
454            _ => None,
455        }
456    }
457}
458
459#[derive(Debug, Clone, PartialEq, Eq, SchemaWrite, SchemaRead)]
460#[wincode(tag_encoding = "u16")]
461pub enum VersionedBlockMarker {
462    #[wincode(tag = 1)]
463    V1(BlockMarkerV1),
464}
465
466impl VersionedBlockMarker {
467    pub const fn new(marker: BlockMarkerV1) -> Self {
468        Self::V1(marker)
469    }
470
471    pub fn from_block_footer(f: BlockFooterV1) -> Self {
472        let f = VersionedBlockFooter::V1(f);
473        let f = BlockMarkerV1::BlockFooter(LengthPrefixed::new(f));
474        Self::new(f)
475    }
476
477    pub fn from_block_header(h: BlockHeaderV1) -> Self {
478        let h = VersionedBlockHeader::V1(h);
479        let h = BlockMarkerV1::BlockHeader(LengthPrefixed::new(h));
480        Self::new(h)
481    }
482
483    pub fn from_update_parent(u: UpdateParentV1) -> Self {
484        let u = VersionedUpdateParent::V1(u);
485        let u = BlockMarkerV1::UpdateParent(LengthPrefixed::new(u));
486        Self::new(u)
487    }
488
489    pub fn from_genesis_cert_block_marker(g: GenesisCertBlockMarker) -> Self {
490        let g = BlockMarkerV1::GenesisCertificate(LengthPrefixed::new(g));
491        Self::new(g)
492    }
493
494    pub fn is_update_parent(&self) -> bool {
495        match self {
496            Self::V1(BlockMarkerV1::UpdateParent(_)) => true,
497            Self::V1(_) => false,
498        }
499    }
500
501    pub fn is_footer(&self) -> bool {
502        match self {
503            Self::V1(BlockMarkerV1::BlockFooter(_)) => true,
504            Self::V1(_) => false,
505        }
506    }
507
508    pub fn is_parent_marker(&self) -> bool {
509        match self {
510            Self::V1(BlockMarkerV1::UpdateParent(_)) | Self::V1(BlockMarkerV1::BlockHeader(_)) => {
511                true
512            }
513            Self::V1(_) => false,
514        }
515    }
516
517    pub fn as_update_parent(&self) -> Option<&UpdateParentV1> {
518        match self {
519            Self::V1(BlockMarkerV1::UpdateParent(update_parent)) => {
520                let VersionedUpdateParent::V1(update_parent) = update_parent.inner();
521                Some(update_parent)
522            }
523            Self::V1(_) => None,
524        }
525    }
526}
527
528#[derive(Debug, Clone, PartialEq, Eq)]
529#[allow(clippy::large_enum_variant)]
530pub enum BlockComponent {
531    EntryBatch(Vec<Entry>),
532    BlockMarker(VersionedBlockMarker),
533}
534
535#[derive(Debug)]
536#[allow(clippy::large_enum_variant)]
537pub enum ParsedBlockComponent {
538    EntryBatch(Vec<EntryView<Bytes>>),
539    BlockMarker(VersionedBlockMarker),
540}
541
542impl BlockComponent {
543    pub(crate) const MAX_ENTRIES: usize = u32::MAX as usize;
544    pub(crate) const ENTRY_COUNT_SIZE: usize = 8;
545    pub(crate) const EMPTY_ENTRY_BATCH: [u8; Self::ENTRY_COUNT_SIZE] = 0u64.to_le_bytes();
546
547    pub fn new_entry_batch(entries: Vec<Entry>) -> Result<Self, BlockComponentError> {
548        if entries.is_empty() {
549            return Err(BlockComponentError::EmptyEntryBatch);
550        }
551
552        if entries.len() >= Self::MAX_ENTRIES {
553            return Err(BlockComponentError::TooManyEntries {
554                count: entries.len(),
555                max: Self::MAX_ENTRIES,
556            });
557        }
558
559        Ok(Self::EntryBatch(entries))
560    }
561
562    pub const fn new_block_marker(marker: VersionedBlockMarker) -> Self {
563        Self::BlockMarker(marker)
564    }
565
566    pub fn new_block_header(parent_slot: Slot, parent_block_id: Hash) -> Self {
567        let header = BlockHeaderV1 {
568            parent_slot,
569            parent_block_id,
570        };
571        Self::new_block_marker(VersionedBlockMarker::from_block_header(header))
572    }
573
574    pub const fn as_marker(&self) -> Option<&VersionedBlockMarker> {
575        match self {
576            Self::BlockMarker(m) => Some(m),
577            _ => None,
578        }
579    }
580
581    pub fn infer_is_entry_batch(data: &[u8]) -> Option<bool> {
582        data.get(..Self::ENTRY_COUNT_SIZE)?
583            .try_into()
584            .ok()
585            .map(|b| u64::from_le_bytes(b) != 0)
586    }
587
588    pub fn infer_is_block_marker(data: &[u8]) -> Option<bool> {
589        Self::infer_is_entry_batch(data).map(|is_entry_batch| !is_entry_batch)
590    }
591
592    /// In Alpenglow an empty entry batch will fail to deserialize
593    /// Leader's should only use an empty entry batch to indicate that they
594    /// are aborting the block
595    pub fn infer_is_empty_entry_batch(data: &[u8]) -> bool {
596        *data == Self::EMPTY_ENTRY_BATCH
597    }
598}
599
600unsafe impl<C: Config> SchemaWrite<C> for BlockComponent {
601    type Src = Self;
602
603    fn size_of(src: &Self::Src) -> WriteResult<usize> {
604        match src {
605            Self::EntryBatch(entries) => {
606                <WincodeVec<Entry, MaxDataShredsLen> as SchemaWrite<C>>::size_of(entries)
607            }
608            Self::BlockMarker(marker) => {
609                let marker_size = <VersionedBlockMarker as SchemaWrite<C>>::size_of(marker)?;
610                Ok(Self::ENTRY_COUNT_SIZE + marker_size)
611            }
612        }
613    }
614
615    fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
616        match src {
617            Self::EntryBatch(entries) => {
618                <WincodeVec<Entry, MaxDataShredsLen> as SchemaWrite<C>>::write(writer, entries)
619            }
620            Self::BlockMarker(marker) => {
621                writer.write(&0u64.to_le_bytes())?;
622                <VersionedBlockMarker as SchemaWrite<C>>::write(writer, marker)
623            }
624        }
625    }
626}
627
628unsafe impl<'de, C: Config> SchemaRead<'de, C> for BlockComponent {
629    type Dst = Self;
630
631    fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
632        let entries =
633            <WincodeVec<Entry, MaxDataShredsLen> as SchemaRead<'de, C>>::get(reader.by_ref())?;
634
635        if entries.is_empty() {
636            dst.write(Self::BlockMarker(<VersionedBlockMarker as SchemaRead<
637                C,
638            >>::get(reader)?));
639        } else if entries.len() >= Self::MAX_ENTRIES {
640            return Err(wincode::ReadError::Custom("Too many entries"));
641        } else {
642            dst.write(Self::EntryBatch(entries));
643        }
644
645        Ok(())
646    }
647}
648
649/// Try to parse a Genesis certificate from a data shred payload
650pub fn genesis_certificate_from_shred(
651    payload: &[u8],
652    shred_version: u16,
653) -> Option<UnverifiedCertificate> {
654    if !BlockComponent::infer_is_block_marker(payload).unwrap_or(false) {
655        return None;
656    }
657    let BlockComponent::BlockMarker(VersionedBlockMarker::V1(marker)) =
658        wincode::config::deserialize_exact(payload, packet_config()).ok()?
659    else {
660        return None;
661    };
662    let BlockMarkerV1::GenesisCertificate(marker) = marker else {
663        return None;
664    };
665    let GenesisCertBlockMarker {
666        slot,
667        block_id,
668        bls_signature,
669        bitmap,
670    } = marker.into_inner();
671    if bitmap.len() > GenesisCertBlockMarker::MAX_BITMAP_SIZE {
672        return None;
673    }
674    Some(UnverifiedCertificate {
675        cert_type: CertificateType::Genesis(Block { slot, block_id }),
676        signature: bls_signature,
677        bitmap,
678        shred_version,
679    })
680}
681
682/// Converts a block footer finalization certificate into certificates ready for BLS verification.
683///
684/// A fast-finalization footer produces one `FinalizeFast` certificate. A slow-finalization footer
685/// produces the corresponding `Notarize` and `Finalize` certificates.
686pub fn finalization_certificates_from_footer(
687    block_final_cert: BlockFinalizationCert,
688    shred_version: u16,
689) -> Option<Vec<UnverifiedCertificate>> {
690    let BlockFinalizationCert {
691        slot,
692        block_id,
693        final_aggregate,
694        notar_aggregate,
695    } = block_final_cert;
696    let block = Block { slot, block_id };
697
698    let final_signature = final_aggregate.uncompress_signature().ok()?;
699    let final_bitmap = final_aggregate.into_bitmap();
700    if let Some(notar_aggregate) = notar_aggregate {
701        let notar_signature = notar_aggregate.uncompress_signature().ok()?;
702        let notar_bitmap = notar_aggregate.into_bitmap();
703        Some(vec![
704            UnverifiedCertificate {
705                cert_type: CertificateType::Notarize(block),
706                signature: notar_signature,
707                bitmap: notar_bitmap,
708                shred_version,
709            },
710            UnverifiedCertificate {
711                cert_type: CertificateType::Finalize(slot),
712                signature: final_signature,
713                bitmap: final_bitmap,
714                shred_version,
715            },
716        ])
717    } else {
718        Some(vec![UnverifiedCertificate {
719            cert_type: CertificateType::FinalizeFast(block),
720            signature: final_signature,
721            bitmap: final_bitmap,
722            shred_version,
723        }])
724    }
725}
726
727#[cfg(test)]
728mod tests {
729    use {
730        super::*,
731        solana_bls_signatures::{
732            BLS_SIGNATURE_AFFINE_SIZE, Keypair as BlsKeypair, Signature as BlsSignature,
733        },
734        std::iter::repeat_n,
735        wincode::config::DEFAULT_PREALLOCATION_SIZE_LIMIT,
736    };
737
738    fn mock_entries(n: usize) -> Vec<Entry> {
739        repeat_n(Entry::default(), n).collect()
740    }
741
742    fn sample_footer() -> BlockFooterV1 {
743        BlockFooterV1 {
744            bank_hash: Hash::new_unique(),
745            block_producer_time_nanos: 1234567890,
746            block_user_agent: b"test-agent".to_vec(),
747            block_final_cert: Some(BlockFinalizationCert::new_for_tests()),
748            skip_reward_cert: None,
749            notar_reward_cert: None,
750        }
751    }
752
753    fn test_votes_aggregate(payload: &[u8], bitmap: Vec<u8>) -> (VotesAggregate, BlsSignature) {
754        let signature: BlsSignature = BlsKeypair::new().sign(payload).into();
755        let aggregate = VotesAggregate::from_cert_signature(CertSignature { signature, bitmap });
756        (aggregate, signature)
757    }
758
759    #[test]
760    fn parse_genesis_certificate_from_shred() {
761        let parent_slot = 41;
762        let block_id = Hash::new_unique();
763        let shred_version = 123;
764        let signature: BlsSignature = BlsKeypair::new().sign(b"genesis").into();
765        let bitmap = vec![0xa5; 64];
766        let marker = GenesisCertBlockMarker {
767            slot: parent_slot,
768            block_id,
769            bls_signature: signature,
770            bitmap: bitmap.clone(),
771        };
772        let component = BlockComponent::new_block_marker(
773            VersionedBlockMarker::from_genesis_cert_block_marker(marker),
774        );
775        let payload = wincode::serialize(&component).unwrap();
776
777        let certificate = genesis_certificate_from_shred(&payload, shred_version).unwrap();
778        assert_eq!(
779            certificate.cert_type,
780            CertificateType::Genesis(Block {
781                slot: parent_slot,
782                block_id,
783            })
784        );
785        assert_eq!(certificate.signature, signature);
786        assert_eq!(certificate.bitmap, bitmap);
787        assert_eq!(certificate.shred_version, shred_version);
788
789        let mut payload_with_trailing_data = payload;
790        payload_with_trailing_data.push(0);
791        assert!(
792            genesis_certificate_from_shred(&payload_with_trailing_data, shred_version,).is_none()
793        );
794    }
795
796    #[test]
797    fn finalization_certificates_from_fast_footer() {
798        let slot = 42;
799        let block_id = Hash::new_unique();
800        let shred_version = 123;
801        let final_bitmap = vec![0x11; 64];
802        let (final_aggregate, final_signature) =
803            test_votes_aggregate(b"fast-finalize", final_bitmap.clone());
804        let certificates = finalization_certificates_from_footer(
805            BlockFinalizationCert {
806                slot,
807                block_id,
808                final_aggregate,
809                notar_aggregate: None,
810            },
811            shred_version,
812        )
813        .unwrap();
814
815        let [certificate] = certificates.as_slice() else {
816            panic!("expected one fast-finalization certificate");
817        };
818        assert_eq!(
819            certificate.cert_type,
820            CertificateType::FinalizeFast(Block { slot, block_id })
821        );
822        assert_eq!(certificate.signature, final_signature);
823        assert_eq!(certificate.bitmap, final_bitmap);
824        assert_eq!(certificate.shred_version, shred_version);
825    }
826
827    #[test]
828    fn finalization_certificates_from_slow_footer() {
829        let slot = 42;
830        let block_id = Hash::new_unique();
831        let shred_version = 123;
832        let final_bitmap = vec![0x11; 64];
833        let notar_bitmap = vec![0x22; 64];
834        let (final_aggregate, final_signature) =
835            test_votes_aggregate(b"finalize", final_bitmap.clone());
836        let (notar_aggregate, notar_signature) =
837            test_votes_aggregate(b"notarize", notar_bitmap.clone());
838        let certificates = finalization_certificates_from_footer(
839            BlockFinalizationCert {
840                slot,
841                block_id,
842                final_aggregate,
843                notar_aggregate: Some(notar_aggregate),
844            },
845            shred_version,
846        )
847        .unwrap();
848
849        let [notarize, finalize] = certificates.as_slice() else {
850            panic!("expected notarize and finalize certificates");
851        };
852        assert_eq!(
853            notarize.cert_type,
854            CertificateType::Notarize(Block { slot, block_id })
855        );
856        assert_eq!(notarize.signature, notar_signature);
857        assert_eq!(notarize.bitmap, notar_bitmap);
858        assert_eq!(notarize.shred_version, shred_version);
859        assert_eq!(finalize.cert_type, CertificateType::Finalize(slot));
860        assert_eq!(finalize.signature, final_signature);
861        assert_eq!(finalize.bitmap, final_bitmap);
862        assert_eq!(finalize.shred_version, shred_version);
863    }
864
865    #[test]
866    fn round_trips() {
867        let header = BlockHeaderV1 {
868            parent_slot: 12345,
869            parent_block_id: Hash::new_unique(),
870        };
871        let bytes = wincode::serialize(&header).unwrap();
872        assert_eq!(
873            header,
874            wincode::deserialize::<BlockHeaderV1>(&bytes).unwrap()
875        );
876
877        let footer = sample_footer();
878        let bytes = wincode::serialize(&footer).unwrap();
879        assert_eq!(
880            footer,
881            wincode::deserialize::<BlockFooterV1>(&bytes).unwrap()
882        );
883
884        let marker = GenesisCertBlockMarker {
885            slot: 999,
886            block_id: Hash::new_unique(),
887            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
888            bitmap: vec![1, 2, 3],
889        };
890        let bytes = wincode::serialize(&marker).unwrap();
891        assert_eq!(
892            marker,
893            wincode::deserialize::<GenesisCertBlockMarker>(&bytes).unwrap()
894        );
895
896        let marker = VersionedBlockMarker::from_block_footer(footer.clone());
897        let bytes = wincode::serialize(&marker).unwrap();
898        assert_eq!(
899            marker,
900            wincode::deserialize::<VersionedBlockMarker>(&bytes).unwrap()
901        );
902
903        let comp = BlockComponent::new_entry_batch(mock_entries(5)).unwrap();
904        let bytes = wincode::serialize(&comp).unwrap();
905        let deser: BlockComponent = wincode::deserialize(&bytes).unwrap();
906        assert_eq!(comp, deser);
907
908        let comp = BlockComponent::new_block_marker(marker);
909        let bytes = wincode::serialize(&comp).unwrap();
910        let deser: BlockComponent = wincode::deserialize(&bytes).unwrap();
911        assert_eq!(comp, deser);
912    }
913
914    #[test]
915    fn length_prefixed_rejects_inner_size_mismatch() {
916        let header = VersionedBlockHeader::V1(BlockHeaderV1 {
917            parent_slot: 12345,
918            parent_block_id: Hash::new_unique(),
919        });
920        let prefixed = LengthPrefixed::new(header);
921        let mut bytes = wincode::serialize(&prefixed).unwrap();
922        let wrong_len = prefixed.len + 1;
923        bytes[..std::mem::size_of::<u16>()].copy_from_slice(&wrong_len.to_le_bytes());
924
925        assert!(matches!(
926            wincode::deserialize::<LengthPrefixed<VersionedBlockHeader>>(&bytes),
927            Err(wincode::ReadError::Custom(
928                "LengthPrefixed: inner serialized size does not match length prefix"
929            ))
930        ));
931    }
932
933    #[test]
934    fn length_prefixed_rejects_oversized_deserialized_inner() {
935        let marker = GenesisCertBlockMarker {
936            slot: 999,
937            block_id: Hash::new_unique(),
938            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
939            bitmap: vec![0xAB; usize::from(u16::MAX) + 1],
940        };
941        let wire = LengthPrefixed {
942            len: 7,
943            inner: marker,
944        };
945        let bytes = wincode::serialize(&wire).unwrap();
946
947        assert!(matches!(
948            wincode::deserialize::<LengthPrefixed<GenesisCertBlockMarker>>(&bytes),
949            Err(wincode::ReadError::Custom(
950                "LengthPrefixed: inner serialized size does not match length prefix"
951            ))
952        ));
953    }
954
955    #[test]
956    fn large_entry_batch_round_trips() {
957        // Ensure an EntryBatch that exceeds wincode's default 4 MiB prealloc
958        // limit can still round-trip.
959        let num_entries = DEFAULT_PREALLOCATION_SIZE_LIMIT / std::mem::size_of::<Entry>() + 1;
960
961        let comp = BlockComponent::new_entry_batch(mock_entries(num_entries)).unwrap();
962        let bytes = wincode::serialize(&comp).unwrap();
963        let deser: BlockComponent = wincode::deserialize(&bytes).unwrap();
964        assert_eq!(comp, deser);
965    }
966}