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