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).map_err(|_| {
573            warn!(
574                "Failed to verify genesis certificate for slot {cert_slot} in bank slot {}",
575                bank.slot()
576            );
577            BlockComponentProcessorError::GenesisCertificateFailedVerification
578        })?;
579
580        Ok(())
581    }
582
583    fn on_footer(
584        &mut self,
585        my_pubkey: &Pubkey,
586        bank: Arc<Bank>,
587        parent_bank: Arc<Bank>,
588        shred_version: u16,
589        footer: VersionedBlockFooter,
590        finalization_cert_sender: Option<&Sender<SmallVec<[Certificate; 2]>>>,
591    ) -> Result<(), BlockComponentProcessorError> {
592        self.stage.on_footer()?;
593
594        let VersionedBlockFooter::V1(footer) = footer;
595
596        Self::enforce_nanosecond_clock_bounds(&bank, &parent_bank, &footer)?;
597
598        let BlockFooterV1 {
599            bank_hash,
600            block_producer_time_nanos,
601            block_user_agent: _,
602            block_final_cert,
603            skip_reward_cert,
604            notar_reward_cert,
605        } = footer;
606
607        let reward_cert = ValidatedRewardCert::try_new(
608            &bank,
609            shred_version,
610            &skip_reward_cert,
611            &notar_reward_cert,
612        )?;
613        let block_producer_time_nanos =
614            Self::block_producer_time_nanos_as_i64(block_producer_time_nanos)?;
615        let final_cert = block_final_cert
616            .map(|final_cert| {
617                ValidatedBlockFinalizationCert::try_from_footer(final_cert, &bank, shred_version)
618                    .map_err(BlockComponentProcessorError::InvalidFinalizationCertificate)
619            })
620            .transpose()?;
621
622        let (footer_input, pool_input) = match final_cert {
623            None => (None, None),
624            Some(cert) => {
625                let (signers, finalize_cert, notarize_cert) = cert.into_parts();
626                let final_slot = finalize_cert.cert_type.slot();
627                (
628                    Some((signers, final_slot)),
629                    Some((finalize_cert, notarize_cert)),
630                )
631            }
632        };
633
634        Self::update_bank_with_footer_fields(
635            &bank,
636            block_producer_time_nanos,
637            Some(bank_hash),
638            reward_cert,
639            footer_input
640                .as_ref()
641                .map(|(validators, slot)| (validators, *slot)),
642        )?;
643
644        // Send finalization cert(s) to consensus pool
645        if let Some((finalize_cert, notarize_cert)) = pool_input
646            && let Some(sender) = finalization_cert_sender
647        {
648            let channel_name = "finalization_cert_sender";
649            let certs = match notarize_cert {
650                None => smallvec![finalize_cert],
651                Some(c) => smallvec![finalize_cert, c],
652            };
653            match sender.try_send(certs) {
654                Ok(()) => (),
655                Err(TrySendError::Full(_)) => {
656                    warn!("{my_pubkey}: channel \"{channel_name}\" is full, dropping msg")
657                }
658                Err(TrySendError::Disconnected(_)) => {
659                    warn!("{my_pubkey}: channel \"{channel_name}\" disconnected")
660                }
661            }
662        }
663
664        Ok(())
665    }
666
667    fn on_header(
668        &mut self,
669        header: &VersionedBlockHeader,
670        bank_parent_slot: Slot,
671    ) -> Result<(), BlockComponentProcessorError> {
672        self.stage.on_header()?;
673
674        let VersionedBlockHeader::V1(header) = header;
675        if header.parent_slot != bank_parent_slot {
676            return Err(BlockComponentProcessorError::HeaderParentSlotMismatch {
677                header_parent_slot: header.parent_slot,
678                bank_parent_slot,
679            });
680        }
681        Ok(())
682    }
683
684    fn on_update_parent(
685        &mut self,
686        slot: Slot,
687        update_parent: &VersionedUpdateParent,
688        allow_initial_update_parent: bool,
689    ) -> Result<(), BlockComponentProcessorError> {
690        if leader_slot_index(slot) != 0 {
691            return Err(BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(slot));
692        }
693
694        self.stage
695            .on_update_parent(update_parent, allow_initial_update_parent)
696    }
697
698    fn enforce_nanosecond_clock_bounds(
699        bank: &Bank,
700        parent_bank: &Bank,
701        footer: &BlockFooterV1,
702    ) -> Result<(), BlockComponentProcessorError> {
703        // Get parent time from the nanosecond clock account, or from the Tower-based
704        // clock for the first Alpenglow block.
705        let parent_time_nanos = parent_bank
706            .get_nanosecond_clock()
707            .unwrap_or_else(|| bank.clock().unix_timestamp.saturating_mul(1_000_000_000));
708
709        let parent_slot = parent_bank.slot();
710        let current_time_nanos =
711            Self::block_producer_time_nanos_as_i64(footer.block_producer_time_nanos)?;
712        let current_slot = bank.slot();
713        let elapsed_slot_duration_nanos =
714            bank.slot_range_duration_nanos(parent_slot.saturating_add(1), current_slot);
715
716        let (lower_bound_nanos, upper_bound_nanos) =
717            Self::nanosecond_time_bounds(parent_time_nanos, elapsed_slot_duration_nanos);
718
719        let is_valid =
720            lower_bound_nanos <= current_time_nanos && current_time_nanos <= upper_bound_nanos;
721
722        match is_valid {
723            true => Ok(()),
724            false => Err(BlockComponentProcessorError::NanosecondClockOutOfBounds),
725        }
726    }
727
728    /// Converts a footer timestamp to the signed nanosecond representation used
729    /// by bank clock state.
730    ///
731    /// The `block_producer_time_nanos` parameter comes from wire-format footer
732    /// data and is rejected if it cannot be represented as `i64`; wrapping it
733    /// would make an extreme future timestamp look negative.
734    fn block_producer_time_nanos_as_i64(
735        block_producer_time_nanos: u64,
736    ) -> Result<i64, BlockComponentProcessorError> {
737        i64::try_from(block_producer_time_nanos)
738            .map_err(|_| BlockComponentProcessorError::NanosecondClockOutOfBounds)
739    }
740
741    /// Given a parent time and elapsed slot duration, calculates inclusive
742    /// block producer timestamp bounds.
743    ///
744    /// `parent_time_nanos` describes the parent bank's nanosecond clock.
745    /// `elapsed_slot_duration_nanos` is the summed duration for all skipped
746    /// and working slots after the parent. The returned `(lower_bound,
747    /// upper_bound)` accepts timestamps where
748    /// `lower_bound <= working_bank_time <= upper_bound`.
749    ///
750    /// Refer to
751    /// https://github.com/solana-foundation/solana-improvement-documents/pull/363
752    /// for details on the bounds calculation.
753    pub fn nanosecond_time_bounds(
754        parent_time_nanos: i64,
755        elapsed_slot_duration_nanos: u128,
756    ) -> (i64, i64) {
757        let min_working_bank_time = parent_time_nanos.saturating_add(1);
758        let max_working_bank_time_offset = elapsed_slot_duration_nanos
759            .saturating_mul(2)
760            .min(i64::MAX as u128) as i64;
761        let max_working_bank_time = parent_time_nanos.saturating_add(max_working_bank_time_offset);
762
763        (min_working_bank_time, max_working_bank_time)
764    }
765
766    pub fn update_bank_with_footer_fields(
767        bank: &Bank,
768        block_producer_time_nanos: i64,
769        bank_hash: Option<Hash>,
770        reward_cert: Option<ValidatedRewardCert>,
771        final_cert_input: Option<(&HashSet<Pubkey>, Slot)>,
772    ) -> Result<(), BankFooterError> {
773        bank.update_clock_from_footer(block_producer_time_nanos);
774        calc_vote_rewards_update_vote_states(
775            bank,
776            reward_cert,
777            final_cert_input,
778            block_producer_time_nanos,
779        )?;
780
781        if let Some(hash) = bank_hash {
782            // Record expected bank hash from footer for later verification when the bank is frozen.
783            bank.set_expected_bank_hash(hash);
784        }
785        Ok(())
786    }
787}
788
789#[cfg(test)]
790mod tests {
791    use {
792        super::*,
793        crate::{
794            bank::{Bank, SlotLeader},
795            bank_forks::BankForks,
796            genesis_utils::{activate_all_features_alpenglow, create_genesis_config},
797        },
798        rand::Rng,
799        solana_bls_signatures::{BLS_SIGNATURE_AFFINE_SIZE, Signature as BLSSignature},
800        solana_clock::DEFAULT_MS_PER_SLOT,
801        solana_entry::{
802            block_component::{
803                BlockFooterV1, BlockHeaderV1, UpdateParentV1, VersionedUpdateParent,
804            },
805            entry::Entry,
806        },
807        solana_hash::Hash,
808        std::{
809            assert_matches,
810            sync::{Arc, RwLock},
811        },
812    };
813
814    const DEFAULT_NS_PER_SLOT: u64 = DEFAULT_MS_PER_SLOT * 1_000_000;
815
816    fn create_test_bank() -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
817        let genesis_config_info = create_genesis_config(10_000);
818        Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config)
819    }
820
821    fn create_test_bank_alpenglow() -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
822        let mut genesis_config_info = create_genesis_config(10_000);
823        activate_all_features_alpenglow(&mut genesis_config_info.genesis_config);
824        Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config)
825    }
826
827    fn create_child_bank(
828        bank_forks: &RwLock<BankForks>,
829        parent: &Arc<Bank>,
830        slot: u64,
831    ) -> Arc<Bank> {
832        Bank::new_from_parent_with_bank_forks(
833            bank_forks,
834            parent.clone(),
835            SlotLeader::new_unique(),
836            slot,
837        )
838    }
839
840    fn test_genesis_cert_marker() -> GenesisCertBlockMarker {
841        GenesisCertBlockMarker {
842            slot: 0,
843            block_id: Hash::default(),
844            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
845            bitmap: vec![],
846        }
847    }
848
849    fn post_migration_status_with_genesis_slot(genesis_slot: Slot) -> MigrationStatus {
850        let migration_status = MigrationStatus::default();
851        let migration_slot = migration_status.record_feature_activation(0);
852        assert!(genesis_slot < migration_slot);
853
854        let genesis_block = Block {
855            slot: genesis_slot,
856            block_id: Hash::default(),
857        };
858        migration_status.set_genesis_block(genesis_block);
859        let cert = Arc::new(GenesisCert {
860            block: genesis_block,
861            signature: CertSignature {
862                signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
863                bitmap: vec![],
864            },
865        });
866        migration_status.set_genesis_certificate(cert);
867        migration_status.enable_alpenglow_during_startup();
868
869        migration_status
870    }
871
872    fn processor_after_header() -> BlockComponentProcessor {
873        BlockComponentProcessor {
874            stage: BlockComponentStage::AcceptingGenesisOrEntries,
875            ..BlockComponentProcessor::default()
876        }
877    }
878
879    fn processor_after_footer() -> BlockComponentProcessor {
880        BlockComponentProcessor {
881            stage: BlockComponentStage::AcceptingAlpentick,
882            ..BlockComponentProcessor::default()
883        }
884    }
885
886    fn processor_done() -> BlockComponentProcessor {
887        BlockComponentProcessor {
888            stage: BlockComponentStage::Done,
889            ..BlockComponentProcessor::default()
890        }
891    }
892
893    fn alpentick(num_hashes: u64) -> [Entry; 1] {
894        [Entry::new(&Hash::default(), num_hashes, vec![])]
895    }
896
897    #[test]
898    fn test_missing_header_error_on_entry_batch() {
899        let migration_status = MigrationStatus::post_migration_status();
900        let mut processor = BlockComponentProcessor::default();
901
902        // Try to process entry batch without header - should fail
903        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
904        assert!(matches!(
905            result,
906            Err(BlockComponentProcessorError::MissingParentMarker)
907        ));
908    }
909
910    #[test]
911    fn test_missing_header_error_on_genesis_certificate() {
912        let migration_status = MigrationStatus::post_migration_status();
913        let mut processor = BlockComponentProcessor::default();
914        let marker =
915            VersionedBlockMarker::from_genesis_cert_block_marker(test_genesis_cert_marker());
916
917        let (parent, bank_forks) = create_test_bank();
918        let bank = create_child_bank(&bank_forks, &parent, 1);
919        let shred_version = rand::rng().random();
920
921        let result = processor.on_marker(
922            bank,
923            parent,
924            shred_version,
925            marker,
926            false,
927            None,
928            &migration_status,
929        );
930        assert!(matches!(
931            result,
932            Err(BlockComponentProcessorError::MissingParentMarker)
933        ));
934    }
935
936    #[test]
937    fn test_genesis_certificate_after_entry_batch_errors() {
938        let migration_status = MigrationStatus::post_migration_status();
939        let mut processor = BlockComponentProcessor::default();
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        let header = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
945            parent_slot: 0,
946            parent_block_id: Hash::default(),
947        });
948        processor
949            .on_marker(
950                bank.clone(),
951                parent.clone(),
952                shred_version,
953                header,
954                false,
955                None,
956                &migration_status,
957            )
958            .unwrap();
959        processor
960            .on_entry_batch(&migration_status, bank.slot(), &[], false)
961            .unwrap();
962
963        let marker =
964            VersionedBlockMarker::from_genesis_cert_block_marker(test_genesis_cert_marker());
965        let result = processor.on_marker(
966            bank,
967            parent,
968            shred_version,
969            marker,
970            false,
971            None,
972            &migration_status,
973        );
974        assert!(matches!(
975            result,
976            Err(BlockComponentProcessorError::GenesisCertificateOutOfOrder)
977        ));
978    }
979
980    #[test]
981    fn test_genesis_certificate_immediately_after_header_passes_order_check() {
982        let migration_status = MigrationStatus::post_migration_status();
983        let mut processor = BlockComponentProcessor::default();
984        let (parent, bank_forks) = create_test_bank();
985        let bank = create_child_bank(&bank_forks, &parent, 1);
986        let shred_version = rand::rng().random();
987
988        let header = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
989            parent_slot: 0,
990            parent_block_id: Hash::default(),
991        });
992        processor
993            .on_marker(
994                bank.clone(),
995                parent.clone(),
996                shred_version,
997                header,
998                false,
999                None,
1000                &migration_status,
1001            )
1002            .unwrap();
1003
1004        let marker =
1005            VersionedBlockMarker::from_genesis_cert_block_marker(test_genesis_cert_marker());
1006        let result = processor.on_marker(
1007            bank,
1008            parent,
1009            shred_version,
1010            marker,
1011            false,
1012            None,
1013            &migration_status,
1014        );
1015        assert!(matches!(
1016            result,
1017            Err(BlockComponentProcessorError::GenesisCertificateInAlpenglowCluster)
1018        ));
1019    }
1020
1021    #[test]
1022    fn test_missing_footer_error_on_slot_full() {
1023        let migration_status = MigrationStatus::post_migration_status();
1024        let processor = processor_after_header();
1025
1026        // Try to mark slot as full without footer - should fail
1027        let result = processor.on_final(&migration_status, 1, 0);
1028        assert!(matches!(
1029            result,
1030            Err(BlockComponentProcessorError::MissingBlockFooter)
1031        ));
1032    }
1033
1034    #[test]
1035    fn test_header_during_tower_bft_slot() {
1036        let migration_status = MigrationStatus::default();
1037        migration_status.record_feature_activation(0);
1038        let mut processor = BlockComponentProcessor::default();
1039        let (parent, bank_forks) = create_test_bank();
1040        let bank = create_child_bank(&bank_forks, &parent, 1);
1041        let marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1042            parent_slot: parent.slot(),
1043            parent_block_id: Hash::default(),
1044        });
1045
1046        assert!(!migration_status.should_allow_block_markers(bank.slot()));
1047        processor
1048            .on_marker(
1049                bank.clone(),
1050                parent,
1051                rand::rng().random(),
1052                marker,
1053                false,
1054                None,
1055                &migration_status,
1056            )
1057            .unwrap();
1058
1059        assert_matches!(
1060            processor.on_final(&migration_status, bank.slot(), bank.parent_slot()),
1061            Err(BlockComponentProcessorError::BlockComponentPreMigration)
1062        );
1063    }
1064
1065    #[test]
1066    fn test_first_alpenglow_block_requires_genesis_certificate_marker() {
1067        let migration_status = post_migration_status_with_genesis_slot(1);
1068        let processor = processor_after_footer();
1069
1070        let result = processor.on_final(&migration_status, 2, 1);
1071        assert!(matches!(
1072            result,
1073            Err(BlockComponentProcessorError::MissingGenesisCertificateMarker)
1074        ));
1075    }
1076
1077    #[test]
1078    fn test_first_alpenglow_block_with_genesis_certificate_marker_succeeds() {
1079        let migration_status = post_migration_status_with_genesis_slot(1);
1080        let (genesis_bank, bank_forks) = create_test_bank();
1081        let parent = create_child_bank(&bank_forks, &genesis_bank, 1);
1082        let parent_block_id = Hash::new_unique();
1083        parent.set_block_id(Some(parent_block_id));
1084        let bank = create_child_bank(&bank_forks, &parent, 2);
1085        let genesis_marker = GenesisCertBlockMarker {
1086            slot: parent.slot(),
1087            block_id: parent_block_id,
1088            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
1089            bitmap: vec![],
1090        };
1091        let mut processor = processor_after_header();
1092        bank.set_hashes_per_tick(Some(42));
1093        assert!(bank.hashes_per_tick().is_some());
1094
1095        processor
1096            .on_genesis_cert_block_marker_leader(bank.clone(), genesis_marker, &migration_status)
1097            .unwrap();
1098        assert!(bank.hashes_per_tick().is_none());
1099        processor.stage = BlockComponentStage::Done;
1100        assert!(processor.on_final(&migration_status, 2, 1).is_ok());
1101    }
1102
1103    #[test]
1104    fn test_genesis_certificate_marker_aborts_tower_bank_during_migration() {
1105        let migration_status = MigrationStatus::default();
1106        migration_status.record_feature_activation(0);
1107        let (genesis_bank, bank_forks) = create_test_bank();
1108        let parent = create_child_bank(&bank_forks, &genesis_bank, 1);
1109        let parent_block_id = Hash::new_unique();
1110        parent.set_block_id(Some(parent_block_id));
1111        let bank = create_child_bank(&bank_forks, &parent, 2);
1112        let genesis_marker = GenesisCertBlockMarker {
1113            slot: parent.slot(),
1114            block_id: parent_block_id,
1115            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
1116            bitmap: vec![],
1117        };
1118        let mut processor = processor_after_header();
1119        bank.set_hashes_per_tick(Some(42));
1120        let tower_hashes_per_tick = bank.hashes_per_tick();
1121        assert!(tower_hashes_per_tick.is_some());
1122
1123        assert_matches!(
1124            processor.on_genesis_cert_block_marker_leader(
1125                bank.clone(),
1126                genesis_marker,
1127                &migration_status,
1128            ),
1129            Err(BlockComponentProcessorError::AlpenglowMigrationTransition)
1130        );
1131
1132        assert!(migration_status.is_ready_to_enable());
1133        assert_eq!(bank.hashes_per_tick(), tower_hashes_per_tick);
1134        assert!(bank.get_alpenglow_genesis_certificate().is_some());
1135    }
1136
1137    #[test]
1138    fn test_first_alpenglow_block_genesis_slot_zero_skips_genesis_certificate_marker_check() {
1139        let migration_status = MigrationStatus::post_migration_status();
1140        let processor = processor_done();
1141
1142        assert!(processor.on_final(&migration_status, 1, 0).is_ok());
1143    }
1144
1145    #[test]
1146    fn test_multiple_headers_error() {
1147        let mut processor = BlockComponentProcessor::default();
1148        let header = VersionedBlockHeader::V1(BlockHeaderV1 {
1149            parent_slot: 0,
1150            parent_block_id: Hash::default(),
1151        });
1152
1153        // First header should succeed
1154        assert!(processor.on_header(&header, 0).is_ok());
1155
1156        // Second header should fail
1157        let result = processor.on_header(&header, 0);
1158        assert!(matches!(
1159            result,
1160            Err(BlockComponentProcessorError::MultipleBlockHeaders)
1161        ));
1162    }
1163
1164    #[test]
1165    fn test_multiple_footers_error() {
1166        let my_pubkey = Pubkey::new_unique();
1167        let mut processor = processor_after_header();
1168
1169        let (parent, bank_forks) = create_test_bank();
1170        let bank = create_child_bank(&bank_forks, &parent, 1);
1171        let shred_version = rand::rng().random();
1172
1173        // Calculate valid timestamp based on parent's time
1174        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1175        let footer_time_nanos = parent_time_nanos + 400_000_000; // parent + 400ms
1176
1177        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1178            bank_hash: Hash::new_unique(),
1179            block_producer_time_nanos: footer_time_nanos as u64,
1180            block_user_agent: vec![],
1181            block_final_cert: None,
1182            skip_reward_cert: None,
1183            notar_reward_cert: None,
1184        });
1185
1186        // First footer should succeed
1187        processor
1188            .on_footer(
1189                &my_pubkey,
1190                bank.clone(),
1191                parent.clone(),
1192                shred_version,
1193                footer.clone(),
1194                None,
1195            )
1196            .unwrap();
1197
1198        // Second footer should fail
1199        let err = processor
1200            .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1201            .unwrap_err();
1202        assert!(matches!(
1203            err,
1204            BlockComponentProcessorError::MultipleBlockFooters
1205        ));
1206    }
1207
1208    #[test]
1209    fn test_on_footer_sets_timestamp() {
1210        let my_pubkey = Pubkey::new_unique();
1211        let mut processor = processor_after_header();
1212
1213        let (parent, bank_forks) = create_test_bank();
1214        let bank = create_child_bank(&bank_forks, &parent, 1);
1215        let shred_version = rand::rng().random();
1216
1217        // Calculate valid timestamp based on parent's time
1218        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1219        let footer_time_nanos = parent_time_nanos + 200_000_000; // parent + 200ms
1220        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1221
1222        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1223            bank_hash: Hash::new_unique(),
1224            block_producer_time_nanos: footer_time_nanos as u64,
1225            block_user_agent: vec![],
1226            block_final_cert: None,
1227            skip_reward_cert: None,
1228            notar_reward_cert: None,
1229        });
1230
1231        processor
1232            .on_footer(
1233                &my_pubkey,
1234                bank.clone(),
1235                parent,
1236                shred_version,
1237                footer,
1238                None,
1239            )
1240            .unwrap();
1241
1242        assert_eq!(processor.stage, BlockComponentStage::AcceptingAlpentick);
1243
1244        // Verify clock sysvar was updated with correct timestamp (nanos converted to seconds)
1245        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1246    }
1247
1248    #[test]
1249    fn test_on_header_sets_flag() {
1250        let mut processor = BlockComponentProcessor::default();
1251        let header = VersionedBlockHeader::V1(BlockHeaderV1 {
1252            parent_slot: 0,
1253            parent_block_id: Hash::default(),
1254        });
1255
1256        processor.on_header(&header, 0).unwrap();
1257        assert_eq!(
1258            processor.stage,
1259            BlockComponentStage::AcceptingGenesisOrEntries
1260        );
1261    }
1262
1263    #[test]
1264    fn test_on_header_parent_slot_mismatch_error() {
1265        let mut processor = BlockComponentProcessor::default();
1266        let header = VersionedBlockHeader::V1(BlockHeaderV1 {
1267            parent_slot: 2,
1268            parent_block_id: Hash::default(),
1269        });
1270
1271        assert!(matches!(
1272            processor.on_header(&header, 0),
1273            Err(BlockComponentProcessorError::HeaderParentSlotMismatch {
1274                header_parent_slot: 2,
1275                bank_parent_slot: 0,
1276            })
1277        ));
1278    }
1279
1280    #[test]
1281    fn test_on_marker_processes_header() {
1282        let migration_status = MigrationStatus::post_migration_status();
1283        let mut processor = BlockComponentProcessor::default();
1284        let marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1285            parent_slot: 0,
1286            parent_block_id: Hash::default(),
1287        });
1288
1289        let (parent, bank_forks) = create_test_bank();
1290        let bank = create_child_bank(&bank_forks, &parent, 1);
1291        let shred_version = rand::rng().random();
1292
1293        processor
1294            .on_marker(
1295                bank,
1296                parent,
1297                shred_version,
1298                marker,
1299                false,
1300                None,
1301                &migration_status,
1302            )
1303            .unwrap();
1304        assert_eq!(
1305            processor.stage,
1306            BlockComponentStage::AcceptingGenesisOrEntries
1307        );
1308    }
1309
1310    #[test]
1311    fn test_on_marker_rejects_header_parent_slot_mismatch() {
1312        let migration_status = MigrationStatus::post_migration_status();
1313        let mut processor = BlockComponentProcessor::default();
1314        let marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1315            parent_slot: 7, // mismatches bank.parent_slot() below (0)
1316            parent_block_id: Hash::default(),
1317        });
1318
1319        let (parent, bank_forks) = create_test_bank();
1320        let bank = create_child_bank(&bank_forks, &parent, 1);
1321        let shred_version = rand::rng().random();
1322
1323        assert!(matches!(
1324            processor.on_marker(
1325                bank,
1326                parent,
1327                shred_version,
1328                marker,
1329                false,
1330                None,
1331                &migration_status
1332            ),
1333            Err(BlockComponentProcessorError::HeaderParentSlotMismatch {
1334                header_parent_slot: 7,
1335                bank_parent_slot: 0,
1336            })
1337        ));
1338    }
1339
1340    #[test]
1341    fn test_on_marker_processes_footer() {
1342        let migration_status = MigrationStatus::post_migration_status();
1343        let mut processor = processor_after_header();
1344
1345        let (parent, bank_forks) = create_test_bank();
1346        let bank = create_child_bank(&bank_forks, &parent, 1);
1347        let shred_version = rand::rng().random();
1348
1349        // Calculate valid timestamp based on parent's time
1350        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1351        let footer_time_nanos = parent_time_nanos + 300_000_000; // parent + 300ms
1352        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1353
1354        let marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1355            bank_hash: Hash::new_unique(),
1356            block_producer_time_nanos: footer_time_nanos as u64,
1357            block_user_agent: vec![],
1358            block_final_cert: None,
1359            skip_reward_cert: None,
1360            notar_reward_cert: None,
1361        });
1362
1363        processor
1364            .on_marker(
1365                bank.clone(),
1366                parent,
1367                shred_version,
1368                marker,
1369                false,
1370                None,
1371                &migration_status,
1372            )
1373            .unwrap();
1374        assert_eq!(processor.stage, BlockComponentStage::AcceptingAlpentick);
1375
1376        // Verify clock sysvar was updated
1377        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1378    }
1379
1380    #[test]
1381    fn test_complete_workflow_success() {
1382        let migration_status = MigrationStatus::post_migration_status();
1383        let mut processor = BlockComponentProcessor::default();
1384        let (parent, bank_forks) = create_test_bank();
1385        let bank = create_child_bank(&bank_forks, &parent, 1);
1386        let shred_version = rand::rng().random();
1387
1388        // Calculate valid timestamp based on parent's time
1389        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1390        let footer_time_nanos = parent_time_nanos + 100_000_000; // parent + 100ms
1391        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1392
1393        // Process header
1394        let header = VersionedBlockHeader::V1(BlockHeaderV1 {
1395            parent_slot: 0,
1396            parent_block_id: Hash::default(),
1397        });
1398        processor.on_header(&header, bank.parent_slot()).unwrap();
1399
1400        // Process some entry batches (not full yet)
1401        processor
1402            .on_entry_batch(&migration_status, 1, &[], false)
1403            .unwrap();
1404
1405        // Process footer with valid timestamp
1406        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1407            bank_hash: Hash::new_unique(),
1408            block_producer_time_nanos: footer_time_nanos as u64,
1409            block_user_agent: vec![],
1410            block_final_cert: None,
1411            skip_reward_cert: None,
1412            notar_reward_cert: None,
1413        });
1414        processor
1415            .on_footer(
1416                &migration_status.my_pubkey(),
1417                bank.clone(),
1418                parent.clone(),
1419                shred_version,
1420                footer,
1421                None,
1422            )
1423            .unwrap();
1424
1425        // Verify clock sysvar was updated
1426        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1427
1428        // Entry batch after footer should fail because the footer is terminal.
1429        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
1430        assert_matches!(
1431            result,
1432            Err(BlockComponentProcessorError::EntryBatchAfterBlockFooter)
1433        );
1434    }
1435
1436    #[test]
1437    fn test_alpentick_position_validation() {
1438        let migration_status = MigrationStatus::post_migration_status();
1439        let mut processor = processor_after_footer();
1440        let good_alpentick = alpentick(1);
1441
1442        processor
1443            .on_entry_batch(&migration_status, 1, &good_alpentick, true)
1444            .unwrap();
1445        assert_matches!(
1446            processor.on_entry_batch(&migration_status, 1, &good_alpentick, true),
1447            Err(BlockComponentProcessorError::InvalidAlpentickPosition)
1448        );
1449
1450        let mut processor = BlockComponentProcessor::default();
1451        assert_matches!(
1452            processor.on_entry_batch(&migration_status, 1, &good_alpentick, true),
1453            Err(BlockComponentProcessorError::MissingParentMarker)
1454        );
1455
1456        let mut processor = processor_after_footer();
1457        let bad_alpentick = alpentick(2);
1458        assert_matches!(
1459            processor.on_entry_batch(&migration_status, 1, &bad_alpentick, true),
1460            Err(BlockComponentProcessorError::EntryBatchAfterBlockFooter)
1461        );
1462
1463        let migration_status = MigrationStatus::default();
1464        let mut processor = BlockComponentProcessor::default();
1465        processor
1466            .on_entry_batch(&migration_status, 1, &good_alpentick, true)
1467            .unwrap();
1468    }
1469
1470    #[test]
1471    fn test_block_marker_detected_pre_migration() {
1472        let migration_status = MigrationStatus::default();
1473        let mut processor = BlockComponentProcessor::default();
1474        let (parent, bank_forks) = create_test_bank();
1475        let bank = create_child_bank(&bank_forks, &parent, 1);
1476        let shred_version = rand::rng().random();
1477
1478        // Try to process a block header marker pre-migration - should fail
1479        let marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1480            parent_slot: 0,
1481            parent_block_id: Hash::default(),
1482        });
1483
1484        let err = processor
1485            .on_marker(
1486                bank,
1487                parent,
1488                shred_version,
1489                marker,
1490                false,
1491                None,
1492                &migration_status,
1493            )
1494            .unwrap_err();
1495        assert!(matches!(
1496            err,
1497            BlockComponentProcessorError::BlockComponentPreMigration
1498        ));
1499    }
1500
1501    #[test]
1502    fn test_footer_and_update_parent_rejected_pre_migration() {
1503        let migration_status = MigrationStatus::default();
1504        let (parent, bank_forks) = create_test_bank();
1505        let bank = create_child_bank(&bank_forks, &parent, 1);
1506        let shred_version = rand::rng().random();
1507
1508        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1509        let footer_marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1510            bank_hash: Hash::new_unique(),
1511            block_producer_time_nanos: (parent_time_nanos + 500_000_000) as u64,
1512            block_user_agent: vec![],
1513            block_final_cert: None,
1514            skip_reward_cert: None,
1515            notar_reward_cert: None,
1516        });
1517
1518        let mut processor = BlockComponentProcessor::default();
1519        assert!(matches!(
1520            processor
1521                .on_marker(
1522                    bank.clone(),
1523                    parent.clone(),
1524                    shred_version,
1525                    footer_marker,
1526                    false,
1527                    None,
1528                    &migration_status
1529                )
1530                .unwrap_err(),
1531            BlockComponentProcessorError::BlockComponentPreMigration
1532        ));
1533
1534        let update_parent_marker = VersionedBlockMarker::from_update_parent(UpdateParentV1 {
1535            new_parent_slot: 0,
1536            new_parent_block_id: Hash::default(),
1537        });
1538
1539        let mut processor = BlockComponentProcessor::default();
1540        assert!(matches!(
1541            processor
1542                .on_marker(
1543                    bank,
1544                    parent,
1545                    shred_version,
1546                    update_parent_marker,
1547                    false,
1548                    None,
1549                    &migration_status
1550                )
1551                .unwrap_err(),
1552            BlockComponentProcessorError::BlockComponentPreMigration
1553        ));
1554    }
1555
1556    #[test]
1557    fn test_entry_batch_pre_migration_succeeds() {
1558        let migration_status = MigrationStatus::default();
1559        let mut processor = BlockComponentProcessor::default();
1560
1561        // Processing entry batches pre-migration (without markers) should succeed
1562        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
1563        assert!(result.is_ok());
1564
1565        // Even with slot full
1566        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
1567        assert!(result.is_ok());
1568
1569        // A Tower block with no markers is valid
1570        assert!(processor.on_final(&migration_status, 1, 0).is_ok());
1571    }
1572
1573    #[test]
1574    fn test_complete_workflow_post_migration() {
1575        let migration_status = MigrationStatus::post_migration_status();
1576        let mut processor = BlockComponentProcessor::default();
1577        let (parent, bank_forks) = create_test_bank();
1578        let bank = create_child_bank(&bank_forks, &parent, 1);
1579        let shred_version = rand::rng().random();
1580
1581        // Process header marker
1582        let header_marker = VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1583            parent_slot: 0,
1584            parent_block_id: Hash::default(),
1585        });
1586        processor
1587            .on_marker(
1588                bank.clone(),
1589                parent.clone(),
1590                shred_version,
1591                header_marker,
1592                false,
1593                None,
1594                &migration_status,
1595            )
1596            .unwrap();
1597
1598        // Process entry batches
1599        processor
1600            .on_entry_batch(&migration_status, 1, &[], false)
1601            .unwrap();
1602
1603        // Calculate valid timestamp based on parent's time
1604        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1605        let footer_time_nanos = parent_time_nanos + 500_000_000; // parent + 500ms
1606        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1607
1608        // Process footer marker
1609        let footer_marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1610            bank_hash: Hash::new_unique(),
1611            block_producer_time_nanos: footer_time_nanos as u64,
1612            block_user_agent: vec![],
1613            block_final_cert: None,
1614            skip_reward_cert: None,
1615            notar_reward_cert: None,
1616        });
1617        processor
1618            .on_marker(
1619                bank.clone(),
1620                parent,
1621                shred_version,
1622                footer_marker,
1623                false,
1624                None,
1625                &migration_status,
1626            )
1627            .unwrap();
1628
1629        // Verify clock sysvar was updated
1630        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1631
1632        // Entry batch after footer should fail because the footer is terminal.
1633        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
1634        assert_matches!(
1635            result,
1636            Err(BlockComponentProcessorError::EntryBatchAfterBlockFooter)
1637        );
1638    }
1639
1640    #[test]
1641    fn test_footer_without_header_errors() {
1642        let my_pubkey = Pubkey::new_unique();
1643        let mut processor = BlockComponentProcessor::default();
1644        let (parent, bank_forks) = create_test_bank();
1645        let bank = create_child_bank(&bank_forks, &parent, 1);
1646        let shred_version = rand::rng().random();
1647
1648        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1649            bank_hash: Hash::new_unique(),
1650            block_producer_time_nanos: 1_000_000_000,
1651            block_user_agent: vec![],
1652            block_final_cert: None,
1653            skip_reward_cert: None,
1654            notar_reward_cert: None,
1655        });
1656
1657        // Try to process footer without header - should fail
1658        let err = processor
1659            .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1660            .unwrap_err();
1661        assert!(matches!(
1662            err,
1663            BlockComponentProcessorError::MissingParentMarker
1664        ));
1665    }
1666
1667    #[test]
1668    fn test_marker_with_footer_at_slot_full() {
1669        let migration_status = MigrationStatus::post_migration_status();
1670        let mut processor = processor_after_header();
1671        let (parent, bank_forks) = create_test_bank();
1672        let bank = create_child_bank(&bank_forks, &parent, 1);
1673        let shred_version = rand::rng().random();
1674
1675        // Calculate valid timestamp based on parent's time
1676        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1677        let footer_time_nanos = parent_time_nanos + 600_000_000; // parent + 600ms
1678        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1679
1680        // Process footer marker
1681        let footer_marker = VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1682            bank_hash: Hash::new_unique(),
1683            block_producer_time_nanos: footer_time_nanos as u64,
1684            block_user_agent: vec![],
1685            block_final_cert: None,
1686            skip_reward_cert: None,
1687            notar_reward_cert: None,
1688        });
1689
1690        // Should succeed - footer is processed
1691        processor
1692            .on_marker(
1693                bank.clone(),
1694                parent,
1695                shred_version,
1696                footer_marker,
1697                false,
1698                None,
1699                &migration_status,
1700            )
1701            .unwrap();
1702        assert_eq!(processor.stage, BlockComponentStage::AcceptingAlpentick);
1703
1704        // Verify clock sysvar was updated
1705        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1706    }
1707
1708    #[test]
1709    fn test_entry_batch_with_header_not_full_succeeds() {
1710        let migration_status = MigrationStatus::post_migration_status();
1711        let mut processor = processor_after_header();
1712
1713        // Process entry batch with header but not full - should succeed even without footer
1714        let result = processor.on_entry_batch(&migration_status, 1, &[], false);
1715        assert!(result.is_ok());
1716    }
1717
1718    #[test]
1719    fn test_footer_sets_epoch_start_timestamp_on_epoch_change() {
1720        let my_pubkey = Pubkey::new_unique();
1721        let mut processor = processor_after_header();
1722        let shred_version = rand::rng().random();
1723
1724        // Create genesis bank
1725        let genesis_config_info = create_genesis_config(10_000);
1726        let (genesis_bank, bank_forks) =
1727            Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config);
1728
1729        // Get epoch schedule to find first slot of next epoch
1730        let epoch_schedule = genesis_bank.epoch_schedule();
1731        let first_slot_in_epoch_1 = epoch_schedule.get_first_slot_in_epoch(1);
1732
1733        // Create parent bank at last slot of epoch 0
1734        let mut parent = genesis_bank.clone();
1735        for slot in 1..first_slot_in_epoch_1 {
1736            parent = create_child_bank(&bank_forks, &parent, slot);
1737        }
1738
1739        // Create bank at first slot of epoch 1
1740        let bank = create_child_bank(&bank_forks, &parent, first_slot_in_epoch_1);
1741
1742        // Verify we're in epoch 1
1743        assert_eq!(bank.epoch(), 1);
1744
1745        // Calculate valid timestamp based on parent's time
1746        let parent_slot = parent.slot();
1747        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1748        let current_slot = bank.slot();
1749        let elapsed_slot_duration_nanos =
1750            bank.slot_range_duration_nanos(parent_slot.saturating_add(1), current_slot);
1751
1752        // Use a timestamp in the middle of the valid range
1753        let (lower_bound, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1754            parent_time_nanos,
1755            elapsed_slot_duration_nanos,
1756        );
1757        let footer_time_nanos = (lower_bound + upper_bound) / 2;
1758        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1759
1760        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1761            bank_hash: Hash::new_unique(),
1762            block_producer_time_nanos: footer_time_nanos as u64,
1763            block_user_agent: vec![],
1764            block_final_cert: None,
1765            skip_reward_cert: None,
1766            notar_reward_cert: None,
1767        });
1768
1769        processor
1770            .on_footer(
1771                &my_pubkey,
1772                bank.clone(),
1773                parent,
1774                shred_version,
1775                footer,
1776                None,
1777            )
1778            .unwrap();
1779
1780        // Verify clock sysvar was updated
1781        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1782
1783        // Verify epoch_start_timestamp was set correctly for the new epoch
1784        assert_eq!(bank.clock().epoch_start_timestamp, expected_time_secs);
1785    }
1786
1787    // Helper function to test clock bounds enforcement
1788    fn test_clock_bounds_helper(
1789        slot_gap: u64,
1790        timestamp_fn: impl FnOnce(i64, i64, i64) -> i64,
1791        should_pass: bool,
1792    ) {
1793        let my_pubkey = Pubkey::new_unique();
1794        let mut processor = processor_after_header();
1795        let shred_version = rand::rng().random();
1796
1797        let (parent, bank_forks) = create_test_bank_alpenglow();
1798        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1799
1800        // Set up clock on parent so validation doesn't skip bounds checking
1801        parent.update_clock_from_footer(parent_time_nanos);
1802
1803        let bank: Arc<Bank> = create_child_bank(&bank_forks, &parent, slot_gap);
1804        let elapsed_slot_duration_nanos = bank.slot_range_duration_nanos(1, slot_gap);
1805
1806        let (lower_bound, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1807            parent_time_nanos,
1808            elapsed_slot_duration_nanos,
1809        );
1810
1811        let footer_time_nanos = timestamp_fn(parent_time_nanos, lower_bound, upper_bound);
1812
1813        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1814            bank_hash: Hash::new_unique(),
1815            block_producer_time_nanos: footer_time_nanos as u64,
1816            block_user_agent: vec![],
1817            block_final_cert: None,
1818            skip_reward_cert: None,
1819            notar_reward_cert: None,
1820        });
1821
1822        let result = processor.on_footer(&my_pubkey, bank, parent, shred_version, footer, None);
1823        if should_pass {
1824            result.unwrap();
1825        } else {
1826            assert!(matches!(
1827                result.unwrap_err(),
1828                BlockComponentProcessorError::NanosecondClockOutOfBounds
1829            ));
1830        }
1831    }
1832
1833    #[test]
1834    fn test_clock_bounds_at_minimum() {
1835        test_clock_bounds_helper(1, |_, lower, _| lower, true);
1836    }
1837
1838    #[test]
1839    fn test_clock_bounds_at_maximum() {
1840        test_clock_bounds_helper(1, |_, _, upper| upper, true);
1841    }
1842
1843    #[test]
1844    fn test_clock_bounds_below_minimum() {
1845        test_clock_bounds_helper(1, |_, lower, _| lower - 1, false);
1846    }
1847
1848    #[test]
1849    fn test_clock_bounds_above_maximum() {
1850        test_clock_bounds_helper(1, |_, _, upper| upper + 1, false);
1851    }
1852
1853    #[test]
1854    fn test_clock_bounds_multi_slot_gap() {
1855        // For 5 slots: upper_bound = parent_time + 2 * 5 * 400ms = parent_time + 4000ms
1856        // Use 2 seconds which is within bounds
1857        test_clock_bounds_helper(5, |_, lower, _| lower + 2_000_000_000, true);
1858    }
1859
1860    #[test]
1861    fn test_clock_bounds_multi_slot_gap_exceeds() {
1862        // Exceed by 1 second beyond the upper bound
1863        test_clock_bounds_helper(5, |_, _, upper| upper + 1_000_000_000, false);
1864    }
1865
1866    #[test]
1867    fn test_clock_bounds_timestamp_equals_parent() {
1868        // Timestamp equal to parent time (should fail, must be strictly greater)
1869        test_clock_bounds_helper(1, |parent_time, _, _| parent_time, false);
1870    }
1871
1872    #[test]
1873    fn test_clock_bounds_without_parent_nanosecond_clock_rejects_out_of_bounds() {
1874        let my_pubkey = Pubkey::new_unique();
1875        let mut processor = processor_after_header();
1876        let shred_version = rand::rng().random();
1877
1878        let (parent, bank_forks) = create_test_bank_alpenglow();
1879        assert_eq!(parent.get_nanosecond_clock(), None);
1880
1881        let bank = create_child_bank(&bank_forks, &parent, 1);
1882        let parent_time_nanos = bank.clock().unix_timestamp.saturating_mul(1_000_000_000);
1883        let elapsed_slot_duration_nanos =
1884            bank.slot_range_duration_nanos(parent.slot().saturating_add(1), bank.slot());
1885        let (_, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1886            parent_time_nanos,
1887            elapsed_slot_duration_nanos,
1888        );
1889
1890        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1891            bank_hash: Hash::new_unique(),
1892            block_producer_time_nanos: u64::try_from(upper_bound.saturating_add(1)).unwrap(),
1893            block_user_agent: vec![],
1894            block_final_cert: None,
1895            skip_reward_cert: None,
1896            notar_reward_cert: None,
1897        });
1898
1899        assert!(matches!(
1900            processor
1901                .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1902                .unwrap_err(),
1903            BlockComponentProcessorError::NanosecondClockOutOfBounds
1904        ));
1905    }
1906
1907    #[test]
1908    fn test_clock_bounds_rejects_timestamp_above_i64() {
1909        let my_pubkey = Pubkey::new_unique();
1910        let mut processor = processor_after_header();
1911        let shred_version = rand::rng().random();
1912
1913        let (parent, bank_forks) = create_test_bank_alpenglow();
1914        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1915        parent.update_clock_from_footer(parent_time_nanos);
1916        let bank = create_child_bank(&bank_forks, &parent, 1);
1917
1918        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1919            bank_hash: Hash::new_unique(),
1920            block_producer_time_nanos: u64::MAX,
1921            block_user_agent: vec![],
1922            block_final_cert: None,
1923            skip_reward_cert: None,
1924            notar_reward_cert: None,
1925        });
1926
1927        assert!(matches!(
1928            processor
1929                .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1930                .unwrap_err(),
1931            BlockComponentProcessorError::NanosecondClockOutOfBounds
1932        ));
1933    }
1934
1935    // Helper function to test nanosecond_time_bounds calculation
1936    fn test_nanosecond_time_bounds_helper(
1937        parent_time_nanos: i64,
1938        elapsed_slot_duration_nanos: u128,
1939        expected_lower: i64,
1940        expected_upper: i64,
1941    ) {
1942        let (lower, upper) = BlockComponentProcessor::nanosecond_time_bounds(
1943            parent_time_nanos,
1944            elapsed_slot_duration_nanos,
1945        );
1946
1947        assert_eq!(lower, expected_lower);
1948        assert_eq!(upper, expected_upper);
1949    }
1950
1951    #[test]
1952    fn test_nanosecond_time_bounds_calculation() {
1953        // Test the nanosecond_time_bounds function directly
1954        // diff_slots = 15 - 10 = 5
1955        // lower = parent_time + 1
1956        // upper = parent_time + 2 * 5 * 400_000_000 = parent_time + 4_000_000_000
1957        let parent_slot = 10;
1958        let parent_time = 1_000_000_000_000; // 1000 seconds in nanos
1959        let working_slot = 15;
1960        let slot_delta = working_slot - parent_slot;
1961        test_nanosecond_time_bounds_helper(
1962            parent_time,
1963            u128::from(slot_delta).saturating_mul(u128::from(DEFAULT_NS_PER_SLOT)),
1964            parent_time + 1,
1965            parent_time + (2 * DEFAULT_NS_PER_SLOT * slot_delta) as i64,
1966        );
1967    }
1968
1969    #[test]
1970    fn test_nanosecond_time_bounds_same_slot() {
1971        // Test with same slot (diff = 0)
1972        // diff_slots = 0
1973        // lower = parent_time + 1
1974        // upper = parent_time + 2 * 0 * 400_000_000 = parent_time
1975        // Note: In this case, lower > upper, so no timestamp would be valid
1976        // This is expected since we shouldn't have the same slot for parent and working bank
1977        let parent_time = 1_000_000_000_000;
1978        test_nanosecond_time_bounds_helper(parent_time, 0, parent_time + 1, parent_time);
1979    }
1980
1981    #[test]
1982    fn test_nanosecond_time_bounds_saturates_upper_bound() {
1983        let parent_time = i64::MAX - 5;
1984        let (lower, upper) =
1985            BlockComponentProcessor::nanosecond_time_bounds(parent_time, u128::MAX);
1986
1987        assert_eq!(lower, parent_time + 1);
1988        assert_eq!(upper, i64::MAX);
1989    }
1990
1991    #[test]
1992    fn test_initial_up_reject() {
1993        let mut processor = BlockComponentProcessor::default();
1994        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
1995            new_parent_slot: 0,
1996            new_parent_block_id: Hash::default(),
1997        });
1998
1999        assert!(matches!(
2000            processor.on_update_parent(4, &update_parent, false),
2001            Err(BlockComponentProcessorError::UnexpectedInitialUpdateParent)
2002        ));
2003        assert_eq!(processor.stage, BlockComponentStage::PreParentMarker);
2004    }
2005
2006    #[test]
2007    fn test_update_parent_rejects_non_first_leader_window_slot() {
2008        let mut processor = BlockComponentProcessor::default();
2009        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
2010            new_parent_slot: 0,
2011            new_parent_block_id: Hash::default(),
2012        });
2013
2014        assert!(matches!(
2015            processor.on_update_parent(5, &update_parent, true),
2016            Err(BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(5))
2017        ));
2018        assert_eq!(processor.stage, BlockComponentStage::PreParentMarker);
2019    }
2020
2021    #[test]
2022    fn test_initial_up_ok() {
2023        let mut processor = BlockComponentProcessor::default();
2024        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
2025            new_parent_slot: 0,
2026            new_parent_block_id: Hash::default(),
2027        });
2028
2029        processor.on_update_parent(4, &update_parent, true).unwrap();
2030        assert_eq!(
2031            processor.stage,
2032            BlockComponentStage::AcceptingEntriesOrFooter {
2033                parent_marker: EntryParentMarker::UpdateParent,
2034            }
2035        );
2036    }
2037
2038    #[test]
2039    fn test_update_parent_after_header_abandoned_bank() {
2040        let mut processor = BlockComponentProcessor::default();
2041        processor
2042            .on_header(
2043                &VersionedBlockHeader::V1(BlockHeaderV1 {
2044                    parent_slot: 0,
2045                    parent_block_id: Hash::default(),
2046                }),
2047                0,
2048            )
2049            .unwrap();
2050
2051        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
2052            new_parent_slot: 0,
2053            new_parent_block_id: Hash::default(),
2054        });
2055
2056        assert!(matches!(
2057            processor.on_update_parent(4, &update_parent, false),
2058            Err(BlockComponentProcessorError::AbandonedBank(_))
2059        ));
2060    }
2061
2062    #[test]
2063    fn test_update_parent_after_footer_error() {
2064        let mut processor = processor_after_footer();
2065        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
2066            new_parent_slot: 0,
2067            new_parent_block_id: Hash::default(),
2068        });
2069
2070        assert_matches!(
2071            processor.on_update_parent(4, &update_parent, false),
2072            Err(BlockComponentProcessorError::SpuriousUpdateParent)
2073        );
2074    }
2075
2076    #[test]
2077    fn test_multiple_update_parents_error() {
2078        let mut processor = BlockComponentProcessor::default();
2079        let update_parent = VersionedUpdateParent::V1(UpdateParentV1 {
2080            new_parent_slot: 0,
2081            new_parent_block_id: Hash::default(),
2082        });
2083
2084        // First should succeed
2085        processor.on_update_parent(4, &update_parent, true).unwrap();
2086
2087        // Second should fail
2088        assert_matches!(
2089            processor.on_update_parent(4, &update_parent, true),
2090            Err(BlockComponentProcessorError::MultipleUpdateParents)
2091        );
2092    }
2093
2094    #[test]
2095    fn test_header_after_update_parent_error() {
2096        let mut processor = BlockComponentProcessor::default();
2097        processor
2098            .on_update_parent(
2099                4,
2100                &VersionedUpdateParent::V1(UpdateParentV1 {
2101                    new_parent_slot: 0,
2102                    new_parent_block_id: Hash::default(),
2103                }),
2104                true,
2105            )
2106            .unwrap();
2107
2108        let header = VersionedBlockHeader::V1(BlockHeaderV1 {
2109            parent_slot: 0,
2110            parent_block_id: Hash::default(),
2111        });
2112
2113        assert!(matches!(
2114            processor.on_header(&header, 0),
2115            Err(BlockComponentProcessorError::SpuriousUpdateParent)
2116        ));
2117    }
2118
2119    #[test]
2120    fn test_workflow_with_update_parent() {
2121        let migration_status = MigrationStatus::post_migration_status();
2122        let mut processor = BlockComponentProcessor::default();
2123        let (parent, bank_forks) = create_test_bank();
2124        let bank = create_child_bank(&bank_forks, &parent, 4);
2125        let slot = bank.slot();
2126        let shred_version = rand::rng().random();
2127
2128        processor
2129            .on_update_parent(
2130                slot,
2131                &VersionedUpdateParent::V1(UpdateParentV1 {
2132                    new_parent_slot: 0,
2133                    new_parent_block_id: Hash::default(),
2134                }),
2135                true,
2136            )
2137            .unwrap();
2138
2139        processor
2140            .on_entry_batch(&migration_status, slot, &[], false)
2141            .unwrap();
2142
2143        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
2144        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
2145            bank_hash: Hash::new_unique(),
2146            block_producer_time_nanos: (parent_time_nanos + 100_000_000) as u64,
2147            block_user_agent: vec![],
2148            block_final_cert: None,
2149            skip_reward_cert: None,
2150            notar_reward_cert: None,
2151        });
2152        processor
2153            .on_footer(
2154                &migration_status.my_pubkey(),
2155                bank,
2156                parent,
2157                shred_version,
2158                footer,
2159                None,
2160            )
2161            .unwrap();
2162
2163        let good_alpentick = alpentick(1);
2164        processor
2165            .on_entry_batch(&migration_status, slot, &good_alpentick, true)
2166            .unwrap();
2167
2168        processor.on_final(&migration_status, slot, 0).unwrap();
2169    }
2170}