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::{CertSignature, Certificate, CertificateType, GenesisCert},
15        consensus_message::Block,
16        migration::MigrationStatus,
17        unverified_vote_message::UnverifiedCertificate,
18    },
19    crossbeam_channel::{Sender, TrySendError},
20    log::*,
21    smallvec::{SmallVec, smallvec},
22    solana_clock::Slot,
23    solana_entry::{
24        block_component::{
25            BlockFooterV1, BlockMarkerV1, GenesisCertBlockMarker, VersionedBlockFooter,
26            VersionedBlockHeader, VersionedBlockMarker, VersionedUpdateParent,
27        },
28        entry::Entry,
29    },
30    solana_hash::Hash,
31    solana_pubkey::Pubkey,
32    std::{collections::HashSet, sync::Arc},
33    thiserror::Error,
34};
35
36pub(crate) mod vote_reward;
37
38#[derive(Debug, Error)]
39pub enum BankFooterError {
40    #[error("calc vote rewards updating vote states failed with \"{0}\"")]
41    CalcVoteRewardUpdateVoteStates(#[from] CalcVoteRewardUpdateVoteStatesError),
42}
43
44#[derive(Debug, Error)]
45pub enum BlockComponentProcessorError {
46    #[error("BlockComponent detected pre-migration")]
47    BlockComponentPreMigration,
48    #[error("GenesisCertificate marker detected when GenesisCertificate is already populated")]
49    GenesisCertificateAlreadyPopulated,
50    #[error("GenesisCertificate marker detected when the cluster has Alpenglow enabled at slot 0")]
51    GenesisCertificateInAlpenglowCluster,
52    #[error("GenesisCertificate marker detected on a block which is not a child of genesis")]
53    GenesisCertificateOnNonChild,
54    #[error("GenesisCertificate was invalid and failed to verify")]
55    GenesisCertificateFailedVerification,
56    #[error("Alpenglow migration became ready; aborting the TowerBFT bank")]
57    AlpenglowMigrationTransition,
58    #[error("GenesisCertificate marker must immediately follow the block header")]
59    GenesisCertificateOutOfOrder,
60    #[error("FinalizationCertificate was invalid or failed to verify {0}")]
61    InvalidFinalizationCertificate(#[from] BlockFinalizationCertError),
62    #[error("Missing block footer")]
63    MissingBlockFooter,
64    #[error("Missing genesis certificate marker")]
65    MissingGenesisCertificateMarker,
66    #[error("Missing parent marker (neither a header nor an update parent was present)")]
67    MissingParentMarker,
68    #[error("Entry batch detected after block footer")]
69    EntryBatchAfterBlockFooter,
70    #[error("Alpentick must be the final block component and appear after block footer")]
71    InvalidAlpentickPosition,
72    #[error("Multiple block footers detected")]
73    MultipleBlockFooters,
74    #[error("Multiple block headers detected")]
75    MultipleBlockHeaders,
76    #[error(
77        "Block header parent slot mismatch: header={header_parent_slot}, bank={bank_parent_slot}"
78    )]
79    HeaderParentSlotMismatch {
80        header_parent_slot: Slot,
81        bank_parent_slot: Slot,
82    },
83    #[error("Multiple update parents detected")]
84    MultipleUpdateParents,
85    #[error("Nanosecond clock out of bounds")]
86    NanosecondClockOutOfBounds,
87    #[error("Spurious update parent")]
88    SpuriousUpdateParent,
89    #[error("UpdateParent marker is only valid in the first slot of a leader window: slot {0}")]
90    UpdateParentNotFirstInLeaderWindow(Slot),
91    #[error(
92        "UpdateParent cannot be the initial parent marker unless replay starts at UpdateParent"
93    )]
94    UnexpectedInitialUpdateParent,
95    #[error("Abandoned bank")]
96    AbandonedBank(VersionedUpdateParent),
97    #[error("invalid reward certs {0}")]
98    InvalidRewardCerts(#[from] ValidatedRewardCertError),
99    #[error("updating bank footer failed with \"{0}\"")]
100    UpdateBankFooter(#[from] BankFooterError),
101}
102
103impl BlockComponentProcessorError {
104    /// Returns whether this error can come from an optimistic-parent prefix
105    /// that a later usable `UpdateParent` makes obsolete.
106    ///
107    /// This only determines soft-dead eligibility. Replay also verifies that
108    /// the failure occurred before the `UpdateParent`.
109    pub fn is_update_parent_recoverable_replay_error(&self) -> bool {
110        match self {
111            BlockComponentProcessorError::MissingParentMarker
112            | BlockComponentProcessorError::EntryBatchAfterBlockFooter
113            | BlockComponentProcessorError::InvalidAlpentickPosition
114            | BlockComponentProcessorError::MultipleBlockFooters
115            | BlockComponentProcessorError::MultipleBlockHeaders
116            | BlockComponentProcessorError::HeaderParentSlotMismatch { .. }
117            | BlockComponentProcessorError::NanosecondClockOutOfBounds
118            | BlockComponentProcessorError::UnexpectedInitialUpdateParent
119            | BlockComponentProcessorError::GenesisCertificateOutOfOrder
120            | BlockComponentProcessorError::GenesisCertificateAlreadyPopulated
121            | BlockComponentProcessorError::GenesisCertificateInAlpenglowCluster
122            | BlockComponentProcessorError::GenesisCertificateOnNonChild
123            | BlockComponentProcessorError::GenesisCertificateFailedVerification
124            | BlockComponentProcessorError::SpuriousUpdateParent
125            | BlockComponentProcessorError::AbandonedBank(_)
126            | BlockComponentProcessorError::InvalidRewardCerts(_)
127            | BlockComponentProcessorError::UpdateBankFooter(_)
128            | BlockComponentProcessorError::InvalidFinalizationCertificate(_) => true,
129            BlockComponentProcessorError::BlockComponentPreMigration
130            | BlockComponentProcessorError::MissingBlockFooter
131            | BlockComponentProcessorError::MissingGenesisCertificateMarker
132            | BlockComponentProcessorError::MultipleUpdateParents
133            | BlockComponentProcessorError::AlpenglowMigrationTransition
134            | BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(_) => false,
135        }
136    }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
140/// The parent marker that established the current entry section.
141enum EntryParentMarker {
142    BlockHeader,
143    UpdateParent,
144}
145
146#[derive(Default, Debug, Clone, PartialEq, Eq)]
147/// The stage within the block we are currently in
148///
149/// All blocks MUST follow this exact shape
150///
151/// Header
152/// Optional Genesis marker - this is the only valid position for a genesis marker
153/// 0 or more Entries
154/// Optional UpdateParent
155/// 0 or more Entries
156/// Footer
157/// Alpentick
158///
159/// Block component processing can start either from the header
160/// or from the UpdateParent.
161enum BlockComponentStage {
162    #[default]
163    /// Beginning of the block, can only accept a parent marker
164    PreParentMarker,
165    /// Immediately after the header, can accept genesis marker, entries or footer
166    AcceptingGenesisOrEntries,
167    /// During the entries section, can accept entries or the footer
168    /// If the parent marker was a block header, can also accept an UpdateParent marker
169    AcceptingEntriesOrFooter { parent_marker: EntryParentMarker },
170    /// After the footer, can only accept the alpentick
171    AcceptingAlpentick,
172    /// After the alpentick, nothing more is accepted
173    Done,
174}
175
176impl BlockComponentStage {
177    /// If current stage is `PreParentMarker`, transition to `AcceptingGenesisOrEntries`
178    fn on_header(&mut self) -> Result<(), BlockComponentProcessorError> {
179        match self {
180            Self::PreParentMarker => {
181                *self = Self::AcceptingGenesisOrEntries;
182                Ok(())
183            }
184            Self::AcceptingGenesisOrEntries
185            | Self::AcceptingEntriesOrFooter {
186                parent_marker: EntryParentMarker::BlockHeader,
187            }
188            | Self::AcceptingAlpentick
189            | Self::Done => Err(BlockComponentProcessorError::MultipleBlockHeaders),
190            Self::AcceptingEntriesOrFooter {
191                parent_marker: EntryParentMarker::UpdateParent,
192            } => Err(BlockComponentProcessorError::SpuriousUpdateParent),
193        }
194    }
195
196    /// If current stage is `AcceptingGenesisOrEntries`, transition to `AcceptingEntriesOrFooter`
197    fn on_genesis_certificate(&mut self) -> Result<(), BlockComponentProcessorError> {
198        match self {
199            Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
200            Self::AcceptingGenesisOrEntries => {
201                *self = Self::AcceptingEntriesOrFooter {
202                    parent_marker: EntryParentMarker::BlockHeader,
203                };
204                Ok(())
205            }
206            Self::AcceptingEntriesOrFooter { .. } | Self::AcceptingAlpentick | Self::Done => {
207                Err(BlockComponentProcessorError::GenesisCertificateOutOfOrder)
208            }
209        }
210    }
211
212    /// If current stage is `AcceptingGenesisOrEntries` or `AcceptingEntriesOrFooter`, transition to
213    /// `AcceptingEntriesOrFooter`
214    fn on_entry_batch(&mut self) -> Result<(), BlockComponentProcessorError> {
215        match self {
216            Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
217            Self::AcceptingGenesisOrEntries => {
218                *self = Self::AcceptingEntriesOrFooter {
219                    parent_marker: EntryParentMarker::BlockHeader,
220                };
221                Ok(())
222            }
223            Self::AcceptingEntriesOrFooter { .. } => Ok(()),
224            Self::AcceptingAlpentick | Self::Done => {
225                Err(BlockComponentProcessorError::EntryBatchAfterBlockFooter)
226            }
227        }
228    }
229
230    /// If current stage is `AcceptingGenesisOrEntries` or `AcceptingEntriesOrFooter`, return `AbandonedBank`
231    /// If current stage is `PreParentMarker` and `allow_initial_update_parent` is specified,
232    /// transition to `AcceptingEntriesOrFooter` with `EntryParentMarker::UpdateParent`
233    fn on_update_parent(
234        &mut self,
235        update_parent: &VersionedUpdateParent,
236        allow_initial_update_parent: bool,
237    ) -> Result<(), BlockComponentProcessorError> {
238        match self {
239            Self::PreParentMarker => {
240                if !allow_initial_update_parent {
241                    return Err(BlockComponentProcessorError::UnexpectedInitialUpdateParent);
242                }
243                *self = Self::AcceptingEntriesOrFooter {
244                    parent_marker: EntryParentMarker::UpdateParent,
245                };
246                Ok(())
247            }
248            Self::AcceptingGenesisOrEntries
249            | Self::AcceptingEntriesOrFooter {
250                parent_marker: EntryParentMarker::BlockHeader,
251            } => {
252                // Only an error in the sense that replay execution of this block
253                // prefix is now over. Replay execution can continue after resetting
254                // bank.
255                Err(BlockComponentProcessorError::AbandonedBank(
256                    update_parent.clone(),
257                ))
258            }
259            Self::AcceptingEntriesOrFooter {
260                parent_marker: EntryParentMarker::UpdateParent,
261            } => Err(BlockComponentProcessorError::MultipleUpdateParents),
262            Self::AcceptingAlpentick | BlockComponentStage::Done => {
263                Err(BlockComponentProcessorError::SpuriousUpdateParent)
264            }
265        }
266    }
267
268    /// If the current stage is `AcceptingGenesisOrEntries`, `AcceptingEntriesOrFooter`
269    /// transition to `AcceptingAlpentick`
270    fn on_footer(&mut self) -> Result<(), BlockComponentProcessorError> {
271        match self {
272            Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
273            Self::AcceptingGenesisOrEntries | Self::AcceptingEntriesOrFooter { .. } => {
274                *self = Self::AcceptingAlpentick;
275                Ok(())
276            }
277            Self::AcceptingAlpentick | Self::Done => {
278                Err(BlockComponentProcessorError::MultipleBlockFooters)
279            }
280        }
281    }
282
283    /// If stage is `AcceptingAlpentick`, transition to `Done`
284    fn on_alpentick(&mut self) -> Result<(), BlockComponentProcessorError> {
285        match self {
286            Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
287            Self::AcceptingGenesisOrEntries => {
288                Err(BlockComponentProcessorError::InvalidAlpentickPosition)
289            }
290            Self::AcceptingEntriesOrFooter { .. } => {
291                Err(BlockComponentProcessorError::InvalidAlpentickPosition)
292            }
293            Self::AcceptingAlpentick => {
294                *self = Self::Done;
295                Ok(())
296            }
297            Self::Done => Err(BlockComponentProcessorError::InvalidAlpentickPosition),
298        }
299    }
300
301    /// Return `Ok(())` only if the stage is `Done`
302    fn on_final(&self) -> Result<(), BlockComponentProcessorError> {
303        match self {
304            Self::Done => Ok(()),
305            Self::AcceptingAlpentick => Err(BlockComponentProcessorError::InvalidAlpentickPosition),
306            Self::PreParentMarker
307            | Self::AcceptingGenesisOrEntries
308            | Self::AcceptingEntriesOrFooter { .. } => {
309                Err(BlockComponentProcessorError::MissingBlockFooter)
310            }
311        }
312    }
313}
314
315#[derive(Default)]
316pub struct BlockComponentProcessor {
317    stage: BlockComponentStage,
318    has_genesis_certificate_marker: bool,
319}
320
321impl BlockComponentProcessor {
322    pub fn on_final(
323        &self,
324        migration_status: &MigrationStatus,
325        slot: Slot,
326        parent_slot: Slot,
327    ) -> Result<(), BlockComponentProcessorError> {
328        // Only allow block markers for slots where they should be present.
329        // TowerBFT blocks must not include block headers.
330        if !migration_status.should_allow_block_markers(slot) {
331            if self.stage == BlockComponentStage::PreParentMarker {
332                return Ok(());
333            } else {
334                return Err(BlockComponentProcessorError::BlockComponentPreMigration);
335            };
336        }
337
338        if Self::requires_genesis_certificate_marker(migration_status, parent_slot)
339            && !self.has_genesis_certificate_marker
340        {
341            return Err(BlockComponentProcessorError::MissingGenesisCertificateMarker);
342        }
343
344        self.stage.on_final()
345    }
346
347    /// Check if `parent_slot` is the alpenglow genesis block for use in enforcing
348    /// that the block has a genesis block marker
349    ///
350    /// Note: We have an exemption for Dev clusters that have alpenglow active at slot 0,
351    /// as these clusters do not need a genesis block marker
352    fn requires_genesis_certificate_marker(
353        migration_status: &MigrationStatus,
354        parent_slot: Slot,
355    ) -> bool {
356        migration_status
357            .genesis_block()
358            .is_some_and(|genesis_block| {
359                genesis_block.slot != 0 && parent_slot == genesis_block.slot
360            })
361    }
362
363    /// Process an entry batch.
364    ///
365    /// Validates that a parent marker (header or update parent) has been processed
366    /// before any entry batches. The terminal Alpenglow tick is the only entry
367    /// batch allowed after the block footer.
368    pub fn on_entry_batch(
369        &mut self,
370        migration_status: &MigrationStatus,
371        slot: Slot,
372        entries: &[Entry],
373        is_final_component: bool,
374    ) -> Result<(), BlockComponentProcessorError> {
375        if !migration_status.should_allow_block_markers(slot) {
376            return Ok(());
377        }
378
379        // The alpentick must be the final block component.
380        // It is fine for other ticks to be present in the block, they will be rejected
381        // for `TooManyTicks` in `verify_ticks()`
382        let is_alpentick = is_final_component
383            && matches!(entries, [entry] if entry.is_tick() && entry.num_hashes == 1);
384
385        if is_alpentick {
386            self.stage.on_alpentick()
387        } else {
388            self.stage.on_entry_batch()
389        }
390    }
391
392    /// Process a block marker:
393    /// - Pre migration, no block markers are allowed
394    /// - During the migration only header and genesis certificate are allowed:
395    ///     - This is in case our node was slow in observing the completion of the migration
396    ///     - By seeing the first alpenglow block, we can advance the migration phase
397    /// - Once the migration is complete all markers are allowed
398    pub fn on_marker(
399        &mut self,
400        bank: Arc<Bank>,
401        parent_bank: Arc<Bank>,
402        shred_version: u16,
403        marker: VersionedBlockMarker,
404        allow_initial_update_parent: bool,
405        finalization_cert_sender: Option<&Sender<SmallVec<[Certificate; 2]>>>,
406        migration_status: &MigrationStatus,
407    ) -> Result<(), BlockComponentProcessorError> {
408        let slot = bank.slot();
409        let VersionedBlockMarker::V1(marker) = marker;
410
411        let markers_fully_enabled = migration_status.should_allow_block_markers(slot);
412        let in_migration = migration_status.is_in_migration();
413        let fast_leader_handover_active =
414            bank.feature_set.snapshot().alpenglow_fast_leader_handover;
415
416        match marker {
417            // Header and genesis cert can be processed either:
418            // - once migration is fully enabled, or
419            // - while we're still in the migration phase (to let us advance it)
420            BlockMarkerV1::BlockHeader(header) if markers_fully_enabled || in_migration => {
421                self.on_header(header.inner(), bank.parent_slot())
422            }
423            BlockMarkerV1::GenesisCertificate(genesis_cert_block_marker)
424                if markers_fully_enabled || in_migration =>
425            {
426                self.on_genesis_cert_block_marker(
427                    bank,
428                    shred_version,
429                    genesis_cert_block_marker.into_inner(),
430                    migration_status,
431                )
432            }
433
434            // Everything else is only valid once migration is complete
435            BlockMarkerV1::BlockFooter(footer) if markers_fully_enabled => self.on_footer(
436                &migration_status.my_pubkey(),
437                bank,
438                parent_bank,
439                shred_version,
440                footer.into_inner(),
441                finalization_cert_sender,
442            ),
443
444            BlockMarkerV1::UpdateParent(update_parent) if markers_fully_enabled => {
445                if fast_leader_handover_active {
446                    self.on_update_parent(slot, update_parent.inner(), allow_initial_update_parent)
447                } else {
448                    Err(BlockComponentProcessorError::SpuriousUpdateParent)
449                }
450            }
451
452            // Any other combination means we saw a marker too early
453            _ => Err(BlockComponentProcessorError::BlockComponentPreMigration),
454        }
455    }
456
457    /// Processes the genesis block marker with full verification
458    pub fn on_genesis_cert_block_marker(
459        &mut self,
460        bank: Arc<Bank>,
461        shred_version: u16,
462        genesis_block_marker: GenesisCertBlockMarker,
463        migration_status: &MigrationStatus,
464    ) -> Result<(), BlockComponentProcessorError> {
465        self.stage.on_genesis_certificate()?;
466        self.process_unvalidated_genesis_cert_block_marker(
467            bank,
468            genesis_block_marker,
469            migration_status,
470            Some(shred_version),
471        )?;
472        Ok(())
473    }
474
475    /// Processes a locally produced genesis certificate marker without verification
476    pub fn on_genesis_cert_block_marker_leader(
477        &mut self,
478        bank: Arc<Bank>,
479        genesis_block_marker: GenesisCertBlockMarker,
480        migration_status: &MigrationStatus,
481    ) -> Result<(), BlockComponentProcessorError> {
482        self.process_unvalidated_genesis_cert_block_marker(
483            bank,
484            genesis_block_marker,
485            migration_status,
486            None,
487        )?;
488        Ok(())
489    }
490
491    /// Performs verification if `shred_version` is specified
492    fn process_unvalidated_genesis_cert_block_marker(
493        &mut self,
494        bank: Arc<Bank>,
495        genesis_block_marker: GenesisCertBlockMarker,
496        migration_status: &MigrationStatus,
497        shred_version: Option<u16>,
498    ) -> Result<(), BlockComponentProcessorError> {
499        // Genesis Certificate is only allowed for direct child of genesis
500        if bank.parent_slot() == 0 {
501            return Err(BlockComponentProcessorError::GenesisCertificateInAlpenglowCluster);
502        }
503
504        let parent_block_id = bank
505            .parent_block_id()
506            .expect("Block id is populated for all slots > 0");
507        if (bank.parent_slot(), parent_block_id)
508            != (genesis_block_marker.slot, genesis_block_marker.block_id)
509        {
510            return Err(BlockComponentProcessorError::GenesisCertificateOnNonChild);
511        }
512
513        if bank.get_alpenglow_genesis_certificate().is_some() {
514            return Err(BlockComponentProcessorError::GenesisCertificateAlreadyPopulated);
515        }
516
517        let genesis_cert = GenesisCert {
518            block: Block {
519                slot: genesis_block_marker.slot,
520                block_id: genesis_block_marker.block_id,
521            },
522            signature: CertSignature {
523                signature: genesis_block_marker.bls_signature,
524                bitmap: genesis_block_marker.bitmap,
525            },
526        };
527        if let Some(shred_version) = shred_version {
528            Self::verify_genesis_certificate(&bank, &genesis_cert, shred_version)?;
529        }
530
531        bank.set_alpenglow_genesis_certificate(&genesis_cert);
532        self.has_genesis_certificate_marker = true;
533
534        if migration_status.is_alpenglow_enabled() {
535            // We participated in the migration, nothing to do
536            bank.set_hashes_per_tick(None);
537            return Ok(());
538        }
539
540        // We missed the migration however we ingested the first alpenglow block.
541        // This is either a result of startup replay, or in some weird cases steady state replay after a network partition.
542        // Either way we ingest the genesis block details moving us to `ReadyToEnable`.
543        // Since this is a direct child of genesis, and we are replaying, we know we have frozen the genesis block.
544        // Then `load_frozen_forks` or `replay_stage` will take care of the rest.
545        warn!(
546            "{}: Alpenglow genesis marker processed during replay of {}. Transitioning Alpenglow \
547             to ReadyToEnable",
548            migration_status.my_pubkey(),
549            bank.slot()
550        );
551        migration_status.set_genesis_block(genesis_cert.block);
552        migration_status.set_genesis_certificate(Arc::new(genesis_cert));
553        assert!(migration_status.is_ready_to_enable());
554
555        // This bank was created with TowerBFT tick configuration. Stop processing it immediately;
556        // replay will discard it, enable Alpenglow, and rebuild it with Alpenglow tick rules.
557        Err(BlockComponentProcessorError::AlpenglowMigrationTransition)
558    }
559
560    fn verify_genesis_certificate(
561        bank: &Bank,
562        cert: &GenesisCert,
563        shred_version: u16,
564    ) -> Result<(), BlockComponentProcessorError> {
565        let cert_slot = cert.block.slot;
566        let unverified_cert = UnverifiedCertificate {
567            cert_type: CertificateType::Genesis(cert.block),
568            signature: cert.signature.signature,
569            bitmap: cert.signature.bitmap.clone(),
570            shred_version,
571        };
572        bank.verify_certificate(unverified_cert, None)
573            .map_err(|_| {
574                warn!(
575                    "Failed to verify genesis certificate for slot {cert_slot} in bank slot {}",
576                    bank.slot()
577                );
578                BlockComponentProcessorError::GenesisCertificateFailedVerification
579            })?;
580
581        Ok(())
582    }
583
584    fn on_footer(
585        &mut self,
586        my_pubkey: &Pubkey,
587        bank: Arc<Bank>,
588        parent_bank: Arc<Bank>,
589        shred_version: u16,
590        footer: VersionedBlockFooter,
591        finalization_cert_sender: Option<&Sender<SmallVec<[Certificate; 2]>>>,
592    ) -> Result<(), BlockComponentProcessorError> {
593        self.stage.on_footer()?;
594
595        let VersionedBlockFooter::V1(footer) = footer;
596
597        Self::enforce_nanosecond_clock_bounds(&bank, &parent_bank, &footer)?;
598
599        let BlockFooterV1 {
600            bank_hash,
601            block_producer_time_nanos,
602            block_user_agent: _,
603            block_final_cert,
604            skip_reward_cert,
605            notar_reward_cert,
606        } = footer;
607
608        let reward_cert = ValidatedRewardCert::try_new(
609            &bank,
610            shred_version,
611            &skip_reward_cert,
612            &notar_reward_cert,
613        )?;
614        let block_producer_time_nanos =
615            Self::block_producer_time_nanos_as_i64(block_producer_time_nanos)?;
616        let final_cert = block_final_cert
617            .map(|final_cert| {
618                ValidatedBlockFinalizationCert::try_from_footer(final_cert, &bank, shred_version)
619                    .map_err(BlockComponentProcessorError::InvalidFinalizationCertificate)
620            })
621            .transpose()?;
622
623        let (footer_input, pool_input) = match final_cert {
624            None => (None, None),
625            Some(cert) => {
626                let (signers, finalize_cert, notarize_cert) = cert.into_parts();
627                let final_slot = finalize_cert.cert_type.slot();
628                (
629                    Some((signers, final_slot)),
630                    Some((finalize_cert, notarize_cert)),
631                )
632            }
633        };
634
635        Self::update_bank_with_footer_fields(
636            &bank,
637            block_producer_time_nanos,
638            Some(bank_hash),
639            reward_cert,
640            footer_input
641                .as_ref()
642                .map(|(validators, slot)| (validators, *slot)),
643        )?;
644
645        // Send finalization cert(s) to consensus pool
646        if let Some((finalize_cert, notarize_cert)) = pool_input
647            && let Some(sender) = finalization_cert_sender
648        {
649            let channel_name = "finalization_cert_sender";
650            let certs = match notarize_cert {
651                None => smallvec![finalize_cert],
652                Some(c) => smallvec![finalize_cert, c],
653            };
654            match sender.try_send(certs) {
655                Ok(()) => (),
656                Err(TrySendError::Full(_)) => {
657                    warn!("{my_pubkey}: channel \"{channel_name}\" is full, dropping msg")
658                }
659                Err(TrySendError::Disconnected(_)) => {
660                    warn!("{my_pubkey}: channel \"{channel_name}\" disconnected")
661                }
662            }
663        }
664
665        Ok(())
666    }
667
668    fn on_header(
669        &mut self,
670        header: &VersionedBlockHeader,
671        bank_parent_slot: Slot,
672    ) -> Result<(), BlockComponentProcessorError> {
673        self.stage.on_header()?;
674
675        let VersionedBlockHeader::V1(header) = header;
676        if header.parent_slot != bank_parent_slot {
677            return Err(BlockComponentProcessorError::HeaderParentSlotMismatch {
678                header_parent_slot: header.parent_slot,
679                bank_parent_slot,
680            });
681        }
682        Ok(())
683    }
684
685    fn on_update_parent(
686        &mut self,
687        slot: Slot,
688        update_parent: &VersionedUpdateParent,
689        allow_initial_update_parent: bool,
690    ) -> Result<(), BlockComponentProcessorError> {
691        if leader_slot_index(slot) != 0 {
692            return Err(BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(slot));
693        }
694
695        self.stage
696            .on_update_parent(update_parent, allow_initial_update_parent)
697    }
698
699    fn enforce_nanosecond_clock_bounds(
700        bank: &Bank,
701        parent_bank: &Bank,
702        footer: &BlockFooterV1,
703    ) -> Result<(), BlockComponentProcessorError> {
704        // Get parent time from the nanosecond clock account, or from the Tower-based
705        // clock for the first Alpenglow block.
706        let parent_time_nanos = parent_bank
707            .get_nanosecond_clock()
708            .unwrap_or_else(|| bank.clock().unix_timestamp.saturating_mul(1_000_000_000));
709
710        let parent_slot = parent_bank.slot();
711        let current_time_nanos =
712            Self::block_producer_time_nanos_as_i64(footer.block_producer_time_nanos)?;
713        let current_slot = bank.slot();
714        let elapsed_slot_duration_nanos =
715            bank.slot_range_duration_nanos(parent_slot.saturating_add(1), current_slot);
716
717        let (lower_bound_nanos, upper_bound_nanos) =
718            Self::nanosecond_time_bounds(parent_time_nanos, elapsed_slot_duration_nanos);
719
720        let is_valid =
721            lower_bound_nanos <= current_time_nanos && current_time_nanos <= upper_bound_nanos;
722
723        match is_valid {
724            true => Ok(()),
725            false => Err(BlockComponentProcessorError::NanosecondClockOutOfBounds),
726        }
727    }
728
729    /// Converts a footer timestamp to the signed nanosecond representation used
730    /// by bank clock state.
731    ///
732    /// The `block_producer_time_nanos` parameter comes from wire-format footer
733    /// data and is rejected if it cannot be represented as `i64`; wrapping it
734    /// would make an extreme future timestamp look negative.
735    fn block_producer_time_nanos_as_i64(
736        block_producer_time_nanos: u64,
737    ) -> Result<i64, BlockComponentProcessorError> {
738        i64::try_from(block_producer_time_nanos)
739            .map_err(|_| BlockComponentProcessorError::NanosecondClockOutOfBounds)
740    }
741
742    /// Given a parent time and elapsed slot duration, calculates inclusive
743    /// block producer timestamp bounds.
744    ///
745    /// `parent_time_nanos` describes the parent bank's nanosecond clock.
746    /// `elapsed_slot_duration_nanos` is the summed duration for all skipped
747    /// and working slots after the parent. The returned `(lower_bound,
748    /// upper_bound)` accepts timestamps where
749    /// `lower_bound <= working_bank_time <= upper_bound`.
750    ///
751    /// Refer to
752    /// https://github.com/solana-foundation/solana-improvement-documents/pull/363
753    /// for details on the bounds calculation.
754    pub fn nanosecond_time_bounds(
755        parent_time_nanos: i64,
756        elapsed_slot_duration_nanos: u128,
757    ) -> (i64, i64) {
758        let min_working_bank_time = parent_time_nanos.saturating_add(1);
759        let max_working_bank_time_offset = elapsed_slot_duration_nanos
760            .saturating_mul(2)
761            .min(i64::MAX as u128) as i64;
762        let max_working_bank_time = parent_time_nanos.saturating_add(max_working_bank_time_offset);
763
764        (min_working_bank_time, max_working_bank_time)
765    }
766
767    pub fn update_bank_with_footer_fields(
768        bank: &Bank,
769        block_producer_time_nanos: i64,
770        bank_hash: Option<Hash>,
771        reward_cert: Option<ValidatedRewardCert>,
772        final_cert_input: Option<(&HashSet<Pubkey>, Slot)>,
773    ) -> Result<(), BankFooterError> {
774        bank.update_clock_from_footer(block_producer_time_nanos);
775        calc_vote_rewards_update_vote_states(
776            bank,
777            reward_cert,
778            final_cert_input,
779            block_producer_time_nanos,
780        )?;
781
782        if let Some(hash) = bank_hash {
783            // Record expected bank hash from footer for later verification when the bank is frozen.
784            bank.set_expected_bank_hash(hash);
785        }
786        Ok(())
787    }
788}
789
790#[cfg(test)]
791mod tests {
792    use {
793        super::*,
794        crate::{
795            bank::{Bank, SlotLeader},
796            bank_forks::BankForks,
797            genesis_utils::{activate_all_features_alpenglow, create_genesis_config},
798        },
799        rand::Rng,
800        solana_bls_signatures::{BLS_SIGNATURE_AFFINE_SIZE, Signature as BLSSignature},
801        solana_clock::DEFAULT_MS_PER_SLOT,
802        solana_entry::{
803            block_component::{
804                BlockFooterV1, BlockHeaderV1, UpdateParentV1, VersionedUpdateParent,
805            },
806            entry::Entry,
807        },
808        solana_hash::Hash,
809        std::{
810            assert_matches,
811            sync::{Arc, RwLock},
812        },
813    };
814
815    const DEFAULT_NS_PER_SLOT: u64 = DEFAULT_MS_PER_SLOT * 1_000_000;
816
817    fn create_test_bank() -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
818        let genesis_config_info = create_genesis_config(10_000);
819        Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config)
820    }
821
822    fn create_test_bank_alpenglow() -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
823        let mut genesis_config_info = create_genesis_config(10_000);
824        activate_all_features_alpenglow(&mut genesis_config_info.genesis_config);
825        Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config)
826    }
827
828    fn create_child_bank(
829        bank_forks: &RwLock<BankForks>,
830        parent: &Arc<Bank>,
831        slot: u64,
832    ) -> Arc<Bank> {
833        Bank::new_from_parent_with_bank_forks(
834            bank_forks,
835            parent.clone(),
836            SlotLeader::new_unique(),
837            slot,
838        )
839    }
840
841    fn test_genesis_cert_marker() -> GenesisCertBlockMarker {
842        GenesisCertBlockMarker {
843            slot: 0,
844            block_id: Hash::default(),
845            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
846            bitmap: vec![],
847        }
848    }
849
850    fn post_migration_status_with_genesis_slot(genesis_slot: Slot) -> MigrationStatus {
851        let migration_status = MigrationStatus::default();
852        let migration_slot = migration_status.record_feature_activation(0);
853        assert!(genesis_slot < migration_slot);
854
855        let genesis_block = Block {
856            slot: genesis_slot,
857            block_id: Hash::default(),
858        };
859        migration_status.set_genesis_block(genesis_block);
860        let cert = Arc::new(GenesisCert {
861            block: genesis_block,
862            signature: CertSignature {
863                signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
864                bitmap: vec![],
865            },
866        });
867        migration_status.set_genesis_certificate(cert);
868        migration_status.enable_alpenglow_during_startup();
869
870        migration_status
871    }
872
873    fn processor_after_header() -> BlockComponentProcessor {
874        BlockComponentProcessor {
875            stage: BlockComponentStage::AcceptingGenesisOrEntries,
876            ..BlockComponentProcessor::default()
877        }
878    }
879
880    fn processor_after_footer() -> BlockComponentProcessor {
881        BlockComponentProcessor {
882            stage: BlockComponentStage::AcceptingAlpentick,
883            ..BlockComponentProcessor::default()
884        }
885    }
886
887    fn processor_done() -> BlockComponentProcessor {
888        BlockComponentProcessor {
889            stage: BlockComponentStage::Done,
890            ..BlockComponentProcessor::default()
891        }
892    }
893
894    fn alpentick(num_hashes: u64) -> [Entry; 1] {
895        [Entry::new(&Hash::default(), num_hashes, vec![])]
896    }
897
898    #[test]
899    fn test_missing_header_error_on_entry_batch() {
900        let migration_status = MigrationStatus::post_migration_status();
901        let mut processor = BlockComponentProcessor::default();
902
903        // Try to process entry batch without header - should fail
904        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
905        assert!(matches!(
906            result,
907            Err(BlockComponentProcessorError::MissingParentMarker)
908        ));
909    }
910
911    #[test]
912    fn test_missing_header_error_on_genesis_certificate() {
913        let migration_status = MigrationStatus::post_migration_status();
914        let mut processor = BlockComponentProcessor::default();
915        let marker =
916            VersionedBlockMarker::from_genesis_cert_block_marker(test_genesis_cert_marker());
917
918        let (parent, bank_forks) = create_test_bank();
919        let bank = create_child_bank(&bank_forks, &parent, 1);
920        let shred_version = rand::rng().random();
921
922        let result = processor.on_marker(
923            bank,
924            parent,
925            shred_version,
926            marker,
927            false,
928            None,
929            &migration_status,
930        );
931        assert!(matches!(
932            result,
933            Err(BlockComponentProcessorError::MissingParentMarker)
934        ));
935    }
936
937    #[test]
938    fn test_genesis_certificate_after_entry_batch_errors() {
939        let migration_status = MigrationStatus::post_migration_status();
940        let mut processor = BlockComponentProcessor::default();
941        let (parent, bank_forks) = create_test_bank();
942        let bank = create_child_bank(&bank_forks, &parent, 1);
943        let shred_version = rand::rng().random();
944
945        let header = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
946            parent_slot: 0,
947            parent_block_id: Hash::default(),
948        });
949        processor
950            .on_marker(
951                bank.clone(),
952                parent.clone(),
953                shred_version,
954                header,
955                false,
956                None,
957                &migration_status,
958            )
959            .unwrap();
960        processor
961            .on_entry_batch(&migration_status, bank.slot(), &[], false)
962            .unwrap();
963
964        let marker =
965            VersionedBlockMarker::from_genesis_cert_block_marker(test_genesis_cert_marker());
966        let result = processor.on_marker(
967            bank,
968            parent,
969            shred_version,
970            marker,
971            false,
972            None,
973            &migration_status,
974        );
975        assert!(matches!(
976            result,
977            Err(BlockComponentProcessorError::GenesisCertificateOutOfOrder)
978        ));
979    }
980
981    #[test]
982    fn test_genesis_certificate_immediately_after_header_passes_order_check() {
983        let migration_status = MigrationStatus::post_migration_status();
984        let mut processor = BlockComponentProcessor::default();
985        let (parent, bank_forks) = create_test_bank();
986        let bank = create_child_bank(&bank_forks, &parent, 1);
987        let shred_version = rand::rng().random();
988
989        let header = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
990            parent_slot: 0,
991            parent_block_id: Hash::default(),
992        });
993        processor
994            .on_marker(
995                bank.clone(),
996                parent.clone(),
997                shred_version,
998                header,
999                false,
1000                None,
1001                &migration_status,
1002            )
1003            .unwrap();
1004
1005        let marker =
1006            VersionedBlockMarker::from_genesis_cert_block_marker(test_genesis_cert_marker());
1007        let result = processor.on_marker(
1008            bank,
1009            parent,
1010            shred_version,
1011            marker,
1012            false,
1013            None,
1014            &migration_status,
1015        );
1016        assert!(matches!(
1017            result,
1018            Err(BlockComponentProcessorError::GenesisCertificateInAlpenglowCluster)
1019        ));
1020    }
1021
1022    #[test]
1023    fn test_missing_footer_error_on_slot_full() {
1024        let migration_status = MigrationStatus::post_migration_status();
1025        let processor = processor_after_header();
1026
1027        // Try to mark slot as full without footer - should fail
1028        let result = processor.on_final(&migration_status, 1, 0);
1029        assert!(matches!(
1030            result,
1031            Err(BlockComponentProcessorError::MissingBlockFooter)
1032        ));
1033    }
1034
1035    #[test]
1036    fn test_header_during_tower_bft_slot() {
1037        let migration_status = MigrationStatus::default();
1038        migration_status.record_feature_activation(0);
1039        let mut processor = BlockComponentProcessor::default();
1040        let (parent, bank_forks) = create_test_bank();
1041        let bank = create_child_bank(&bank_forks, &parent, 1);
1042        let marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1043            parent_slot: parent.slot(),
1044            parent_block_id: Hash::default(),
1045        });
1046
1047        assert!(!migration_status.should_allow_block_markers(bank.slot()));
1048        processor
1049            .on_marker(
1050                bank.clone(),
1051                parent,
1052                rand::rng().random(),
1053                marker,
1054                false,
1055                None,
1056                &migration_status,
1057            )
1058            .unwrap();
1059
1060        assert_matches!(
1061            processor.on_final(&migration_status, bank.slot(), bank.parent_slot()),
1062            Err(BlockComponentProcessorError::BlockComponentPreMigration)
1063        );
1064    }
1065
1066    #[test]
1067    fn test_first_alpenglow_block_requires_genesis_certificate_marker() {
1068        let migration_status = post_migration_status_with_genesis_slot(1);
1069        let processor = processor_after_footer();
1070
1071        let result = processor.on_final(&migration_status, 2, 1);
1072        assert!(matches!(
1073            result,
1074            Err(BlockComponentProcessorError::MissingGenesisCertificateMarker)
1075        ));
1076    }
1077
1078    #[test]
1079    fn test_first_alpenglow_block_with_genesis_certificate_marker_succeeds() {
1080        let migration_status = post_migration_status_with_genesis_slot(1);
1081        let (genesis_bank, bank_forks) = create_test_bank();
1082        let parent = create_child_bank(&bank_forks, &genesis_bank, 1);
1083        let parent_block_id = Hash::new_unique();
1084        parent.set_block_id(Some(parent_block_id));
1085        let bank = create_child_bank(&bank_forks, &parent, 2);
1086        let genesis_marker = GenesisCertBlockMarker {
1087            slot: parent.slot(),
1088            block_id: parent_block_id,
1089            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
1090            bitmap: vec![],
1091        };
1092        let mut processor = processor_after_header();
1093        bank.set_hashes_per_tick(Some(42));
1094        assert!(bank.hashes_per_tick().is_some());
1095
1096        processor
1097            .on_genesis_cert_block_marker_leader(bank.clone(), genesis_marker, &migration_status)
1098            .unwrap();
1099        assert!(bank.hashes_per_tick().is_none());
1100        processor.stage = BlockComponentStage::Done;
1101        assert!(processor.on_final(&migration_status, 2, 1).is_ok());
1102    }
1103
1104    #[test]
1105    fn test_genesis_certificate_marker_aborts_tower_bank_during_migration() {
1106        let migration_status = MigrationStatus::default();
1107        migration_status.record_feature_activation(0);
1108        let (genesis_bank, bank_forks) = create_test_bank();
1109        let parent = create_child_bank(&bank_forks, &genesis_bank, 1);
1110        let parent_block_id = Hash::new_unique();
1111        parent.set_block_id(Some(parent_block_id));
1112        let bank = create_child_bank(&bank_forks, &parent, 2);
1113        let genesis_marker = GenesisCertBlockMarker {
1114            slot: parent.slot(),
1115            block_id: parent_block_id,
1116            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
1117            bitmap: vec![],
1118        };
1119        let mut processor = processor_after_header();
1120        bank.set_hashes_per_tick(Some(42));
1121        let tower_hashes_per_tick = bank.hashes_per_tick();
1122        assert!(tower_hashes_per_tick.is_some());
1123
1124        assert_matches!(
1125            processor.on_genesis_cert_block_marker_leader(
1126                bank.clone(),
1127                genesis_marker,
1128                &migration_status,
1129            ),
1130            Err(BlockComponentProcessorError::AlpenglowMigrationTransition)
1131        );
1132
1133        assert!(migration_status.is_ready_to_enable());
1134        assert_eq!(bank.hashes_per_tick(), tower_hashes_per_tick);
1135        assert!(bank.get_alpenglow_genesis_certificate().is_some());
1136    }
1137
1138    #[test]
1139    fn test_first_alpenglow_block_genesis_slot_zero_skips_genesis_certificate_marker_check() {
1140        let migration_status = MigrationStatus::post_migration_status();
1141        let processor = processor_done();
1142
1143        assert!(processor.on_final(&migration_status, 1, 0).is_ok());
1144    }
1145
1146    #[test]
1147    fn test_multiple_headers_error() {
1148        let mut processor = BlockComponentProcessor::default();
1149        let header = VersionedBlockHeader::V1(BlockHeaderV1 {
1150            parent_slot: 0,
1151            parent_block_id: Hash::default(),
1152        });
1153
1154        // First header should succeed
1155        assert!(processor.on_header(&header, 0).is_ok());
1156
1157        // Second header should fail
1158        let result = processor.on_header(&header, 0);
1159        assert!(matches!(
1160            result,
1161            Err(BlockComponentProcessorError::MultipleBlockHeaders)
1162        ));
1163    }
1164
1165    #[test]
1166    fn test_multiple_footers_error() {
1167        let my_pubkey = Pubkey::new_unique();
1168        let mut processor = processor_after_header();
1169
1170        let (parent, bank_forks) = create_test_bank();
1171        let bank = create_child_bank(&bank_forks, &parent, 1);
1172        let shred_version = rand::rng().random();
1173
1174        // Calculate valid timestamp based on parent's time
1175        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1176        let footer_time_nanos = parent_time_nanos + 400_000_000; // parent + 400ms
1177
1178        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1179            bank_hash: Hash::new_unique(),
1180            block_producer_time_nanos: footer_time_nanos as u64,
1181            block_user_agent: vec![],
1182            block_final_cert: None,
1183            skip_reward_cert: None,
1184            notar_reward_cert: None,
1185        });
1186
1187        // First footer should succeed
1188        processor
1189            .on_footer(
1190                &my_pubkey,
1191                bank.clone(),
1192                parent.clone(),
1193                shred_version,
1194                footer.clone(),
1195                None,
1196            )
1197            .unwrap();
1198
1199        // Second footer should fail
1200        let err = processor
1201            .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1202            .unwrap_err();
1203        assert!(matches!(
1204            err,
1205            BlockComponentProcessorError::MultipleBlockFooters
1206        ));
1207    }
1208
1209    #[test]
1210    fn test_on_footer_sets_timestamp() {
1211        let my_pubkey = Pubkey::new_unique();
1212        let mut processor = processor_after_header();
1213
1214        let (parent, bank_forks) = create_test_bank();
1215        let bank = create_child_bank(&bank_forks, &parent, 1);
1216        let shred_version = rand::rng().random();
1217
1218        // Calculate valid timestamp based on parent's time
1219        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1220        let footer_time_nanos = parent_time_nanos + 200_000_000; // parent + 200ms
1221        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1222
1223        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1224            bank_hash: Hash::new_unique(),
1225            block_producer_time_nanos: footer_time_nanos as u64,
1226            block_user_agent: vec![],
1227            block_final_cert: None,
1228            skip_reward_cert: None,
1229            notar_reward_cert: None,
1230        });
1231
1232        processor
1233            .on_footer(
1234                &my_pubkey,
1235                bank.clone(),
1236                parent,
1237                shred_version,
1238                footer,
1239                None,
1240            )
1241            .unwrap();
1242
1243        assert_eq!(processor.stage, BlockComponentStage::AcceptingAlpentick);
1244
1245        // Verify clock sysvar was updated with correct timestamp (nanos converted to seconds)
1246        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1247    }
1248
1249    #[test]
1250    fn test_on_header_sets_flag() {
1251        let mut processor = BlockComponentProcessor::default();
1252        let header = VersionedBlockHeader::V1(BlockHeaderV1 {
1253            parent_slot: 0,
1254            parent_block_id: Hash::default(),
1255        });
1256
1257        processor.on_header(&header, 0).unwrap();
1258        assert_eq!(
1259            processor.stage,
1260            BlockComponentStage::AcceptingGenesisOrEntries
1261        );
1262    }
1263
1264    #[test]
1265    fn test_on_header_parent_slot_mismatch_error() {
1266        let mut processor = BlockComponentProcessor::default();
1267        let header = VersionedBlockHeader::V1(BlockHeaderV1 {
1268            parent_slot: 2,
1269            parent_block_id: Hash::default(),
1270        });
1271
1272        assert!(matches!(
1273            processor.on_header(&header, 0),
1274            Err(BlockComponentProcessorError::HeaderParentSlotMismatch {
1275                header_parent_slot: 2,
1276                bank_parent_slot: 0,
1277            })
1278        ));
1279    }
1280
1281    #[test]
1282    fn test_on_marker_processes_header() {
1283        let migration_status = MigrationStatus::post_migration_status();
1284        let mut processor = BlockComponentProcessor::default();
1285        let marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1286            parent_slot: 0,
1287            parent_block_id: Hash::default(),
1288        });
1289
1290        let (parent, bank_forks) = create_test_bank();
1291        let bank = create_child_bank(&bank_forks, &parent, 1);
1292        let shred_version = rand::rng().random();
1293
1294        processor
1295            .on_marker(
1296                bank,
1297                parent,
1298                shred_version,
1299                marker,
1300                false,
1301                None,
1302                &migration_status,
1303            )
1304            .unwrap();
1305        assert_eq!(
1306            processor.stage,
1307            BlockComponentStage::AcceptingGenesisOrEntries
1308        );
1309    }
1310
1311    #[test]
1312    fn test_on_marker_rejects_header_parent_slot_mismatch() {
1313        let migration_status = MigrationStatus::post_migration_status();
1314        let mut processor = BlockComponentProcessor::default();
1315        let marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1316            parent_slot: 7, // mismatches bank.parent_slot() below (0)
1317            parent_block_id: Hash::default(),
1318        });
1319
1320        let (parent, bank_forks) = create_test_bank();
1321        let bank = create_child_bank(&bank_forks, &parent, 1);
1322        let shred_version = rand::rng().random();
1323
1324        assert!(matches!(
1325            processor.on_marker(
1326                bank,
1327                parent,
1328                shred_version,
1329                marker,
1330                false,
1331                None,
1332                &migration_status
1333            ),
1334            Err(BlockComponentProcessorError::HeaderParentSlotMismatch {
1335                header_parent_slot: 7,
1336                bank_parent_slot: 0,
1337            })
1338        ));
1339    }
1340
1341    #[test]
1342    fn test_on_marker_processes_footer() {
1343        let migration_status = MigrationStatus::post_migration_status();
1344        let mut processor = processor_after_header();
1345
1346        let (parent, bank_forks) = create_test_bank();
1347        let bank = create_child_bank(&bank_forks, &parent, 1);
1348        let shred_version = rand::rng().random();
1349
1350        // Calculate valid timestamp based on parent's time
1351        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1352        let footer_time_nanos = parent_time_nanos + 300_000_000; // parent + 300ms
1353        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1354
1355        let marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1356            bank_hash: Hash::new_unique(),
1357            block_producer_time_nanos: footer_time_nanos as u64,
1358            block_user_agent: vec![],
1359            block_final_cert: None,
1360            skip_reward_cert: None,
1361            notar_reward_cert: None,
1362        });
1363
1364        processor
1365            .on_marker(
1366                bank.clone(),
1367                parent,
1368                shred_version,
1369                marker,
1370                false,
1371                None,
1372                &migration_status,
1373            )
1374            .unwrap();
1375        assert_eq!(processor.stage, BlockComponentStage::AcceptingAlpentick);
1376
1377        // Verify clock sysvar was updated
1378        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1379    }
1380
1381    #[test]
1382    fn test_complete_workflow_success() {
1383        let migration_status = MigrationStatus::post_migration_status();
1384        let mut processor = BlockComponentProcessor::default();
1385        let (parent, bank_forks) = create_test_bank();
1386        let bank = create_child_bank(&bank_forks, &parent, 1);
1387        let shred_version = rand::rng().random();
1388
1389        // Calculate valid timestamp based on parent's time
1390        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1391        let footer_time_nanos = parent_time_nanos + 100_000_000; // parent + 100ms
1392        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1393
1394        // Process header
1395        let header = VersionedBlockHeader::V1(BlockHeaderV1 {
1396            parent_slot: 0,
1397            parent_block_id: Hash::default(),
1398        });
1399        processor.on_header(&header, bank.parent_slot()).unwrap();
1400
1401        // Process some entry batches (not full yet)
1402        processor
1403            .on_entry_batch(&migration_status, 1, &[], false)
1404            .unwrap();
1405
1406        // Process footer with valid timestamp
1407        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1408            bank_hash: Hash::new_unique(),
1409            block_producer_time_nanos: footer_time_nanos as u64,
1410            block_user_agent: vec![],
1411            block_final_cert: None,
1412            skip_reward_cert: None,
1413            notar_reward_cert: None,
1414        });
1415        processor
1416            .on_footer(
1417                &migration_status.my_pubkey(),
1418                bank.clone(),
1419                parent.clone(),
1420                shred_version,
1421                footer,
1422                None,
1423            )
1424            .unwrap();
1425
1426        // Verify clock sysvar was updated
1427        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1428
1429        // Entry batch after footer should fail because the footer is terminal.
1430        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
1431        assert_matches!(
1432            result,
1433            Err(BlockComponentProcessorError::EntryBatchAfterBlockFooter)
1434        );
1435    }
1436
1437    #[test]
1438    fn test_alpentick_position_validation() {
1439        let migration_status = MigrationStatus::post_migration_status();
1440        let mut processor = processor_after_footer();
1441        let good_alpentick = alpentick(1);
1442
1443        processor
1444            .on_entry_batch(&migration_status, 1, &good_alpentick, true)
1445            .unwrap();
1446        assert_matches!(
1447            processor.on_entry_batch(&migration_status, 1, &good_alpentick, true),
1448            Err(BlockComponentProcessorError::InvalidAlpentickPosition)
1449        );
1450
1451        let mut processor = BlockComponentProcessor::default();
1452        assert_matches!(
1453            processor.on_entry_batch(&migration_status, 1, &good_alpentick, true),
1454            Err(BlockComponentProcessorError::MissingParentMarker)
1455        );
1456
1457        let mut processor = processor_after_footer();
1458        let bad_alpentick = alpentick(2);
1459        assert_matches!(
1460            processor.on_entry_batch(&migration_status, 1, &bad_alpentick, true),
1461            Err(BlockComponentProcessorError::EntryBatchAfterBlockFooter)
1462        );
1463
1464        let migration_status = MigrationStatus::default();
1465        let mut processor = BlockComponentProcessor::default();
1466        processor
1467            .on_entry_batch(&migration_status, 1, &good_alpentick, true)
1468            .unwrap();
1469    }
1470
1471    #[test]
1472    fn test_block_marker_detected_pre_migration() {
1473        let migration_status = MigrationStatus::default();
1474        let mut processor = BlockComponentProcessor::default();
1475        let (parent, bank_forks) = create_test_bank();
1476        let bank = create_child_bank(&bank_forks, &parent, 1);
1477        let shred_version = rand::rng().random();
1478
1479        // Try to process a block header marker pre-migration - should fail
1480        let marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1481            parent_slot: 0,
1482            parent_block_id: Hash::default(),
1483        });
1484
1485        let err = processor
1486            .on_marker(
1487                bank,
1488                parent,
1489                shred_version,
1490                marker,
1491                false,
1492                None,
1493                &migration_status,
1494            )
1495            .unwrap_err();
1496        assert!(matches!(
1497            err,
1498            BlockComponentProcessorError::BlockComponentPreMigration
1499        ));
1500    }
1501
1502    #[test]
1503    fn test_footer_and_update_parent_rejected_pre_migration() {
1504        let migration_status = MigrationStatus::default();
1505        let (parent, bank_forks) = create_test_bank();
1506        let bank = create_child_bank(&bank_forks, &parent, 1);
1507        let shred_version = rand::rng().random();
1508
1509        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1510        let footer_marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1511            bank_hash: Hash::new_unique(),
1512            block_producer_time_nanos: (parent_time_nanos + 500_000_000) as u64,
1513            block_user_agent: vec![],
1514            block_final_cert: None,
1515            skip_reward_cert: None,
1516            notar_reward_cert: None,
1517        });
1518
1519        let mut processor = BlockComponentProcessor::default();
1520        assert!(matches!(
1521            processor
1522                .on_marker(
1523                    bank.clone(),
1524                    parent.clone(),
1525                    shred_version,
1526                    footer_marker,
1527                    false,
1528                    None,
1529                    &migration_status
1530                )
1531                .unwrap_err(),
1532            BlockComponentProcessorError::BlockComponentPreMigration
1533        ));
1534
1535        let update_parent_marker = VersionedBlockMarker::from_update_parent(UpdateParentV1 {
1536            new_parent_slot: 0,
1537            new_parent_block_id: Hash::default(),
1538        });
1539
1540        let mut processor = BlockComponentProcessor::default();
1541        assert!(matches!(
1542            processor
1543                .on_marker(
1544                    bank,
1545                    parent,
1546                    shred_version,
1547                    update_parent_marker,
1548                    false,
1549                    None,
1550                    &migration_status
1551                )
1552                .unwrap_err(),
1553            BlockComponentProcessorError::BlockComponentPreMigration
1554        ));
1555    }
1556
1557    #[test]
1558    fn test_entry_batch_pre_migration_succeeds() {
1559        let migration_status = MigrationStatus::default();
1560        let mut processor = BlockComponentProcessor::default();
1561
1562        // Processing entry batches pre-migration (without markers) should succeed
1563        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
1564        assert!(result.is_ok());
1565
1566        // Even with slot full
1567        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
1568        assert!(result.is_ok());
1569
1570        // A Tower block with no markers is valid
1571        assert!(processor.on_final(&migration_status, 1, 0).is_ok());
1572    }
1573
1574    #[test]
1575    fn test_complete_workflow_post_migration() {
1576        let migration_status = MigrationStatus::post_migration_status();
1577        let mut processor = BlockComponentProcessor::default();
1578        let (parent, bank_forks) = create_test_bank();
1579        let bank = create_child_bank(&bank_forks, &parent, 1);
1580        let shred_version = rand::rng().random();
1581
1582        // Process header marker
1583        let header_marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1584            parent_slot: 0,
1585            parent_block_id: Hash::default(),
1586        });
1587        processor
1588            .on_marker(
1589                bank.clone(),
1590                parent.clone(),
1591                shred_version,
1592                header_marker,
1593                false,
1594                None,
1595                &migration_status,
1596            )
1597            .unwrap();
1598
1599        // Process entry batches
1600        processor
1601            .on_entry_batch(&migration_status, 1, &[], false)
1602            .unwrap();
1603
1604        // Calculate valid timestamp based on parent's time
1605        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1606        let footer_time_nanos = parent_time_nanos + 500_000_000; // parent + 500ms
1607        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1608
1609        // Process footer marker
1610        let footer_marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1611            bank_hash: Hash::new_unique(),
1612            block_producer_time_nanos: footer_time_nanos as u64,
1613            block_user_agent: vec![],
1614            block_final_cert: None,
1615            skip_reward_cert: None,
1616            notar_reward_cert: None,
1617        });
1618        processor
1619            .on_marker(
1620                bank.clone(),
1621                parent,
1622                shred_version,
1623                footer_marker,
1624                false,
1625                None,
1626                &migration_status,
1627            )
1628            .unwrap();
1629
1630        // Verify clock sysvar was updated
1631        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1632
1633        // Entry batch after footer should fail because the footer is terminal.
1634        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
1635        assert_matches!(
1636            result,
1637            Err(BlockComponentProcessorError::EntryBatchAfterBlockFooter)
1638        );
1639    }
1640
1641    #[test]
1642    fn test_footer_without_header_errors() {
1643        let my_pubkey = Pubkey::new_unique();
1644        let mut processor = BlockComponentProcessor::default();
1645        let (parent, bank_forks) = create_test_bank();
1646        let bank = create_child_bank(&bank_forks, &parent, 1);
1647        let shred_version = rand::rng().random();
1648
1649        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1650            bank_hash: Hash::new_unique(),
1651            block_producer_time_nanos: 1_000_000_000,
1652            block_user_agent: vec![],
1653            block_final_cert: None,
1654            skip_reward_cert: None,
1655            notar_reward_cert: None,
1656        });
1657
1658        // Try to process footer without header - should fail
1659        let err = processor
1660            .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1661            .unwrap_err();
1662        assert!(matches!(
1663            err,
1664            BlockComponentProcessorError::MissingParentMarker
1665        ));
1666    }
1667
1668    #[test]
1669    fn test_marker_with_footer_at_slot_full() {
1670        let migration_status = MigrationStatus::post_migration_status();
1671        let mut processor = processor_after_header();
1672        let (parent, bank_forks) = create_test_bank();
1673        let bank = create_child_bank(&bank_forks, &parent, 1);
1674        let shred_version = rand::rng().random();
1675
1676        // Calculate valid timestamp based on parent's time
1677        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1678        let footer_time_nanos = parent_time_nanos + 600_000_000; // parent + 600ms
1679        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1680
1681        // Process footer marker
1682        let footer_marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1683            bank_hash: Hash::new_unique(),
1684            block_producer_time_nanos: footer_time_nanos as u64,
1685            block_user_agent: vec![],
1686            block_final_cert: None,
1687            skip_reward_cert: None,
1688            notar_reward_cert: None,
1689        });
1690
1691        // Should succeed - footer is processed
1692        processor
1693            .on_marker(
1694                bank.clone(),
1695                parent,
1696                shred_version,
1697                footer_marker,
1698                false,
1699                None,
1700                &migration_status,
1701            )
1702            .unwrap();
1703        assert_eq!(processor.stage, BlockComponentStage::AcceptingAlpentick);
1704
1705        // Verify clock sysvar was updated
1706        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1707    }
1708
1709    #[test]
1710    fn test_entry_batch_with_header_not_full_succeeds() {
1711        let migration_status = MigrationStatus::post_migration_status();
1712        let mut processor = processor_after_header();
1713
1714        // Process entry batch with header but not full - should succeed even without footer
1715        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
1716        assert!(result.is_ok());
1717    }
1718
1719    #[test]
1720    fn test_footer_sets_epoch_start_timestamp_on_epoch_change() {
1721        let my_pubkey = Pubkey::new_unique();
1722        let mut processor = processor_after_header();
1723        let shred_version = rand::rng().random();
1724
1725        // Create genesis bank
1726        let genesis_config_info = create_genesis_config(10_000);
1727        let (genesis_bank, bank_forks) =
1728            Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config);
1729
1730        // Get epoch schedule to find first slot of next epoch
1731        let epoch_schedule = genesis_bank.epoch_schedule();
1732        let first_slot_in_epoch_1 = epoch_schedule.get_first_slot_in_epoch(1);
1733
1734        // Create parent bank at last slot of epoch 0
1735        let mut parent = genesis_bank.clone();
1736        for slot in 1..first_slot_in_epoch_1 {
1737            parent = create_child_bank(&bank_forks, &parent, slot);
1738        }
1739
1740        // Create bank at first slot of epoch 1
1741        let bank = create_child_bank(&bank_forks, &parent, first_slot_in_epoch_1);
1742
1743        // Verify we're in epoch 1
1744        assert_eq!(bank.epoch(), 1);
1745
1746        // Calculate valid timestamp based on parent's time
1747        let parent_slot = parent.slot();
1748        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1749        let current_slot = bank.slot();
1750        let elapsed_slot_duration_nanos =
1751            bank.slot_range_duration_nanos(parent_slot.saturating_add(1), current_slot);
1752
1753        // Use a timestamp in the middle of the valid range
1754        let (lower_bound, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1755            parent_time_nanos,
1756            elapsed_slot_duration_nanos,
1757        );
1758        let footer_time_nanos = (lower_bound + upper_bound) / 2;
1759        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1760
1761        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1762            bank_hash: Hash::new_unique(),
1763            block_producer_time_nanos: footer_time_nanos as u64,
1764            block_user_agent: vec![],
1765            block_final_cert: None,
1766            skip_reward_cert: None,
1767            notar_reward_cert: None,
1768        });
1769
1770        processor
1771            .on_footer(
1772                &my_pubkey,
1773                bank.clone(),
1774                parent,
1775                shred_version,
1776                footer,
1777                None,
1778            )
1779            .unwrap();
1780
1781        // Verify clock sysvar was updated
1782        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1783
1784        // Verify epoch_start_timestamp was set correctly for the new epoch
1785        assert_eq!(bank.clock().epoch_start_timestamp, expected_time_secs);
1786    }
1787
1788    // Helper function to test clock bounds enforcement
1789    fn test_clock_bounds_helper(
1790        slot_gap: u64,
1791        timestamp_fn: impl FnOnce(i64, i64, i64) -> i64,
1792        should_pass: bool,
1793    ) {
1794        let my_pubkey = Pubkey::new_unique();
1795        let mut processor = processor_after_header();
1796        let shred_version = rand::rng().random();
1797
1798        let (parent, bank_forks) = create_test_bank_alpenglow();
1799        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1800
1801        // Set up clock on parent so validation doesn't skip bounds checking
1802        parent.update_clock_from_footer(parent_time_nanos);
1803
1804        let bank: Arc<Bank> = create_child_bank(&bank_forks, &parent, slot_gap);
1805        let elapsed_slot_duration_nanos = bank.slot_range_duration_nanos(1, slot_gap);
1806
1807        let (lower_bound, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1808            parent_time_nanos,
1809            elapsed_slot_duration_nanos,
1810        );
1811
1812        let footer_time_nanos = timestamp_fn(parent_time_nanos, lower_bound, upper_bound);
1813
1814        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1815            bank_hash: Hash::new_unique(),
1816            block_producer_time_nanos: footer_time_nanos as u64,
1817            block_user_agent: vec![],
1818            block_final_cert: None,
1819            skip_reward_cert: None,
1820            notar_reward_cert: None,
1821        });
1822
1823        let result = processor.on_footer(&my_pubkey, bank, parent, shred_version, footer, None);
1824        if should_pass {
1825            result.unwrap();
1826        } else {
1827            assert!(matches!(
1828                result.unwrap_err(),
1829                BlockComponentProcessorError::NanosecondClockOutOfBounds
1830            ));
1831        }
1832    }
1833
1834    #[test]
1835    fn test_clock_bounds_at_minimum() {
1836        test_clock_bounds_helper(1, |_, lower, _| lower, true);
1837    }
1838
1839    #[test]
1840    fn test_clock_bounds_at_maximum() {
1841        test_clock_bounds_helper(1, |_, _, upper| upper, true);
1842    }
1843
1844    #[test]
1845    fn test_clock_bounds_below_minimum() {
1846        test_clock_bounds_helper(1, |_, lower, _| lower - 1, false);
1847    }
1848
1849    #[test]
1850    fn test_clock_bounds_above_maximum() {
1851        test_clock_bounds_helper(1, |_, _, upper| upper + 1, false);
1852    }
1853
1854    #[test]
1855    fn test_clock_bounds_multi_slot_gap() {
1856        // For 5 slots: upper_bound = parent_time + 2 * 5 * 400ms = parent_time + 4000ms
1857        // Use 2 seconds which is within bounds
1858        test_clock_bounds_helper(5, |_, lower, _| lower + 2_000_000_000, true);
1859    }
1860
1861    #[test]
1862    fn test_clock_bounds_multi_slot_gap_exceeds() {
1863        // Exceed by 1 second beyond the upper bound
1864        test_clock_bounds_helper(5, |_, _, upper| upper + 1_000_000_000, false);
1865    }
1866
1867    #[test]
1868    fn test_clock_bounds_timestamp_equals_parent() {
1869        // Timestamp equal to parent time (should fail, must be strictly greater)
1870        test_clock_bounds_helper(1, |parent_time, _, _| parent_time, false);
1871    }
1872
1873    #[test]
1874    fn test_clock_bounds_without_parent_nanosecond_clock_rejects_out_of_bounds() {
1875        let my_pubkey = Pubkey::new_unique();
1876        let mut processor = processor_after_header();
1877        let shred_version = rand::rng().random();
1878
1879        let (parent, bank_forks) = create_test_bank_alpenglow();
1880        assert_eq!(parent.get_nanosecond_clock(), None);
1881
1882        let bank = create_child_bank(&bank_forks, &parent, 1);
1883        let parent_time_nanos = bank.clock().unix_timestamp.saturating_mul(1_000_000_000);
1884        let elapsed_slot_duration_nanos =
1885            bank.slot_range_duration_nanos(parent.slot().saturating_add(1), bank.slot());
1886        let (_, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1887            parent_time_nanos,
1888            elapsed_slot_duration_nanos,
1889        );
1890
1891        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1892            bank_hash: Hash::new_unique(),
1893            block_producer_time_nanos: u64::try_from(upper_bound.saturating_add(1)).unwrap(),
1894            block_user_agent: vec![],
1895            block_final_cert: None,
1896            skip_reward_cert: None,
1897            notar_reward_cert: None,
1898        });
1899
1900        assert!(matches!(
1901            processor
1902                .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1903                .unwrap_err(),
1904            BlockComponentProcessorError::NanosecondClockOutOfBounds
1905        ));
1906    }
1907
1908    #[test]
1909    fn test_clock_bounds_rejects_timestamp_above_i64() {
1910        let my_pubkey = Pubkey::new_unique();
1911        let mut processor = processor_after_header();
1912        let shred_version = rand::rng().random();
1913
1914        let (parent, bank_forks) = create_test_bank_alpenglow();
1915        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1916        parent.update_clock_from_footer(parent_time_nanos);
1917        let bank = create_child_bank(&bank_forks, &parent, 1);
1918
1919        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1920            bank_hash: Hash::new_unique(),
1921            block_producer_time_nanos: u64::MAX,
1922            block_user_agent: vec![],
1923            block_final_cert: None,
1924            skip_reward_cert: None,
1925            notar_reward_cert: None,
1926        });
1927
1928        assert!(matches!(
1929            processor
1930                .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1931                .unwrap_err(),
1932            BlockComponentProcessorError::NanosecondClockOutOfBounds
1933        ));
1934    }
1935
1936    // Helper function to test nanosecond_time_bounds calculation
1937    fn test_nanosecond_time_bounds_helper(
1938        parent_time_nanos: i64,
1939        elapsed_slot_duration_nanos: u128,
1940        expected_lower: i64,
1941        expected_upper: i64,
1942    ) {
1943        let (lower, upper) = BlockComponentProcessor::nanosecond_time_bounds(
1944            parent_time_nanos,
1945            elapsed_slot_duration_nanos,
1946        );
1947
1948        assert_eq!(lower, expected_lower);
1949        assert_eq!(upper, expected_upper);
1950    }
1951
1952    #[test]
1953    fn test_nanosecond_time_bounds_calculation() {
1954        // Test the nanosecond_time_bounds function directly
1955        // diff_slots = 15 - 10 = 5
1956        // lower = parent_time + 1
1957        // upper = parent_time + 2 * 5 * 400_000_000 = parent_time + 4_000_000_000
1958        let parent_slot = 10;
1959        let parent_time = 1_000_000_000_000; // 1000 seconds in nanos
1960        let working_slot = 15;
1961        let slot_delta = working_slot - parent_slot;
1962        test_nanosecond_time_bounds_helper(
1963            parent_time,
1964            u128::from(slot_delta).saturating_mul(u128::from(DEFAULT_NS_PER_SLOT)),
1965            parent_time + 1,
1966            parent_time + (2 * DEFAULT_NS_PER_SLOT * slot_delta) as i64,
1967        );
1968    }
1969
1970    #[test]
1971    fn test_nanosecond_time_bounds_same_slot() {
1972        // Test with same slot (diff = 0)
1973        // diff_slots = 0
1974        // lower = parent_time + 1
1975        // upper = parent_time + 2 * 0 * 400_000_000 = parent_time
1976        // Note: In this case, lower > upper, so no timestamp would be valid
1977        // This is expected since we shouldn't have the same slot for parent and working bank
1978        let parent_time = 1_000_000_000_000;
1979        test_nanosecond_time_bounds_helper(parent_time, 0, parent_time + 1, parent_time);
1980    }
1981
1982    #[test]
1983    fn test_nanosecond_time_bounds_saturates_upper_bound() {
1984        let parent_time = i64::MAX - 5;
1985        let (lower, upper) =
1986            BlockComponentProcessor::nanosecond_time_bounds(parent_time, u128::MAX);
1987
1988        assert_eq!(lower, parent_time + 1);
1989        assert_eq!(upper, i64::MAX);
1990    }
1991
1992    #[test]
1993    fn test_initial_up_reject() {
1994        let mut processor = BlockComponentProcessor::default();
1995        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
1996            new_parent_slot: 0,
1997            new_parent_block_id: Hash::default(),
1998        });
1999
2000        assert!(matches!(
2001            processor.on_update_parent(4, &update_parent, false),
2002            Err(BlockComponentProcessorError::UnexpectedInitialUpdateParent)
2003        ));
2004        assert_eq!(processor.stage, BlockComponentStage::PreParentMarker);
2005    }
2006
2007    #[test]
2008    fn test_update_parent_rejects_non_first_leader_window_slot() {
2009        let mut processor = BlockComponentProcessor::default();
2010        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
2011            new_parent_slot: 0,
2012            new_parent_block_id: Hash::default(),
2013        });
2014
2015        assert!(matches!(
2016            processor.on_update_parent(5, &update_parent, true),
2017            Err(BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(5))
2018        ));
2019        assert_eq!(processor.stage, BlockComponentStage::PreParentMarker);
2020    }
2021
2022    #[test]
2023    fn test_initial_up_ok() {
2024        let mut processor = BlockComponentProcessor::default();
2025        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
2026            new_parent_slot: 0,
2027            new_parent_block_id: Hash::default(),
2028        });
2029
2030        processor.on_update_parent(4, &update_parent, true).unwrap();
2031        assert_eq!(
2032            processor.stage,
2033            BlockComponentStage::AcceptingEntriesOrFooter {
2034                parent_marker: EntryParentMarker::UpdateParent,
2035            }
2036        );
2037    }
2038
2039    #[test]
2040    fn test_update_parent_after_header_abandoned_bank() {
2041        let mut processor = BlockComponentProcessor::default();
2042        processor
2043            .on_header(
2044                &VersionedBlockHeader::V1(BlockHeaderV1 {
2045                    parent_slot: 0,
2046                    parent_block_id: Hash::default(),
2047                }),
2048                0,
2049            )
2050            .unwrap();
2051
2052        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
2053            new_parent_slot: 0,
2054            new_parent_block_id: Hash::default(),
2055        });
2056
2057        assert!(matches!(
2058            processor.on_update_parent(4, &update_parent, false),
2059            Err(BlockComponentProcessorError::AbandonedBank(_))
2060        ));
2061    }
2062
2063    #[test]
2064    fn test_update_parent_after_footer_error() {
2065        let mut processor = processor_after_footer();
2066        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
2067            new_parent_slot: 0,
2068            new_parent_block_id: Hash::default(),
2069        });
2070
2071        assert_matches!(
2072            processor.on_update_parent(4, &update_parent, false),
2073            Err(BlockComponentProcessorError::SpuriousUpdateParent)
2074        );
2075    }
2076
2077    #[test]
2078    fn test_multiple_update_parents_error() {
2079        let mut processor = BlockComponentProcessor::default();
2080        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
2081            new_parent_slot: 0,
2082            new_parent_block_id: Hash::default(),
2083        });
2084
2085        // First should succeed
2086        processor.on_update_parent(4, &update_parent, true).unwrap();
2087
2088        // Second should fail
2089        assert_matches!(
2090            processor.on_update_parent(4, &update_parent, true),
2091            Err(BlockComponentProcessorError::MultipleUpdateParents)
2092        );
2093    }
2094
2095    #[test]
2096    fn test_header_after_update_parent_error() {
2097        let mut processor = BlockComponentProcessor::default();
2098        processor
2099            .on_update_parent(
2100                4,
2101                &VersionedUpdateParent::V1(UpdateParentV1 {
2102                    new_parent_slot: 0,
2103                    new_parent_block_id: Hash::default(),
2104                }),
2105                true,
2106            )
2107            .unwrap();
2108
2109        let header = VersionedBlockHeader::V1(BlockHeaderV1 {
2110            parent_slot: 0,
2111            parent_block_id: Hash::default(),
2112        });
2113
2114        assert!(matches!(
2115            processor.on_header(&header, 0),
2116            Err(BlockComponentProcessorError::SpuriousUpdateParent)
2117        ));
2118    }
2119
2120    #[test]
2121    fn test_workflow_with_update_parent() {
2122        let migration_status = MigrationStatus::post_migration_status();
2123        let mut processor = BlockComponentProcessor::default();
2124        let (parent, bank_forks) = create_test_bank();
2125        let bank = create_child_bank(&bank_forks, &parent, 4);
2126        let slot = bank.slot();
2127        let shred_version = rand::rng().random();
2128
2129        processor
2130            .on_update_parent(
2131                slot,
2132                &VersionedUpdateParent::V1(UpdateParentV1 {
2133                    new_parent_slot: 0,
2134                    new_parent_block_id: Hash::default(),
2135                }),
2136                true,
2137            )
2138            .unwrap();
2139
2140        processor
2141            .on_entry_batch(&migration_status, slot, &[], false)
2142            .unwrap();
2143
2144        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
2145        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
2146            bank_hash: Hash::new_unique(),
2147            block_producer_time_nanos: (parent_time_nanos + 100_000_000) as u64,
2148            block_user_agent: vec![],
2149            block_final_cert: None,
2150            skip_reward_cert: None,
2151            notar_reward_cert: None,
2152        });
2153        processor
2154            .on_footer(
2155                &migration_status.my_pubkey(),
2156                bank,
2157                parent,
2158                shred_version,
2159                footer,
2160                None,
2161            )
2162            .unwrap();
2163
2164        let good_alpentick = alpentick(1);
2165        processor
2166            .on_entry_batch(&migration_status, slot, &good_alpentick, true)
2167            .unwrap();
2168
2169        processor.on_final(&migration_status, slot, 0).unwrap();
2170    }
2171}