1use {
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 unsafe struct PodBLSSignature(BLSSignature);
160 unsafe struct PodBLSSignatureCompressed(BLSSignatureCompressed);
163}
164
165#[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#[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#[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 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 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 pub fn uncompress_signature(&self) -> Result<BLSSignature, BlsError> {
318 Ok(BLSSignature::from(self.signature.try_as_affine()?))
319 }
320
321 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#[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 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 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}