1use {
2 crate::{
3 bank::Bank,
4 block_component_processor::vote_reward::{
5 CalcVoteRewardUpdateVoteStatesError, calc_vote_rewards_update_vote_states,
6 },
7 leader_schedule_utils::leader_slot_index,
8 validated_block_finalization::{
9 BlockFinalizationCertError, ValidatedBlockFinalizationCert,
10 },
11 validated_reward_certificate::{Error as ValidatedRewardCertError, ValidatedRewardCert},
12 },
13 agave_transaction_view::transaction_data::TransactionData,
14 agave_votor_messages::{
15 certificate::{CertSignature, Certificate, CertificateType, GenesisCert},
16 consensus_message::Block,
17 migration::MigrationStatus,
18 unverified_vote_message::UnverifiedCertificate,
19 },
20 crossbeam_channel::{Sender, TrySendError},
21 log::*,
22 smallvec::{SmallVec, smallvec},
23 solana_clock::Slot,
24 solana_entry::{
25 block_component::{
26 BlockFooterV1, BlockMarkerV1, GenesisCertBlockMarker, VersionedBlockFooter,
27 VersionedBlockHeader, VersionedBlockMarker, VersionedUpdateParent,
28 },
29 entry::EntryView,
30 },
31 solana_hash::Hash,
32 solana_pubkey::Pubkey,
33 std::{collections::HashSet, sync::Arc},
34 thiserror::Error,
35};
36
37pub(crate) mod vote_reward;
38
39#[derive(Debug, Error)]
40pub enum BankFooterError {
41 #[error("calc vote rewards updating vote states failed with \"{0}\"")]
42 CalcVoteRewardUpdateVoteStates(#[from] CalcVoteRewardUpdateVoteStatesError),
43}
44
45#[derive(Debug, Error)]
46pub enum BlockComponentProcessorError {
47 #[error("BlockComponent detected pre-migration")]
48 BlockComponentPreMigration,
49 #[error("GenesisCertificate marker detected when GenesisCertificate is already populated")]
50 GenesisCertificateAlreadyPopulated,
51 #[error("GenesisCertificate marker detected when the cluster has Alpenglow enabled at slot 0")]
52 GenesisCertificateInAlpenglowCluster,
53 #[error("GenesisCertificate marker detected on a block which is not a child of genesis")]
54 GenesisCertificateOnNonChild,
55 #[error("GenesisCertificate was invalid and failed to verify")]
56 GenesisCertificateFailedVerification,
57 #[error("Alpenglow migration became ready; aborting the TowerBFT bank")]
58 AlpenglowMigrationTransition,
59 #[error("GenesisCertificate marker must immediately follow the block header")]
60 GenesisCertificateOutOfOrder,
61 #[error("FinalizationCertificate was invalid or failed to verify {0}")]
62 InvalidFinalizationCertificate(#[from] BlockFinalizationCertError),
63 #[error("Missing block footer")]
64 MissingBlockFooter,
65 #[error("Missing genesis certificate marker")]
66 MissingGenesisCertificateMarker,
67 #[error("Missing parent marker (neither a header nor an update parent was present)")]
68 MissingParentMarker,
69 #[error("Entry batch detected after block footer")]
70 EntryBatchAfterBlockFooter,
71 #[error("Alpentick must be the final block component and appear after block footer")]
72 InvalidAlpentickPosition,
73 #[error("Multiple block footers detected")]
74 MultipleBlockFooters,
75 #[error("Multiple block headers detected")]
76 MultipleBlockHeaders,
77 #[error(
78 "Block header parent slot mismatch: header={header_parent_slot}, bank={bank_parent_slot}"
79 )]
80 HeaderParentSlotMismatch {
81 header_parent_slot: Slot,
82 bank_parent_slot: Slot,
83 },
84 #[error("Multiple update parents detected")]
85 MultipleUpdateParents,
86 #[error("Nanosecond clock out of bounds")]
87 NanosecondClockOutOfBounds,
88 #[error("Spurious update parent")]
89 SpuriousUpdateParent,
90 #[error("UpdateParent marker is only valid in the first slot of a leader window: slot {0}")]
91 UpdateParentNotFirstInLeaderWindow(Slot),
92 #[error(
93 "UpdateParent cannot be the initial parent marker unless replay starts at UpdateParent"
94 )]
95 UnexpectedInitialUpdateParent,
96 #[error("Abandoned bank")]
97 AbandonedBank(VersionedUpdateParent),
98 #[error("invalid reward certs {0}")]
99 InvalidRewardCerts(#[from] ValidatedRewardCertError),
100 #[error("updating bank footer failed with \"{0}\"")]
101 UpdateBankFooter(#[from] BankFooterError),
102}
103
104impl BlockComponentProcessorError {
105 pub fn is_update_parent_recoverable_replay_error(&self) -> bool {
111 match self {
112 BlockComponentProcessorError::MissingParentMarker
113 | BlockComponentProcessorError::EntryBatchAfterBlockFooter
114 | BlockComponentProcessorError::InvalidAlpentickPosition
115 | BlockComponentProcessorError::MultipleBlockFooters
116 | BlockComponentProcessorError::MultipleBlockHeaders
117 | BlockComponentProcessorError::HeaderParentSlotMismatch { .. }
118 | BlockComponentProcessorError::NanosecondClockOutOfBounds
119 | BlockComponentProcessorError::UnexpectedInitialUpdateParent
120 | BlockComponentProcessorError::GenesisCertificateOutOfOrder
121 | BlockComponentProcessorError::GenesisCertificateAlreadyPopulated
122 | BlockComponentProcessorError::GenesisCertificateInAlpenglowCluster
123 | BlockComponentProcessorError::GenesisCertificateOnNonChild
124 | BlockComponentProcessorError::GenesisCertificateFailedVerification
125 | BlockComponentProcessorError::SpuriousUpdateParent
126 | BlockComponentProcessorError::AbandonedBank(_)
127 | BlockComponentProcessorError::InvalidRewardCerts(_)
128 | BlockComponentProcessorError::UpdateBankFooter(_)
129 | BlockComponentProcessorError::InvalidFinalizationCertificate(_) => true,
130 BlockComponentProcessorError::BlockComponentPreMigration
131 | BlockComponentProcessorError::MissingBlockFooter
132 | BlockComponentProcessorError::MissingGenesisCertificateMarker
133 | BlockComponentProcessorError::MultipleUpdateParents
134 | BlockComponentProcessorError::AlpenglowMigrationTransition
135 | BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(_) => false,
136 }
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141enum EntryParentMarker {
143 BlockHeader,
144 UpdateParent,
145}
146
147#[derive(Default, Debug, Clone, PartialEq, Eq)]
148enum BlockComponentStage {
163 #[default]
164 PreParentMarker,
166 AcceptingGenesisOrEntries,
168 AcceptingEntriesOrFooter { parent_marker: EntryParentMarker },
171 AcceptingAlpentick,
173 Done,
175}
176
177impl BlockComponentStage {
178 fn on_header(&mut self) -> Result<(), BlockComponentProcessorError> {
180 match self {
181 Self::PreParentMarker => {
182 *self = Self::AcceptingGenesisOrEntries;
183 Ok(())
184 }
185 Self::AcceptingGenesisOrEntries
186 | Self::AcceptingEntriesOrFooter {
187 parent_marker: EntryParentMarker::BlockHeader,
188 }
189 | Self::AcceptingAlpentick
190 | Self::Done => Err(BlockComponentProcessorError::MultipleBlockHeaders),
191 Self::AcceptingEntriesOrFooter {
192 parent_marker: EntryParentMarker::UpdateParent,
193 } => Err(BlockComponentProcessorError::SpuriousUpdateParent),
194 }
195 }
196
197 fn on_genesis_certificate(&mut self) -> Result<(), BlockComponentProcessorError> {
199 match self {
200 Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
201 Self::AcceptingGenesisOrEntries => {
202 *self = Self::AcceptingEntriesOrFooter {
203 parent_marker: EntryParentMarker::BlockHeader,
204 };
205 Ok(())
206 }
207 Self::AcceptingEntriesOrFooter { .. } | Self::AcceptingAlpentick | Self::Done => {
208 Err(BlockComponentProcessorError::GenesisCertificateOutOfOrder)
209 }
210 }
211 }
212
213 fn on_entry_batch(&mut self) -> Result<(), BlockComponentProcessorError> {
216 match self {
217 Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
218 Self::AcceptingGenesisOrEntries => {
219 *self = Self::AcceptingEntriesOrFooter {
220 parent_marker: EntryParentMarker::BlockHeader,
221 };
222 Ok(())
223 }
224 Self::AcceptingEntriesOrFooter { .. } => Ok(()),
225 Self::AcceptingAlpentick | Self::Done => {
226 Err(BlockComponentProcessorError::EntryBatchAfterBlockFooter)
227 }
228 }
229 }
230
231 fn on_update_parent(
235 &mut self,
236 update_parent: &VersionedUpdateParent,
237 allow_initial_update_parent: bool,
238 ) -> Result<(), BlockComponentProcessorError> {
239 match self {
240 Self::PreParentMarker => {
241 if !allow_initial_update_parent {
242 return Err(BlockComponentProcessorError::UnexpectedInitialUpdateParent);
243 }
244 *self = Self::AcceptingEntriesOrFooter {
245 parent_marker: EntryParentMarker::UpdateParent,
246 };
247 Ok(())
248 }
249 Self::AcceptingGenesisOrEntries
250 | Self::AcceptingEntriesOrFooter {
251 parent_marker: EntryParentMarker::BlockHeader,
252 } => {
253 Err(BlockComponentProcessorError::AbandonedBank(
257 update_parent.clone(),
258 ))
259 }
260 Self::AcceptingEntriesOrFooter {
261 parent_marker: EntryParentMarker::UpdateParent,
262 } => Err(BlockComponentProcessorError::MultipleUpdateParents),
263 Self::AcceptingAlpentick | BlockComponentStage::Done => {
264 Err(BlockComponentProcessorError::SpuriousUpdateParent)
265 }
266 }
267 }
268
269 fn on_footer(&mut self) -> Result<(), BlockComponentProcessorError> {
272 match self {
273 Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
274 Self::AcceptingGenesisOrEntries | Self::AcceptingEntriesOrFooter { .. } => {
275 *self = Self::AcceptingAlpentick;
276 Ok(())
277 }
278 Self::AcceptingAlpentick | Self::Done => {
279 Err(BlockComponentProcessorError::MultipleBlockFooters)
280 }
281 }
282 }
283
284 fn on_alpentick(&mut self) -> Result<(), BlockComponentProcessorError> {
286 match self {
287 Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
288 Self::AcceptingGenesisOrEntries => {
289 Err(BlockComponentProcessorError::InvalidAlpentickPosition)
290 }
291 Self::AcceptingEntriesOrFooter { .. } => {
292 Err(BlockComponentProcessorError::InvalidAlpentickPosition)
293 }
294 Self::AcceptingAlpentick => {
295 *self = Self::Done;
296 Ok(())
297 }
298 Self::Done => Err(BlockComponentProcessorError::InvalidAlpentickPosition),
299 }
300 }
301
302 fn on_final(&self) -> Result<(), BlockComponentProcessorError> {
304 match self {
305 Self::Done => Ok(()),
306 Self::AcceptingAlpentick => Err(BlockComponentProcessorError::InvalidAlpentickPosition),
307 Self::PreParentMarker
308 | Self::AcceptingGenesisOrEntries
309 | Self::AcceptingEntriesOrFooter { .. } => {
310 Err(BlockComponentProcessorError::MissingBlockFooter)
311 }
312 }
313 }
314}
315
316#[derive(Default)]
317pub struct BlockComponentProcessor {
318 stage: BlockComponentStage,
319 has_genesis_certificate_marker: bool,
320}
321
322impl BlockComponentProcessor {
323 pub fn on_final(
324 &self,
325 migration_status: &MigrationStatus,
326 slot: Slot,
327 parent_slot: Slot,
328 ) -> Result<(), BlockComponentProcessorError> {
329 if !migration_status.should_allow_block_markers(slot) {
332 if self.stage == BlockComponentStage::PreParentMarker {
333 return Ok(());
334 } else {
335 return Err(BlockComponentProcessorError::BlockComponentPreMigration);
336 };
337 }
338
339 if Self::requires_genesis_certificate_marker(migration_status, parent_slot)
340 && !self.has_genesis_certificate_marker
341 {
342 return Err(BlockComponentProcessorError::MissingGenesisCertificateMarker);
343 }
344
345 self.stage.on_final()
346 }
347
348 fn requires_genesis_certificate_marker(
354 migration_status: &MigrationStatus,
355 parent_slot: Slot,
356 ) -> bool {
357 migration_status
358 .genesis_block()
359 .is_some_and(|genesis_block| {
360 genesis_block.slot != 0 && parent_slot == genesis_block.slot
361 })
362 }
363
364 pub fn on_entry_batch<D: TransactionData>(
370 &mut self,
371 migration_status: &MigrationStatus,
372 slot: Slot,
373 entries: &[EntryView<D>],
374 is_final_component: bool,
375 ) -> Result<(), BlockComponentProcessorError> {
376 if !migration_status.should_allow_block_markers(slot) {
377 return Ok(());
378 }
379
380 let is_alpentick = is_final_component
384 && matches!(entries, [entry] if entry.is_tick() && entry.num_hashes == 1);
385
386 if is_alpentick {
387 self.stage.on_alpentick()
388 } else {
389 self.stage.on_entry_batch()
390 }
391 }
392
393 pub fn on_marker(
400 &mut self,
401 bank: Arc<Bank>,
402 parent_bank: Arc<Bank>,
403 shred_version: u16,
404 marker: VersionedBlockMarker,
405 allow_initial_update_parent: bool,
406 finalization_cert_sender: Option<&Sender<SmallVec<[Certificate; 2]>>>,
407 migration_status: &MigrationStatus,
408 ) -> Result<(), BlockComponentProcessorError> {
409 let slot = bank.slot();
410 let VersionedBlockMarker::V1(marker) = marker;
411
412 let markers_fully_enabled = migration_status.should_allow_block_markers(slot);
413 let in_migration = migration_status.is_in_migration();
414 let fast_leader_handover_active =
415 bank.feature_set.snapshot().alpenglow_fast_leader_handover;
416
417 match marker {
418 BlockMarkerV1::BlockHeader(header) if markers_fully_enabled || in_migration => {
422 self.on_header(header.inner(), bank.parent_slot())
423 }
424 BlockMarkerV1::GenesisCertificate(genesis_cert_block_marker)
425 if markers_fully_enabled || in_migration =>
426 {
427 self.on_genesis_cert_block_marker(
428 bank,
429 shred_version,
430 genesis_cert_block_marker.into_inner(),
431 migration_status,
432 )
433 }
434
435 BlockMarkerV1::BlockFooter(footer) if markers_fully_enabled => self.on_footer(
437 &migration_status.my_pubkey(),
438 bank,
439 parent_bank,
440 shred_version,
441 footer.into_inner(),
442 finalization_cert_sender,
443 ),
444
445 BlockMarkerV1::UpdateParent(update_parent) if markers_fully_enabled => {
446 if fast_leader_handover_active {
447 self.on_update_parent(slot, update_parent.inner(), allow_initial_update_parent)
448 } else {
449 Err(BlockComponentProcessorError::SpuriousUpdateParent)
450 }
451 }
452
453 _ => Err(BlockComponentProcessorError::BlockComponentPreMigration),
455 }
456 }
457
458 pub fn on_genesis_cert_block_marker(
460 &mut self,
461 bank: Arc<Bank>,
462 shred_version: u16,
463 genesis_block_marker: GenesisCertBlockMarker,
464 migration_status: &MigrationStatus,
465 ) -> Result<(), BlockComponentProcessorError> {
466 self.stage.on_genesis_certificate()?;
467 self.process_unvalidated_genesis_cert_block_marker(
468 bank,
469 genesis_block_marker,
470 migration_status,
471 Some(shred_version),
472 )?;
473 Ok(())
474 }
475
476 pub fn on_genesis_cert_block_marker_leader(
478 &mut self,
479 bank: Arc<Bank>,
480 genesis_block_marker: GenesisCertBlockMarker,
481 migration_status: &MigrationStatus,
482 ) -> Result<(), BlockComponentProcessorError> {
483 self.process_unvalidated_genesis_cert_block_marker(
484 bank,
485 genesis_block_marker,
486 migration_status,
487 None,
488 )?;
489 Ok(())
490 }
491
492 fn process_unvalidated_genesis_cert_block_marker(
494 &mut self,
495 bank: Arc<Bank>,
496 genesis_block_marker: GenesisCertBlockMarker,
497 migration_status: &MigrationStatus,
498 shred_version: Option<u16>,
499 ) -> Result<(), BlockComponentProcessorError> {
500 if bank.parent_slot() == 0 {
502 return Err(BlockComponentProcessorError::GenesisCertificateInAlpenglowCluster);
503 }
504
505 let parent_block_id = bank
506 .parent_block_id()
507 .expect("Block id is populated for all slots > 0");
508 if (bank.parent_slot(), parent_block_id)
509 != (genesis_block_marker.slot, genesis_block_marker.block_id)
510 {
511 return Err(BlockComponentProcessorError::GenesisCertificateOnNonChild);
512 }
513
514 if bank.get_alpenglow_genesis_certificate().is_some() {
515 return Err(BlockComponentProcessorError::GenesisCertificateAlreadyPopulated);
516 }
517
518 let genesis_cert = GenesisCert {
519 block: Block {
520 slot: genesis_block_marker.slot,
521 block_id: genesis_block_marker.block_id,
522 },
523 signature: CertSignature {
524 signature: genesis_block_marker.bls_signature,
525 bitmap: genesis_block_marker.bitmap,
526 },
527 };
528 if let Some(shred_version) = shred_version {
529 Self::verify_genesis_certificate(&bank, &genesis_cert, shred_version)?;
530 }
531
532 bank.set_alpenglow_genesis_certificate(&genesis_cert);
533 self.has_genesis_certificate_marker = true;
534
535 if migration_status.is_alpenglow_enabled() {
536 bank.set_hashes_per_tick(None);
538 return Ok(());
539 }
540
541 warn!(
547 "{}: Alpenglow genesis marker processed during replay of {}. Transitioning Alpenglow \
548 to ReadyToEnable",
549 migration_status.my_pubkey(),
550 bank.slot()
551 );
552 migration_status.set_genesis_block(genesis_cert.block);
553 migration_status.set_genesis_certificate(Arc::new(genesis_cert));
554 assert!(migration_status.is_ready_to_enable());
555
556 Err(BlockComponentProcessorError::AlpenglowMigrationTransition)
559 }
560
561 fn verify_genesis_certificate(
562 bank: &Bank,
563 cert: &GenesisCert,
564 shred_version: u16,
565 ) -> Result<(), BlockComponentProcessorError> {
566 let cert_slot = cert.block.slot;
567 let unverified_cert = UnverifiedCertificate {
568 cert_type: CertificateType::Genesis(cert.block),
569 signature: cert.signature.signature,
570 bitmap: cert.signature.bitmap.clone(),
571 shred_version,
572 };
573 bank.verify_certificate(unverified_cert).map_err(|_| {
574 warn!(
575 "Failed to verify genesis certificate for slot {cert_slot} in bank slot {}",
576 bank.slot()
577 );
578 BlockComponentProcessorError::GenesisCertificateFailedVerification
579 })?;
580
581 Ok(())
582 }
583
584 fn on_footer(
585 &mut self,
586 my_pubkey: &Pubkey,
587 bank: Arc<Bank>,
588 parent_bank: Arc<Bank>,
589 shred_version: u16,
590 footer: VersionedBlockFooter,
591 finalization_cert_sender: Option<&Sender<SmallVec<[Certificate; 2]>>>,
592 ) -> Result<(), BlockComponentProcessorError> {
593 self.stage.on_footer()?;
594
595 let VersionedBlockFooter::V1(footer) = footer;
596
597 Self::enforce_nanosecond_clock_bounds(&bank, &parent_bank, &footer)?;
598
599 let BlockFooterV1 {
600 bank_hash,
601 block_producer_time_nanos,
602 block_user_agent: _,
603 block_final_cert,
604 skip_reward_cert,
605 notar_reward_cert,
606 } = footer;
607
608 let reward_cert = ValidatedRewardCert::try_new(
609 &bank,
610 shred_version,
611 &skip_reward_cert,
612 ¬ar_reward_cert,
613 )?;
614 let block_producer_time_nanos =
615 Self::block_producer_time_nanos_as_i64(block_producer_time_nanos)?;
616 let final_cert = block_final_cert
617 .map(|final_cert| {
618 ValidatedBlockFinalizationCert::try_from_footer(final_cert, &bank, shred_version)
619 .map_err(BlockComponentProcessorError::InvalidFinalizationCertificate)
620 })
621 .transpose()?;
622
623 let (footer_input, pool_input) = match final_cert {
624 None => (None, None),
625 Some(cert) => {
626 let (signers, finalize_cert, notarize_cert) = cert.into_parts();
627 let final_slot = finalize_cert.cert_type.slot();
628 (
629 Some((signers, final_slot)),
630 Some((finalize_cert, notarize_cert)),
631 )
632 }
633 };
634
635 Self::update_bank_with_footer_fields(
636 &bank,
637 block_producer_time_nanos,
638 Some(bank_hash),
639 reward_cert,
640 footer_input
641 .as_ref()
642 .map(|(validators, slot)| (validators, *slot)),
643 )?;
644
645 if let Some((finalize_cert, notarize_cert)) = pool_input
647 && let Some(sender) = finalization_cert_sender
648 {
649 let channel_name = "finalization_cert_sender";
650 let certs = match notarize_cert {
651 None => smallvec![finalize_cert],
652 Some(c) => smallvec![finalize_cert, c],
653 };
654 match sender.try_send(certs) {
655 Ok(()) => (),
656 Err(TrySendError::Full(_)) => {
657 warn!("{my_pubkey}: channel \"{channel_name}\" is full, dropping msg")
658 }
659 Err(TrySendError::Disconnected(_)) => {
660 warn!("{my_pubkey}: channel \"{channel_name}\" disconnected")
661 }
662 }
663 }
664
665 Ok(())
666 }
667
668 fn on_header(
669 &mut self,
670 header: &VersionedBlockHeader,
671 bank_parent_slot: Slot,
672 ) -> Result<(), BlockComponentProcessorError> {
673 self.stage.on_header()?;
674
675 let VersionedBlockHeader::V1(header) = header;
676 if header.parent_slot != bank_parent_slot {
677 return Err(BlockComponentProcessorError::HeaderParentSlotMismatch {
678 header_parent_slot: header.parent_slot,
679 bank_parent_slot,
680 });
681 }
682 Ok(())
683 }
684
685 fn on_update_parent(
686 &mut self,
687 slot: Slot,
688 update_parent: &VersionedUpdateParent,
689 allow_initial_update_parent: bool,
690 ) -> Result<(), BlockComponentProcessorError> {
691 if leader_slot_index(slot) != 0 {
692 return Err(BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(slot));
693 }
694
695 self.stage
696 .on_update_parent(update_parent, allow_initial_update_parent)
697 }
698
699 fn enforce_nanosecond_clock_bounds(
700 bank: &Bank,
701 parent_bank: &Bank,
702 footer: &BlockFooterV1,
703 ) -> Result<(), BlockComponentProcessorError> {
704 let parent_time_nanos = parent_bank
707 .get_nanosecond_clock()
708 .unwrap_or_else(|| bank.clock().unix_timestamp.saturating_mul(1_000_000_000));
709
710 let parent_slot = parent_bank.slot();
711 let current_time_nanos =
712 Self::block_producer_time_nanos_as_i64(footer.block_producer_time_nanos)?;
713 let current_slot = bank.slot();
714 let elapsed_slot_duration_nanos =
715 bank.slot_range_duration_nanos(parent_slot.saturating_add(1), current_slot);
716
717 let (lower_bound_nanos, upper_bound_nanos) =
718 Self::nanosecond_time_bounds(parent_time_nanos, elapsed_slot_duration_nanos);
719
720 let is_valid =
721 lower_bound_nanos <= current_time_nanos && current_time_nanos <= upper_bound_nanos;
722
723 match is_valid {
724 true => Ok(()),
725 false => Err(BlockComponentProcessorError::NanosecondClockOutOfBounds),
726 }
727 }
728
729 fn block_producer_time_nanos_as_i64(
736 block_producer_time_nanos: u64,
737 ) -> Result<i64, BlockComponentProcessorError> {
738 i64::try_from(block_producer_time_nanos)
739 .map_err(|_| BlockComponentProcessorError::NanosecondClockOutOfBounds)
740 }
741
742 pub fn nanosecond_time_bounds(
755 parent_time_nanos: i64,
756 elapsed_slot_duration_nanos: u128,
757 ) -> (i64, i64) {
758 let min_working_bank_time = parent_time_nanos.saturating_add(1);
759 let max_working_bank_time_offset = elapsed_slot_duration_nanos
760 .saturating_mul(2)
761 .min(i64::MAX as u128) as i64;
762 let max_working_bank_time = parent_time_nanos.saturating_add(max_working_bank_time_offset);
763
764 (min_working_bank_time, max_working_bank_time)
765 }
766
767 pub fn update_bank_with_footer_fields(
768 bank: &Bank,
769 block_producer_time_nanos: i64,
770 bank_hash: Option<Hash>,
771 reward_cert: Option<ValidatedRewardCert>,
772 final_cert_input: Option<(&HashSet<Pubkey>, Slot)>,
773 ) -> Result<(), BankFooterError> {
774 bank.update_clock_from_footer(block_producer_time_nanos);
775 calc_vote_rewards_update_vote_states(
776 bank,
777 reward_cert,
778 final_cert_input,
779 block_producer_time_nanos,
780 )?;
781
782 if let Some(hash) = bank_hash {
783 bank.set_expected_bank_hash(hash);
785 }
786 Ok(())
787 }
788}
789
790#[cfg(test)]
791mod tests {
792 use {
793 super::*,
794 crate::{
795 bank::{Bank, SlotLeader},
796 bank_forks::BankForks,
797 genesis_utils::{activate_all_features_alpenglow, create_genesis_config},
798 },
799 bytes::Bytes,
800 rand::Rng,
801 solana_bls_signatures::{BLS_SIGNATURE_AFFINE_SIZE, Signature as BLSSignature},
802 solana_clock::DEFAULT_MS_PER_SLOT,
803 solana_entry::block_component::{
804 BlockFooterV1, BlockHeaderV1, UpdateParentV1, VersionedUpdateParent,
805 },
806 solana_hash::Hash,
807 solana_leader_schedule::NUM_CONSECUTIVE_LEADER_SLOTS,
808 std::{
809 assert_matches,
810 sync::{Arc, RwLock},
811 },
812 test_case::test_case,
813 };
814
815 const DEFAULT_NS_PER_SLOT: u64 = DEFAULT_MS_PER_SLOT * 1_000_000;
816
817 fn create_test_bank() -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
818 let genesis_config_info = create_genesis_config(10_000);
819 Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config)
820 }
821
822 fn create_test_bank_alpenglow() -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
823 let mut genesis_config_info = create_genesis_config(10_000);
824 activate_all_features_alpenglow(&mut genesis_config_info.genesis_config);
825 Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config)
826 }
827
828 fn create_child_bank(
829 bank_forks: &RwLock<BankForks>,
830 parent: &Arc<Bank>,
831 slot: u64,
832 ) -> Arc<Bank> {
833 Bank::new_from_parent_with_bank_forks(
834 bank_forks,
835 parent.clone(),
836 SlotLeader::new_unique(),
837 slot,
838 )
839 }
840
841 fn test_genesis_cert_marker() -> GenesisCertBlockMarker {
842 GenesisCertBlockMarker {
843 slot: 0,
844 block_id: Hash::default(),
845 bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
846 bitmap: vec![],
847 }
848 }
849
850 fn post_migration_status_with_genesis_slot(genesis_slot: Slot) -> MigrationStatus {
851 let migration_status = MigrationStatus::default();
852 let migration_slot = migration_status.record_feature_activation(0);
853 assert!(genesis_slot < migration_slot);
854
855 let genesis_block = Block::new_unique(genesis_slot);
856 migration_status.set_genesis_block(genesis_block);
857 let cert = Arc::new(GenesisCert {
858 block: genesis_block,
859 signature: CertSignature {
860 signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
861 bitmap: vec![],
862 },
863 });
864 migration_status.set_genesis_certificate(cert);
865 migration_status.enable_alpenglow_during_startup();
866
867 migration_status
868 }
869
870 fn processor_after_header() -> BlockComponentProcessor {
871 BlockComponentProcessor {
872 stage: BlockComponentStage::AcceptingGenesisOrEntries,
873 ..BlockComponentProcessor::default()
874 }
875 }
876
877 fn processor_after_footer() -> BlockComponentProcessor {
878 BlockComponentProcessor {
879 stage: BlockComponentStage::AcceptingAlpentick,
880 ..BlockComponentProcessor::default()
881 }
882 }
883
884 #[test]
885 fn test_first_alpenglow_block_requires_genesis_certificate_marker() {
886 let migration_status = post_migration_status_with_genesis_slot(1);
887 let processor = processor_after_footer();
888
889 let result = processor.on_final(&migration_status, 2, 1);
890 assert!(matches!(
891 result,
892 Err(BlockComponentProcessorError::MissingGenesisCertificateMarker)
893 ));
894 }
895
896 #[test]
897 fn test_first_alpenglow_block_with_genesis_certificate_marker_succeeds() {
898 let migration_status = post_migration_status_with_genesis_slot(1);
899 let (genesis_bank, bank_forks) = create_test_bank();
900 let parent = create_child_bank(&bank_forks, &genesis_bank, 1);
901 let parent_block_id = Hash::new_unique();
902 parent.set_block_id(Some(parent_block_id));
903 let bank = create_child_bank(&bank_forks, &parent, 2);
904 let genesis_marker = GenesisCertBlockMarker {
905 slot: parent.slot(),
906 block_id: parent_block_id,
907 bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
908 bitmap: vec![],
909 };
910 let mut processor = processor_after_header();
911 bank.set_hashes_per_tick(Some(42));
912 assert!(bank.hashes_per_tick().is_some());
913
914 processor
915 .on_genesis_cert_block_marker_leader(bank.clone(), genesis_marker, &migration_status)
916 .unwrap();
917 assert!(bank.hashes_per_tick().is_none());
918 processor.stage = BlockComponentStage::Done;
919 assert!(processor.on_final(&migration_status, 2, 1).is_ok());
920 }
921
922 #[test]
923 fn test_genesis_certificate_marker_aborts_tower_bank_during_migration() {
924 let migration_status = MigrationStatus::default();
925 migration_status.record_feature_activation(0);
926 let (genesis_bank, bank_forks) = create_test_bank();
927 let parent = create_child_bank(&bank_forks, &genesis_bank, 1);
928 let parent_block_id = Hash::new_unique();
929 parent.set_block_id(Some(parent_block_id));
930 let bank = create_child_bank(&bank_forks, &parent, 2);
931 let genesis_marker = GenesisCertBlockMarker {
932 slot: parent.slot(),
933 block_id: parent_block_id,
934 bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
935 bitmap: vec![],
936 };
937 let mut processor = processor_after_header();
938 bank.set_hashes_per_tick(Some(42));
939 let tower_hashes_per_tick = bank.hashes_per_tick();
940 assert!(tower_hashes_per_tick.is_some());
941
942 assert_matches!(
943 processor.on_genesis_cert_block_marker_leader(
944 bank.clone(),
945 genesis_marker,
946 &migration_status,
947 ),
948 Err(BlockComponentProcessorError::AlpenglowMigrationTransition)
949 );
950
951 assert!(migration_status.is_ready_to_enable());
952 assert_eq!(bank.hashes_per_tick(), tower_hashes_per_tick);
953 assert!(bank.get_alpenglow_genesis_certificate().is_some());
954 }
955
956 #[test]
957 fn test_on_footer_sets_timestamp() {
958 let my_pubkey = Pubkey::new_unique();
959 let mut processor = processor_after_header();
960
961 let (parent, bank_forks) = create_test_bank();
962 let bank = create_child_bank(&bank_forks, &parent, 1);
963 let shred_version = rand::rng().random();
964
965 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
967 let footer_time_nanos = parent_time_nanos + 200_000_000; let expected_time_secs = footer_time_nanos / 1_000_000_000;
969
970 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
971 bank_hash: Hash::new_unique(),
972 block_producer_time_nanos: footer_time_nanos as u64,
973 block_user_agent: vec![],
974 block_final_cert: None,
975 skip_reward_cert: None,
976 notar_reward_cert: None,
977 });
978
979 processor
980 .on_footer(
981 &my_pubkey,
982 bank.clone(),
983 parent,
984 shred_version,
985 footer,
986 None,
987 )
988 .unwrap();
989
990 assert_eq!(processor.stage, BlockComponentStage::AcceptingAlpentick);
991
992 assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
994 }
995
996 #[test]
997 fn test_footer_sets_epoch_start_timestamp_on_epoch_change() {
998 let my_pubkey = Pubkey::new_unique();
999 let mut processor = processor_after_header();
1000 let shred_version = rand::rng().random();
1001
1002 let genesis_config_info = create_genesis_config(10_000);
1004 let (genesis_bank, bank_forks) =
1005 Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config);
1006
1007 let epoch_schedule = genesis_bank.epoch_schedule();
1009 let first_slot_in_epoch_1 = epoch_schedule.get_first_slot_in_epoch(1);
1010
1011 let mut parent = genesis_bank.clone();
1013 for slot in 1..first_slot_in_epoch_1 {
1014 parent = create_child_bank(&bank_forks, &parent, slot);
1015 }
1016
1017 let bank = create_child_bank(&bank_forks, &parent, first_slot_in_epoch_1);
1019
1020 assert_eq!(bank.epoch(), 1);
1022
1023 let parent_slot = parent.slot();
1025 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1026 let current_slot = bank.slot();
1027 let elapsed_slot_duration_nanos =
1028 bank.slot_range_duration_nanos(parent_slot.saturating_add(1), current_slot);
1029
1030 let (lower_bound, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1032 parent_time_nanos,
1033 elapsed_slot_duration_nanos,
1034 );
1035 let footer_time_nanos = (lower_bound + upper_bound) / 2;
1036 let expected_time_secs = footer_time_nanos / 1_000_000_000;
1037
1038 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1039 bank_hash: Hash::new_unique(),
1040 block_producer_time_nanos: footer_time_nanos as u64,
1041 block_user_agent: vec![],
1042 block_final_cert: None,
1043 skip_reward_cert: None,
1044 notar_reward_cert: None,
1045 });
1046
1047 processor
1048 .on_footer(
1049 &my_pubkey,
1050 bank.clone(),
1051 parent,
1052 shred_version,
1053 footer,
1054 None,
1055 )
1056 .unwrap();
1057
1058 assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1060
1061 assert_eq!(bank.clock().epoch_start_timestamp, expected_time_secs);
1063 }
1064
1065 #[test_case(1, |_, lower, _| lower, true; "at_minimum")]
1067 #[test_case(1, |_, _, upper| upper, true; "at_maximum")]
1068 #[test_case(1, |_, lower, _| lower - 1, false; "below_minimum")]
1069 #[test_case(1, |_, _, upper| upper + 1, false; "above_maximum")]
1070 #[test_case(5, |_, lower, _| lower + 2_000_000_000, true; "multi_slot_gap")]
1073 #[test_case(5, |_, _, upper| upper + 1_000_000_000, false; "multi_slot_gap_exceeds")]
1075 #[test_case(1, |parent_time, _, _| parent_time, false; "timestamp_equals_parent")]
1077 fn test_clock_bounds(
1078 slot_gap: u64,
1079 timestamp_fn: impl FnOnce(i64, i64, i64) -> i64,
1080 should_pass: bool,
1081 ) {
1082 let my_pubkey = Pubkey::new_unique();
1083 let mut processor = processor_after_header();
1084 let shred_version = rand::rng().random();
1085
1086 let (parent, bank_forks) = create_test_bank_alpenglow();
1087 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1088
1089 parent.update_clock_from_footer(parent_time_nanos);
1091
1092 let bank: Arc<Bank> = create_child_bank(&bank_forks, &parent, slot_gap);
1093 let elapsed_slot_duration_nanos = bank.slot_range_duration_nanos(1, slot_gap);
1094
1095 let (lower_bound, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1096 parent_time_nanos,
1097 elapsed_slot_duration_nanos,
1098 );
1099
1100 let footer_time_nanos = timestamp_fn(parent_time_nanos, lower_bound, upper_bound);
1101
1102 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1103 bank_hash: Hash::new_unique(),
1104 block_producer_time_nanos: footer_time_nanos as u64,
1105 block_user_agent: vec![],
1106 block_final_cert: None,
1107 skip_reward_cert: None,
1108 notar_reward_cert: None,
1109 });
1110
1111 let result = processor.on_footer(&my_pubkey, bank, parent, shred_version, footer, None);
1112 if should_pass {
1113 result.unwrap();
1114 } else {
1115 assert!(matches!(
1116 result.unwrap_err(),
1117 BlockComponentProcessorError::NanosecondClockOutOfBounds
1118 ));
1119 }
1120 }
1121
1122 #[test]
1123 fn test_clock_bounds_without_parent_nanosecond_clock_rejects_out_of_bounds() {
1124 let my_pubkey = Pubkey::new_unique();
1125 let mut processor = processor_after_header();
1126 let shred_version = rand::rng().random();
1127
1128 let (parent, bank_forks) = create_test_bank_alpenglow();
1129 assert_eq!(parent.get_nanosecond_clock(), None);
1130
1131 let bank = create_child_bank(&bank_forks, &parent, 1);
1132 let parent_time_nanos = bank.clock().unix_timestamp.saturating_mul(1_000_000_000);
1133 let elapsed_slot_duration_nanos =
1134 bank.slot_range_duration_nanos(parent.slot().saturating_add(1), bank.slot());
1135 let (_, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1136 parent_time_nanos,
1137 elapsed_slot_duration_nanos,
1138 );
1139
1140 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1141 bank_hash: Hash::new_unique(),
1142 block_producer_time_nanos: u64::try_from(upper_bound.saturating_add(1)).unwrap(),
1143 block_user_agent: vec![],
1144 block_final_cert: None,
1145 skip_reward_cert: None,
1146 notar_reward_cert: None,
1147 });
1148
1149 assert!(matches!(
1150 processor
1151 .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1152 .unwrap_err(),
1153 BlockComponentProcessorError::NanosecondClockOutOfBounds
1154 ));
1155 }
1156
1157 #[test]
1158 fn test_clock_bounds_rejects_timestamp_above_i64() {
1159 let my_pubkey = Pubkey::new_unique();
1160 let mut processor = processor_after_header();
1161 let shred_version = rand::rng().random();
1162
1163 let (parent, bank_forks) = create_test_bank_alpenglow();
1164 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1165 parent.update_clock_from_footer(parent_time_nanos);
1166 let bank = create_child_bank(&bank_forks, &parent, 1);
1167
1168 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1169 bank_hash: Hash::new_unique(),
1170 block_producer_time_nanos: u64::MAX,
1171 block_user_agent: vec![],
1172 block_final_cert: None,
1173 skip_reward_cert: None,
1174 notar_reward_cert: None,
1175 });
1176
1177 assert!(matches!(
1178 processor
1179 .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1180 .unwrap_err(),
1181 BlockComponentProcessorError::NanosecondClockOutOfBounds
1182 ));
1183 }
1184
1185 fn test_nanosecond_time_bounds_helper(
1187 parent_time_nanos: i64,
1188 elapsed_slot_duration_nanos: u128,
1189 expected_lower: i64,
1190 expected_upper: i64,
1191 ) {
1192 let (lower, upper) = BlockComponentProcessor::nanosecond_time_bounds(
1193 parent_time_nanos,
1194 elapsed_slot_duration_nanos,
1195 );
1196
1197 assert_eq!(lower, expected_lower);
1198 assert_eq!(upper, expected_upper);
1199 }
1200
1201 #[test]
1202 fn test_nanosecond_time_bounds_calculation() {
1203 let parent_slot = 10;
1208 let parent_time = 1_000_000_000_000; let working_slot = 15;
1210 let slot_delta = working_slot - parent_slot;
1211 test_nanosecond_time_bounds_helper(
1212 parent_time,
1213 u128::from(slot_delta).saturating_mul(u128::from(DEFAULT_NS_PER_SLOT)),
1214 parent_time + 1,
1215 parent_time + (2 * DEFAULT_NS_PER_SLOT * slot_delta) as i64,
1216 );
1217 }
1218
1219 #[test]
1220 fn test_nanosecond_time_bounds_same_slot() {
1221 let parent_time = 1_000_000_000_000;
1228 test_nanosecond_time_bounds_helper(parent_time, 0, parent_time + 1, parent_time);
1229 }
1230
1231 #[test]
1232 fn test_nanosecond_time_bounds_saturates_upper_bound() {
1233 let parent_time = i64::MAX - 5;
1234 let (lower, upper) =
1235 BlockComponentProcessor::nanosecond_time_bounds(parent_time, u128::MAX);
1236
1237 assert_eq!(lower, parent_time + 1);
1238 assert_eq!(upper, i64::MAX);
1239 }
1240
1241 #[test]
1245 fn test_processor_component_sequences() {
1246 use BlockComponentProcessorError as E;
1247
1248 type Step = Box<
1249 dyn FnOnce(
1250 &mut BlockComponentProcessor,
1251 &MigrationStatus,
1252 ) -> Result<(), BlockComponentProcessorError>,
1253 >;
1254
1255 let post_migration = MigrationStatus::post_migration_status();
1256 let pre_migration = MigrationStatus::default();
1257 let in_migration = MigrationStatus::default();
1259 in_migration.record_feature_activation(0);
1260
1261 let (parent, bank_forks) = create_test_bank();
1262 let slot = NUM_CONSECUTIVE_LEADER_SLOTS.get() as Slot;
1264 let bank = create_child_bank(&bank_forks, &parent, slot);
1265 assert_eq!(leader_slot_index(slot), 0);
1266 let bank_not_window_start = create_child_bank(&bank_forks, &parent, slot + 1);
1269 assert_ne!(leader_slot_index(slot + 1), 0);
1270 let shred_version: u16 = rand::rng().random();
1271
1272 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1274 let footer_time_nanos = u64::try_from(parent_time_nanos + 400_000_000).unwrap();
1275
1276 let marker_step = {
1278 let (bank, parent) = (bank.clone(), parent.clone());
1279 move |marker: VersionedBlockMarker, allow_initial_update_parent: bool| -> Step {
1280 let (bank, parent) = (bank.clone(), parent.clone());
1281 Box::new(move |processor, migration_status| {
1282 processor.on_marker(
1283 bank,
1284 parent,
1285 shred_version,
1286 marker,
1287 allow_initial_update_parent,
1288 None,
1289 migration_status,
1290 )
1291 })
1292 }
1293 };
1294 let batch_step = move |entries: Vec<EntryView<Bytes>>, is_final: bool| -> Step {
1296 Box::new(move |processor, migration_status| {
1297 processor.on_entry_batch(migration_status, slot, &entries, is_final)
1298 })
1299 };
1300
1301 let header_with_parent_slot = |parent_slot: Slot| {
1303 marker_step(
1304 VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1305 parent_slot,
1306 parent_block_id: Hash::default(),
1307 }),
1308 false,
1309 )
1310 };
1311 let header = || header_with_parent_slot(0);
1312 let genesis_cert = || {
1313 marker_step(
1314 VersionedBlockMarker::from_genesis_cert_block_marker(test_genesis_cert_marker()),
1315 false,
1316 )
1317 };
1318 let update_parent = |allow_initial_update_parent: bool| {
1319 marker_step(
1320 VersionedBlockMarker::from_update_parent(UpdateParentV1 {
1321 new_parent_slot: 0,
1322 new_parent_block_id: Hash::default(),
1323 }),
1324 allow_initial_update_parent,
1325 )
1326 };
1327 let update_parent_not_window_start = {
1330 let (bank, parent) = (bank_not_window_start.clone(), parent.clone());
1331 move |allow_initial_update_parent: bool| -> Step {
1332 let (bank, parent) = (bank.clone(), parent.clone());
1333 Box::new(move |processor, migration_status| {
1334 processor.on_marker(
1335 bank,
1336 parent,
1337 shred_version,
1338 VersionedBlockMarker::from_update_parent(UpdateParentV1 {
1339 new_parent_slot: 0,
1340 new_parent_block_id: Hash::default(),
1341 }),
1342 allow_initial_update_parent,
1343 None,
1344 migration_status,
1345 )
1346 })
1347 }
1348 };
1349 let footer = || {
1350 marker_step(
1351 VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1352 bank_hash: Hash::new_unique(),
1353 block_producer_time_nanos: footer_time_nanos,
1354 block_user_agent: vec![],
1355 block_final_cert: None,
1356 skip_reward_cert: None,
1357 notar_reward_cert: None,
1358 }),
1359 false,
1360 )
1361 };
1362 let entries = || {
1363 batch_step(
1364 vec![EntryView {
1365 num_hashes: 2,
1366 hash: Hash::default(),
1367 transactions: vec![],
1368 }],
1369 false,
1370 )
1371 };
1372 let final_entries = || {
1375 batch_step(
1376 vec![EntryView {
1377 num_hashes: 2,
1378 hash: Hash::default(),
1379 transactions: vec![],
1380 }],
1381 true,
1382 )
1383 };
1384 let tick = |is_final: bool| {
1387 batch_step(
1388 vec![EntryView {
1389 num_hashes: 1,
1390 hash: Hash::default(),
1391 transactions: vec![],
1392 }],
1393 is_final,
1394 )
1395 };
1396
1397 let abandoned = || {
1398 E::AbandonedBank(VersionedUpdateParent::V1(UpdateParentV1 {
1399 new_parent_slot: 0,
1400 new_parent_block_id: Hash::default(),
1401 }))
1402 };
1403
1404 #[rustfmt::skip]
1405 let cases: Vec<(&MigrationStatus, Vec<Step>, Result<(), E>)> = vec![
1406 (&pre_migration, vec![entries(), tick(true)], Ok(())),
1408 (&pre_migration, vec![entries(), tick(false)], Ok(())),
1409 (&pre_migration, vec![tick(false)], Ok(())),
1410 (&pre_migration, vec![tick(true), entries()], Ok(())),
1411 (&pre_migration, vec![entries(), entries(), tick(false), tick(true)], Ok(())),
1412 (&pre_migration, vec![header()], Err(E::BlockComponentPreMigration)),
1413 (&pre_migration, vec![genesis_cert()], Err(E::BlockComponentPreMigration)),
1414 (&pre_migration, vec![footer()], Err(E::BlockComponentPreMigration)),
1415 (&pre_migration, vec![update_parent(false)], Err(E::BlockComponentPreMigration)),
1416 (&pre_migration, vec![update_parent(true)], Err(E::BlockComponentPreMigration)),
1417
1418 (&in_migration, vec![entries(), tick(true)], Ok(())),
1422 (&in_migration, vec![header()], Err(E::BlockComponentPreMigration)),
1423 (&in_migration, vec![genesis_cert()], Err(E::MissingParentMarker)),
1425 (&in_migration, vec![header(), genesis_cert()], Err(E::GenesisCertificateInAlpenglowCluster)),
1427 (&in_migration, vec![footer()], Err(E::BlockComponentPreMigration)),
1428 (&in_migration, vec![update_parent(false)], Err(E::BlockComponentPreMigration)),
1429 (&in_migration, vec![update_parent(true)], Err(E::BlockComponentPreMigration)),
1430
1431 (&post_migration, vec![header(), footer(), tick(true)], Ok(())),
1437 (&post_migration, vec![header(), entries(), entries(), footer(), tick(true)], Ok(())),
1438 (&post_migration, vec![update_parent(true), entries(), footer(), tick(true)], Ok(())),
1439 (&post_migration, vec![update_parent(true), footer(), tick(true)], Ok(())),
1440 (&post_migration, vec![header(), tick(false), footer(), tick(true)], Ok(())),
1442 (&post_migration, vec![update_parent(true), tick(false), footer(), tick(true)], Ok(())),
1443
1444 (&post_migration, vec![], Err(E::MissingBlockFooter)),
1446
1447 (&post_migration, vec![entries()], Err(E::MissingParentMarker)),
1449 (&post_migration, vec![genesis_cert()], Err(E::MissingParentMarker)),
1450 (&post_migration, vec![footer()], Err(E::MissingParentMarker)),
1451 (&post_migration, vec![tick(true)], Err(E::MissingParentMarker)),
1452 (&post_migration, vec![tick(false)], Err(E::MissingParentMarker)),
1453
1454 (&post_migration, vec![update_parent(false)], Err(E::UnexpectedInitialUpdateParent)),
1456
1457 (&post_migration, vec![header(), entries(), genesis_cert()], Err(E::GenesisCertificateOutOfOrder)),
1459 (&post_migration, vec![header(), footer(), genesis_cert()], Err(E::GenesisCertificateOutOfOrder)),
1460 (&post_migration, vec![update_parent(true), genesis_cert()], Err(E::GenesisCertificateOutOfOrder)),
1461 (&post_migration, vec![header(), genesis_cert()], Err(E::GenesisCertificateInAlpenglowCluster)),
1463
1464 (&post_migration, vec![header(), header()], Err(E::MultipleBlockHeaders)),
1466 (&post_migration, vec![header(), entries(), header()], Err(E::MultipleBlockHeaders)),
1467 (&post_migration, vec![header(), footer(), header()], Err(E::MultipleBlockHeaders)),
1468 (&post_migration, vec![update_parent(true), header()], Err(E::SpuriousUpdateParent)),
1469 (&post_migration, vec![update_parent(true), update_parent(true)], Err(E::MultipleUpdateParents)),
1470 (&post_migration, vec![update_parent(true), update_parent(false)], Err(E::MultipleUpdateParents)),
1471
1472 (&post_migration, vec![header_with_parent_slot(3)], Err(E::HeaderParentSlotMismatch { header_parent_slot: 3, bank_parent_slot: 0 })),
1474
1475 (&post_migration, vec![update_parent_not_window_start(true)], Err(E::UpdateParentNotFirstInLeaderWindow(5))),
1478 (&post_migration, vec![update_parent_not_window_start(false)], Err(E::UpdateParentNotFirstInLeaderWindow(5))),
1479
1480 (&post_migration, vec![header(), update_parent(false)], Err(abandoned())),
1483 (&post_migration, vec![header(), update_parent(true)], Err(abandoned())),
1484 (&post_migration, vec![header(), entries(), update_parent(false)], Err(abandoned())),
1485 (&post_migration, vec![header(), footer(), update_parent(false)], Err(E::SpuriousUpdateParent)),
1486 (&post_migration, vec![header(), footer(), update_parent(true)], Err(E::SpuriousUpdateParent)),
1487
1488 (&post_migration, vec![header(), tick(true)], Err(E::InvalidAlpentickPosition)),
1490 (&post_migration, vec![header(), entries(), tick(true)], Err(E::InvalidAlpentickPosition)),
1491 (&post_migration, vec![update_parent(true), tick(true)], Err(E::InvalidAlpentickPosition)),
1492
1493 (&post_migration, vec![header(), footer(), entries(), tick(true)], Err(E::EntryBatchAfterBlockFooter)),
1495 (&post_migration, vec![header(), footer(), footer()], Err(E::MultipleBlockFooters)),
1496 (&post_migration, vec![header(), footer(), tick(false), tick(true)], Err(E::EntryBatchAfterBlockFooter)),
1498 (&post_migration, vec![header(), footer(), final_entries()], Err(E::EntryBatchAfterBlockFooter)),
1500
1501 (&post_migration, vec![header(), footer(), tick(true), header()], Err(E::MultipleBlockHeaders)),
1503 (&post_migration, vec![header(), footer(), tick(true), genesis_cert()], Err(E::GenesisCertificateOutOfOrder)),
1504 (&post_migration, vec![header(), footer(), tick(true), entries()], Err(E::EntryBatchAfterBlockFooter)),
1505 (&post_migration, vec![header(), footer(), tick(true), tick(false)], Err(E::EntryBatchAfterBlockFooter)),
1506 (&post_migration, vec![header(), footer(), tick(true), tick(true)], Err(E::InvalidAlpentickPosition)),
1508 (&post_migration, vec![header(), footer(), tick(true), footer()], Err(E::MultipleBlockFooters)),
1509 (&post_migration, vec![header(), footer(), tick(true), update_parent(false)], Err(E::SpuriousUpdateParent)),
1510
1511 (&post_migration, vec![header()], Err(E::MissingBlockFooter)),
1513 (&post_migration, vec![header(), entries()], Err(E::MissingBlockFooter)),
1514 (&post_migration, vec![update_parent(true), entries()], Err(E::MissingBlockFooter)),
1515 (&post_migration, vec![header(), footer()], Err(E::InvalidAlpentickPosition)),
1517 ];
1518
1519 for (case_index, (migration_status, steps, expected)) in cases.into_iter().enumerate() {
1520 let mut processor = BlockComponentProcessor::default();
1521 let result = steps
1522 .into_iter()
1523 .try_for_each(|step| step(&mut processor, migration_status))
1524 .and_then(|()| processor.on_final(migration_status, slot, bank.parent_slot()));
1525
1526 match (result, expected) {
1527 (Ok(()), Ok(())) => (),
1528 (Err(err), Err(expected_err)) => {
1529 assert_eq!(
1530 std::mem::discriminant(&err),
1531 std::mem::discriminant(&expected_err),
1532 "case {case_index}: got {err:?}, expected {expected_err:?}"
1533 );
1534 }
1535 (result, expected) => {
1536 panic!("case {case_index}: got {result:?}, expected {expected:?}");
1537 }
1538 }
1539 }
1540 }
1541}