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::{Certificate, CertificateType},
136        reward_certificate::{NotarRewardCertificate, SkipRewardCertificate},
137    },
138    solana_bls_signatures::{
139        BlsError, Signature as BLSSignature, SignatureCompressed as BLSSignatureCompressed,
140        signature::AsSignatureAffine,
141    },
142    solana_clock::Slot,
143    solana_hash::Hash,
144    std::mem::MaybeUninit,
145    wincode::{
146        ReadResult, SchemaRead, SchemaWrite, WriteResult,
147        config::{Config, DefaultConfig},
148        containers::Vec as WincodeVec,
149        error::write_length_encoding_overflow,
150        io::{Reader, Writer},
151        len::{BincodeLen, FixIntLen},
152        pod_wrapper,
153    },
154};
155
156pod_wrapper! {
157    // Use `BLSSignature` directly once `BLSSignature` wincode support
158    // is released in solana-sdk.
159    unsafe struct PodBLSSignature(BLSSignature);
160    // Use `BLSSignatureCompressed` directly once `BLSSignature` wincode support
161    // is released in solana-sdk.
162    unsafe struct PodBLSSignatureCompressed(BLSSignatureCompressed);
163}
164
165/// Wraps a value with a u16 length prefix for TLV-style serialization.
166///
167/// The length prefix represents the serialized byte size of the inner value.
168#[derive(Debug, Clone, PartialEq, Eq, SchemaRead, SchemaWrite)]
169pub struct LengthPrefixed<T> {
170    len: u16,
171    inner: T,
172}
173
174impl<T> LengthPrefixed<T>
175where
176    T: SchemaWrite<DefaultConfig, Src = T>,
177{
178    pub fn new(inner: T) -> Self {
179        let inner_size = T::size_of(&inner).unwrap();
180        let len = inner_size
181            .try_into()
182            .map_err(|_| write_length_encoding_overflow("u16::MAX"))
183            .unwrap();
184        Self { len, inner }
185    }
186}
187
188impl<T> LengthPrefixed<T> {
189    pub fn inner(&self) -> &T {
190        &self.inner
191    }
192
193    pub fn into_inner(self) -> T {
194        self.inner
195    }
196}
197
198#[derive(Debug, thiserror::Error)]
199pub enum BlockComponentError {
200    #[error("Entry count {count} exceeds max {max}")]
201    TooManyEntries { count: usize, max: usize },
202    #[error("Entry batch cannot be empty")]
203    EmptyEntryBatch,
204}
205
206/// Block production metadata. User agent is capped at 255 bytes.
207#[derive(Clone, PartialEq, Eq, Debug, SchemaWrite, SchemaRead)]
208pub struct BlockFooterV1 {
209    pub bank_hash: Hash,
210    pub block_producer_time_nanos: u64,
211    #[wincode(with = "WincodeVec<u8, FixIntLen<u8>>")]
212    pub block_user_agent: Vec<u8>,
213    pub block_final_cert: Option<BlockFinalizationCert>,
214    pub skip_reward_cert: Option<SkipRewardCertificate>,
215    pub notar_reward_cert: Option<NotarRewardCertificate>,
216}
217
218#[derive(Clone, PartialEq, Eq, Debug, SchemaWrite, SchemaRead)]
219pub struct BlockHeaderV1 {
220    pub parent_slot: Slot,
221    pub parent_block_id: Hash,
222}
223
224#[derive(Clone, PartialEq, Eq, Debug, SchemaWrite, SchemaRead)]
225pub struct UpdateParentV1 {
226    pub new_parent_slot: Slot,
227    pub new_parent_block_id: Hash,
228}
229
230/// Attests to genesis block finalization with a BLS aggregate signature.
231#[derive(Clone, PartialEq, Eq, Debug, SchemaWrite, SchemaRead)]
232pub struct GenesisCertBlockMarker {
233    pub slot: Slot,
234    pub block_id: Hash,
235    #[wincode(with = "PodBLSSignature")]
236    pub bls_signature: BLSSignature,
237    #[wincode(with = "WincodeVec<u8, BincodeLen>")]
238    pub bitmap: Vec<u8>,
239}
240
241impl GenesisCertBlockMarker {
242    /// Max bitmap size in bytes (supports up to 4096 validators).
243    pub const MAX_BITMAP_SIZE: usize = 512;
244}
245
246impl TryFrom<Certificate> for GenesisCertBlockMarker {
247    type Error = String;
248
249    fn try_from(cert: Certificate) -> Result<Self, Self::Error> {
250        let CertificateType::Genesis(block) = cert.cert_type else {
251            return Err("expected genesis certificate".into());
252        };
253        if cert.bitmap.len() > Self::MAX_BITMAP_SIZE {
254            return Err(format!(
255                "bitmap size {} exceeds max {}",
256                cert.bitmap.len(),
257                Self::MAX_BITMAP_SIZE
258            ));
259        }
260        Ok(Self {
261            slot: block.slot,
262            block_id: block.block_id,
263            bls_signature: cert.signature,
264            bitmap: cert.bitmap,
265        })
266    }
267}
268
269#[derive(Clone, PartialEq, Eq, Debug, SchemaWrite, SchemaRead)]
270pub struct BlockFinalizationCert {
271    pub slot: Slot,
272    pub block_id: Hash,
273    pub final_aggregate: VotesAggregate,
274    pub notar_aggregate: Option<VotesAggregate>,
275}
276
277impl BlockFinalizationCert {
278    #[cfg(feature = "dev-context-only-utils")]
279    pub fn new_for_tests() -> BlockFinalizationCert {
280        BlockFinalizationCert {
281            slot: 1234567890,
282            block_id: Hash::new_from_array([1u8; 32]),
283            final_aggregate: VotesAggregate {
284                signature: BLSSignatureCompressed(
285                    [0; solana_bls_signatures::BLS_SIGNATURE_COMPRESSED_SIZE],
286                ),
287                bitmap: vec![42; 64],
288            },
289            notar_aggregate: None,
290        }
291    }
292}
293
294#[derive(Clone, PartialEq, Eq, Debug, SchemaRead, SchemaWrite)]
295pub struct VotesAggregate {
296    #[wincode(with = "PodBLSSignatureCompressed")]
297    signature: BLSSignatureCompressed,
298    #[wincode(with = "WincodeVec<u8, FixIntLen<u16>>")]
299    bitmap: Vec<u8>,
300}
301
302impl VotesAggregate {
303    /// Creates a VotesAggregate from a Certificate's signature and bitmap.
304    ///
305    /// # Panics
306    /// Panics if the signature cannot be converted to compressed format.
307    /// This should never happen for valid certificates from the consensus pool.
308    pub fn from_certificate(cert: &Certificate) -> Self {
309        Self {
310            signature: BLSSignatureCompressed::try_from(&cert.signature)
311                .expect("valid certificate signature should convert to compressed format"),
312            bitmap: cert.bitmap.clone(),
313        }
314    }
315
316    /// Uncompresses the signature.
317    pub fn uncompress_signature(&self) -> Result<BLSSignature, BlsError> {
318        Ok(BLSSignature::from(self.signature.try_as_affine()?))
319    }
320
321    /// Consumes self and returns the bitmap.
322    pub fn into_bitmap(self) -> Vec<u8> {
323        self.bitmap
324    }
325}
326
327#[derive(Debug, Clone, PartialEq, Eq, SchemaWrite, SchemaRead)]
328#[wincode(tag_encoding = "u8")]
329pub enum VersionedBlockFooter {
330    #[wincode(tag = 1)]
331    V1(BlockFooterV1),
332}
333
334#[derive(Debug, Clone, PartialEq, Eq, SchemaWrite, SchemaRead)]
335#[wincode(tag_encoding = "u8")]
336pub enum VersionedBlockHeader {
337    #[wincode(tag = 1)]
338    V1(BlockHeaderV1),
339}
340
341#[derive(Debug, Clone, PartialEq, Eq, SchemaWrite, SchemaRead)]
342#[wincode(tag_encoding = "u8")]
343pub enum VersionedUpdateParent {
344    #[wincode(tag = 1)]
345    V1(UpdateParentV1),
346}
347
348/// TLV-encoded marker variants.
349#[allow(clippy::large_enum_variant)]
350#[derive(Debug, Clone, PartialEq, Eq, SchemaWrite, SchemaRead)]
351#[wincode(tag_encoding = "u8")]
352pub enum BlockMarkerV1 {
353    BlockFooter(LengthPrefixed<VersionedBlockFooter>),
354    BlockHeader(LengthPrefixed<VersionedBlockHeader>),
355    UpdateParent(LengthPrefixed<VersionedUpdateParent>),
356    GenesisCertificate(LengthPrefixed<GenesisCertBlockMarker>),
357}
358
359impl BlockMarkerV1 {
360    pub fn new_block_footer(f: VersionedBlockFooter) -> Self {
361        Self::BlockFooter(LengthPrefixed::new(f))
362    }
363
364    pub fn new_block_header(h: VersionedBlockHeader) -> Self {
365        Self::BlockHeader(LengthPrefixed::new(h))
366    }
367
368    pub fn new_update_parent(u: VersionedUpdateParent) -> Self {
369        Self::UpdateParent(LengthPrefixed::new(u))
370    }
371
372    pub fn new_genesis_certificate(c: GenesisCertBlockMarker) -> Self {
373        Self::GenesisCertificate(LengthPrefixed::new(c))
374    }
375
376    pub fn as_block_footer(&self) -> Option<&VersionedBlockFooter> {
377        match self {
378            Self::BlockFooter(lp) => Some(lp.inner()),
379            _ => None,
380        }
381    }
382
383    pub fn as_block_header(&self) -> Option<&VersionedBlockHeader> {
384        match self {
385            Self::BlockHeader(lp) => Some(lp.inner()),
386            _ => None,
387        }
388    }
389
390    pub fn as_update_parent(&self) -> Option<&VersionedUpdateParent> {
391        match self {
392            Self::UpdateParent(lp) => Some(lp.inner()),
393            _ => None,
394        }
395    }
396
397    pub fn as_genesis_certificate(&self) -> Option<&GenesisCertBlockMarker> {
398        match self {
399            Self::GenesisCertificate(lp) => Some(lp.inner()),
400            _ => None,
401        }
402    }
403}
404
405#[derive(Debug, Clone, PartialEq, Eq, SchemaWrite, SchemaRead)]
406#[wincode(tag_encoding = "u16")]
407pub enum VersionedBlockMarker {
408    #[wincode(tag = 1)]
409    V1(BlockMarkerV1),
410}
411
412impl VersionedBlockMarker {
413    pub const fn new(marker: BlockMarkerV1) -> Self {
414        Self::V1(marker)
415    }
416
417    pub fn from_block_footer(f: BlockFooterV1) -> Self {
418        let f = VersionedBlockFooter::V1(f);
419        let f = BlockMarkerV1::BlockFooter(LengthPrefixed::new(f));
420        Self::new(f)
421    }
422
423    pub fn from_block_header(h: BlockHeaderV1) -> Self {
424        let h = VersionedBlockHeader::V1(h);
425        let h = BlockMarkerV1::BlockHeader(LengthPrefixed::new(h));
426        Self::new(h)
427    }
428
429    pub fn from_update_parent(u: UpdateParentV1) -> Self {
430        let u = VersionedUpdateParent::V1(u);
431        let u = BlockMarkerV1::UpdateParent(LengthPrefixed::new(u));
432        Self::new(u)
433    }
434
435    pub fn from_genesis_cert_block_marker(g: GenesisCertBlockMarker) -> Self {
436        let g = BlockMarkerV1::GenesisCertificate(LengthPrefixed::new(g));
437        Self::new(g)
438    }
439
440    pub fn is_update_parent(&self) -> bool {
441        match self {
442            Self::V1(BlockMarkerV1::UpdateParent(_)) => true,
443            Self::V1(_) => false,
444        }
445    }
446
447    pub fn is_footer(&self) -> bool {
448        match self {
449            Self::V1(BlockMarkerV1::BlockFooter(_)) => true,
450            Self::V1(_) => false,
451        }
452    }
453}
454
455#[derive(Debug, Clone, PartialEq, Eq)]
456#[allow(clippy::large_enum_variant)]
457pub enum BlockComponent {
458    EntryBatch(Vec<Entry>),
459    BlockMarker(VersionedBlockMarker),
460}
461
462impl BlockComponent {
463    const MAX_ENTRIES: usize = u32::MAX as usize;
464    const ENTRY_COUNT_SIZE: usize = 8;
465    const EMPTY_ENTRY_BATCH: [u8; Self::ENTRY_COUNT_SIZE] = 0u64.to_le_bytes();
466
467    pub fn new_entry_batch(entries: Vec<Entry>) -> Result<Self, BlockComponentError> {
468        if entries.is_empty() {
469            return Err(BlockComponentError::EmptyEntryBatch);
470        }
471
472        if entries.len() >= Self::MAX_ENTRIES {
473            return Err(BlockComponentError::TooManyEntries {
474                count: entries.len(),
475                max: Self::MAX_ENTRIES,
476            });
477        }
478
479        Ok(Self::EntryBatch(entries))
480    }
481
482    pub const fn new_block_marker(marker: VersionedBlockMarker) -> Self {
483        Self::BlockMarker(marker)
484    }
485
486    pub fn new_block_header(parent_slot: Slot, parent_block_id: Hash) -> Self {
487        let header = BlockHeaderV1 {
488            parent_slot,
489            parent_block_id,
490        };
491        Self::new_block_marker(VersionedBlockMarker::from_block_header(header))
492    }
493
494    pub const fn as_marker(&self) -> Option<&VersionedBlockMarker> {
495        match self {
496            Self::BlockMarker(m) => Some(m),
497            _ => None,
498        }
499    }
500
501    pub fn infer_is_entry_batch(data: &[u8]) -> Option<bool> {
502        data.get(..Self::ENTRY_COUNT_SIZE)?
503            .try_into()
504            .ok()
505            .map(|b| u64::from_le_bytes(b) != 0)
506    }
507
508    pub fn infer_is_block_marker(data: &[u8]) -> Option<bool> {
509        Self::infer_is_entry_batch(data).map(|is_entry_batch| !is_entry_batch)
510    }
511
512    /// In Alpenglow an empty entry batch will fail to deserialize
513    /// Leader's should only use an empty entry batch to indicate that they
514    /// are aborting the block
515    pub fn infer_is_empty_entry_batch(data: &[u8]) -> bool {
516        *data == Self::EMPTY_ENTRY_BATCH
517    }
518}
519
520unsafe impl<C: Config> SchemaWrite<C> for BlockComponent {
521    type Src = Self;
522
523    fn size_of(src: &Self::Src) -> WriteResult<usize> {
524        match src {
525            Self::EntryBatch(entries) => {
526                <WincodeVec<Entry, MaxDataShredsLen> as SchemaWrite<C>>::size_of(entries)
527            }
528            Self::BlockMarker(marker) => {
529                let marker_size = <VersionedBlockMarker as SchemaWrite<C>>::size_of(marker)?;
530                Ok(Self::ENTRY_COUNT_SIZE + marker_size)
531            }
532        }
533    }
534
535    fn write(mut writer: impl Writer, src: &Self::Src) -> WriteResult<()> {
536        match src {
537            Self::EntryBatch(entries) => {
538                <WincodeVec<Entry, MaxDataShredsLen> as SchemaWrite<C>>::write(writer, entries)
539            }
540            Self::BlockMarker(marker) => {
541                writer.write(&0u64.to_le_bytes())?;
542                <VersionedBlockMarker as SchemaWrite<C>>::write(writer, marker)
543            }
544        }
545    }
546}
547
548unsafe impl<'de, C: Config> SchemaRead<'de, C> for BlockComponent {
549    type Dst = Self;
550
551    fn read(mut reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
552        let entries =
553            <WincodeVec<Entry, MaxDataShredsLen> as SchemaRead<'de, C>>::get(reader.by_ref())?;
554
555        if entries.is_empty() {
556            dst.write(Self::BlockMarker(<VersionedBlockMarker as SchemaRead<
557                C,
558            >>::get(reader)?));
559        } else if entries.len() >= Self::MAX_ENTRIES {
560            return Err(wincode::ReadError::Custom("Too many entries"));
561        } else {
562            dst.write(Self::EntryBatch(entries));
563        }
564
565        Ok(())
566    }
567}
568
569#[cfg(test)]
570mod tests {
571    use {
572        super::*, solana_bls_signatures::BLS_SIGNATURE_AFFINE_SIZE, std::iter::repeat_n,
573        wincode::config::DEFAULT_PREALLOCATION_SIZE_LIMIT,
574    };
575
576    fn mock_entries(n: usize) -> Vec<Entry> {
577        repeat_n(Entry::default(), n).collect()
578    }
579
580    fn sample_footer() -> BlockFooterV1 {
581        BlockFooterV1 {
582            bank_hash: Hash::new_unique(),
583            block_producer_time_nanos: 1234567890,
584            block_user_agent: b"test-agent".to_vec(),
585            block_final_cert: Some(BlockFinalizationCert::new_for_tests()),
586            skip_reward_cert: None,
587            notar_reward_cert: None,
588        }
589    }
590
591    #[test]
592    fn round_trips() {
593        let header = BlockHeaderV1 {
594            parent_slot: 12345,
595            parent_block_id: Hash::new_unique(),
596        };
597        let bytes = wincode::serialize(&header).unwrap();
598        assert_eq!(
599            header,
600            wincode::deserialize::<BlockHeaderV1>(&bytes).unwrap()
601        );
602
603        let footer = sample_footer();
604        let bytes = wincode::serialize(&footer).unwrap();
605        assert_eq!(
606            footer,
607            wincode::deserialize::<BlockFooterV1>(&bytes).unwrap()
608        );
609
610        let marker = GenesisCertBlockMarker {
611            slot: 999,
612            block_id: Hash::new_unique(),
613            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
614            bitmap: vec![1, 2, 3],
615        };
616        let bytes = wincode::serialize(&marker).unwrap();
617        assert_eq!(
618            marker,
619            wincode::deserialize::<GenesisCertBlockMarker>(&bytes).unwrap()
620        );
621
622        let marker = VersionedBlockMarker::from_block_footer(footer.clone());
623        let bytes = wincode::serialize(&marker).unwrap();
624        assert_eq!(
625            marker,
626            wincode::deserialize::<VersionedBlockMarker>(&bytes).unwrap()
627        );
628
629        let comp = BlockComponent::new_entry_batch(mock_entries(5)).unwrap();
630        let bytes = wincode::serialize(&comp).unwrap();
631        let deser: BlockComponent = wincode::deserialize(&bytes).unwrap();
632        assert_eq!(comp, deser);
633
634        let comp = BlockComponent::new_block_marker(marker);
635        let bytes = wincode::serialize(&comp).unwrap();
636        let deser: BlockComponent = wincode::deserialize(&bytes).unwrap();
637        assert_eq!(comp, deser);
638    }
639
640    #[test]
641    fn large_entry_batch_round_trips() {
642        // Ensure an EntryBatch that exceeds wincode's default 4 MiB prealloc
643        // limit can still round-trip.
644        let num_entries = DEFAULT_PREALLOCATION_SIZE_LIMIT / std::mem::size_of::<Entry>() + 1;
645
646        let comp = BlockComponent::new_entry_batch(mock_entries(num_entries)).unwrap();
647        let bytes = wincode::serialize(&comp).unwrap();
648        let deser: BlockComponent = wincode::deserialize(&bytes).unwrap();
649        assert_eq!(comp, deser);
650    }
651}