Skip to main content

solana_runtime/
block_component_processor.rs

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