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_transaction_view::transaction_data::TransactionData,
14    agave_votor_messages::{
15        certificate::{CertSignature, Certificate, CertificateType, GenesisCert},
16        consensus_message::Block,
17        migration::MigrationStatus,
18        unverified_vote_message::UnverifiedCertificate,
19    },
20    crossbeam_channel::{Sender, TrySendError},
21    log::*,
22    smallvec::{SmallVec, smallvec},
23    solana_clock::Slot,
24    solana_entry::{
25        block_component::{
26            BlockFooterV1, BlockMarkerV1, GenesisCertBlockMarker, VersionedBlockFooter,
27            VersionedBlockHeader, VersionedBlockMarker, VersionedUpdateParent,
28        },
29        entry::EntryView,
30    },
31    solana_hash::Hash,
32    solana_pubkey::Pubkey,
33    std::{collections::HashSet, sync::Arc},
34    thiserror::Error,
35};
36
37pub(crate) mod vote_reward;
38
39#[derive(Debug, Error)]
40pub enum BankFooterError {
41    #[error("calc vote rewards updating vote states failed with \"{0}\"")]
42    CalcVoteRewardUpdateVoteStates(#[from] CalcVoteRewardUpdateVoteStatesError),
43}
44
45#[derive(Debug, Error)]
46pub enum BlockComponentProcessorError {
47    #[error("BlockComponent detected pre-migration")]
48    BlockComponentPreMigration,
49    #[error("GenesisCertificate marker detected when GenesisCertificate is already populated")]
50    GenesisCertificateAlreadyPopulated,
51    #[error("GenesisCertificate marker detected when the cluster has Alpenglow enabled at slot 0")]
52    GenesisCertificateInAlpenglowCluster,
53    #[error("GenesisCertificate marker detected on a block which is not a child of genesis")]
54    GenesisCertificateOnNonChild,
55    #[error("GenesisCertificate was invalid and failed to verify")]
56    GenesisCertificateFailedVerification,
57    #[error("Alpenglow migration became ready; aborting the TowerBFT bank")]
58    AlpenglowMigrationTransition,
59    #[error("GenesisCertificate marker must immediately follow the block header")]
60    GenesisCertificateOutOfOrder,
61    #[error("FinalizationCertificate was invalid or failed to verify {0}")]
62    InvalidFinalizationCertificate(#[from] BlockFinalizationCertError),
63    #[error("Missing block footer")]
64    MissingBlockFooter,
65    #[error("Missing genesis certificate marker")]
66    MissingGenesisCertificateMarker,
67    #[error("Missing parent marker (neither a header nor an update parent was present)")]
68    MissingParentMarker,
69    #[error("Entry batch detected after block footer")]
70    EntryBatchAfterBlockFooter,
71    #[error("Alpentick must be the final block component and appear after block footer")]
72    InvalidAlpentickPosition,
73    #[error("Multiple block footers detected")]
74    MultipleBlockFooters,
75    #[error("Multiple block headers detected")]
76    MultipleBlockHeaders,
77    #[error(
78        "Block header parent slot mismatch: header={header_parent_slot}, bank={bank_parent_slot}"
79    )]
80    HeaderParentSlotMismatch {
81        header_parent_slot: Slot,
82        bank_parent_slot: Slot,
83    },
84    #[error("Multiple update parents detected")]
85    MultipleUpdateParents,
86    #[error("Nanosecond clock out of bounds")]
87    NanosecondClockOutOfBounds,
88    #[error("Spurious update parent")]
89    SpuriousUpdateParent,
90    #[error("UpdateParent marker is only valid in the first slot of a leader window: slot {0}")]
91    UpdateParentNotFirstInLeaderWindow(Slot),
92    #[error(
93        "UpdateParent cannot be the initial parent marker unless replay starts at UpdateParent"
94    )]
95    UnexpectedInitialUpdateParent,
96    #[error("Abandoned bank")]
97    AbandonedBank(VersionedUpdateParent),
98    #[error("invalid reward certs {0}")]
99    InvalidRewardCerts(#[from] ValidatedRewardCertError),
100    #[error("updating bank footer failed with \"{0}\"")]
101    UpdateBankFooter(#[from] BankFooterError),
102}
103
104impl BlockComponentProcessorError {
105    /// Returns whether this error can come from an optimistic-parent prefix
106    /// that a later usable `UpdateParent` makes obsolete.
107    ///
108    /// This only determines soft-dead eligibility. Replay also verifies that
109    /// the failure occurred before the `UpdateParent`.
110    pub fn is_update_parent_recoverable_replay_error(&self) -> bool {
111        match self {
112            BlockComponentProcessorError::MissingParentMarker
113            | BlockComponentProcessorError::EntryBatchAfterBlockFooter
114            | BlockComponentProcessorError::InvalidAlpentickPosition
115            | BlockComponentProcessorError::MultipleBlockFooters
116            | BlockComponentProcessorError::MultipleBlockHeaders
117            | BlockComponentProcessorError::HeaderParentSlotMismatch { .. }
118            | BlockComponentProcessorError::NanosecondClockOutOfBounds
119            | BlockComponentProcessorError::UnexpectedInitialUpdateParent
120            | BlockComponentProcessorError::GenesisCertificateOutOfOrder
121            | BlockComponentProcessorError::GenesisCertificateAlreadyPopulated
122            | BlockComponentProcessorError::GenesisCertificateInAlpenglowCluster
123            | BlockComponentProcessorError::GenesisCertificateOnNonChild
124            | BlockComponentProcessorError::GenesisCertificateFailedVerification
125            | BlockComponentProcessorError::SpuriousUpdateParent
126            | BlockComponentProcessorError::AbandonedBank(_)
127            | BlockComponentProcessorError::InvalidRewardCerts(_)
128            | BlockComponentProcessorError::UpdateBankFooter(_)
129            | BlockComponentProcessorError::InvalidFinalizationCertificate(_) => true,
130            BlockComponentProcessorError::BlockComponentPreMigration
131            | BlockComponentProcessorError::MissingBlockFooter
132            | BlockComponentProcessorError::MissingGenesisCertificateMarker
133            | BlockComponentProcessorError::MultipleUpdateParents
134            | BlockComponentProcessorError::AlpenglowMigrationTransition
135            | BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(_) => false,
136        }
137    }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
141/// The parent marker that established the current entry section.
142enum EntryParentMarker {
143    BlockHeader,
144    UpdateParent,
145}
146
147#[derive(Default, Debug, Clone, PartialEq, Eq)]
148/// The stage within the block we are currently in
149///
150/// All blocks MUST follow this exact shape
151///
152/// Header
153/// Optional Genesis marker - this is the only valid position for a genesis marker
154/// 0 or more Entries
155/// Optional UpdateParent
156/// 0 or more Entries
157/// Footer
158/// Alpentick
159///
160/// Block component processing can start either from the header
161/// or from the UpdateParent.
162enum BlockComponentStage {
163    #[default]
164    /// Beginning of the block, can only accept a parent marker
165    PreParentMarker,
166    /// Immediately after the header, can accept genesis marker, entries or footer
167    AcceptingGenesisOrEntries,
168    /// During the entries section, can accept entries or the footer
169    /// If the parent marker was a block header, can also accept an UpdateParent marker
170    AcceptingEntriesOrFooter { parent_marker: EntryParentMarker },
171    /// After the footer, can only accept the alpentick
172    AcceptingAlpentick,
173    /// After the alpentick, nothing more is accepted
174    Done,
175}
176
177impl BlockComponentStage {
178    /// If current stage is `PreParentMarker`, transition to `AcceptingGenesisOrEntries`
179    fn on_header(&mut self) -> Result<(), BlockComponentProcessorError> {
180        match self {
181            Self::PreParentMarker => {
182                *self = Self::AcceptingGenesisOrEntries;
183                Ok(())
184            }
185            Self::AcceptingGenesisOrEntries
186            | Self::AcceptingEntriesOrFooter {
187                parent_marker: EntryParentMarker::BlockHeader,
188            }
189            | Self::AcceptingAlpentick
190            | Self::Done => Err(BlockComponentProcessorError::MultipleBlockHeaders),
191            Self::AcceptingEntriesOrFooter {
192                parent_marker: EntryParentMarker::UpdateParent,
193            } => Err(BlockComponentProcessorError::SpuriousUpdateParent),
194        }
195    }
196
197    /// If current stage is `AcceptingGenesisOrEntries`, transition to `AcceptingEntriesOrFooter`
198    fn on_genesis_certificate(&mut self) -> Result<(), BlockComponentProcessorError> {
199        match self {
200            Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
201            Self::AcceptingGenesisOrEntries => {
202                *self = Self::AcceptingEntriesOrFooter {
203                    parent_marker: EntryParentMarker::BlockHeader,
204                };
205                Ok(())
206            }
207            Self::AcceptingEntriesOrFooter { .. } | Self::AcceptingAlpentick | Self::Done => {
208                Err(BlockComponentProcessorError::GenesisCertificateOutOfOrder)
209            }
210        }
211    }
212
213    /// If current stage is `AcceptingGenesisOrEntries` or `AcceptingEntriesOrFooter`, transition to
214    /// `AcceptingEntriesOrFooter`
215    fn on_entry_batch(&mut self) -> Result<(), BlockComponentProcessorError> {
216        match self {
217            Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
218            Self::AcceptingGenesisOrEntries => {
219                *self = Self::AcceptingEntriesOrFooter {
220                    parent_marker: EntryParentMarker::BlockHeader,
221                };
222                Ok(())
223            }
224            Self::AcceptingEntriesOrFooter { .. } => Ok(()),
225            Self::AcceptingAlpentick | Self::Done => {
226                Err(BlockComponentProcessorError::EntryBatchAfterBlockFooter)
227            }
228        }
229    }
230
231    /// If current stage is `AcceptingGenesisOrEntries` or `AcceptingEntriesOrFooter`, return `AbandonedBank`
232    /// If current stage is `PreParentMarker` and `allow_initial_update_parent` is specified,
233    /// transition to `AcceptingEntriesOrFooter` with `EntryParentMarker::UpdateParent`
234    fn on_update_parent(
235        &mut self,
236        update_parent: &VersionedUpdateParent,
237        allow_initial_update_parent: bool,
238    ) -> Result<(), BlockComponentProcessorError> {
239        match self {
240            Self::PreParentMarker => {
241                if !allow_initial_update_parent {
242                    return Err(BlockComponentProcessorError::UnexpectedInitialUpdateParent);
243                }
244                *self = Self::AcceptingEntriesOrFooter {
245                    parent_marker: EntryParentMarker::UpdateParent,
246                };
247                Ok(())
248            }
249            Self::AcceptingGenesisOrEntries
250            | Self::AcceptingEntriesOrFooter {
251                parent_marker: EntryParentMarker::BlockHeader,
252            } => {
253                // Only an error in the sense that replay execution of this block
254                // prefix is now over. Replay execution can continue after resetting
255                // bank.
256                Err(BlockComponentProcessorError::AbandonedBank(
257                    update_parent.clone(),
258                ))
259            }
260            Self::AcceptingEntriesOrFooter {
261                parent_marker: EntryParentMarker::UpdateParent,
262            } => Err(BlockComponentProcessorError::MultipleUpdateParents),
263            Self::AcceptingAlpentick | BlockComponentStage::Done => {
264                Err(BlockComponentProcessorError::SpuriousUpdateParent)
265            }
266        }
267    }
268
269    /// If the current stage is `AcceptingGenesisOrEntries`, `AcceptingEntriesOrFooter`
270    /// transition to `AcceptingAlpentick`
271    fn on_footer(&mut self) -> Result<(), BlockComponentProcessorError> {
272        match self {
273            Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
274            Self::AcceptingGenesisOrEntries | Self::AcceptingEntriesOrFooter { .. } => {
275                *self = Self::AcceptingAlpentick;
276                Ok(())
277            }
278            Self::AcceptingAlpentick | Self::Done => {
279                Err(BlockComponentProcessorError::MultipleBlockFooters)
280            }
281        }
282    }
283
284    /// If stage is `AcceptingAlpentick`, transition to `Done`
285    fn on_alpentick(&mut self) -> Result<(), BlockComponentProcessorError> {
286        match self {
287            Self::PreParentMarker => Err(BlockComponentProcessorError::MissingParentMarker),
288            Self::AcceptingGenesisOrEntries => {
289                Err(BlockComponentProcessorError::InvalidAlpentickPosition)
290            }
291            Self::AcceptingEntriesOrFooter { .. } => {
292                Err(BlockComponentProcessorError::InvalidAlpentickPosition)
293            }
294            Self::AcceptingAlpentick => {
295                *self = Self::Done;
296                Ok(())
297            }
298            Self::Done => Err(BlockComponentProcessorError::InvalidAlpentickPosition),
299        }
300    }
301
302    /// Return `Ok(())` only if the stage is `Done`
303    fn on_final(&self) -> Result<(), BlockComponentProcessorError> {
304        match self {
305            Self::Done => Ok(()),
306            Self::AcceptingAlpentick => Err(BlockComponentProcessorError::InvalidAlpentickPosition),
307            Self::PreParentMarker
308            | Self::AcceptingGenesisOrEntries
309            | Self::AcceptingEntriesOrFooter { .. } => {
310                Err(BlockComponentProcessorError::MissingBlockFooter)
311            }
312        }
313    }
314}
315
316#[derive(Default)]
317pub struct BlockComponentProcessor {
318    stage: BlockComponentStage,
319    has_genesis_certificate_marker: bool,
320}
321
322impl BlockComponentProcessor {
323    pub fn on_final(
324        &self,
325        migration_status: &MigrationStatus,
326        slot: Slot,
327        parent_slot: Slot,
328    ) -> Result<(), BlockComponentProcessorError> {
329        // Only allow block markers for slots where they should be present.
330        // TowerBFT blocks must not include block headers.
331        if !migration_status.should_allow_block_markers(slot) {
332            if self.stage == BlockComponentStage::PreParentMarker {
333                return Ok(());
334            } else {
335                return Err(BlockComponentProcessorError::BlockComponentPreMigration);
336            };
337        }
338
339        if Self::requires_genesis_certificate_marker(migration_status, parent_slot)
340            && !self.has_genesis_certificate_marker
341        {
342            return Err(BlockComponentProcessorError::MissingGenesisCertificateMarker);
343        }
344
345        self.stage.on_final()
346    }
347
348    /// Check if `parent_slot` is the alpenglow genesis block for use in enforcing
349    /// that the block has a genesis block marker
350    ///
351    /// Note: We have an exemption for Dev clusters that have alpenglow active at slot 0,
352    /// as these clusters do not need a genesis block marker
353    fn requires_genesis_certificate_marker(
354        migration_status: &MigrationStatus,
355        parent_slot: Slot,
356    ) -> bool {
357        migration_status
358            .genesis_block()
359            .is_some_and(|genesis_block| {
360                genesis_block.slot != 0 && parent_slot == genesis_block.slot
361            })
362    }
363
364    /// Process an entry batch.
365    ///
366    /// Validates that a parent marker (header or update parent) has been processed
367    /// before any entry batches. The terminal Alpenglow tick is the only entry
368    /// batch allowed after the block footer.
369    pub fn on_entry_batch<D: TransactionData>(
370        &mut self,
371        migration_status: &MigrationStatus,
372        slot: Slot,
373        entries: &[EntryView<D>],
374        is_final_component: bool,
375    ) -> Result<(), BlockComponentProcessorError> {
376        if !migration_status.should_allow_block_markers(slot) {
377            return Ok(());
378        }
379
380        // The alpentick must be the final block component.
381        // It is fine for other ticks to be present in the block, they will be rejected
382        // for `TooManyTicks` in `verify_ticks()`
383        let is_alpentick = is_final_component
384            && matches!(entries, [entry] if entry.is_tick() && entry.num_hashes == 1);
385
386        if is_alpentick {
387            self.stage.on_alpentick()
388        } else {
389            self.stage.on_entry_batch()
390        }
391    }
392
393    /// Process a block marker:
394    /// - Pre migration, no block markers are allowed
395    /// - During the migration only header and genesis certificate are allowed:
396    ///     - This is in case our node was slow in observing the completion of the migration
397    ///     - By seeing the first alpenglow block, we can advance the migration phase
398    /// - Once the migration is complete all markers are allowed
399    pub fn on_marker(
400        &mut self,
401        bank: Arc<Bank>,
402        parent_bank: Arc<Bank>,
403        shred_version: u16,
404        marker: VersionedBlockMarker,
405        allow_initial_update_parent: bool,
406        finalization_cert_sender: Option<&Sender<SmallVec<[Certificate; 2]>>>,
407        migration_status: &MigrationStatus,
408    ) -> Result<(), BlockComponentProcessorError> {
409        let slot = bank.slot();
410        let VersionedBlockMarker::V1(marker) = marker;
411
412        let markers_fully_enabled = migration_status.should_allow_block_markers(slot);
413        let in_migration = migration_status.is_in_migration();
414        let fast_leader_handover_active =
415            bank.feature_set.snapshot().alpenglow_fast_leader_handover;
416
417        match marker {
418            // Header and genesis cert can be processed either:
419            // - once migration is fully enabled, or
420            // - while we're still in the migration phase (to let us advance it)
421            BlockMarkerV1::BlockHeader(header) if markers_fully_enabled || in_migration => {
422                self.on_header(header.inner(), bank.parent_slot())
423            }
424            BlockMarkerV1::GenesisCertificate(genesis_cert_block_marker)
425                if markers_fully_enabled || in_migration =>
426            {
427                self.on_genesis_cert_block_marker(
428                    bank,
429                    shred_version,
430                    genesis_cert_block_marker.into_inner(),
431                    migration_status,
432                )
433            }
434
435            // Everything else is only valid once migration is complete
436            BlockMarkerV1::BlockFooter(footer) if markers_fully_enabled => self.on_footer(
437                &migration_status.my_pubkey(),
438                bank,
439                parent_bank,
440                shred_version,
441                footer.into_inner(),
442                finalization_cert_sender,
443            ),
444
445            BlockMarkerV1::UpdateParent(update_parent) if markers_fully_enabled => {
446                if fast_leader_handover_active {
447                    self.on_update_parent(slot, update_parent.inner(), allow_initial_update_parent)
448                } else {
449                    Err(BlockComponentProcessorError::SpuriousUpdateParent)
450                }
451            }
452
453            // Any other combination means we saw a marker too early
454            _ => Err(BlockComponentProcessorError::BlockComponentPreMigration),
455        }
456    }
457
458    /// Processes the genesis block marker with full verification
459    pub fn on_genesis_cert_block_marker(
460        &mut self,
461        bank: Arc<Bank>,
462        shred_version: u16,
463        genesis_block_marker: GenesisCertBlockMarker,
464        migration_status: &MigrationStatus,
465    ) -> Result<(), BlockComponentProcessorError> {
466        self.stage.on_genesis_certificate()?;
467        self.process_unvalidated_genesis_cert_block_marker(
468            bank,
469            genesis_block_marker,
470            migration_status,
471            Some(shred_version),
472        )?;
473        Ok(())
474    }
475
476    /// Processes a locally produced genesis certificate marker without verification
477    pub fn on_genesis_cert_block_marker_leader(
478        &mut self,
479        bank: Arc<Bank>,
480        genesis_block_marker: GenesisCertBlockMarker,
481        migration_status: &MigrationStatus,
482    ) -> Result<(), BlockComponentProcessorError> {
483        self.process_unvalidated_genesis_cert_block_marker(
484            bank,
485            genesis_block_marker,
486            migration_status,
487            None,
488        )?;
489        Ok(())
490    }
491
492    /// Performs verification if `shred_version` is specified
493    fn process_unvalidated_genesis_cert_block_marker(
494        &mut self,
495        bank: Arc<Bank>,
496        genesis_block_marker: GenesisCertBlockMarker,
497        migration_status: &MigrationStatus,
498        shred_version: Option<u16>,
499    ) -> Result<(), BlockComponentProcessorError> {
500        // Genesis Certificate is only allowed for direct child of genesis
501        if bank.parent_slot() == 0 {
502            return Err(BlockComponentProcessorError::GenesisCertificateInAlpenglowCluster);
503        }
504
505        let parent_block_id = bank
506            .parent_block_id()
507            .expect("Block id is populated for all slots > 0");
508        if (bank.parent_slot(), parent_block_id)
509            != (genesis_block_marker.slot, genesis_block_marker.block_id)
510        {
511            return Err(BlockComponentProcessorError::GenesisCertificateOnNonChild);
512        }
513
514        if bank.get_alpenglow_genesis_certificate().is_some() {
515            return Err(BlockComponentProcessorError::GenesisCertificateAlreadyPopulated);
516        }
517
518        let genesis_cert = GenesisCert {
519            block: Block {
520                slot: genesis_block_marker.slot,
521                block_id: genesis_block_marker.block_id,
522            },
523            signature: CertSignature {
524                signature: genesis_block_marker.bls_signature,
525                bitmap: genesis_block_marker.bitmap,
526            },
527        };
528        if let Some(shred_version) = shred_version {
529            Self::verify_genesis_certificate(&bank, &genesis_cert, shred_version)?;
530        }
531
532        bank.set_alpenglow_genesis_certificate(&genesis_cert);
533        self.has_genesis_certificate_marker = true;
534
535        if migration_status.is_alpenglow_enabled() {
536            // We participated in the migration, nothing to do
537            bank.set_hashes_per_tick(None);
538            return Ok(());
539        }
540
541        // We missed the migration however we ingested the first alpenglow block.
542        // This is either a result of startup replay, or in some weird cases steady state replay after a network partition.
543        // Either way we ingest the genesis block details moving us to `ReadyToEnable`.
544        // Since this is a direct child of genesis, and we are replaying, we know we have frozen the genesis block.
545        // Then `load_frozen_forks` or `replay_stage` will take care of the rest.
546        warn!(
547            "{}: Alpenglow genesis marker processed during replay of {}. Transitioning Alpenglow \
548             to ReadyToEnable",
549            migration_status.my_pubkey(),
550            bank.slot()
551        );
552        migration_status.set_genesis_block(genesis_cert.block);
553        migration_status.set_genesis_certificate(Arc::new(genesis_cert));
554        assert!(migration_status.is_ready_to_enable());
555
556        // This bank was created with TowerBFT tick configuration. Stop processing it immediately;
557        // replay will discard it, enable Alpenglow, and rebuild it with Alpenglow tick rules.
558        Err(BlockComponentProcessorError::AlpenglowMigrationTransition)
559    }
560
561    fn verify_genesis_certificate(
562        bank: &Bank,
563        cert: &GenesisCert,
564        shred_version: u16,
565    ) -> Result<(), BlockComponentProcessorError> {
566        let cert_slot = cert.block.slot;
567        let unverified_cert = UnverifiedCertificate {
568            cert_type: CertificateType::Genesis(cert.block),
569            signature: cert.signature.signature,
570            bitmap: cert.signature.bitmap.clone(),
571            shred_version,
572        };
573        bank.verify_certificate(unverified_cert).map_err(|_| {
574            warn!(
575                "Failed to verify genesis certificate for slot {cert_slot} in bank slot {}",
576                bank.slot()
577            );
578            BlockComponentProcessorError::GenesisCertificateFailedVerification
579        })?;
580
581        Ok(())
582    }
583
584    fn on_footer(
585        &mut self,
586        my_pubkey: &Pubkey,
587        bank: Arc<Bank>,
588        parent_bank: Arc<Bank>,
589        shred_version: u16,
590        footer: VersionedBlockFooter,
591        finalization_cert_sender: Option<&Sender<SmallVec<[Certificate; 2]>>>,
592    ) -> Result<(), BlockComponentProcessorError> {
593        self.stage.on_footer()?;
594
595        let VersionedBlockFooter::V1(footer) = footer;
596
597        Self::enforce_nanosecond_clock_bounds(&bank, &parent_bank, &footer)?;
598
599        let BlockFooterV1 {
600            bank_hash,
601            block_producer_time_nanos,
602            block_user_agent: _,
603            block_final_cert,
604            skip_reward_cert,
605            notar_reward_cert,
606        } = footer;
607
608        let reward_cert = ValidatedRewardCert::try_new(
609            &bank,
610            shred_version,
611            &skip_reward_cert,
612            &notar_reward_cert,
613        )?;
614        let block_producer_time_nanos =
615            Self::block_producer_time_nanos_as_i64(block_producer_time_nanos)?;
616        let final_cert = block_final_cert
617            .map(|final_cert| {
618                ValidatedBlockFinalizationCert::try_from_footer(final_cert, &bank, shred_version)
619                    .map_err(BlockComponentProcessorError::InvalidFinalizationCertificate)
620            })
621            .transpose()?;
622
623        let (footer_input, pool_input) = match final_cert {
624            None => (None, None),
625            Some(cert) => {
626                let (signers, finalize_cert, notarize_cert) = cert.into_parts();
627                let final_slot = finalize_cert.cert_type.slot();
628                (
629                    Some((signers, final_slot)),
630                    Some((finalize_cert, notarize_cert)),
631                )
632            }
633        };
634
635        Self::update_bank_with_footer_fields(
636            &bank,
637            block_producer_time_nanos,
638            Some(bank_hash),
639            reward_cert,
640            footer_input
641                .as_ref()
642                .map(|(validators, slot)| (validators, *slot)),
643        )?;
644
645        // Send finalization cert(s) to consensus pool
646        if let Some((finalize_cert, notarize_cert)) = pool_input
647            && let Some(sender) = finalization_cert_sender
648        {
649            let channel_name = "finalization_cert_sender";
650            let certs = match notarize_cert {
651                None => smallvec![finalize_cert],
652                Some(c) => smallvec![finalize_cert, c],
653            };
654            match sender.try_send(certs) {
655                Ok(()) => (),
656                Err(TrySendError::Full(_)) => {
657                    warn!("{my_pubkey}: channel \"{channel_name}\" is full, dropping msg")
658                }
659                Err(TrySendError::Disconnected(_)) => {
660                    warn!("{my_pubkey}: channel \"{channel_name}\" disconnected")
661                }
662            }
663        }
664
665        Ok(())
666    }
667
668    fn on_header(
669        &mut self,
670        header: &VersionedBlockHeader,
671        bank_parent_slot: Slot,
672    ) -> Result<(), BlockComponentProcessorError> {
673        self.stage.on_header()?;
674
675        let VersionedBlockHeader::V1(header) = header;
676        if header.parent_slot != bank_parent_slot {
677            return Err(BlockComponentProcessorError::HeaderParentSlotMismatch {
678                header_parent_slot: header.parent_slot,
679                bank_parent_slot,
680            });
681        }
682        Ok(())
683    }
684
685    fn on_update_parent(
686        &mut self,
687        slot: Slot,
688        update_parent: &VersionedUpdateParent,
689        allow_initial_update_parent: bool,
690    ) -> Result<(), BlockComponentProcessorError> {
691        if leader_slot_index(slot) != 0 {
692            return Err(BlockComponentProcessorError::UpdateParentNotFirstInLeaderWindow(slot));
693        }
694
695        self.stage
696            .on_update_parent(update_parent, allow_initial_update_parent)
697    }
698
699    fn enforce_nanosecond_clock_bounds(
700        bank: &Bank,
701        parent_bank: &Bank,
702        footer: &BlockFooterV1,
703    ) -> Result<(), BlockComponentProcessorError> {
704        // Get parent time from the nanosecond clock account, or from the Tower-based
705        // clock for the first Alpenglow block.
706        let parent_time_nanos = parent_bank
707            .get_nanosecond_clock()
708            .unwrap_or_else(|| bank.clock().unix_timestamp.saturating_mul(1_000_000_000));
709
710        let parent_slot = parent_bank.slot();
711        let current_time_nanos =
712            Self::block_producer_time_nanos_as_i64(footer.block_producer_time_nanos)?;
713        let current_slot = bank.slot();
714        let elapsed_slot_duration_nanos =
715            bank.slot_range_duration_nanos(parent_slot.saturating_add(1), current_slot);
716
717        let (lower_bound_nanos, upper_bound_nanos) =
718            Self::nanosecond_time_bounds(parent_time_nanos, elapsed_slot_duration_nanos);
719
720        let is_valid =
721            lower_bound_nanos <= current_time_nanos && current_time_nanos <= upper_bound_nanos;
722
723        match is_valid {
724            true => Ok(()),
725            false => Err(BlockComponentProcessorError::NanosecondClockOutOfBounds),
726        }
727    }
728
729    /// Converts a footer timestamp to the signed nanosecond representation used
730    /// by bank clock state.
731    ///
732    /// The `block_producer_time_nanos` parameter comes from wire-format footer
733    /// data and is rejected if it cannot be represented as `i64`; wrapping it
734    /// would make an extreme future timestamp look negative.
735    fn block_producer_time_nanos_as_i64(
736        block_producer_time_nanos: u64,
737    ) -> Result<i64, BlockComponentProcessorError> {
738        i64::try_from(block_producer_time_nanos)
739            .map_err(|_| BlockComponentProcessorError::NanosecondClockOutOfBounds)
740    }
741
742    /// Given a parent time and elapsed slot duration, calculates inclusive
743    /// block producer timestamp bounds.
744    ///
745    /// `parent_time_nanos` describes the parent bank's nanosecond clock.
746    /// `elapsed_slot_duration_nanos` is the summed duration for all skipped
747    /// and working slots after the parent. The returned `(lower_bound,
748    /// upper_bound)` accepts timestamps where
749    /// `lower_bound <= working_bank_time <= upper_bound`.
750    ///
751    /// Refer to
752    /// https://github.com/solana-foundation/solana-improvement-documents/pull/363
753    /// for details on the bounds calculation.
754    pub fn nanosecond_time_bounds(
755        parent_time_nanos: i64,
756        elapsed_slot_duration_nanos: u128,
757    ) -> (i64, i64) {
758        let min_working_bank_time = parent_time_nanos.saturating_add(1);
759        let max_working_bank_time_offset = elapsed_slot_duration_nanos
760            .saturating_mul(2)
761            .min(i64::MAX as u128) as i64;
762        let max_working_bank_time = parent_time_nanos.saturating_add(max_working_bank_time_offset);
763
764        (min_working_bank_time, max_working_bank_time)
765    }
766
767    pub fn update_bank_with_footer_fields(
768        bank: &Bank,
769        block_producer_time_nanos: i64,
770        bank_hash: Option<Hash>,
771        reward_cert: Option<ValidatedRewardCert>,
772        final_cert_input: Option<(&HashSet<Pubkey>, Slot)>,
773    ) -> Result<(), BankFooterError> {
774        bank.update_clock_from_footer(block_producer_time_nanos);
775        calc_vote_rewards_update_vote_states(
776            bank,
777            reward_cert,
778            final_cert_input,
779            block_producer_time_nanos,
780        )?;
781
782        if let Some(hash) = bank_hash {
783            // Record expected bank hash from footer for later verification when the bank is frozen.
784            bank.set_expected_bank_hash(hash);
785        }
786        Ok(())
787    }
788}
789
790#[cfg(test)]
791mod tests {
792    use {
793        super::*,
794        crate::{
795            bank::{Bank, SlotLeader},
796            bank_forks::BankForks,
797            genesis_utils::{activate_all_features_alpenglow, create_genesis_config},
798        },
799        bytes::Bytes,
800        rand::Rng,
801        solana_bls_signatures::{BLS_SIGNATURE_AFFINE_SIZE, Signature as BLSSignature},
802        solana_clock::DEFAULT_MS_PER_SLOT,
803        solana_entry::block_component::{
804            BlockFooterV1, BlockHeaderV1, UpdateParentV1, VersionedUpdateParent,
805        },
806        solana_hash::Hash,
807        solana_leader_schedule::NUM_CONSECUTIVE_LEADER_SLOTS,
808        std::{
809            assert_matches,
810            sync::{Arc, RwLock},
811        },
812        test_case::test_case,
813    };
814
815    const DEFAULT_NS_PER_SLOT: u64 = DEFAULT_MS_PER_SLOT * 1_000_000;
816
817    fn create_test_bank() -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
818        let genesis_config_info = create_genesis_config(10_000);
819        Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config)
820    }
821
822    fn create_test_bank_alpenglow() -> (Arc<Bank>, Arc<RwLock<BankForks>>) {
823        let mut genesis_config_info = create_genesis_config(10_000);
824        activate_all_features_alpenglow(&mut genesis_config_info.genesis_config);
825        Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config)
826    }
827
828    fn create_child_bank(
829        bank_forks: &RwLock<BankForks>,
830        parent: &Arc<Bank>,
831        slot: u64,
832    ) -> Arc<Bank> {
833        Bank::new_from_parent_with_bank_forks(
834            bank_forks,
835            parent.clone(),
836            SlotLeader::new_unique(),
837            slot,
838        )
839    }
840
841    fn test_genesis_cert_marker() -> GenesisCertBlockMarker {
842        GenesisCertBlockMarker {
843            slot: 0,
844            block_id: Hash::default(),
845            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
846            bitmap: vec![],
847        }
848    }
849
850    fn post_migration_status_with_genesis_slot(genesis_slot: Slot) -> MigrationStatus {
851        let migration_status = MigrationStatus::default();
852        let migration_slot = migration_status.record_feature_activation(0);
853        assert!(genesis_slot < migration_slot);
854
855        let genesis_block = Block::new_unique(genesis_slot);
856        migration_status.set_genesis_block(genesis_block);
857        let cert = Arc::new(GenesisCert {
858            block: genesis_block,
859            signature: CertSignature {
860                signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
861                bitmap: vec![],
862            },
863        });
864        migration_status.set_genesis_certificate(cert);
865        migration_status.enable_alpenglow_during_startup();
866
867        migration_status
868    }
869
870    fn processor_after_header() -> BlockComponentProcessor {
871        BlockComponentProcessor {
872            stage: BlockComponentStage::AcceptingGenesisOrEntries,
873            ..BlockComponentProcessor::default()
874        }
875    }
876
877    fn processor_after_footer() -> BlockComponentProcessor {
878        BlockComponentProcessor {
879            stage: BlockComponentStage::AcceptingAlpentick,
880            ..BlockComponentProcessor::default()
881        }
882    }
883
884    #[test]
885    fn test_first_alpenglow_block_requires_genesis_certificate_marker() {
886        let migration_status = post_migration_status_with_genesis_slot(1);
887        let processor = processor_after_footer();
888
889        let result = processor.on_final(&migration_status, 2, 1);
890        assert!(matches!(
891            result,
892            Err(BlockComponentProcessorError::MissingGenesisCertificateMarker)
893        ));
894    }
895
896    #[test]
897    fn test_first_alpenglow_block_with_genesis_certificate_marker_succeeds() {
898        let migration_status = post_migration_status_with_genesis_slot(1);
899        let (genesis_bank, bank_forks) = create_test_bank();
900        let parent = create_child_bank(&bank_forks, &genesis_bank, 1);
901        let parent_block_id = Hash::new_unique();
902        parent.set_block_id(Some(parent_block_id));
903        let bank = create_child_bank(&bank_forks, &parent, 2);
904        let genesis_marker = GenesisCertBlockMarker {
905            slot: parent.slot(),
906            block_id: parent_block_id,
907            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
908            bitmap: vec![],
909        };
910        let mut processor = processor_after_header();
911        bank.set_hashes_per_tick(Some(42));
912        assert!(bank.hashes_per_tick().is_some());
913
914        processor
915            .on_genesis_cert_block_marker_leader(bank.clone(), genesis_marker, &migration_status)
916            .unwrap();
917        assert!(bank.hashes_per_tick().is_none());
918        processor.stage = BlockComponentStage::Done;
919        assert!(processor.on_final(&migration_status, 2, 1).is_ok());
920    }
921
922    #[test]
923    fn test_genesis_certificate_marker_aborts_tower_bank_during_migration() {
924        let migration_status = MigrationStatus::default();
925        migration_status.record_feature_activation(0);
926        let (genesis_bank, bank_forks) = create_test_bank();
927        let parent = create_child_bank(&bank_forks, &genesis_bank, 1);
928        let parent_block_id = Hash::new_unique();
929        parent.set_block_id(Some(parent_block_id));
930        let bank = create_child_bank(&bank_forks, &parent, 2);
931        let genesis_marker = GenesisCertBlockMarker {
932            slot: parent.slot(),
933            block_id: parent_block_id,
934            bls_signature: BLSSignature([0; BLS_SIGNATURE_AFFINE_SIZE]),
935            bitmap: vec![],
936        };
937        let mut processor = processor_after_header();
938        bank.set_hashes_per_tick(Some(42));
939        let tower_hashes_per_tick = bank.hashes_per_tick();
940        assert!(tower_hashes_per_tick.is_some());
941
942        assert_matches!(
943            processor.on_genesis_cert_block_marker_leader(
944                bank.clone(),
945                genesis_marker,
946                &migration_status,
947            ),
948            Err(BlockComponentProcessorError::AlpenglowMigrationTransition)
949        );
950
951        assert!(migration_status.is_ready_to_enable());
952        assert_eq!(bank.hashes_per_tick(), tower_hashes_per_tick);
953        assert!(bank.get_alpenglow_genesis_certificate().is_some());
954    }
955
956    #[test]
957    fn test_on_footer_sets_timestamp() {
958        let my_pubkey = Pubkey::new_unique();
959        let mut processor = processor_after_header();
960
961        let (parent, bank_forks) = create_test_bank();
962        let bank = create_child_bank(&bank_forks, &parent, 1);
963        let shred_version = rand::rng().random();
964
965        // Calculate valid timestamp based on parent's time
966        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
967        let footer_time_nanos = parent_time_nanos + 200_000_000; // parent + 200ms
968        let expected_time_secs = footer_time_nanos / 1_000_000_000;
969
970        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
971            bank_hash: Hash::new_unique(),
972            block_producer_time_nanos: footer_time_nanos as u64,
973            block_user_agent: vec![],
974            block_final_cert: None,
975            skip_reward_cert: None,
976            notar_reward_cert: None,
977        });
978
979        processor
980            .on_footer(
981                &my_pubkey,
982                bank.clone(),
983                parent,
984                shred_version,
985                footer,
986                None,
987            )
988            .unwrap();
989
990        assert_eq!(processor.stage, BlockComponentStage::AcceptingAlpentick);
991
992        // Verify clock sysvar was updated with correct timestamp (nanos converted to seconds)
993        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
994    }
995
996    #[test]
997    fn test_footer_sets_epoch_start_timestamp_on_epoch_change() {
998        let my_pubkey = Pubkey::new_unique();
999        let mut processor = processor_after_header();
1000        let shred_version = rand::rng().random();
1001
1002        // Create genesis bank
1003        let genesis_config_info = create_genesis_config(10_000);
1004        let (genesis_bank, bank_forks) =
1005            Bank::new_with_bank_forks_for_tests(&genesis_config_info.genesis_config);
1006
1007        // Get epoch schedule to find first slot of next epoch
1008        let epoch_schedule = genesis_bank.epoch_schedule();
1009        let first_slot_in_epoch_1 = epoch_schedule.get_first_slot_in_epoch(1);
1010
1011        // Create parent bank at last slot of epoch 0
1012        let mut parent = genesis_bank.clone();
1013        for slot in 1..first_slot_in_epoch_1 {
1014            parent = create_child_bank(&bank_forks, &parent, slot);
1015        }
1016
1017        // Create bank at first slot of epoch 1
1018        let bank = create_child_bank(&bank_forks, &parent, first_slot_in_epoch_1);
1019
1020        // Verify we're in epoch 1
1021        assert_eq!(bank.epoch(), 1);
1022
1023        // Calculate valid timestamp based on parent's time
1024        let parent_slot = parent.slot();
1025        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1026        let current_slot = bank.slot();
1027        let elapsed_slot_duration_nanos =
1028            bank.slot_range_duration_nanos(parent_slot.saturating_add(1), current_slot);
1029
1030        // Use a timestamp in the middle of the valid range
1031        let (lower_bound, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1032            parent_time_nanos,
1033            elapsed_slot_duration_nanos,
1034        );
1035        let footer_time_nanos = (lower_bound + upper_bound) / 2;
1036        let expected_time_secs = footer_time_nanos / 1_000_000_000;
1037
1038        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1039            bank_hash: Hash::new_unique(),
1040            block_producer_time_nanos: footer_time_nanos as u64,
1041            block_user_agent: vec![],
1042            block_final_cert: None,
1043            skip_reward_cert: None,
1044            notar_reward_cert: None,
1045        });
1046
1047        processor
1048            .on_footer(
1049                &my_pubkey,
1050                bank.clone(),
1051                parent,
1052                shred_version,
1053                footer,
1054                None,
1055            )
1056            .unwrap();
1057
1058        // Verify clock sysvar was updated
1059        assert_eq!(bank.clock().unix_timestamp, expected_time_secs);
1060
1061        // Verify epoch_start_timestamp was set correctly for the new epoch
1062        assert_eq!(bank.clock().epoch_start_timestamp, expected_time_secs);
1063    }
1064
1065    // Test clock bounds enforcement
1066    #[test_case(1, |_, lower, _| lower, true; "at_minimum")]
1067    #[test_case(1, |_, _, upper| upper, true; "at_maximum")]
1068    #[test_case(1, |_, lower, _| lower - 1, false; "below_minimum")]
1069    #[test_case(1, |_, _, upper| upper + 1, false; "above_maximum")]
1070    // For 5 slots: upper_bound = parent_time + 2 * 5 * 400ms = parent_time + 4000ms
1071    // Use 2 seconds which is within bounds
1072    #[test_case(5, |_, lower, _| lower + 2_000_000_000, true; "multi_slot_gap")]
1073    // Exceed by 1 second beyond the upper bound
1074    #[test_case(5, |_, _, upper| upper + 1_000_000_000, false; "multi_slot_gap_exceeds")]
1075    // Timestamp equal to parent time (should fail, must be strictly greater)
1076    #[test_case(1, |parent_time, _, _| parent_time, false; "timestamp_equals_parent")]
1077    fn test_clock_bounds(
1078        slot_gap: u64,
1079        timestamp_fn: impl FnOnce(i64, i64, i64) -> i64,
1080        should_pass: bool,
1081    ) {
1082        let my_pubkey = Pubkey::new_unique();
1083        let mut processor = processor_after_header();
1084        let shred_version = rand::rng().random();
1085
1086        let (parent, bank_forks) = create_test_bank_alpenglow();
1087        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1088
1089        // Set up clock on parent so validation doesn't skip bounds checking
1090        parent.update_clock_from_footer(parent_time_nanos);
1091
1092        let bank: Arc<Bank> = create_child_bank(&bank_forks, &parent, slot_gap);
1093        let elapsed_slot_duration_nanos = bank.slot_range_duration_nanos(1, slot_gap);
1094
1095        let (lower_bound, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1096            parent_time_nanos,
1097            elapsed_slot_duration_nanos,
1098        );
1099
1100        let footer_time_nanos = timestamp_fn(parent_time_nanos, lower_bound, upper_bound);
1101
1102        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1103            bank_hash: Hash::new_unique(),
1104            block_producer_time_nanos: footer_time_nanos as u64,
1105            block_user_agent: vec![],
1106            block_final_cert: None,
1107            skip_reward_cert: None,
1108            notar_reward_cert: None,
1109        });
1110
1111        let result = processor.on_footer(&my_pubkey, bank, parent, shred_version, footer, None);
1112        if should_pass {
1113            result.unwrap();
1114        } else {
1115            assert!(matches!(
1116                result.unwrap_err(),
1117                BlockComponentProcessorError::NanosecondClockOutOfBounds
1118            ));
1119        }
1120    }
1121
1122    #[test]
1123    fn test_clock_bounds_without_parent_nanosecond_clock_rejects_out_of_bounds() {
1124        let my_pubkey = Pubkey::new_unique();
1125        let mut processor = processor_after_header();
1126        let shred_version = rand::rng().random();
1127
1128        let (parent, bank_forks) = create_test_bank_alpenglow();
1129        assert_eq!(parent.get_nanosecond_clock(), None);
1130
1131        let bank = create_child_bank(&bank_forks, &parent, 1);
1132        let parent_time_nanos = bank.clock().unix_timestamp.saturating_mul(1_000_000_000);
1133        let elapsed_slot_duration_nanos =
1134            bank.slot_range_duration_nanos(parent.slot().saturating_add(1), bank.slot());
1135        let (_, upper_bound) = BlockComponentProcessor::nanosecond_time_bounds(
1136            parent_time_nanos,
1137            elapsed_slot_duration_nanos,
1138        );
1139
1140        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1141            bank_hash: Hash::new_unique(),
1142            block_producer_time_nanos: u64::try_from(upper_bound.saturating_add(1)).unwrap(),
1143            block_user_agent: vec![],
1144            block_final_cert: None,
1145            skip_reward_cert: None,
1146            notar_reward_cert: None,
1147        });
1148
1149        assert!(matches!(
1150            processor
1151                .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1152                .unwrap_err(),
1153            BlockComponentProcessorError::NanosecondClockOutOfBounds
1154        ));
1155    }
1156
1157    #[test]
1158    fn test_clock_bounds_rejects_timestamp_above_i64() {
1159        let my_pubkey = Pubkey::new_unique();
1160        let mut processor = processor_after_header();
1161        let shred_version = rand::rng().random();
1162
1163        let (parent, bank_forks) = create_test_bank_alpenglow();
1164        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1165        parent.update_clock_from_footer(parent_time_nanos);
1166        let bank = create_child_bank(&bank_forks, &parent, 1);
1167
1168        let footer = VersionedBlockFooter::V1(BlockFooterV1 {
1169            bank_hash: Hash::new_unique(),
1170            block_producer_time_nanos: u64::MAX,
1171            block_user_agent: vec![],
1172            block_final_cert: None,
1173            skip_reward_cert: None,
1174            notar_reward_cert: None,
1175        });
1176
1177        assert!(matches!(
1178            processor
1179                .on_footer(&my_pubkey, bank, parent, shred_version, footer, None)
1180                .unwrap_err(),
1181            BlockComponentProcessorError::NanosecondClockOutOfBounds
1182        ));
1183    }
1184
1185    // Helper function to test nanosecond_time_bounds calculation
1186    fn test_nanosecond_time_bounds_helper(
1187        parent_time_nanos: i64,
1188        elapsed_slot_duration_nanos: u128,
1189        expected_lower: i64,
1190        expected_upper: i64,
1191    ) {
1192        let (lower, upper) = BlockComponentProcessor::nanosecond_time_bounds(
1193            parent_time_nanos,
1194            elapsed_slot_duration_nanos,
1195        );
1196
1197        assert_eq!(lower, expected_lower);
1198        assert_eq!(upper, expected_upper);
1199    }
1200
1201    #[test]
1202    fn test_nanosecond_time_bounds_calculation() {
1203        // Test the nanosecond_time_bounds function directly
1204        // diff_slots = 15 - 10 = 5
1205        // lower = parent_time + 1
1206        // upper = parent_time + 2 * 5 * 400_000_000 = parent_time + 4_000_000_000
1207        let parent_slot = 10;
1208        let parent_time = 1_000_000_000_000; // 1000 seconds in nanos
1209        let working_slot = 15;
1210        let slot_delta = working_slot - parent_slot;
1211        test_nanosecond_time_bounds_helper(
1212            parent_time,
1213            u128::from(slot_delta).saturating_mul(u128::from(DEFAULT_NS_PER_SLOT)),
1214            parent_time + 1,
1215            parent_time + (2 * DEFAULT_NS_PER_SLOT * slot_delta) as i64,
1216        );
1217    }
1218
1219    #[test]
1220    fn test_nanosecond_time_bounds_same_slot() {
1221        // Test with same slot (diff = 0)
1222        // diff_slots = 0
1223        // lower = parent_time + 1
1224        // upper = parent_time + 2 * 0 * 400_000_000 = parent_time
1225        // Note: In this case, lower > upper, so no timestamp would be valid
1226        // This is expected since we shouldn't have the same slot for parent and working bank
1227        let parent_time = 1_000_000_000_000;
1228        test_nanosecond_time_bounds_helper(parent_time, 0, parent_time + 1, parent_time);
1229    }
1230
1231    #[test]
1232    fn test_nanosecond_time_bounds_saturates_upper_bound() {
1233        let parent_time = i64::MAX - 5;
1234        let (lower, upper) =
1235            BlockComponentProcessor::nanosecond_time_bounds(parent_time, u128::MAX);
1236
1237        assert_eq!(lower, parent_time + 1);
1238        assert_eq!(upper, i64::MAX);
1239    }
1240
1241    /// Each case runs a component sequence against a fresh processor — one
1242    /// `on_marker` / `on_entry_batch` call per component — then `on_final`,
1243    /// as on a full slot.
1244    #[test]
1245    fn test_processor_component_sequences() {
1246        use BlockComponentProcessorError as E;
1247
1248        type Step = Box<
1249            dyn FnOnce(
1250                &mut BlockComponentProcessor,
1251                &MigrationStatus,
1252            ) -> Result<(), BlockComponentProcessorError>,
1253        >;
1254
1255        let post_migration = MigrationStatus::post_migration_status();
1256        let pre_migration = MigrationStatus::default();
1257        // Feature activated but migration not yet complete
1258        let in_migration = MigrationStatus::default();
1259        in_migration.record_feature_activation(0);
1260
1261        let (parent, bank_forks) = create_test_bank();
1262        // First slot of a leader window so UpdateParent passes the window check
1263        let slot = NUM_CONSECUTIVE_LEADER_SLOTS.get() as Slot;
1264        let bank = create_child_bank(&bank_forks, &parent, slot);
1265        assert_eq!(leader_slot_index(slot), 0);
1266        // A bank whose slot is not the first in its leader window, for the
1267        // UpdateParent window check
1268        let bank_not_window_start = create_child_bank(&bank_forks, &parent, slot + 1);
1269        assert_ne!(leader_slot_index(slot + 1), 0);
1270        let shred_version: u16 = rand::rng().random();
1271
1272        // A timestamp inside the footer clock bounds for every case below
1273        let parent_time_nanos = parent.clock().unix_timestamp.saturating_mul(1_000_000_000);
1274        let footer_time_nanos = u64::try_from(parent_time_nanos + 400_000_000).unwrap();
1275
1276        // One step = one direct `on_marker` call
1277        let marker_step = {
1278            let (bank, parent) = (bank.clone(), parent.clone());
1279            move |marker: VersionedBlockMarker, allow_initial_update_parent: bool| -> Step {
1280                let (bank, parent) = (bank.clone(), parent.clone());
1281                Box::new(move |processor, migration_status| {
1282                    processor.on_marker(
1283                        bank,
1284                        parent,
1285                        shred_version,
1286                        marker,
1287                        allow_initial_update_parent,
1288                        None,
1289                        migration_status,
1290                    )
1291                })
1292            }
1293        };
1294        // One step = one direct `on_entry_batch` call
1295        let batch_step = move |entries: Vec<EntryView<Bytes>>, is_final: bool| -> Step {
1296            Box::new(move |processor, migration_status| {
1297                processor.on_entry_batch(migration_status, slot, &entries, is_final)
1298            })
1299        };
1300
1301        // Step builders: real wire types, fresh per case
1302        let header_with_parent_slot = |parent_slot: Slot| {
1303            marker_step(
1304                VersionedBlockMarker::from_block_header(BlockHeaderV1 {
1305                    parent_slot,
1306                    parent_block_id: Hash::default(),
1307                }),
1308                false,
1309            )
1310        };
1311        let header = || header_with_parent_slot(0);
1312        let genesis_cert = || {
1313            marker_step(
1314                VersionedBlockMarker::from_genesis_cert_block_marker(test_genesis_cert_marker()),
1315                false,
1316            )
1317        };
1318        let update_parent = |allow_initial_update_parent: bool| {
1319            marker_step(
1320                VersionedBlockMarker::from_update_parent(UpdateParentV1 {
1321                    new_parent_slot: 0,
1322                    new_parent_block_id: Hash::default(),
1323                }),
1324                allow_initial_update_parent,
1325            )
1326        };
1327        // Same UpdateParent marker, sent to a bank whose slot is not the
1328        // first in its leader window
1329        let update_parent_not_window_start = {
1330            let (bank, parent) = (bank_not_window_start.clone(), parent.clone());
1331            move |allow_initial_update_parent: bool| -> Step {
1332                let (bank, parent) = (bank.clone(), parent.clone());
1333                Box::new(move |processor, migration_status| {
1334                    processor.on_marker(
1335                        bank,
1336                        parent,
1337                        shred_version,
1338                        VersionedBlockMarker::from_update_parent(UpdateParentV1 {
1339                            new_parent_slot: 0,
1340                            new_parent_block_id: Hash::default(),
1341                        }),
1342                        allow_initial_update_parent,
1343                        None,
1344                        migration_status,
1345                    )
1346                })
1347            }
1348        };
1349        let footer = || {
1350            marker_step(
1351                VersionedBlockMarker::from_block_footer(BlockFooterV1 {
1352                    bank_hash: Hash::new_unique(),
1353                    block_producer_time_nanos: footer_time_nanos,
1354                    block_user_agent: vec![],
1355                    block_final_cert: None,
1356                    skip_reward_cert: None,
1357                    notar_reward_cert: None,
1358                }),
1359                false,
1360            )
1361        };
1362        let entries = || {
1363            batch_step(
1364                vec![EntryView {
1365                    num_hashes: 2,
1366                    hash: Hash::default(),
1367                    transactions: vec![],
1368                }],
1369                false,
1370            )
1371        };
1372        // Final batch with num_hashes != 1: never classified as the alpentick,
1373        // even as the last component of a full slot
1374        let final_entries = || {
1375            batch_step(
1376                vec![EntryView {
1377                    num_hashes: 2,
1378                    hash: Hash::default(),
1379                    transactions: vec![],
1380                }],
1381                true,
1382            )
1383        };
1384        // Single tick with num_hashes == 1: classified as the alpentick only
1385        // when it is the final component of a full slot
1386        let tick = |is_final: bool| {
1387            batch_step(
1388                vec![EntryView {
1389                    num_hashes: 1,
1390                    hash: Hash::default(),
1391                    transactions: vec![],
1392                }],
1393                is_final,
1394            )
1395        };
1396
1397        let abandoned = || {
1398            E::AbandonedBank(VersionedUpdateParent::V1(UpdateParentV1 {
1399                new_parent_slot: 0,
1400                new_parent_block_id: Hash::default(),
1401            }))
1402        };
1403
1404        #[rustfmt::skip]
1405        let cases: Vec<(&MigrationStatus, Vec<Step>, Result<(), E>)> = vec![
1406            // - Pre migration
1407            (&pre_migration, vec![entries(), tick(true)], Ok(())),
1408            (&pre_migration, vec![entries(), tick(false)], Ok(())),
1409            (&pre_migration, vec![tick(false)], Ok(())),
1410            (&pre_migration, vec![tick(true), entries()], Ok(())),
1411            (&pre_migration, vec![entries(), entries(), tick(false), tick(true)], Ok(())),
1412            (&pre_migration, vec![header()], Err(E::BlockComponentPreMigration)),
1413            (&pre_migration, vec![genesis_cert()], Err(E::BlockComponentPreMigration)),
1414            (&pre_migration, vec![footer()], Err(E::BlockComponentPreMigration)),
1415            (&pre_migration, vec![update_parent(false)], Err(E::BlockComponentPreMigration)),
1416            (&pre_migration, vec![update_parent(true)], Err(E::BlockComponentPreMigration)),
1417
1418            // - In migration
1419            // header and genesis cert are processed so a slow node can catch up,
1420            // other markers are still rejected
1421            (&in_migration, vec![entries(), tick(true)], Ok(())),
1422            (&in_migration, vec![header()], Err(E::BlockComponentPreMigration)),
1423            // genesis cert reaches the stage check instead of the migration gate
1424            (&in_migration, vec![genesis_cert()], Err(E::MissingParentMarker)),
1425            // header passes on_marker: the genesis cert reaches deep validation
1426            (&in_migration, vec![header(), genesis_cert()], Err(E::GenesisCertificateInAlpenglowCluster)),
1427            (&in_migration, vec![footer()], Err(E::BlockComponentPreMigration)),
1428            (&in_migration, vec![update_parent(false)], Err(E::BlockComponentPreMigration)),
1429            (&in_migration, vec![update_parent(true)], Err(E::BlockComponentPreMigration)),
1430
1431            // - Post migration
1432
1433            // Valid block
1434            // a valid genesis cert block needs a parent slot != 0, covered by
1435            // test_first_alpenglow_block_with_genesis_certificate_marker_succeeds
1436            (&post_migration, vec![header(), footer(), tick(true)], Ok(())),
1437            (&post_migration, vec![header(), entries(), entries(), footer(), tick(true)], Ok(())),
1438            (&post_migration, vec![update_parent(true), entries(), footer(), tick(true)], Ok(())),
1439            (&post_migration, vec![update_parent(true), footer(), tick(true)], Ok(())),
1440            // Alpentick-shaped batch mid-block is a plain entry batch, not the alpentick
1441            (&post_migration, vec![header(), tick(false), footer(), tick(true)], Ok(())),
1442            (&post_migration, vec![update_parent(true), tick(false), footer(), tick(true)], Ok(())),
1443
1444            // Empty block
1445            (&post_migration, vec![], Err(E::MissingBlockFooter)),
1446
1447            // MissingParentMarker
1448            (&post_migration, vec![entries()], Err(E::MissingParentMarker)),
1449            (&post_migration, vec![genesis_cert()], Err(E::MissingParentMarker)),
1450            (&post_migration, vec![footer()], Err(E::MissingParentMarker)),
1451            (&post_migration, vec![tick(true)], Err(E::MissingParentMarker)),
1452            (&post_migration, vec![tick(false)], Err(E::MissingParentMarker)),
1453
1454            // From-shred-zero replay must not accept an initial UpdateParent
1455            (&post_migration, vec![update_parent(false)], Err(E::UnexpectedInitialUpdateParent)),
1456
1457            // Genesis cert position
1458            (&post_migration, vec![header(), entries(), genesis_cert()], Err(E::GenesisCertificateOutOfOrder)),
1459            (&post_migration, vec![header(), footer(), genesis_cert()], Err(E::GenesisCertificateOutOfOrder)),
1460            (&post_migration, vec![update_parent(true), genesis_cert()], Err(E::GenesisCertificateOutOfOrder)),
1461            // correct position, but this cluster runs Alpenglow from slot 0
1462            (&post_migration, vec![header(), genesis_cert()], Err(E::GenesisCertificateInAlpenglowCluster)),
1463
1464            // Duplicate / misplaced parent markers
1465            (&post_migration, vec![header(), header()], Err(E::MultipleBlockHeaders)),
1466            (&post_migration, vec![header(), entries(), header()], Err(E::MultipleBlockHeaders)),
1467            (&post_migration, vec![header(), footer(), header()], Err(E::MultipleBlockHeaders)),
1468            (&post_migration, vec![update_parent(true), header()], Err(E::SpuriousUpdateParent)),
1469            (&post_migration, vec![update_parent(true), update_parent(true)], Err(E::MultipleUpdateParents)),
1470            (&post_migration, vec![update_parent(true), update_parent(false)], Err(E::MultipleUpdateParents)),
1471
1472            // Header parent slot must match the bank parent slot
1473            (&post_migration, vec![header_with_parent_slot(3)], Err(E::HeaderParentSlotMismatch { header_parent_slot: 3, bank_parent_slot: 0 })),
1474
1475            // UpdateParent is only valid in the first slot of a leader window,
1476            // whatever the flag
1477            (&post_migration, vec![update_parent_not_window_start(true)], Err(E::UpdateParentNotFirstInLeaderWindow(5))),
1478            (&post_migration, vec![update_parent_not_window_start(false)], Err(E::UpdateParentNotFirstInLeaderWindow(5))),
1479
1480            // Mid-block UpdateParent: controlled abort (fast leader handover)
1481            // the flag only matters as the first component
1482            (&post_migration, vec![header(), update_parent(false)], Err(abandoned())),
1483            (&post_migration, vec![header(), update_parent(true)], Err(abandoned())),
1484            (&post_migration, vec![header(), entries(), update_parent(false)], Err(abandoned())),
1485            (&post_migration, vec![header(), footer(), update_parent(false)], Err(E::SpuriousUpdateParent)),
1486            (&post_migration, vec![header(), footer(), update_parent(true)], Err(E::SpuriousUpdateParent)),
1487
1488            // Alpentick (tick with is_final) only directly after the footer
1489            (&post_migration, vec![header(), tick(true)], Err(E::InvalidAlpentickPosition)),
1490            (&post_migration, vec![header(), entries(), tick(true)], Err(E::InvalidAlpentickPosition)),
1491            (&post_migration, vec![update_parent(true), tick(true)], Err(E::InvalidAlpentickPosition)),
1492
1493            // Footer is terminal
1494            (&post_migration, vec![header(), footer(), entries(), tick(true)], Err(E::EntryBatchAfterBlockFooter)),
1495            (&post_migration, vec![header(), footer(), footer()], Err(E::MultipleBlockFooters)),
1496            // Alpentick-shaped but non-final after the footer: plain entry batch
1497            (&post_migration, vec![header(), footer(), tick(false), tick(true)], Err(E::EntryBatchAfterBlockFooter)),
1498            // Final but not alpentick-shaped after the footer: plain entry batch
1499            (&post_migration, vec![header(), footer(), final_entries()], Err(E::EntryBatchAfterBlockFooter)),
1500
1501            // Nothing after the alpentick
1502            (&post_migration, vec![header(), footer(), tick(true), header()], Err(E::MultipleBlockHeaders)),
1503            (&post_migration, vec![header(), footer(), tick(true), genesis_cert()], Err(E::GenesisCertificateOutOfOrder)),
1504            (&post_migration, vec![header(), footer(), tick(true), entries()], Err(E::EntryBatchAfterBlockFooter)),
1505            (&post_migration, vec![header(), footer(), tick(true), tick(false)], Err(E::EntryBatchAfterBlockFooter)),
1506            // A second alpentick after the real one
1507            (&post_migration, vec![header(), footer(), tick(true), tick(true)], Err(E::InvalidAlpentickPosition)),
1508            (&post_migration, vec![header(), footer(), tick(true), footer()], Err(E::MultipleBlockFooters)),
1509            (&post_migration, vec![header(), footer(), tick(true), update_parent(false)], Err(E::SpuriousUpdateParent)),
1510
1511            // Missing tail on a full slot
1512            (&post_migration, vec![header()], Err(E::MissingBlockFooter)),
1513            (&post_migration, vec![header(), entries()], Err(E::MissingBlockFooter)),
1514            (&post_migration, vec![update_parent(true), entries()], Err(E::MissingBlockFooter)),
1515            // Footer but no alpentick
1516            (&post_migration, vec![header(), footer()], Err(E::InvalidAlpentickPosition)),
1517        ];
1518
1519        for (case_index, (migration_status, steps, expected)) in cases.into_iter().enumerate() {
1520            let mut processor = BlockComponentProcessor::default();
1521            let result = steps
1522                .into_iter()
1523                .try_for_each(|step| step(&mut processor, migration_status))
1524                .and_then(|()| processor.on_final(migration_status, slot, bank.parent_slot()));
1525
1526            match (result, expected) {
1527                (Ok(()), Ok(())) => (),
1528                (Err(err), Err(expected_err)) => {
1529                    assert_eq!(
1530                        std::mem::discriminant(&err),
1531                        std::mem::discriminant(&expected_err),
1532                        "case {case_index}: got {err:?}, expected {expected_err:?}"
1533                    );
1534                }
1535                (result, expected) => {
1536                    panic!("case {case_index}: got {result:?}, expected {expected:?}");
1537                }
1538            }
1539        }
1540    }
1541}