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