1use {
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 unsafe struct PodBLSSignature(BLSSignature);
163 unsafe struct PodBLSSignatureCompressed(BLSSignatureCompressed);
166}
167
168#[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#[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#[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 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 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 pub fn uncompress_signature(&self) -> Result<BLSSignature, BlsError> {
347 Ok(BLSSignature::from(self.signature.try_as_affine()?))
348 }
349
350 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#[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 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
598pub 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
631pub 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 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}