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_votor_messages::{
14 certificate::{Certificate, CertificateType},
15 consensus_message::{Block, ConsensusMessage},
16 migration::MigrationStatus,
17 unverified_vote_message::UnverifiedCertificate,
18 },
19 crossbeam_channel::Sender,
20 log::*,
21 solana_clock::Slot,
22 solana_entry::block_component::{
23 BlockFooterV1, BlockMarkerV1, GenesisCertBlockMarker, VersionedBlockFooter,
24 VersionedBlockHeader, VersionedBlockMarker, VersionedUpdateParent,
25 },
26 solana_hash::Hash,
27 solana_pubkey::Pubkey,
28 std::{collections::HashSet, sync::Arc},
29 thiserror::Error,
30};
31
32pub(crate) mod vote_reward;
33
34#[derive(Debug, Error)]
35pub enum BankFooterError {
36 #[error("calc vote rewards updating vote states failed with \"{0}\"")]
37 CalcVoteRewardUpdateVoteStates(#[from] CalcVoteRewardUpdateVoteStatesError),
38}
39
40#[derive(Debug, Error)]
41pub enum BlockComponentProcessorError {
42 #[error("BlockComponent detected pre-migration")]
43 BlockComponentPreMigration,
44 #[error("GenesisCertificate marker detected when GenesisCertificate is already populated")]
45 GenesisCertificateAlreadyPopulated,
46 #[error("GenesisCertificate marker detected when the cluster has Alpenglow enabled at slot 0")]
47 GenesisCertificateInAlpenglowCluster,
48 #[error("GenesisCertificate marker detected on a block which is not a child of genesis")]
49 GenesisCertificateOnNonChild,
50 #[error("GenesisCertificate was invalid and failed to verify")]
51 GenesisCertificateFailedVerification,
52 #[error("GenesisCertificate marker must immediately follow the block header")]
53 GenesisCertificateOutOfOrder,
54 #[error("FinalizationCertificate was invalid or failed to verify {0}")]
55 InvalidFinalizationCertificate(#[from] BlockFinalizationCertError),
56 #[error("Missing block footer")]
57 MissingBlockFooter,
58 #[error("Missing parent marker (neither a header nor an update parent was present)")]
59 MissingParentMarker,
60 #[error("Multiple block footers detected")]
61 MultipleBlockFooters,
62 #[error("Multiple block headers detected")]
63 MultipleBlockHeaders,
64 #[error(
65 "Block header parent slot mismatch: header={header_parent_slot}, bank={bank_parent_slot}"
66 )]
67 HeaderParentSlotMismatch {
68 header_parent_slot: Slot,
69 bank_parent_slot: Slot,
70 },
71 #[error("Multiple update parents detected")]
72 MultipleUpdateParents,
73 #[error("Nanosecond clock out of bounds")]
74 NanosecondClockOutOfBounds,
75 #[error("Spurious update parent")]
76 SpuriousUpdateParent,
77 #[error("UpdateParent marker is only valid in the first slot of a leader window: slot {0}")]
78 UpdateParentNotFirstInLeaderWindow(Slot),
79 #[error(
80 "UpdateParent cannot be the initial parent marker unless replay starts at UpdateParent"
81 )]
82 UnexpectedInitialUpdateParent,
83 #[error("Abandoned bank")]
84 AbandonedBank(VersionedUpdateParent),
85 #[error("invalid reward certs {0}")]
86 InvalidRewardCerts(#[from] ValidatedRewardCertError),
87 #[error("updating bank footer failed with \"{0}\"")]
88 UpdateBankFooter(#[from] BankFooterError),
89}
90
91impl BlockComponentProcessorError {
92 pub fn is_update_parent_recoverable_replay_error(&self) -> bool {
93 match self {
94 BlockComponentProcessorError::MissingParentMarker
95 | BlockComponentProcessorError::MultipleBlockFooters
96 | BlockComponentProcessorError::MultipleBlockHeaders
97 | BlockComponentProcessorError::HeaderParentSlotMismatch { .. }
98 | BlockComponentProcessorError::NanosecondClockOutOfBounds
99 | BlockComponentProcessorError::UnexpectedInitialUpdateParent
100 | BlockComponentProcessorError::GenesisCertificateOutOfOrder
101 | BlockComponentProcessorError::AbandonedBank(_)
102 | BlockComponentProcessorError::InvalidRewardCerts(_)
103 | BlockComponentProcessorError::UpdateBankFooter(_)
104 | BlockComponentProcessorError::InvalidFinalizationCertificate(_) => true,
105 BlockComponentProcessorError::BlockComponentPreMigration
106 | BlockComponentProcessorError::GenesisCertificateAlreadyPopulated
107 | BlockComponentProcessorError::GenesisCertificateInAlpenglowCluster
108 | BlockComponentProcessorError::GenesisCertificateOnNonChild
109 | BlockComponentProcessorError::GenesisCertificateFailedVerification
110 | BlockComponentProcessorError::MissingBlockFooter
111 | BlockComponentProcessorError::MultipleUpdateParents
112 | BlockComponentProcessorError::SpuriousUpdateParent
113 | BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(_) => false,
114 }
115 }
116}
117
118#[derive(Default)]
119pub struct BlockComponentProcessor {
120 has_header: bool,
121 has_footer: bool,
122 has_entry_batch: bool,
123 update_parent: Option<VersionedUpdateParent>,
124}
125
126impl BlockComponentProcessor {
127 pub fn on_final(
128 &self,
129 migration_status: &MigrationStatus,
130 slot: Slot,
131 ) -> Result<(), BlockComponentProcessorError> {
132 if !migration_status.should_allow_block_markers(slot) {
134 return Ok(());
135 }
136
137 if !self.has_footer {
139 return Err(BlockComponentProcessorError::MissingBlockFooter);
140 }
141
142 if !self.has_header && self.update_parent.is_none() {
143 return Err(BlockComponentProcessorError::MissingParentMarker);
144 }
145
146 Ok(())
147 }
148
149 pub fn on_entry_batch(
154 &mut self,
155 migration_status: &MigrationStatus,
156 slot: Slot,
157 ) -> Result<(), BlockComponentProcessorError> {
158 if !migration_status.should_allow_block_markers(slot) {
159 self.has_entry_batch = true;
160 return Ok(());
161 }
162
163 if !self.has_header && self.update_parent.is_none() {
165 return Err(BlockComponentProcessorError::MissingParentMarker);
166 }
167
168 self.has_entry_batch = true;
169 Ok(())
170 }
171
172 pub fn on_marker(
179 &mut self,
180 bank: Arc<Bank>,
181 parent_bank: Arc<Bank>,
182 shred_version: u16,
183 marker: VersionedBlockMarker,
184 allow_initial_update_parent: bool,
185 finalization_cert_sender: Option<&Sender<ConsensusMessage>>,
186 migration_status: &MigrationStatus,
187 ) -> Result<(), BlockComponentProcessorError> {
188 let slot = bank.slot();
189 let VersionedBlockMarker::V1(marker) = marker;
190
191 let markers_fully_enabled = migration_status.should_allow_block_markers(slot);
192 let in_migration = migration_status.is_in_migration();
193 let fast_leader_handover_active =
194 bank.feature_set.snapshot().alpenglow_fast_leader_handover;
195
196 match marker {
197 BlockMarkerV1::BlockHeader(header) if markers_fully_enabled || in_migration => {
201 self.on_header(header.inner(), bank.parent_slot())
202 }
203 BlockMarkerV1::GenesisCertificate(genesis_cert_block_marker)
204 if markers_fully_enabled || in_migration =>
205 {
206 self.on_genesis_cert_block_marker(
207 bank,
208 shred_version,
209 genesis_cert_block_marker.into_inner(),
210 migration_status,
211 )
212 }
213
214 BlockMarkerV1::BlockFooter(footer) if markers_fully_enabled => self.on_footer(
216 bank,
217 parent_bank,
218 shred_version,
219 footer.into_inner(),
220 finalization_cert_sender,
221 ),
222
223 BlockMarkerV1::UpdateParent(update_parent) if markers_fully_enabled => {
224 if fast_leader_handover_active {
225 self.on_update_parent(slot, update_parent.inner(), allow_initial_update_parent)
226 } else {
227 Err(BlockComponentProcessorError::SpuriousUpdateParent)
228 }
229 }
230
231 _ => Err(BlockComponentProcessorError::BlockComponentPreMigration),
233 }
234 }
235
236 pub fn on_genesis_cert_block_marker(
238 &self,
239 bank: Arc<Bank>,
240 shred_version: u16,
241 genesis_block_marker: GenesisCertBlockMarker,
242 migration_status: &MigrationStatus,
243 ) -> Result<(), BlockComponentProcessorError> {
244 self.validate_genesis_cert_block_marker_position()?;
245 self.process_genesis_cert_block_marker(
246 bank,
247 genesis_block_marker,
248 migration_status,
249 Some(shred_version),
250 )
251 }
252
253 pub fn on_genesis_cert_block_marker_leader(
256 &self,
257 bank: Arc<Bank>,
258 genesis_block_marker: GenesisCertBlockMarker,
259 migration_status: &MigrationStatus,
260 ) -> Result<(), BlockComponentProcessorError> {
261 self.process_genesis_cert_block_marker(bank, genesis_block_marker, migration_status, None)
262 }
263
264 fn validate_genesis_cert_block_marker_position(
265 &self,
266 ) -> Result<(), BlockComponentProcessorError> {
267 if !self.has_header {
268 return Err(BlockComponentProcessorError::MissingParentMarker);
269 }
270
271 if self.has_entry_batch || self.has_footer || self.update_parent.is_some() {
272 return Err(BlockComponentProcessorError::GenesisCertificateOutOfOrder);
273 }
274
275 Ok(())
276 }
277
278 fn process_genesis_cert_block_marker(
280 &self,
281 bank: Arc<Bank>,
282 genesis_block_marker: GenesisCertBlockMarker,
283 migration_status: &MigrationStatus,
284 shred_version: Option<u16>,
285 ) -> Result<(), BlockComponentProcessorError> {
286 if bank.parent_slot() == 0 {
288 return Err(BlockComponentProcessorError::GenesisCertificateInAlpenglowCluster);
289 }
290
291 let parent_block_id = bank
292 .parent_block_id()
293 .expect("Block id is populated for all slots > 0");
294 if (bank.parent_slot(), parent_block_id)
295 != (genesis_block_marker.slot, genesis_block_marker.block_id)
296 {
297 return Err(BlockComponentProcessorError::GenesisCertificateOnNonChild);
298 }
299
300 if bank.get_alpenglow_genesis_certificate().is_some() {
301 return Err(BlockComponentProcessorError::GenesisCertificateAlreadyPopulated);
302 }
303
304 let genesis_cert_type = CertificateType::Genesis(Block {
305 slot: genesis_block_marker.slot,
306 block_id: genesis_block_marker.block_id,
307 });
308 let genesis_cert = match shred_version {
309 Some(shred_version) => {
310 let unverified_genesis_cert = UnverifiedCertificate {
311 cert_type: genesis_cert_type,
312 signature: genesis_block_marker.bls_signature,
313 bitmap: genesis_block_marker.bitmap,
314 shred_version,
315 };
316 Self::verify_genesis_certificate(&bank, unverified_genesis_cert)?
317 }
318 None => Certificate {
319 cert_type: genesis_cert_type,
320 signature: genesis_block_marker.bls_signature,
321 bitmap: genesis_block_marker.bitmap,
322 },
323 };
324 bank.set_alpenglow_genesis_certificate(&genesis_cert);
325 bank.set_hashes_per_tick(None);
326
327 if migration_status.is_alpenglow_enabled() {
328 return Ok(());
330 }
331
332 warn!(
338 "{}: Alpenglow genesis marker processed during replay of {}. Transitioning Alpenglow \
339 to ReadyToEnable",
340 migration_status.my_pubkey(),
341 bank.slot()
342 );
343 migration_status.set_genesis_block(
344 genesis_cert
345 .cert_type
346 .to_block()
347 .expect("Genesis cert must correspond to a block"),
348 );
349 migration_status.set_genesis_certificate(Arc::new(genesis_cert));
350 assert!(migration_status.is_ready_to_enable());
351
352 Ok(())
353 }
354
355 fn verify_genesis_certificate(
356 bank: &Bank,
357 cert: UnverifiedCertificate,
358 ) -> Result<Certificate, BlockComponentProcessorError> {
359 debug_assert!(cert.cert_type.is_genesis());
360
361 let cert_slot = cert.cert_type.slot();
362 let cert = bank.verify_certificate(cert).map_err(|_| {
363 warn!(
364 "Failed to verify genesis certificate for slot {cert_slot} in bank slot {}",
365 bank.slot()
366 );
367 BlockComponentProcessorError::GenesisCertificateFailedVerification
368 })?;
369
370 Ok(cert)
371 }
372
373 fn on_footer(
374 &mut self,
375 bank: Arc<Bank>,
376 parent_bank: Arc<Bank>,
377 shred_version: u16,
378 footer: VersionedBlockFooter,
379 finalization_cert_sender: Option<&Sender<ConsensusMessage>>,
380 ) -> Result<(), BlockComponentProcessorError> {
381 if !self.has_header && self.update_parent.is_none() {
382 return Err(BlockComponentProcessorError::MissingParentMarker);
383 }
384
385 if self.has_footer {
386 return Err(BlockComponentProcessorError::MultipleBlockFooters);
387 }
388
389 let VersionedBlockFooter::V1(footer) = footer;
390
391 Self::enforce_nanosecond_clock_bounds(&bank, &parent_bank, &footer)?;
392
393 let BlockFooterV1 {
394 bank_hash,
395 block_producer_time_nanos,
396 block_user_agent: _,
397 block_final_cert,
398 skip_reward_cert,
399 notar_reward_cert,
400 } = footer;
401
402 let reward_cert = ValidatedRewardCert::try_new(
403 &bank,
404 shred_version,
405 &skip_reward_cert,
406 ¬ar_reward_cert,
407 )?;
408 let block_producer_time_nanos =
409 Self::block_producer_time_nanos_as_i64(block_producer_time_nanos)?;
410 let final_cert = block_final_cert
411 .map(|final_cert| {
412 ValidatedBlockFinalizationCert::try_from_footer(final_cert, &bank, shred_version)
413 .map_err(BlockComponentProcessorError::InvalidFinalizationCertificate)
414 })
415 .transpose()?;
416
417 let (footer_input, pool_input) = match final_cert {
418 None => (None, None),
419 Some(cert) => {
420 let (signers, finalize_cert, notarize_cert) = cert.into_parts();
421 let final_slot = finalize_cert.cert_type.slot();
422 (
423 Some((signers, final_slot)),
424 Some((finalize_cert, notarize_cert)),
425 )
426 }
427 };
428
429 Self::update_bank_with_footer_fields(
430 &bank,
431 block_producer_time_nanos,
432 Some(bank_hash),
433 reward_cert,
434 footer_input
435 .as_ref()
436 .map(|(validators, slot)| (validators, *slot)),
437 )?;
438
439 if let Some((finalize_cert, notarize_cert)) = pool_input
441 && let Some(sender) = finalization_cert_sender
442 {
443 if let Some(notarize_cert) = notarize_cert {
444 let cert = ConsensusMessage::Certificate(notarize_cert);
445 let _ = sender
447 .send(cert)
448 .inspect_err(|_| info!("ConsensusMessage sender disconnected"));
449 }
450 let cert = ConsensusMessage::Certificate(finalize_cert);
451 let _ = sender
453 .send(cert)
454 .inspect_err(|_| info!("ConsensusMessage sender disconnected"));
455 }
456
457 self.has_footer = true;
458 Ok(())
459 }
460
461 fn on_header(
462 &mut self,
463 header: &VersionedBlockHeader,
464 bank_parent_slot: Slot,
465 ) -> Result<(), BlockComponentProcessorError> {
466 if self.has_header {
467 return Err(BlockComponentProcessorError::MultipleBlockHeaders);
468 }
469
470 if self.update_parent.is_some() {
471 return Err(BlockComponentProcessorError::SpuriousUpdateParent);
472 }
473
474 let VersionedBlockHeader::V1(header) = header;
475 if header.parent_slot != bank_parent_slot {
476 return Err(BlockComponentProcessorError::HeaderParentSlotMismatch {
477 header_parent_slot: header.parent_slot,
478 bank_parent_slot,
479 });
480 }
481
482 self.has_header = true;
483 Ok(())
484 }
485
486 fn on_update_parent(
487 &mut self,
488 slot: Slot,
489 update_parent: &VersionedUpdateParent,
490 allow_initial_update_parent: bool,
491 ) -> Result<(), BlockComponentProcessorError> {
492 if self.update_parent.is_some() {
493 return Err(BlockComponentProcessorError::MultipleUpdateParents);
494 }
495
496 if leader_slot_index(slot) != 0 {
497 return Err(BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(slot));
498 }
499
500 if !self.has_header && !allow_initial_update_parent {
501 return Err(BlockComponentProcessorError::UnexpectedInitialUpdateParent);
502 }
503
504 self.update_parent = Some(update_parent.clone());
505
506 if self.has_header {
507 Err(BlockComponentProcessorError::AbandonedBank(
511 update_parent.clone(),
512 ))
513 } else {
514 Ok(())
515 }
516 }
517
518 fn enforce_nanosecond_clock_bounds(
519 bank: &Bank,
520 parent_bank: &Bank,
521 footer: &BlockFooterV1,
522 ) -> Result<(), BlockComponentProcessorError> {
523 let parent_time_nanos = parent_bank
526 .get_nanosecond_clock()
527 .unwrap_or_else(|| bank.clock().unix_timestamp.saturating_mul(1_000_000_000));
528
529 let parent_slot = parent_bank.slot();
530 let current_time_nanos =
531 Self::block_producer_time_nanos_as_i64(footer.block_producer_time_nanos)?;
532 let current_slot = bank.slot();
533 let elapsed_slot_duration_nanos =
534 bank.slot_range_duration_nanos(parent_slot.saturating_add(1), current_slot);
535
536 let (lower_bound_nanos, upper_bound_nanos) =
537 Self::nanosecond_time_bounds(parent_time_nanos, elapsed_slot_duration_nanos);
538
539 let is_valid =
540 lower_bound_nanos <= current_time_nanos && current_time_nanos <= upper_bound_nanos;
541
542 match is_valid {
543 true => Ok(()),
544 false => Err(BlockComponentProcessorError::NanosecondClockOutOfBounds),
545 }
546 }
547
548 fn block_producer_time_nanos_as_i64(
555 block_producer_time_nanos: u64,
556 ) -> Result<i64, BlockComponentProcessorError> {
557 i64::try_from(block_producer_time_nanos)
558 .map_err(|_| BlockComponentProcessorError::NanosecondClockOutOfBounds)
559 }
560
561 pub fn nanosecond_time_bounds(
574 parent_time_nanos: i64,
575 elapsed_slot_duration_nanos: u128,
576 ) -> (i64, i64) {
577 let min_working_bank_time = parent_time_nanos.saturating_add(1);
578 let max_working_bank_time_offset = elapsed_slot_duration_nanos
579 .saturating_mul(2)
580 .min(i64::MAX as u128) as i64;
581 let max_working_bank_time = parent_time_nanos.saturating_add(max_working_bank_time_offset);
582
583 (min_working_bank_time, max_working_bank_time)
584 }
585
586 pub fn update_bank_with_footer_fields(
587 bank: &Bank,
588 block_producer_time_nanos: i64,
589 bank_hash: Option<Hash>,
590 reward_cert: Option<ValidatedRewardCert>,
591 final_cert_input: Option<(&HashSet<Pubkey>, Slot)>,
592 ) -> Result<(), BankFooterError> {
593 bank.update_clock_from_footer(block_producer_time_nanos);
594 calc_vote_rewards_update_vote_states(
595 bank,
596 reward_cert,
597 final_cert_input,
598 block_producer_time_nanos,
599 )?;
600
601 if let Some(hash) = bank_hash {
602 bank.set_expected_bank_hash(hash);
604 }
605 Ok(())
606 }
607}
608
609#[cfg(test)]
610mod tests {
611 use {
612 super::*,
613 crate::{
614 bank::{Bank, SlotLeader},
615 bank_forks::BankForks,
616 genesis_utils::{activate_all_features_alpenglow, create_genesis_config},
617 },
618 rand::Rng,
619 solana_bls_signatures::BLS_SIGNATURE_AFFINE_SIZE,
620 solana_clock::DEFAULT_MS_PER_SLOT,
621 solana_entry::block_component::{
622 BlockFooterV1, BlockHeaderV1, UpdateParentV1, VersionedUpdateParent,
623 },
624 solana_hash::Hash,
625 std::sync::{Arc, RwLock},
626 };
627
628 const DEFAULT_NS_PER_SLOT: u64 = DEFAULT_MS_PER_SLOT * 1_000_000;
629
630 fn create_test_bank() -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
631 let genesis_config_info = create_genesis_config(10_000);
632 Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config)
633 }
634
635 fn create_test_bank_alpenglow() -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
636 let mut genesis_config_info = create_genesis_config(10_000);
637 activate_all_features_alpenglow(&mut genesis_config_info.genesis_config);
638 Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config)
639 }
640
641 fn create_child_bank(
642 bank_forks: &RwLock<BankForks>,
643 parent: &Arc<Bank>,
644 slot: u64,
645 ) -> Arc<Bank> {
646 Bank::new_from_parent_with_bank_forks(
647 bank_forks,
648 parent.clone(),
649 SlotLeader::new_unique(),
650 slot,
651 )
652 }
653
654 fn test_genesis_cert_marker() -> GenesisCertBlockMarker {
655 GenesisCertBlockMarker {
656 slot: 0,
657 block_id: Hash::default(),
658 bls_signature: solana_bls_signatures::Signature([0; BLS_SIGNATURE_AFFINE_SIZE]),
659 bitmap: vec![],
660 }
661 }
662
663 #[test]
664 fn test_missing_header_error_on_entry_batch() {
665 let migration_status = MigrationStatus::post_migration_status();
666 let mut processor = BlockComponentProcessor::default();
667
668 let result = processor.on_entry_batch(&migration_status, 1);
670 assert!(matches!(
671 result,
672 Err(BlockComponentProcessorError::MissingParentMarker)
673 ));
674 }
675
676 #[test]
677 fn test_missing_header_error_on_genesis_certificate() {
678 let migration_status = MigrationStatus::post_migration_status();
679 let mut processor = BlockComponentProcessor::default();
680 let marker =
681 VersionedBlockMarker::from_genesis_cert_block_marker(test_genesis_cert_marker());
682
683 let (parent, bank_forks) = create_test_bank();
684 let bank = create_child_bank(&bank_forks, &parent, 1);
685 let shred_version = rand::rng().random();
686
687 let result = processor.on_marker(
688 bank,
689 parent,
690 shred_version,
691 marker,
692 false,
693 None,
694 &migration_status,
695 );
696 assert!(matches!(
697 result,
698 Err(BlockComponentProcessorError::MissingParentMarker)
699 ));
700 }
701
702 #[test]
703 fn test_genesis_certificate_after_entry_batch_errors() {
704 let migration_status = MigrationStatus::post_migration_status();
705 let mut processor = BlockComponentProcessor::default();
706 let (parent, bank_forks) = create_test_bank();
707 let bank = create_child_bank(&bank_forks, &parent, 1);
708 let shred_version = rand::rng().random();
709
710 let header = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
711 parent_slot: 0,
712 parent_block_id: Hash::default(),
713 });
714 processor
715 .on_marker(
716 bank.clone(),
717 parent.clone(),
718 shred_version,
719 header,
720 false,
721 None,
722 &migration_status,
723 )
724 .unwrap();
725 processor
726 .on_entry_batch(&migration_status, bank.slot())
727 .unwrap();
728
729 let marker =
730 VersionedBlockMarker::from_genesis_cert_block_marker(test_genesis_cert_marker());
731 let result = processor.on_marker(
732 bank,
733 parent,
734 shred_version,
735 marker,
736 false,
737 None,
738 &migration_status,
739 );
740 assert!(matches!(
741 result,
742 Err(BlockComponentProcessorError::GenesisCertificateOutOfOrder)
743 ));
744 }
745
746 #[test]
747 fn test_genesis_certificate_immediately_after_header_passes_order_check() {
748 let migration_status = MigrationStatus::post_migration_status();
749 let mut processor = BlockComponentProcessor::default();
750 let (parent, bank_forks) = create_test_bank();
751 let bank = create_child_bank(&bank_forks, &parent, 1);
752 let shred_version = rand::rng().random();
753
754 let header = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
755 parent_slot: 0,
756 parent_block_id: Hash::default(),
757 });
758 processor
759 .on_marker(
760 bank.clone(),
761 parent.clone(),
762 shred_version,
763 header,
764 false,
765 None,
766 &migration_status,
767 )
768 .unwrap();
769
770 let marker =
771 VersionedBlockMarker::from_genesis_cert_block_marker(test_genesis_cert_marker());
772 let result = processor.on_marker(
773 bank,
774 parent,
775 shred_version,
776 marker,
777 false,
778 None,
779 &migration_status,
780 );
781 assert!(matches!(
782 result,
783 Err(BlockComponentProcessorError::GenesisCertificateInAlpenglowCluster)
784 ));
785 }
786
787 #[test]
788 fn test_missing_footer_error_on_slot_full() {
789 let migration_status = MigrationStatus::post_migration_status();
790 let processor = BlockComponentProcessor {
791 has_header: true,
792 ..BlockComponentProcessor::default()
793 };
794
795 let result = processor.on_final(&migration_status, 1);
797 assert!(matches!(
798 result,
799 Err(BlockComponentProcessorError::MissingBlockFooter)
800 ));
801 }
802
803 #[test]
804 fn test_multiple_headers_error() {
805 let mut processor = BlockComponentProcessor::default();
806 let header = VersionedBlockHeader::V1(BlockHeaderV1 {
807 parent_slot: 0,
808 parent_block_id: Hash::default(),
809 });
810
811 assert!(processor.on_header(&header, 0).is_ok());
813
814 let result = processor.on_header(&header, 0);
816 assert!(matches!(
817 result,
818 Err(BlockComponentProcessorError::MultipleBlockHeaders)
819 ));
820 }
821
822 #[test]
823 fn test_multiple_footers_error() {
824 let mut processor = BlockComponentProcessor {
825 has_header: true,
826 ..Default::default()
827 };
828
829 let (parent, bank_forks) = create_test_bank();
830 let bank = create_child_bank(&bank_forks, &parent, 1);
831 let shred_version = rand::rng().random();
832
833 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
835 let footer_time_nanos = parent_time_nanos + 400_000_000; let footer = VersionedBlockFooter::V1(BlockFooterV1 {
838 bank_hash: Hash::new_unique(),
839 block_producer_time_nanos: footer_time_nanos as u64,
840 block_user_agent: vec![],
841 block_final_cert: None,
842 skip_reward_cert: None,
843 notar_reward_cert: None,
844 });
845
846 processor
848 .on_footer(
849 bank.clone(),
850 parent.clone(),
851 shred_version,
852 footer.clone(),
853 None,
854 )
855 .unwrap();
856
857 let err = processor
859 .on_footer(bank, parent, shred_version, footer, None)
860 .unwrap_err();
861 assert!(matches!(
862 err,
863 BlockComponentProcessorError::MultipleBlockFooters
864 ));
865 }
866
867 #[test]
868 fn test_on_footer_sets_timestamp() {
869 let mut processor = BlockComponentProcessor {
870 has_header: true,
871 ..Default::default()
872 };
873
874 let (parent, bank_forks) = create_test_bank();
875 let bank = create_child_bank(&bank_forks, &parent, 1);
876 let shred_version = rand::rng().random();
877
878 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
880 let footer_time_nanos = parent_time_nanos + 200_000_000; let expected_time_secs = footer_time_nanos / 1_000_000_000;
882
883 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
884 bank_hash: Hash::new_unique(),
885 block_producer_time_nanos: footer_time_nanos as u64,
886 block_user_agent: vec![],
887 block_final_cert: None,
888 skip_reward_cert: None,
889 notar_reward_cert: None,
890 });
891
892 processor
893 .on_footer(bank.clone(), parent, shred_version, footer, None)
894 .unwrap();
895
896 assert!(processor.has_footer);
897
898 assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
900 }
901
902 #[test]
903 fn test_on_header_sets_flag() {
904 let mut processor = BlockComponentProcessor::default();
905 let header = VersionedBlockHeader::V1(BlockHeaderV1 {
906 parent_slot: 0,
907 parent_block_id: Hash::default(),
908 });
909
910 processor.on_header(&header, 0).unwrap();
911 assert!(processor.has_header);
912 }
913
914 #[test]
915 fn test_on_header_parent_slot_mismatch_error() {
916 let mut processor = BlockComponentProcessor::default();
917 let header = VersionedBlockHeader::V1(BlockHeaderV1 {
918 parent_slot: 2,
919 parent_block_id: Hash::default(),
920 });
921
922 assert!(matches!(
923 processor.on_header(&header, 0),
924 Err(BlockComponentProcessorError::HeaderParentSlotMismatch {
925 header_parent_slot: 2,
926 bank_parent_slot: 0,
927 })
928 ));
929 }
930
931 #[test]
932 fn test_on_marker_processes_header() {
933 let migration_status = MigrationStatus::post_migration_status();
934 let mut processor = BlockComponentProcessor::default();
935 let marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
936 parent_slot: 0,
937 parent_block_id: Hash::default(),
938 });
939
940 let (parent, bank_forks) = create_test_bank();
941 let bank = create_child_bank(&bank_forks, &parent, 1);
942 let shred_version = rand::rng().random();
943
944 processor
945 .on_marker(
946 bank,
947 parent,
948 shred_version,
949 marker,
950 false,
951 None,
952 &migration_status,
953 )
954 .unwrap();
955 assert!(processor.has_header);
956 }
957
958 #[test]
959 fn test_on_marker_rejects_header_parent_slot_mismatch() {
960 let migration_status = MigrationStatus::post_migration_status();
961 let mut processor = BlockComponentProcessor::default();
962 let marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
963 parent_slot: 7, parent_block_id: Hash::default(),
965 });
966
967 let (parent, bank_forks) = create_test_bank();
968 let bank = create_child_bank(&bank_forks, &parent, 1);
969 let shred_version = rand::rng().random();
970
971 assert!(matches!(
972 processor.on_marker(
973 bank,
974 parent,
975 shred_version,
976 marker,
977 false,
978 None,
979 &migration_status
980 ),
981 Err(BlockComponentProcessorError::HeaderParentSlotMismatch {
982 header_parent_slot: 7,
983 bank_parent_slot: 0,
984 })
985 ));
986 }
987
988 #[test]
989 fn test_on_marker_processes_footer() {
990 let migration_status = MigrationStatus::post_migration_status();
991 let mut processor = BlockComponentProcessor {
992 has_header: true,
993 ..Default::default()
994 };
995
996 let (parent, bank_forks) = create_test_bank();
997 let bank = create_child_bank(&bank_forks, &parent, 1);
998 let shred_version = rand::rng().random();
999
1000 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1002 let footer_time_nanos = parent_time_nanos + 300_000_000; let expected_time_secs = footer_time_nanos / 1_000_000_000;
1004
1005 let marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1006 bank_hash: Hash::new_unique(),
1007 block_producer_time_nanos: footer_time_nanos as u64,
1008 block_user_agent: vec![],
1009 block_final_cert: None,
1010 skip_reward_cert: None,
1011 notar_reward_cert: None,
1012 });
1013
1014 processor
1015 .on_marker(
1016 bank.clone(),
1017 parent,
1018 shred_version,
1019 marker,
1020 false,
1021 None,
1022 &migration_status,
1023 )
1024 .unwrap();
1025 assert!(processor.has_footer);
1026
1027 assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1029 }
1030
1031 #[test]
1032 fn test_complete_workflow_success() {
1033 let migration_status = MigrationStatus::post_migration_status();
1034 let mut processor = BlockComponentProcessor::default();
1035 let (parent, bank_forks) = create_test_bank();
1036 let bank = create_child_bank(&bank_forks, &parent, 1);
1037 let shred_version = rand::rng().random();
1038
1039 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1041 let footer_time_nanos = parent_time_nanos + 100_000_000; let expected_time_secs = footer_time_nanos / 1_000_000_000;
1043
1044 let header = VersionedBlockHeader::V1(BlockHeaderV1 {
1046 parent_slot: 0,
1047 parent_block_id: Hash::default(),
1048 });
1049 processor.on_header(&header, bank.parent_slot()).unwrap();
1050
1051 assert!(processor.on_entry_batch(&migration_status, 1).is_ok());
1053
1054 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1056 bank_hash: Hash::new_unique(),
1057 block_producer_time_nanos: footer_time_nanos as u64,
1058 block_user_agent: vec![],
1059 block_final_cert: None,
1060 skip_reward_cert: None,
1061 notar_reward_cert: None,
1062 });
1063 processor
1064 .on_footer(bank.clone(), parent.clone(), shred_version, footer, None)
1065 .unwrap();
1066
1067 assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1069
1070 let result = processor.on_entry_batch(&migration_status, 1);
1072 assert!(result.is_ok());
1073 }
1074
1075 #[test]
1076 fn test_block_marker_detected_pre_migration() {
1077 let migration_status = MigrationStatus::default();
1078 let mut processor = BlockComponentProcessor::default();
1079 let (parent, bank_forks) = create_test_bank();
1080 let bank = create_child_bank(&bank_forks, &parent, 1);
1081 let shred_version = rand::rng().random();
1082
1083 let marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1085 parent_slot: 0,
1086 parent_block_id: Hash::default(),
1087 });
1088
1089 let err = processor
1090 .on_marker(
1091 bank,
1092 parent,
1093 shred_version,
1094 marker,
1095 false,
1096 None,
1097 &migration_status,
1098 )
1099 .unwrap_err();
1100 assert!(matches!(
1101 err,
1102 BlockComponentProcessorError::BlockComponentPreMigration
1103 ));
1104 }
1105
1106 #[test]
1107 fn test_footer_and_update_parent_rejected_pre_migration() {
1108 let migration_status = MigrationStatus::default();
1109 let (parent, bank_forks) = create_test_bank();
1110 let bank = create_child_bank(&bank_forks, &parent, 1);
1111 let shred_version = rand::rng().random();
1112
1113 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1114 let footer_marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1115 bank_hash: Hash::new_unique(),
1116 block_producer_time_nanos: (parent_time_nanos + 500_000_000) as u64,
1117 block_user_agent: vec![],
1118 block_final_cert: None,
1119 skip_reward_cert: None,
1120 notar_reward_cert: None,
1121 });
1122
1123 let mut processor = BlockComponentProcessor::default();
1124 assert!(matches!(
1125 processor
1126 .on_marker(
1127 bank.clone(),
1128 parent.clone(),
1129 shred_version,
1130 footer_marker,
1131 false,
1132 None,
1133 &migration_status
1134 )
1135 .unwrap_err(),
1136 BlockComponentProcessorError::BlockComponentPreMigration
1137 ));
1138
1139 let update_parent_marker = VersionedBlockMarker::from_update_parent(UpdateParentV1 {
1140 new_parent_slot: 0,
1141 new_parent_block_id: Hash::default(),
1142 });
1143
1144 let mut processor = BlockComponentProcessor::default();
1145 assert!(matches!(
1146 processor
1147 .on_marker(
1148 bank,
1149 parent,
1150 shred_version,
1151 update_parent_marker,
1152 false,
1153 None,
1154 &migration_status
1155 )
1156 .unwrap_err(),
1157 BlockComponentProcessorError::BlockComponentPreMigration
1158 ));
1159 }
1160
1161 #[test]
1162 fn test_entry_batch_pre_migration_succeeds() {
1163 let migration_status = MigrationStatus::default();
1164 let mut processor = BlockComponentProcessor::default();
1165
1166 let result = processor.on_entry_batch(&migration_status, 1);
1168 assert!(result.is_ok());
1169
1170 let result = processor.on_entry_batch(&migration_status, 1);
1172 assert!(result.is_ok());
1173 }
1174
1175 #[test]
1176 fn test_complete_workflow_post_migration() {
1177 let migration_status = MigrationStatus::post_migration_status();
1178 let mut processor = BlockComponentProcessor::default();
1179 let (parent, bank_forks) = create_test_bank();
1180 let bank = create_child_bank(&bank_forks, &parent, 1);
1181 let shred_version = rand::rng().random();
1182
1183 let header_marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1185 parent_slot: 0,
1186 parent_block_id: Hash::default(),
1187 });
1188 processor
1189 .on_marker(
1190 bank.clone(),
1191 parent.clone(),
1192 shred_version,
1193 header_marker,
1194 false,
1195 None,
1196 &migration_status,
1197 )
1198 .unwrap();
1199
1200 assert!(processor.on_entry_batch(&migration_status, 1).is_ok());
1202
1203 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1205 let footer_time_nanos = parent_time_nanos + 500_000_000; let expected_time_secs = footer_time_nanos / 1_000_000_000;
1207
1208 let footer_marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1210 bank_hash: Hash::new_unique(),
1211 block_producer_time_nanos: footer_time_nanos as u64,
1212 block_user_agent: vec![],
1213 block_final_cert: None,
1214 skip_reward_cert: None,
1215 notar_reward_cert: None,
1216 });
1217 processor
1218 .on_marker(
1219 bank.clone(),
1220 parent,
1221 shred_version,
1222 footer_marker,
1223 false,
1224 None,
1225 &migration_status,
1226 )
1227 .unwrap();
1228
1229 assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1231
1232 let result = processor.on_entry_batch(&migration_status, 1);
1234 assert!(result.is_ok());
1235 }
1236
1237 #[test]
1238 fn test_footer_without_header_errors() {
1239 let mut processor = BlockComponentProcessor::default();
1240 let (parent, bank_forks) = create_test_bank();
1241 let bank = create_child_bank(&bank_forks, &parent, 1);
1242 let shred_version = rand::rng().random();
1243
1244 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1245 bank_hash: Hash::new_unique(),
1246 block_producer_time_nanos: 1_000_000_000,
1247 block_user_agent: vec![],
1248 block_final_cert: None,
1249 skip_reward_cert: None,
1250 notar_reward_cert: None,
1251 });
1252
1253 let err = processor
1255 .on_footer(bank, parent, shred_version, footer, None)
1256 .unwrap_err();
1257 assert!(matches!(
1258 err,
1259 BlockComponentProcessorError::MissingParentMarker
1260 ));
1261 }
1262
1263 #[test]
1264 fn test_marker_with_footer_at_slot_full() {
1265 let migration_status = MigrationStatus::post_migration_status();
1266 let mut processor = BlockComponentProcessor::default();
1267 let (parent, bank_forks) = create_test_bank();
1268 let bank = create_child_bank(&bank_forks, &parent, 1);
1269 let shred_version = rand::rng().random();
1270
1271 processor.has_header = true;
1273
1274 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1276 let footer_time_nanos = parent_time_nanos + 600_000_000; let expected_time_secs = footer_time_nanos / 1_000_000_000;
1278
1279 let footer_marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1281 bank_hash: Hash::new_unique(),
1282 block_producer_time_nanos: footer_time_nanos as u64,
1283 block_user_agent: vec![],
1284 block_final_cert: None,
1285 skip_reward_cert: None,
1286 notar_reward_cert: None,
1287 });
1288
1289 processor
1291 .on_marker(
1292 bank.clone(),
1293 parent,
1294 shred_version,
1295 footer_marker,
1296 false,
1297 None,
1298 &migration_status,
1299 )
1300 .unwrap();
1301 assert!(processor.has_footer);
1302
1303 assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1305 }
1306
1307 #[test]
1308 fn test_entry_batch_with_header_not_full_succeeds() {
1309 let migration_status = MigrationStatus::post_migration_status();
1310 let mut processor = BlockComponentProcessor {
1311 has_header: true,
1312 ..Default::default()
1313 };
1314
1315 let result = processor.on_entry_batch(&migration_status, 1);
1317 assert!(result.is_ok());
1318 }
1319
1320 #[test]
1321 fn test_footer_sets_epoch_start_timestamp_on_epoch_change() {
1322 let mut processor = BlockComponentProcessor {
1323 has_header: true,
1324 ..Default::default()
1325 };
1326 let shred_version = rand::rng().random();
1327
1328 let genesis_config_info = create_genesis_config(10_000);
1330 let (genesis_bank, bank_forks) =
1331 Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config);
1332
1333 let epoch_schedule = genesis_bank.epoch_schedule();
1335 let first_slot_in_epoch_1 = epoch_schedule.get_first_slot_in_epoch(1);
1336
1337 let mut parent = genesis_bank.clone();
1339 for slot in 1..first_slot_in_epoch_1 {
1340 parent = create_child_bank(&bank_forks, &parent, slot);
1341 }
1342
1343 let bank = create_child_bank(&bank_forks, &parent, first_slot_in_epoch_1);
1345
1346 assert_eq!(bank.epoch(), 1);
1348
1349 let parent_slot = parent.slot();
1351 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1352 let current_slot = bank.slot();
1353 let elapsed_slot_duration_nanos =
1354 bank.slot_range_duration_nanos(parent_slot.saturating_add(1), current_slot);
1355
1356 let (lower_bound, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1358 parent_time_nanos,
1359 elapsed_slot_duration_nanos,
1360 );
1361 let footer_time_nanos = (lower_bound + upper_bound) / 2;
1362 let expected_time_secs = footer_time_nanos / 1_000_000_000;
1363
1364 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1365 bank_hash: Hash::new_unique(),
1366 block_producer_time_nanos: footer_time_nanos as u64,
1367 block_user_agent: vec![],
1368 block_final_cert: None,
1369 skip_reward_cert: None,
1370 notar_reward_cert: None,
1371 });
1372
1373 processor
1374 .on_footer(bank.clone(), parent, shred_version, footer, None)
1375 .unwrap();
1376
1377 assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1379
1380 assert_eq!(bank.clock().epoch_start_timestamp, expected_time_secs);
1382 }
1383
1384 fn test_clock_bounds_helper(
1386 slot_gap: u64,
1387 timestamp_fn: impl FnOnce(i64, i64, i64) -> i64,
1388 should_pass: bool,
1389 ) {
1390 let mut processor = BlockComponentProcessor {
1391 has_header: true,
1392 ..Default::default()
1393 };
1394 let shred_version = rand::rng().random();
1395
1396 let (parent, bank_forks) = create_test_bank_alpenglow();
1397 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1398
1399 parent.update_clock_from_footer(parent_time_nanos);
1401
1402 let bank: Arc<Bank> = create_child_bank(&bank_forks, &parent, slot_gap);
1403 let elapsed_slot_duration_nanos = bank.slot_range_duration_nanos(1, slot_gap);
1404
1405 let (lower_bound, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1406 parent_time_nanos,
1407 elapsed_slot_duration_nanos,
1408 );
1409
1410 let footer_time_nanos = timestamp_fn(parent_time_nanos, lower_bound, upper_bound);
1411
1412 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1413 bank_hash: Hash::new_unique(),
1414 block_producer_time_nanos: footer_time_nanos as u64,
1415 block_user_agent: vec![],
1416 block_final_cert: None,
1417 skip_reward_cert: None,
1418 notar_reward_cert: None,
1419 });
1420
1421 let result = processor.on_footer(bank, parent, shred_version, footer, None);
1422 if should_pass {
1423 result.unwrap();
1424 } else {
1425 assert!(matches!(
1426 result.unwrap_err(),
1427 BlockComponentProcessorError::NanosecondClockOutOfBounds
1428 ));
1429 }
1430 }
1431
1432 #[test]
1433 fn test_clock_bounds_at_minimum() {
1434 test_clock_bounds_helper(1, |_, lower, _| lower, true);
1435 }
1436
1437 #[test]
1438 fn test_clock_bounds_at_maximum() {
1439 test_clock_bounds_helper(1, |_, _, upper| upper, true);
1440 }
1441
1442 #[test]
1443 fn test_clock_bounds_below_minimum() {
1444 test_clock_bounds_helper(1, |_, lower, _| lower - 1, false);
1445 }
1446
1447 #[test]
1448 fn test_clock_bounds_above_maximum() {
1449 test_clock_bounds_helper(1, |_, _, upper| upper + 1, false);
1450 }
1451
1452 #[test]
1453 fn test_clock_bounds_multi_slot_gap() {
1454 test_clock_bounds_helper(5, |_, lower, _| lower + 2_000_000_000, true);
1457 }
1458
1459 #[test]
1460 fn test_clock_bounds_multi_slot_gap_exceeds() {
1461 test_clock_bounds_helper(5, |_, _, upper| upper + 1_000_000_000, false);
1463 }
1464
1465 #[test]
1466 fn test_clock_bounds_timestamp_equals_parent() {
1467 test_clock_bounds_helper(1, |parent_time, _, _| parent_time, false);
1469 }
1470
1471 #[test]
1472 fn test_clock_bounds_without_parent_nanosecond_clock_rejects_out_of_bounds() {
1473 let mut processor = BlockComponentProcessor {
1474 has_header: true,
1475 ..Default::default()
1476 };
1477 let shred_version = rand::rng().random();
1478
1479 let (parent, bank_forks) = create_test_bank_alpenglow();
1480 assert_eq!(parent.get_nanosecond_clock(), None);
1481
1482 let bank = create_child_bank(&bank_forks, &parent, 1);
1483 let parent_time_nanos = bank.clock().unix_timestamp.saturating_mul(1_000_000_000);
1484 let elapsed_slot_duration_nanos =
1485 bank.slot_range_duration_nanos(parent.slot().saturating_add(1), bank.slot());
1486 let (_, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1487 parent_time_nanos,
1488 elapsed_slot_duration_nanos,
1489 );
1490
1491 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1492 bank_hash: Hash::new_unique(),
1493 block_producer_time_nanos: u64::try_from(upper_bound.saturating_add(1)).unwrap(),
1494 block_user_agent: vec![],
1495 block_final_cert: None,
1496 skip_reward_cert: None,
1497 notar_reward_cert: None,
1498 });
1499
1500 assert!(matches!(
1501 processor
1502 .on_footer(bank, parent, shred_version, footer, None)
1503 .unwrap_err(),
1504 BlockComponentProcessorError::NanosecondClockOutOfBounds
1505 ));
1506 }
1507
1508 #[test]
1509 fn test_clock_bounds_rejects_timestamp_above_i64() {
1510 let mut processor = BlockComponentProcessor {
1511 has_header: true,
1512 ..Default::default()
1513 };
1514 let shred_version = rand::rng().random();
1515
1516 let (parent, bank_forks) = create_test_bank_alpenglow();
1517 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1518 parent.update_clock_from_footer(parent_time_nanos);
1519 let bank = create_child_bank(&bank_forks, &parent, 1);
1520
1521 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1522 bank_hash: Hash::new_unique(),
1523 block_producer_time_nanos: u64::MAX,
1524 block_user_agent: vec![],
1525 block_final_cert: None,
1526 skip_reward_cert: None,
1527 notar_reward_cert: None,
1528 });
1529
1530 assert!(matches!(
1531 processor
1532 .on_footer(bank, parent, shred_version, footer, None)
1533 .unwrap_err(),
1534 BlockComponentProcessorError::NanosecondClockOutOfBounds
1535 ));
1536 }
1537
1538 fn test_nanosecond_time_bounds_helper(
1540 parent_time_nanos: i64,
1541 elapsed_slot_duration_nanos: u128,
1542 expected_lower: i64,
1543 expected_upper: i64,
1544 ) {
1545 let (lower, upper) = BlockComponentProcessor::nanosecond_time_bounds(
1546 parent_time_nanos,
1547 elapsed_slot_duration_nanos,
1548 );
1549
1550 assert_eq!(lower, expected_lower);
1551 assert_eq!(upper, expected_upper);
1552 }
1553
1554 #[test]
1555 fn test_nanosecond_time_bounds_calculation() {
1556 let parent_slot = 10;
1561 let parent_time = 1_000_000_000_000; let working_slot = 15;
1563 let slot_delta = working_slot - parent_slot;
1564 test_nanosecond_time_bounds_helper(
1565 parent_time,
1566 u128::from(slot_delta).saturating_mul(u128::from(DEFAULT_NS_PER_SLOT)),
1567 parent_time + 1,
1568 parent_time + (2 * DEFAULT_NS_PER_SLOT * slot_delta) as i64,
1569 );
1570 }
1571
1572 #[test]
1573 fn test_nanosecond_time_bounds_same_slot() {
1574 let parent_time = 1_000_000_000_000;
1581 test_nanosecond_time_bounds_helper(parent_time, 0, parent_time + 1, parent_time);
1582 }
1583
1584 #[test]
1585 fn test_nanosecond_time_bounds_saturates_upper_bound() {
1586 let parent_time = i64::MAX - 5;
1587 let (lower, upper) =
1588 BlockComponentProcessor::nanosecond_time_bounds(parent_time, u128::MAX);
1589
1590 assert_eq!(lower, parent_time + 1);
1591 assert_eq!(upper, i64::MAX);
1592 }
1593
1594 #[test]
1595 fn test_initial_up_reject() {
1596 let mut processor = BlockComponentProcessor::default();
1597 let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
1598 new_parent_slot: 0,
1599 new_parent_block_id: Hash::default(),
1600 });
1601
1602 assert!(matches!(
1603 processor.on_update_parent(4, &update_parent, false),
1604 Err(BlockComponentProcessorError::UnexpectedInitialUpdateParent)
1605 ));
1606 assert!(processor.update_parent.is_none());
1607 }
1608
1609 #[test]
1610 fn test_update_parent_rejects_non_first_leader_window_slot() {
1611 let mut processor = BlockComponentProcessor::default();
1612 let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
1613 new_parent_slot: 0,
1614 new_parent_block_id: Hash::default(),
1615 });
1616
1617 assert!(matches!(
1618 processor.on_update_parent(5, &update_parent, true),
1619 Err(BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(5))
1620 ));
1621 assert!(processor.update_parent.is_none());
1622 }
1623
1624 #[test]
1625 fn test_initial_up_ok() {
1626 let mut processor = BlockComponentProcessor::default();
1627 let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
1628 new_parent_slot: 0,
1629 new_parent_block_id: Hash::default(),
1630 });
1631
1632 processor.on_update_parent(4, &update_parent, true).unwrap();
1633 assert!(processor.update_parent.is_some());
1634 }
1635
1636 #[test]
1637 fn test_update_parent_after_header_abandoned_bank() {
1638 let mut processor = BlockComponentProcessor::default();
1639 processor
1640 .on_header(
1641 &VersionedBlockHeader::V1(BlockHeaderV1 {
1642 parent_slot: 0,
1643 parent_block_id: Hash::default(),
1644 }),
1645 0,
1646 )
1647 .unwrap();
1648
1649 let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
1650 new_parent_slot: 0,
1651 new_parent_block_id: Hash::default(),
1652 });
1653
1654 assert!(matches!(
1655 processor.on_update_parent(4, &update_parent, false),
1656 Err(BlockComponentProcessorError::AbandonedBank(_))
1657 ));
1658 }
1659
1660 #[test]
1661 fn test_multiple_update_parents_error() {
1662 let mut processor = BlockComponentProcessor::default();
1663 let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
1664 new_parent_slot: 0,
1665 new_parent_block_id: Hash::default(),
1666 });
1667
1668 processor.on_update_parent(4, &update_parent, true).unwrap();
1670
1671 assert!(matches!(
1673 processor.on_update_parent(4, &update_parent, true),
1674 Err(BlockComponentProcessorError::MultipleUpdateParents)
1675 ));
1676 }
1677
1678 #[test]
1679 fn test_header_after_update_parent_error() {
1680 let mut processor = BlockComponentProcessor::default();
1681 processor
1682 .on_update_parent(
1683 4,
1684 &VersionedUpdateParent::V1(UpdateParentV1 {
1685 new_parent_slot: 0,
1686 new_parent_block_id: Hash::default(),
1687 }),
1688 true,
1689 )
1690 .unwrap();
1691
1692 let header = VersionedBlockHeader::V1(BlockHeaderV1 {
1693 parent_slot: 0,
1694 parent_block_id: Hash::default(),
1695 });
1696
1697 assert!(matches!(
1698 processor.on_header(&header, 0),
1699 Err(BlockComponentProcessorError::SpuriousUpdateParent)
1700 ));
1701 }
1702
1703 #[test]
1704 fn test_workflow_with_update_parent() {
1705 let migration_status = MigrationStatus::post_migration_status();
1706 let mut processor = BlockComponentProcessor::default();
1707 let (parent, bank_forks) = create_test_bank();
1708 let bank = create_child_bank(&bank_forks, &parent, 4);
1709 let shred_version = rand::rng().random();
1710
1711 processor
1712 .on_update_parent(
1713 bank.slot(),
1714 &VersionedUpdateParent::V1(UpdateParentV1 {
1715 new_parent_slot: 0,
1716 new_parent_block_id: Hash::default(),
1717 }),
1718 true,
1719 )
1720 .unwrap();
1721
1722 assert!(processor.on_entry_batch(&migration_status, 1).is_ok());
1723
1724 let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1725 let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1726 bank_hash: Hash::new_unique(),
1727 block_producer_time_nanos: (parent_time_nanos + 100_000_000) as u64,
1728 block_user_agent: vec![],
1729 block_final_cert: None,
1730 skip_reward_cert: None,
1731 notar_reward_cert: None,
1732 });
1733 processor
1734 .on_footer(bank, parent, shred_version, footer, None)
1735 .unwrap();
1736
1737 assert!(processor.on_final(&migration_status, 1).is_ok());
1738 }
1739}