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