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