Skip to main content

solana_runtime/
block_component_processor.rs

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        // Only require block markers (header/footer) for slots where they should be present
133        if !migration_status.should_allow_block_markers(slot) {
134            return Ok(());
135        }
136
137        // Post-migration: both header and footer are required
138        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    /// Process an entry batch.
150    ///
151    /// Validates that a parent marker (header or update parent) has been
152    /// processed before any entry batches.
153    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        // We must have either a header or an update parent prior to processing entry batches.
164        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    /// Process a block marker:
173    /// - Pre migration, no block markers are allowed
174    /// - During the migration only header and genesis certificate are allowed:
175    ///     - This is in case our node was slow in observing the completion of the migration
176    ///     - By seeing the first alpenglow block, we can advance the migration phase
177    /// - Once the migration is complete all markers are allowed
178    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            // Header and genesis cert can be processed either:
198            // - once migration is fully enabled, or
199            // - while we're still in the migration phase (to let us advance it)
200            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            // Everything else is only valid once migration is complete
215            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            // Any other combination means we saw a marker too early
232            _ => Err(BlockComponentProcessorError::BlockComponentPreMigration),
233        }
234    }
235
236    /// Processes the genesis block marker with full verification
237    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    /// Processes a locally produced genesis certificate marker without
254    /// re-verifying the certificate signature.
255    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    /// Performs verification if `shred_version` is specified
279    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        // Genesis Certificate is only allowed for direct child of genesis
287        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            // We participated in the migration, nothing to do
329            return Ok(());
330        }
331
332        // We missed the migration however we ingested the first alpenglow block.
333        // This is either a result of startup replay, or in some weird cases steady state replay after a network partition.
334        // Either way we ingest the genesis block details moving us to `ReadyToEnable`.
335        // Since this is a direct child of genesis, and we are replaying, we know we have frozen the genesis block.
336        // Then `load_frozen_forks` or `replay_stage` will take care of the rest.
337        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            &notar_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        // Send finalization cert(s) to consensus pool
440        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                // TODO blocking send.
446                let _ = sender
447                    .send(cert)
448                    .inspect_err(|_| info!("ConsensusMessage sender disconnected"));
449            }
450            let cert = ConsensusMessage::Certificate(finalize_cert);
451            // TODO blocking send.
452            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            // Only an error in the sense that replay execution of this block
508            // prefix is now over. Replay execution can continue after resetting
509            // bank.
510            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        // Get parent time from the nanosecond clock account, or from the Tower-based
524        // clock for the first Alpenglow block.
525        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    /// Converts a footer timestamp to the signed nanosecond representation used
549    /// by bank clock state.
550    ///
551    /// The `block_producer_time_nanos` parameter comes from wire-format footer
552    /// data and is rejected if it cannot be represented as `i64`; wrapping it
553    /// would make an extreme future timestamp look negative.
554    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    /// Given a parent time and elapsed slot duration, calculates inclusive
562    /// block producer timestamp bounds.
563    ///
564    /// `parent_time_nanos` describes the parent bank's nanosecond clock.
565    /// `elapsed_slot_duration_nanos` is the summed duration for all skipped
566    /// and working slots after the parent. The returned `(lower_bound,
567    /// upper_bound)` accepts timestamps where
568    /// `lower_bound <= working_bank_time <= upper_bound`.
569    ///
570    /// Refer to
571    /// https://github.com/solana-foundation/solana-improvement-documents/pull/363
572    /// for details on the bounds calculation.
573    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            // Record expected bank hash from footer for later verification when the bank is frozen.
603            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        // Try to process entry batch without header - should fail
669        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        // Try to mark slot as full without footer - should fail
796        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        // First header should succeed
812        assert!(processor.on_header(&header, 0).is_ok());
813
814        // Second header should fail
815        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        // Calculate valid timestamp based on parent's time
834        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; // parent + 400ms
836
837        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        // First footer should succeed
847        processor
848            .on_footer(
849                bank.clone(),
850                parent.clone(),
851                shred_version,
852                footer.clone(),
853                None,
854            )
855            .unwrap();
856
857        // Second footer should fail
858        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        // Calculate valid timestamp based on parent's time
879        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; // parent + 200ms
881        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        // Verify clock sysvar was updated with correct timestamp (nanos converted to seconds)
899        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, // mismatches bank.parent_slot() below (0)
964            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        // Calculate valid timestamp based on parent's time
1001        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; // parent + 300ms
1003        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        // Verify clock sysvar was updated
1028        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        // Calculate valid timestamp based on parent's time
1040        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; // parent + 100ms
1042        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1043
1044        // Process header
1045        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        // Process some entry batches (not full yet)
1052        assert!(processor.on_entry_batch(&migration_status, 1).is_ok());
1053
1054        // Process footer with valid timestamp
1055        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        // Verify clock sysvar was updated
1068        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1069
1070        // Entry batch after footer should still succeed
1071        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        // Try to process a block header marker pre-migration - should fail
1084        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        // Processing entry batches pre-migration (without markers) should succeed
1167        let result = processor.on_entry_batch(&migration_status, 1);
1168        assert!(result.is_ok());
1169
1170        // Even with slot full
1171        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        // Process header marker
1184        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        // Process entry batches
1201        assert!(processor.on_entry_batch(&migration_status, 1).is_ok());
1202
1203        // Calculate valid timestamp based on parent's time
1204        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; // parent + 500ms
1206        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1207
1208        // Process footer marker
1209        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        // Verify clock sysvar was updated
1230        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1231
1232        // Entry batch after footer should still succeed
1233        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        // Try to process footer without header - should fail
1254        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        // Process header first
1272        processor.has_header = true;
1273
1274        // Calculate valid timestamp based on parent's time
1275        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; // parent + 600ms
1277        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1278
1279        // Process footer marker
1280        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        // Should succeed - footer is processed
1290        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        // Verify clock sysvar was updated
1304        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        // Process entry batch with header but not full - should succeed even without footer
1316        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        // Create genesis bank
1329        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        // Get epoch schedule to find first slot of next epoch
1334        let epoch_schedule = genesis_bank.epoch_schedule();
1335        let first_slot_in_epoch_1 = epoch_schedule.get_first_slot_in_epoch(1);
1336
1337        // Create parent bank at last slot of epoch 0
1338        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        // Create bank at first slot of epoch 1
1344        let bank = create_child_bank(&bank_forks, &parent, first_slot_in_epoch_1);
1345
1346        // Verify we're in epoch 1
1347        assert_eq!(bank.epoch(), 1);
1348
1349        // Calculate valid timestamp based on parent's time
1350        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        // Use a timestamp in the middle of the valid range
1357        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        // Verify clock sysvar was updated
1378        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1379
1380        // Verify epoch_start_timestamp was set correctly for the new epoch
1381        assert_eq!(bank.clock().epoch_start_timestamp, expected_time_secs);
1382    }
1383
1384    // Helper function to test clock bounds enforcement
1385    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        // Set up clock on parent so validation doesn't skip bounds checking
1400        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        // For 5 slots: upper_bound = parent_time + 2 * 5 * 400ms = parent_time + 4000ms
1455        // Use 2 seconds which is within bounds
1456        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        // Exceed by 1 second beyond the upper bound
1462        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        // Timestamp equal to parent time (should fail, must be strictly greater)
1468        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    // Helper function to test nanosecond_time_bounds calculation
1539    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        // Test the nanosecond_time_bounds function directly
1557        // diff_slots = 15 - 10 = 5
1558        // lower = parent_time + 1
1559        // upper = parent_time + 2 * 5 * 400_000_000 = parent_time + 4_000_000_000
1560        let parent_slot = 10;
1561        let parent_time = 1_000_000_000_000; // 1000 seconds in nanos
1562        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        // Test with same slot (diff = 0)
1575        // diff_slots = 0
1576        // lower = parent_time + 1
1577        // upper = parent_time + 2 * 0 * 400_000_000 = parent_time
1578        // Note: In this case, lower > upper, so no timestamp would be valid
1579        // This is expected since we shouldn't have the same slot for parent and working bank
1580        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        // First should succeed
1669        processor.on_update_parent(4, &update_parent, true).unwrap();
1670
1671        // Second should fail
1672        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}