1use {
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 unsafe struct PodBLSSignature(BLSSignature);
164 unsafe struct PodBLSSignatureCompressed(BLSSignatureCompressed);
167}
168
169#[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#[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#[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 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 #[cfg(feature = "dev-context-only-utils")]
335 pub fn new(signature: BLSSignatureCompressed, bitmap: Vec<u8>) -> Self {
336 Self { signature, bitmap }
337 }
338
339 pub fn signature(&self) -> &BLSSignatureCompressed {
341 &self.signature
342 }
343
344 pub fn bitmap(&self) -> &[u8] {
346 &self.bitmap
347 }
348
349 pub fn as_parts(&self) -> (&BLSSignatureCompressed, &[u8]) {
353 let Self { signature, bitmap } = self;
354 (signature, bitmap)
355 }
356
357 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 pub fn uncompress_signature(&self) -> Result<BLSSignature, BlsError> {
372 Ok(BLSSignature::from(self.signature.try_as_affine()?))
373 }
374
375 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#[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 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
649pub 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
682pub 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 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}