Skip to main content

solana_runtime/
validated_block_finalization.rs

1//! Validated block finalization certificates.
2//!
3//! This module provides [`ValidatedBlockFinalizationCert`], a type that represents
4//! a validated proof that a block has been finalized. The type can only be constructed
5//! through validation, ensuring type-safe guarantees about certificate validity.
6
7use {
8    crate::bank::Bank,
9    agave_votor_messages::{
10        certificate::{
11            CertSignature, Certificate, CertificateType, FastFinalizeCert, FinalizeCert, NotarCert,
12        },
13        consensus_message::{Block, BlockId},
14        finalized_slot::FinalizedSlot,
15        unverified_vote_message::UnverifiedCertificate,
16    },
17    solana_bls_signatures::BlsError,
18    solana_clock::Slot,
19    solana_entry::block_component::{BlockFinalizationCert, VotesAggregate},
20    solana_pubkey::Pubkey,
21    solana_signer_store::{DecodeError, Decoded, decode},
22    std::collections::HashSet,
23    thiserror::Error,
24};
25
26/// Error type for block finalization certificate validation.
27#[derive(Debug, Error, PartialEq, Eq)]
28pub enum BlockFinalizationCertError {
29    /// BLS signature conversion failed
30    #[error("BLS signature conversion for {0:?} failed: {1}")]
31    BlsError(CertificateType, BlsError),
32
33    /// Certificate signature verification failed
34    #[error("Certificate verification for {0:?} failed")]
35    CertificateVerificationFailed(CertificateType),
36
37    /// Missing epoch stakes for slot
38    #[error("Missing epoch stakes for {0:?}")]
39    MissingEpochStakes(CertificateType),
40
41    /// Base 3 finalization certificate
42    #[error("Finalization certificate was base 3 for {0:?}")]
43    Base3Finalization(CertificateType),
44
45    /// Decoding error
46    #[error("Unable to decode certificate {0:?}: {1:?}")]
47    DecodeError(CertificateType, DecodeError),
48}
49
50/// There are two ways a block can be finalized:
51/// - `Finalize`: Slow finalization with both a Finalize certificate and a Notarize certificate
52/// - `FastFinalize`: Fast finalization with a single FinalizeFast certificate
53#[derive(Clone, Debug)]
54#[allow(clippy::large_enum_variant)]
55enum ValidatedBlockFinalizationCertKind {
56    /// Slow finalization: requires both a Finalize cert and a Notarize cert for the same slot
57    Finalize {
58        /// The finalize certificate
59        finalize_cert: FinalizeCert,
60        /// The notarize certificate
61        notarize_cert: NotarCert,
62    },
63    /// Fast finalization: a single FastFinalize certificate
64    FastFinalize(FastFinalizeCert),
65}
66
67/// A validated proof that a block has been finalized.
68///
69/// This type can only be constructed through validation methods, providing
70/// compile-time guarantees that the contained certificates are valid.
71#[derive(Clone, Debug)]
72pub struct ValidatedBlockFinalizationCert {
73    kind: ValidatedBlockFinalizationCertKind,
74    signers: HashSet<Pubkey>,
75}
76
77impl ValidatedBlockFinalizationCert {
78    /// Creates a validated block finalization certificate from a footer's final certificate.
79    ///
80    /// This method performs both BLS signature conversion and stake threshold verification
81    /// against the provided bank's epoch stakes.
82    ///
83    /// # Errors
84    /// Returns an error if:
85    /// - BLS signature conversion fails
86    /// - Certificate signature verification fails
87    /// - The certificates don't meet the required stake thresholds
88    pub fn try_from_footer(
89        block_final_cert: BlockFinalizationCert,
90        bank: &Bank,
91        shred_version: u16,
92    ) -> Result<Self, BlockFinalizationCertError> {
93        let block = Block {
94            slot: block_final_cert.slot,
95            block_id: BlockId::from(block_final_cert.block_id),
96        };
97
98        if let Some(notar_aggregate) = block_final_cert.notar_aggregate {
99            // Slow finalization
100            let notarize_cert_type = CertificateType::Notarize(block);
101            let finalize_cert_type = CertificateType::Finalize(block.slot);
102
103            let unverified_notar_cert = UnverifiedCertificate {
104                cert_type: notarize_cert_type,
105                signature: notar_aggregate
106                    .uncompress_signature()
107                    .map_err(|e| BlockFinalizationCertError::BlsError(notarize_cert_type, e))?,
108                bitmap: notar_aggregate.into_bitmap(),
109                shred_version,
110            };
111            let unverified_finalize_cert = UnverifiedCertificate {
112                cert_type: finalize_cert_type,
113                signature: block_final_cert
114                    .final_aggregate
115                    .uncompress_signature()
116                    .map_err(|e| BlockFinalizationCertError::BlsError(finalize_cert_type, e))?,
117                bitmap: block_final_cert.final_aggregate.into_bitmap(),
118                shred_version,
119            };
120
121            // Verify both certificates
122            let notar_signature = Self::verify_certificate(bank, unverified_notar_cert)?;
123            let finalize_signature = Self::verify_certificate(bank, unverified_finalize_cert)?;
124
125            let finalize_signers =
126                Self::extract_signers(bank, finalize_cert_type, &finalize_signature.bitmap)?;
127            let mut signers =
128                Self::extract_signers(bank, notarize_cert_type, &notar_signature.bitmap)?;
129            signers.extend(finalize_signers);
130
131            let notarize_cert = NotarCert {
132                block,
133                signature: notar_signature,
134            };
135            let finalize_cert = FinalizeCert {
136                slot: block.slot,
137                signature: finalize_signature,
138            };
139
140            Ok(Self {
141                kind: ValidatedBlockFinalizationCertKind::Finalize {
142                    finalize_cert,
143                    notarize_cert,
144                },
145                signers,
146            })
147        } else {
148            // Fast finalization
149            let fast_finalize_cert_type = CertificateType::FinalizeFast(block);
150
151            let fast_finalize_cert = UnverifiedCertificate {
152                cert_type: fast_finalize_cert_type,
153                signature: block_final_cert
154                    .final_aggregate
155                    .uncompress_signature()
156                    .map_err(|e| {
157                        BlockFinalizationCertError::BlsError(fast_finalize_cert_type, e)
158                    })?,
159                bitmap: block_final_cert.final_aggregate.into_bitmap(),
160                shred_version,
161            };
162
163            let signature = Self::verify_certificate(bank, fast_finalize_cert)?;
164            let signers = Self::extract_signers(bank, fast_finalize_cert_type, &signature.bitmap)?;
165            let fast_finalize_cert = FastFinalizeCert { block, signature };
166            Ok(Self {
167                kind: ValidatedBlockFinalizationCertKind::FastFinalize(fast_finalize_cert),
168                signers,
169            })
170        }
171    }
172
173    /// Creates a validated block finalization certificate from already-validated certificates.
174    ///
175    /// This is intended for use by the consensus pool, where certificates have already been
176    /// validated by the bls sigverifier
177    ///
178    /// # Safety
179    /// The caller must ensure that the provided certificates have been properly validated.
180    /// Using unvalidated certificates will compromise the type's safety guarantees.
181    pub fn from_validated_slow(
182        finalize_cert: FinalizeCert,
183        notarize_cert: NotarCert,
184        bank: &Bank,
185    ) -> Self {
186        debug_assert_eq!(notarize_cert.block.slot, finalize_cert.slot);
187
188        let finalize_signers = Self::extract_signers(
189            bank,
190            CertificateType::Finalize(finalize_cert.slot),
191            &finalize_cert.signature.bitmap,
192        )
193        .expect("Certificate should have been validated");
194        let mut signers = Self::extract_signers(
195            bank,
196            CertificateType::Notarize(notarize_cert.block),
197            &notarize_cert.signature.bitmap,
198        )
199        .expect("Certificate should have been validated");
200        signers.extend(finalize_signers);
201        Self {
202            kind: ValidatedBlockFinalizationCertKind::Finalize {
203                finalize_cert,
204                notarize_cert,
205            },
206            signers,
207        }
208    }
209
210    /// Creates a validated fast finalization certificate from an already-validated certificate.
211    ///
212    /// This is intended for use by the consensus pool, where certificates have already been
213    /// validated by the bls sigverifier
214    ///
215    /// # Safety
216    /// The caller must ensure that the provided certificate has been properly validated.
217    /// Using an unvalidated certificate will compromise the type's safety guarantees.
218    pub fn from_validated_fast(cert: FastFinalizeCert, bank: &Bank) -> Self {
219        let cert_type = CertificateType::FinalizeFast(cert.block);
220        let signers = Self::extract_signers(bank, cert_type, &cert.signature.bitmap)
221            .expect("Certificate should have been validated");
222        Self {
223            kind: ValidatedBlockFinalizationCertKind::FastFinalize(cert),
224            signers,
225        }
226    }
227
228    /// Returns the slot that is finalized.
229    pub fn slot(&self) -> FinalizedSlot {
230        match &self.kind {
231            ValidatedBlockFinalizationCertKind::Finalize {
232                finalize_cert,
233                notarize_cert,
234            } => {
235                debug_assert_eq!(finalize_cert.slot, notarize_cert.block.slot);
236                FinalizedSlot::Slow(finalize_cert.slot)
237            }
238            ValidatedBlockFinalizationCertKind::FastFinalize(certificate) => {
239                FinalizedSlot::Fast(certificate.block.slot)
240            }
241        }
242    }
243
244    /// Returns the block that is finalized (slot, block_id).
245    pub fn block(&self) -> Block {
246        match &self.kind {
247            ValidatedBlockFinalizationCertKind::Finalize { notarize_cert, .. } => {
248                notarize_cert.block
249            }
250            ValidatedBlockFinalizationCertKind::FastFinalize(cert) => cert.block,
251        }
252    }
253
254    /// Returns true if this is a fast finalization.
255    pub fn is_fast(&self) -> bool {
256        matches!(
257            self.kind,
258            ValidatedBlockFinalizationCertKind::FastFinalize(_)
259        )
260    }
261
262    /// Returns cloned copies of the certificates that prove this finalization.
263    ///
264    /// For slow finalization this returns the finalize certificate followed by the notarize
265    /// certificate. For fast finalization this returns the single fast-finalize certificate.
266    pub fn clone_certificates(&self) -> Vec<Certificate> {
267        match &self.kind {
268            ValidatedBlockFinalizationCertKind::Finalize {
269                finalize_cert,
270                notarize_cert,
271            } => {
272                let notar = Certificate {
273                    cert_type: CertificateType::Notarize(notarize_cert.block),
274                    signature: notarize_cert.signature.signature,
275                    bitmap: notarize_cert.signature.bitmap.clone(),
276                };
277                let finalize = Certificate {
278                    cert_type: CertificateType::Finalize(finalize_cert.slot),
279                    signature: finalize_cert.signature.signature,
280                    bitmap: finalize_cert.signature.bitmap.clone(),
281                };
282                vec![finalize, notar]
283            }
284            ValidatedBlockFinalizationCertKind::FastFinalize(cert) => {
285                let cert = Certificate {
286                    cert_type: CertificateType::FinalizeFast(cert.block),
287                    signature: cert.signature.signature,
288                    bitmap: cert.signature.bitmap.clone(),
289                };
290                vec![cert]
291            }
292        }
293    }
294
295    /// Returns the data needed to calculating and paying vote rewards.
296    pub fn vote_rewards_input(&self) -> (&HashSet<Pubkey>, Slot) {
297        (&self.signers, self.slot().slot())
298    }
299
300    /// Consumes self and returns the contained certificates and the signers.
301    ///
302    /// For slow finalization, returns (signers, finalize_cert, Some(notarize_cert)).
303    /// For fast finalization, returns (signers, fast_finalize_cert, None).
304    pub(crate) fn into_parts(self) -> (HashSet<Pubkey>, Certificate, Option<Certificate>) {
305        let signers = self.signers;
306        let (final_cert, notar_cert) = match self.kind {
307            ValidatedBlockFinalizationCertKind::Finalize {
308                finalize_cert,
309                notarize_cert,
310            } => {
311                let notar = Certificate {
312                    cert_type: CertificateType::Notarize(notarize_cert.block),
313                    signature: notarize_cert.signature.signature,
314                    bitmap: notarize_cert.signature.bitmap,
315                };
316                let finalize = Certificate {
317                    cert_type: CertificateType::Finalize(finalize_cert.slot),
318                    signature: finalize_cert.signature.signature,
319                    bitmap: finalize_cert.signature.bitmap,
320                };
321                (finalize, Some(notar))
322            }
323            ValidatedBlockFinalizationCertKind::FastFinalize(cert) => {
324                let cert = Certificate {
325                    cert_type: CertificateType::FinalizeFast(cert.block),
326                    signature: cert.signature.signature,
327                    bitmap: cert.signature.bitmap,
328                };
329                (cert, None)
330            }
331        };
332        (signers, final_cert, notar_cert)
333    }
334
335    /// Converts this validated certificate into a [`BlockFinalizationCert`] for inclusion in a block footer.
336    pub fn to_block_final_cert(&self) -> BlockFinalizationCert {
337        match &self.kind {
338            ValidatedBlockFinalizationCertKind::Finalize {
339                finalize_cert,
340                notarize_cert,
341            } => {
342                let slot = finalize_cert.slot;
343                let block_id = notarize_cert.block.block_id;
344                BlockFinalizationCert {
345                    slot,
346                    block_id: block_id.to_hash(),
347                    final_aggregate: VotesAggregate::from_cert_signature(
348                        finalize_cert.signature.clone(),
349                    ),
350                    notar_aggregate: Some(VotesAggregate::from_cert_signature(
351                        notarize_cert.signature.clone(),
352                    )),
353                }
354            }
355            ValidatedBlockFinalizationCertKind::FastFinalize(cert) => {
356                let block = cert.block;
357                BlockFinalizationCert {
358                    slot: block.slot,
359                    block_id: block.block_id.to_hash(),
360                    final_aggregate: VotesAggregate::from_cert_signature(cert.signature.clone()),
361                    notar_aggregate: None,
362                }
363            }
364        }
365    }
366
367    /// Verifies the certificate and returns `CertSignature` on success.
368    fn verify_certificate(
369        bank: &Bank,
370        cert: UnverifiedCertificate,
371    ) -> Result<CertSignature, BlockFinalizationCertError> {
372        let cert_type = cert.cert_type;
373        let cert = bank
374            .verify_certificate(cert)
375            .map_err(|_| BlockFinalizationCertError::CertificateVerificationFailed(cert_type))?;
376        debug_assert_eq!(cert.cert_type, cert_type);
377        Ok(CertSignature {
378            signature: cert.signature,
379            bitmap: cert.bitmap,
380        })
381    }
382
383    fn extract_signers(
384        bank: &Bank,
385        cert_type: CertificateType,
386        bitmap: &[u8],
387    ) -> Result<HashSet<Pubkey>, BlockFinalizationCertError> {
388        let Some(epoch_stakes) = bank.epoch_stakes_from_slot(cert_type.slot()) else {
389            return Err(BlockFinalizationCertError::MissingEpochStakes(cert_type));
390        };
391        let rank_map = epoch_stakes.bls_pubkey_to_rank_map();
392        let decoded = decode(bitmap, rank_map.len());
393        let ranks = match decoded {
394            Ok(Decoded::Base2(ranks)) => ranks,
395            Ok(Decoded::Base3(_, _)) => {
396                return Err(BlockFinalizationCertError::Base3Finalization(cert_type));
397            }
398            Err(err) => return Err(BlockFinalizationCertError::DecodeError(cert_type, err)),
399        };
400
401        Ok(ranks
402            .iter_ones()
403            .filter_map(|rank| {
404                rank_map
405                    .get_pubkey_stake_entry(rank)
406                    .map(|entry| entry.vote_account_pubkey)
407            })
408            .collect())
409    }
410}
411
412#[cfg(test)]
413mod tests {
414    use {
415        super::*,
416        crate::genesis_utils::{ValidatorVoteKeypairs, create_genesis_config_with_vote_accounts},
417        agave_votor_messages::{vote::Vote, wire::get_vote_payload_to_sign},
418        bitvec::prelude::*,
419        rand::Rng,
420        solana_bls_signatures::SignatureProjective,
421        solana_entry::block_component::VotesAggregate,
422        solana_signer_store::encode_base2,
423        std::sync::Arc,
424    };
425
426    /// Creates a bank with BLS-enabled validators for testing certificate verification.
427    /// Returns (bank, validator_keypairs) where bank has validators with BLS keys.
428    fn create_bank_with_bls_validators(
429        num_validators: usize,
430        stakes: Vec<u64>,
431    ) -> (Arc<Bank>, Vec<ValidatorVoteKeypairs>) {
432        assert_eq!(num_validators, stakes.len());
433        let validator_keypairs: Vec<ValidatorVoteKeypairs> = (0..num_validators)
434            .map(|_| ValidatorVoteKeypairs::new_rand())
435            .collect();
436
437        let genesis_config_info =
438            create_genesis_config_with_vote_accounts(10_000_000, &validator_keypairs, stakes);
439
440        let bank = Arc::new(Bank::new_for_tests(&genesis_config_info.genesis_config));
441        (bank, validator_keypairs)
442    }
443
444    /// Build a cert signature by manually aggregating BLS signatures and encoding bitmap.
445    /// `signing_ranks` specifies which validator ranks are signing.
446    fn build_cert_signature(
447        vote: Vote,
448        shred_version: u16,
449        signing_ranks: &[usize],
450        validator_keypairs: &[ValidatorVoteKeypairs],
451    ) -> CertSignature {
452        let serialized_vote = get_vote_payload_to_sign(vote, shred_version);
453
454        // Aggregate signatures
455        let mut signature = SignatureProjective::identity();
456        for &rank in signing_ranks {
457            let sig = validator_keypairs[rank].bls_keypair.sign(&serialized_vote);
458            signature.aggregate_with(std::iter::once(&sig)).unwrap();
459        }
460
461        // Build bitmap
462        let max_rank = signing_ranks.iter().copied().max().unwrap_or(0);
463        let mut bitvec = BitVec::<u8, Lsb0>::repeat(false, max_rank.saturating_add(1));
464        for &rank in signing_ranks {
465            bitvec.set(rank, true);
466        }
467        let bitmap = encode_base2(&bitvec).expect("Failed to encode bitmap");
468
469        CertSignature {
470            signature: signature.into(),
471            bitmap,
472        }
473    }
474
475    fn vote_pubkeys_for_ranks(bank: &Bank, ranks: &[usize]) -> HashSet<Pubkey> {
476        let rank_map = bank
477            .epoch_stakes_from_slot(bank.slot())
478            .unwrap()
479            .bls_pubkey_to_rank_map();
480        ranks
481            .iter()
482            .map(|rank| {
483                rank_map
484                    .get_pubkey_stake_entry(*rank)
485                    .unwrap()
486                    .vote_account_pubkey
487            })
488            .collect()
489    }
490
491    #[test]
492    fn test_verify_final_cert_valid() {
493        // Create 10 validators with descending stakes (1000, 900, 800, ...)
494        // Total stake = 5500
495        let num_validators = 10;
496        let stakes: Vec<u64> = (0..num_validators)
497            .map(|i| (1000u64).saturating_sub((i as u64).saturating_mul(100)))
498            .collect();
499        let (bank, validator_keypairs) = create_bank_with_bls_validators(num_validators, stakes);
500        let shred_version = rand::rng().random();
501
502        let block = Block::new_unique(bank.slot());
503
504        // Test 1: Fast finalize (requires 80% stake = 4400)
505        // Top 6 validators = 1000+900+800+700+600+500 = 4500 (>= 80%)
506        {
507            let cert_type = CertificateType::FinalizeFast(block);
508            let vote = Vote::new_notarization_vote(block);
509            let signing_ranks: Vec<usize> = (0..6).collect();
510            let fast_finalize_cert_sig =
511                build_cert_signature(vote, shred_version, &signing_ranks, &validator_keypairs);
512            let fast_finalize_cert = Certificate {
513                cert_type,
514                signature: fast_finalize_cert_sig.signature,
515                bitmap: fast_finalize_cert_sig.bitmap.clone(),
516            };
517
518            let block_final_cert = BlockFinalizationCert {
519                slot: block.slot,
520                block_id: block.block_id.to_hash(),
521                final_aggregate: VotesAggregate::from_cert_signature(fast_finalize_cert_sig),
522                notar_aggregate: None,
523            };
524
525            let validated = ValidatedBlockFinalizationCert::try_from_footer(
526                block_final_cert,
527                &bank,
528                shred_version,
529            )
530            .expect("Valid fast finalize certificate should pass verification");
531            assert_eq!(validated.clone_certificates(), vec![fast_finalize_cert]);
532        }
533
534        // Test 2: Slow finalize (requires 60% stake = 3300 for both certs)
535        // Top 4 validators = 1000+900+800+700 = 3400 (>= 60%)
536        {
537            let notarize_cert_type = CertificateType::Notarize(block);
538            let notarize_vote = Vote::new_notarization_vote(block);
539            let notarize_signing_ranks: Vec<usize> = (0..4).collect();
540            let notarize_cert_sig = build_cert_signature(
541                notarize_vote,
542                shred_version,
543                &notarize_signing_ranks,
544                &validator_keypairs,
545            );
546            let notarize_cert = Certificate {
547                cert_type: notarize_cert_type,
548                signature: notarize_cert_sig.signature,
549                bitmap: notarize_cert_sig.bitmap.clone(),
550            };
551
552            let finalize_cert_type = CertificateType::Finalize(block.slot);
553            let finalize_vote = Vote::new_finalization_vote(block.slot);
554            let finalize_signing_ranks: Vec<usize> = (0..4).collect();
555            let finalize_cert_sig = build_cert_signature(
556                finalize_vote,
557                shred_version,
558                &finalize_signing_ranks,
559                &validator_keypairs,
560            );
561            let finalize_cert = Certificate {
562                cert_type: finalize_cert_type,
563                signature: finalize_cert_sig.signature,
564                bitmap: finalize_cert_sig.bitmap.clone(),
565            };
566
567            let block_final_cert = BlockFinalizationCert {
568                slot: block.slot,
569                block_id: block.block_id.to_hash(),
570                final_aggregate: VotesAggregate::from_cert_signature(finalize_cert_sig),
571                notar_aggregate: Some(VotesAggregate::from_cert_signature(notarize_cert_sig)),
572            };
573
574            let validated = ValidatedBlockFinalizationCert::try_from_footer(
575                block_final_cert,
576                &bank,
577                shred_version,
578            )
579            .expect("Valid slow finalize certificate should pass verification");
580            assert_eq!(
581                validated.clone_certificates(),
582                vec![finalize_cert, notarize_cert]
583            );
584        }
585    }
586
587    #[test]
588    fn test_slow_finalization_signers_include_notarize_and_finalize_signers() {
589        let stakes = vec![1000, 900, 800, 700, 100];
590        let (bank, validator_keypairs) = create_bank_with_bls_validators(stakes.len(), stakes);
591        let shred_version = rand::rng().random();
592
593        let block = Block::new_unique(bank.slot());
594
595        let notarize_vote = Vote::new_notarization_vote(block);
596        let notarize_signing_ranks = vec![0, 1, 3];
597        let notarize_cert = NotarCert {
598            block,
599            signature: build_cert_signature(
600                notarize_vote,
601                shred_version,
602                &notarize_signing_ranks,
603                &validator_keypairs,
604            ),
605        };
606
607        let finalize_vote = Vote::new_finalization_vote(block.slot);
608        let finalize_signing_ranks = vec![0, 1, 2];
609        let finalize_cert = FinalizeCert {
610            slot: block.slot,
611            signature: build_cert_signature(
612                finalize_vote,
613                shred_version,
614                &finalize_signing_ranks,
615                &validator_keypairs,
616            ),
617        };
618
619        let mut expected_signers = vote_pubkeys_for_ranks(&bank, &notarize_signing_ranks);
620        expected_signers.extend(vote_pubkeys_for_ranks(&bank, &finalize_signing_ranks));
621
622        let block_final_cert = BlockFinalizationCert {
623            slot: block.slot,
624            block_id: block.block_id.to_hash(),
625            final_aggregate: VotesAggregate::from_cert_signature(finalize_cert.signature.clone()),
626            notar_aggregate: Some(VotesAggregate::from_cert_signature(
627                notarize_cert.signature.clone(),
628            )),
629        };
630        let validated =
631            ValidatedBlockFinalizationCert::try_from_footer(block_final_cert, &bank, shred_version)
632                .expect("Valid slow finalize certificate should pass verification");
633        let (signers, _, _) = validated.into_parts();
634        assert_eq!(signers, expected_signers);
635
636        let validated = ValidatedBlockFinalizationCert::from_validated_slow(
637            finalize_cert,
638            notarize_cert,
639            &bank,
640        );
641        let (signers, _, _) = validated.into_parts();
642        assert_eq!(signers, expected_signers);
643    }
644
645    #[test]
646    fn test_verify_final_cert_insufficient_stake() {
647        // Create 10 validators with descending stakes (1000, 900, 800, ...)
648        // Total stake = 5500
649        let num_validators = 10;
650        let stakes: Vec<u64> = (0..num_validators)
651            .map(|i| (1000u64).saturating_sub((i as u64).saturating_mul(100)))
652            .collect();
653        let (bank, validator_keypairs) = create_bank_with_bls_validators(num_validators, stakes);
654        let shred_version = rand::rng().random();
655
656        let block = Block::new_unique(bank.slot());
657
658        // Fast finalize with insufficient stake (requires 80% = 4400)
659        // Top 5 validators = 1000+900+800+700+600 = 4000 (< 80%)
660        {
661            let vote = Vote::new_notarization_vote(block);
662            let signing_ranks: Vec<usize> = (0..5).collect();
663            let fast_finalize_cert_sig =
664                build_cert_signature(vote, shred_version, &signing_ranks, &validator_keypairs);
665
666            let block_final_cert = BlockFinalizationCert {
667                slot: block.slot,
668                block_id: block.block_id.to_hash(),
669                final_aggregate: VotesAggregate::from_cert_signature(fast_finalize_cert_sig),
670                notar_aggregate: None,
671            };
672
673            let err = ValidatedBlockFinalizationCert::try_from_footer(
674                block_final_cert,
675                &bank,
676                shred_version,
677            )
678            .unwrap_err();
679            assert!(
680                matches!(
681                    err,
682                    BlockFinalizationCertError::CertificateVerificationFailed { .. }
683                ),
684                "Fast finalize with insufficient stake should fail verification.  {err:?}"
685            );
686        }
687
688        // Slow finalize with insufficient notarize stake (requires 60% = 3300)
689        // Top 3 validators = 1000+900+800 = 2700 (< 60%)
690        {
691            let notarize_vote = Vote::new_notarization_vote(block);
692            let notarize_signing_ranks: Vec<usize> = (0..3).collect();
693            let notarize_cert_sig = build_cert_signature(
694                notarize_vote,
695                shred_version,
696                &notarize_signing_ranks,
697                &validator_keypairs,
698            );
699
700            // Finalize cert has enough stake
701            let finalize_vote = Vote::new_finalization_vote(block.slot);
702            let finalize_signing_ranks: Vec<usize> = (0..4).collect();
703            let finalize_cert_sig = build_cert_signature(
704                finalize_vote,
705                shred_version,
706                &finalize_signing_ranks,
707                &validator_keypairs,
708            );
709
710            let block_final_cert = BlockFinalizationCert {
711                slot: block.slot,
712                block_id: block.block_id.to_hash(),
713                final_aggregate: VotesAggregate::from_cert_signature(finalize_cert_sig),
714                notar_aggregate: Some(VotesAggregate::from_cert_signature(notarize_cert_sig)),
715            };
716
717            let err = ValidatedBlockFinalizationCert::try_from_footer(
718                block_final_cert,
719                &bank,
720                shred_version,
721            )
722            .unwrap_err();
723            assert!(
724                matches!(
725                    err,
726                    BlockFinalizationCertError::CertificateVerificationFailed { .. }
727                ),
728                "Slow finalize with insufficient notarize stake should fail verification"
729            );
730        }
731
732        // Slow finalize with insufficient finalize stake (requires 60% = 3300)
733        // Notarize has enough stake, but finalize doesn't
734        {
735            // Notarize cert has enough stake
736            let notarize_vote = Vote::new_notarization_vote(block);
737            let notarize_signing_ranks: Vec<usize> = (0..4).collect();
738            let notarize_cert_sig = build_cert_signature(
739                notarize_vote,
740                shred_version,
741                &notarize_signing_ranks,
742                &validator_keypairs,
743            );
744
745            // Finalize cert has insufficient stake
746            // Top 3 validators = 1000+900+800 = 2700 (< 60%)
747            let finalize_vote = Vote::new_finalization_vote(block.slot);
748            let finalize_signing_ranks: Vec<usize> = (0..3).collect();
749            let finalize_cert_sig = build_cert_signature(
750                finalize_vote,
751                shred_version,
752                &finalize_signing_ranks,
753                &validator_keypairs,
754            );
755
756            let block_final_cert = BlockFinalizationCert {
757                slot: block.slot,
758                block_id: block.block_id.to_hash(),
759                final_aggregate: VotesAggregate::from_cert_signature(finalize_cert_sig),
760                notar_aggregate: Some(VotesAggregate::from_cert_signature(notarize_cert_sig)),
761            };
762
763            let err = ValidatedBlockFinalizationCert::try_from_footer(
764                block_final_cert,
765                &bank,
766                shred_version,
767            )
768            .unwrap_err();
769            assert!(
770                matches!(
771                    err,
772                    BlockFinalizationCertError::CertificateVerificationFailed { .. }
773                ),
774                "Slow finalize with insufficient finalize stake should fail verification"
775            );
776        }
777    }
778}