1use {
8 crate::bank::Bank,
9 agave_votor_messages::{
10 certificate::{
11 CertSignature, Certificate, CertificateType, FastFinalizeCert, FinalizeCert, NotarCert,
12 },
13 consensus_message::Block,
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#[derive(Debug, Error, PartialEq, Eq)]
28pub enum BlockFinalizationCertError {
29 #[error("BLS signature conversion for {0:?} failed: {1}")]
31 BlsError(CertificateType, BlsError),
32
33 #[error("Certificate verification for {0:?} failed")]
35 CertificateVerificationFailed(CertificateType),
36
37 #[error("Missing epoch stakes for {0:?}")]
39 MissingEpochStakes(CertificateType),
40
41 #[error("Finalization certificate was base 3 for {0:?}")]
43 Base3Finalization(CertificateType),
44
45 #[error("Unable to decode certificate {0:?}: {1:?}")]
47 DecodeError(CertificateType, DecodeError),
48}
49
50#[derive(Clone, Debug)]
54#[allow(clippy::large_enum_variant)]
55enum ValidatedBlockFinalizationCertKind {
56 Finalize {
58 finalize_cert: FinalizeCert,
60 notarize_cert: NotarCert,
62 },
63 FastFinalize(FastFinalizeCert),
65}
66
67#[derive(Clone, Debug)]
72pub struct ValidatedBlockFinalizationCert {
73 kind: ValidatedBlockFinalizationCertKind,
74 signers: HashSet<Pubkey>,
75}
76
77impl ValidatedBlockFinalizationCert {
78 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: block_final_cert.block_id,
96 };
97
98 if let Some(notar_aggregate) = block_final_cert.notar_aggregate {
99 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 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, ¬ar_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 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 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 ¬arize_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 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 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 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 pub fn is_fast(&self) -> bool {
256 matches!(
257 self.kind,
258 ValidatedBlockFinalizationCertKind::FastFinalize(_)
259 )
260 }
261
262 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 pub fn vote_rewards_input(&self) -> (&HashSet<Pubkey>, Slot) {
297 (&self.signers, self.slot().slot())
298 }
299
300 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 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,
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,
360 final_aggregate: VotesAggregate::from_cert_signature(cert.signature.clone()),
361 notar_aggregate: None,
362 }
363 }
364 }
365 }
366
367 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::{
417 ValidatorVoteKeypairs, create_genesis_config_with_alpenglow_vote_accounts,
418 },
419 agave_votor_messages::{vote::Vote, wire::get_vote_payload_to_sign},
420 bitvec::prelude::*,
421 rand::Rng,
422 solana_bls_signatures::SignatureProjective,
423 solana_entry::block_component::VotesAggregate,
424 solana_hash::Hash,
425 solana_signer_store::encode_base2,
426 std::sync::Arc,
427 };
428
429 fn create_bank_with_bls_validators(
432 num_validators: usize,
433 stakes: Vec<u64>,
434 ) -> (Arc<Bank>, Vec<ValidatorVoteKeypairs>) {
435 assert_eq!(num_validators, stakes.len());
436 let validator_keypairs: Vec<ValidatorVoteKeypairs> = (0..num_validators)
437 .map(|_| ValidatorVoteKeypairs::new_rand())
438 .collect();
439
440 let genesis_config_info = create_genesis_config_with_alpenglow_vote_accounts(
441 10_000_000,
442 &validator_keypairs,
443 stakes,
444 );
445
446 let bank = Arc::new(Bank::new_for_tests(&genesis_config_info.genesis_config));
447 (bank, validator_keypairs)
448 }
449
450 fn build_cert_signature(
453 vote: Vote,
454 shred_version: u16,
455 signing_ranks: &[usize],
456 validator_keypairs: &[ValidatorVoteKeypairs],
457 ) -> CertSignature {
458 let serialized_vote = get_vote_payload_to_sign(vote, shred_version);
459
460 let mut signature = SignatureProjective::identity();
462 for &rank in signing_ranks {
463 let sig = validator_keypairs[rank].bls_keypair.sign(&serialized_vote);
464 signature.aggregate_with(std::iter::once(&sig)).unwrap();
465 }
466
467 let max_rank = signing_ranks.iter().copied().max().unwrap_or(0);
469 let mut bitvec = BitVec::<u8, Lsb0>::repeat(false, max_rank.saturating_add(1));
470 for &rank in signing_ranks {
471 bitvec.set(rank, true);
472 }
473 let bitmap = encode_base2(&bitvec).expect("Failed to encode bitmap");
474
475 CertSignature {
476 signature: signature.into(),
477 bitmap,
478 }
479 }
480
481 fn vote_pubkeys_for_ranks(bank: &Bank, ranks: &[usize]) -> HashSet<Pubkey> {
482 let rank_map = bank
483 .epoch_stakes_from_slot(bank.slot())
484 .unwrap()
485 .bls_pubkey_to_rank_map();
486 ranks
487 .iter()
488 .map(|rank| {
489 rank_map
490 .get_pubkey_stake_entry(*rank)
491 .unwrap()
492 .vote_account_pubkey
493 })
494 .collect()
495 }
496
497 #[test]
498 fn test_verify_final_cert_valid() {
499 let num_validators = 10;
502 let stakes: Vec<u64> = (0..num_validators)
503 .map(|i| (1000u64).saturating_sub((i as u64).saturating_mul(100)))
504 .collect();
505 let (bank, validator_keypairs) = create_bank_with_bls_validators(num_validators, stakes);
506 let shred_version = rand::rng().random();
507
508 let block = Block {
509 slot: bank.slot(),
510 block_id: Hash::new_unique(),
511 };
512
513 {
516 let cert_type = CertificateType::FinalizeFast(block);
517 let vote = Vote::new_notarization_vote(block);
518 let signing_ranks: Vec<usize> = (0..6).collect();
519 let fast_finalize_cert_sig =
520 build_cert_signature(vote, shred_version, &signing_ranks, &validator_keypairs);
521 let fast_finalize_cert = Certificate {
522 cert_type,
523 signature: fast_finalize_cert_sig.signature,
524 bitmap: fast_finalize_cert_sig.bitmap.clone(),
525 };
526
527 let block_final_cert = BlockFinalizationCert {
528 slot: block.slot,
529 block_id: block.block_id,
530 final_aggregate: VotesAggregate::from_cert_signature(fast_finalize_cert_sig),
531 notar_aggregate: None,
532 };
533
534 let validated = ValidatedBlockFinalizationCert::try_from_footer(
535 block_final_cert,
536 &bank,
537 shred_version,
538 )
539 .expect("Valid fast finalize certificate should pass verification");
540 assert_eq!(validated.clone_certificates(), vec![fast_finalize_cert]);
541 }
542
543 {
546 let notarize_cert_type = CertificateType::Notarize(block);
547 let notarize_vote = Vote::new_notarization_vote(block);
548 let notarize_signing_ranks: Vec<usize> = (0..4).collect();
549 let notarize_cert_sig = build_cert_signature(
550 notarize_vote,
551 shred_version,
552 ¬arize_signing_ranks,
553 &validator_keypairs,
554 );
555 let notarize_cert = Certificate {
556 cert_type: notarize_cert_type,
557 signature: notarize_cert_sig.signature,
558 bitmap: notarize_cert_sig.bitmap.clone(),
559 };
560
561 let finalize_cert_type = CertificateType::Finalize(block.slot);
562 let finalize_vote = Vote::new_finalization_vote(block.slot);
563 let finalize_signing_ranks: Vec<usize> = (0..4).collect();
564 let finalize_cert_sig = build_cert_signature(
565 finalize_vote,
566 shred_version,
567 &finalize_signing_ranks,
568 &validator_keypairs,
569 );
570 let finalize_cert = Certificate {
571 cert_type: finalize_cert_type,
572 signature: finalize_cert_sig.signature,
573 bitmap: finalize_cert_sig.bitmap.clone(),
574 };
575
576 let block_final_cert = BlockFinalizationCert {
577 slot: block.slot,
578 block_id: block.block_id,
579 final_aggregate: VotesAggregate::from_cert_signature(finalize_cert_sig),
580 notar_aggregate: Some(VotesAggregate::from_cert_signature(notarize_cert_sig)),
581 };
582
583 let validated = ValidatedBlockFinalizationCert::try_from_footer(
584 block_final_cert,
585 &bank,
586 shred_version,
587 )
588 .expect("Valid slow finalize certificate should pass verification");
589 assert_eq!(
590 validated.clone_certificates(),
591 vec![finalize_cert, notarize_cert]
592 );
593 }
594 }
595
596 #[test]
597 fn test_slow_finalization_signers_include_notarize_and_finalize_signers() {
598 let stakes = vec![1000, 900, 800, 700, 100];
599 let (bank, validator_keypairs) = create_bank_with_bls_validators(stakes.len(), stakes);
600 let shred_version = rand::rng().random();
601
602 let block = Block {
603 slot: bank.slot(),
604 block_id: Hash::new_unique(),
605 };
606
607 let notarize_vote = Vote::new_notarization_vote(block);
608 let notarize_signing_ranks = vec![0, 1, 3];
609 let notarize_cert = NotarCert {
610 block,
611 signature: build_cert_signature(
612 notarize_vote,
613 shred_version,
614 ¬arize_signing_ranks,
615 &validator_keypairs,
616 ),
617 };
618
619 let finalize_vote = Vote::new_finalization_vote(block.slot);
620 let finalize_signing_ranks = vec![0, 1, 2];
621 let finalize_cert = FinalizeCert {
622 slot: block.slot,
623 signature: build_cert_signature(
624 finalize_vote,
625 shred_version,
626 &finalize_signing_ranks,
627 &validator_keypairs,
628 ),
629 };
630
631 let mut expected_signers = vote_pubkeys_for_ranks(&bank, ¬arize_signing_ranks);
632 expected_signers.extend(vote_pubkeys_for_ranks(&bank, &finalize_signing_ranks));
633
634 let block_final_cert = BlockFinalizationCert {
635 slot: block.slot,
636 block_id: block.block_id,
637 final_aggregate: VotesAggregate::from_cert_signature(finalize_cert.signature.clone()),
638 notar_aggregate: Some(VotesAggregate::from_cert_signature(
639 notarize_cert.signature.clone(),
640 )),
641 };
642 let validated =
643 ValidatedBlockFinalizationCert::try_from_footer(block_final_cert, &bank, shred_version)
644 .expect("Valid slow finalize certificate should pass verification");
645 let (signers, _, _) = validated.into_parts();
646 assert_eq!(signers, expected_signers);
647
648 let validated = ValidatedBlockFinalizationCert::from_validated_slow(
649 finalize_cert,
650 notarize_cert,
651 &bank,
652 );
653 let (signers, _, _) = validated.into_parts();
654 assert_eq!(signers, expected_signers);
655 }
656
657 #[test]
658 fn test_verify_final_cert_insufficient_stake() {
659 let num_validators = 10;
662 let stakes: Vec<u64> = (0..num_validators)
663 .map(|i| (1000u64).saturating_sub((i as u64).saturating_mul(100)))
664 .collect();
665 let (bank, validator_keypairs) = create_bank_with_bls_validators(num_validators, stakes);
666 let shred_version = rand::rng().random();
667
668 let block = Block {
669 slot: bank.slot(),
670 block_id: Hash::new_unique(),
671 };
672
673 {
676 let vote = Vote::new_notarization_vote(block);
677 let signing_ranks: Vec<usize> = (0..5).collect();
678 let fast_finalize_cert_sig =
679 build_cert_signature(vote, shred_version, &signing_ranks, &validator_keypairs);
680
681 let block_final_cert = BlockFinalizationCert {
682 slot: block.slot,
683 block_id: block.block_id,
684 final_aggregate: VotesAggregate::from_cert_signature(fast_finalize_cert_sig),
685 notar_aggregate: None,
686 };
687
688 let err = ValidatedBlockFinalizationCert::try_from_footer(
689 block_final_cert,
690 &bank,
691 shred_version,
692 )
693 .unwrap_err();
694 assert!(
695 matches!(
696 err,
697 BlockFinalizationCertError::CertificateVerificationFailed { .. }
698 ),
699 "Fast finalize with insufficient stake should fail verification. {err:?}"
700 );
701 }
702
703 {
706 let notarize_vote = Vote::new_notarization_vote(block);
707 let notarize_signing_ranks: Vec<usize> = (0..3).collect();
708 let notarize_cert_sig = build_cert_signature(
709 notarize_vote,
710 shred_version,
711 ¬arize_signing_ranks,
712 &validator_keypairs,
713 );
714
715 let finalize_vote = Vote::new_finalization_vote(block.slot);
717 let finalize_signing_ranks: Vec<usize> = (0..4).collect();
718 let finalize_cert_sig = build_cert_signature(
719 finalize_vote,
720 shred_version,
721 &finalize_signing_ranks,
722 &validator_keypairs,
723 );
724
725 let block_final_cert = BlockFinalizationCert {
726 slot: block.slot,
727 block_id: block.block_id,
728 final_aggregate: VotesAggregate::from_cert_signature(finalize_cert_sig),
729 notar_aggregate: Some(VotesAggregate::from_cert_signature(notarize_cert_sig)),
730 };
731
732 let err = ValidatedBlockFinalizationCert::try_from_footer(
733 block_final_cert,
734 &bank,
735 shred_version,
736 )
737 .unwrap_err();
738 assert!(
739 matches!(
740 err,
741 BlockFinalizationCertError::CertificateVerificationFailed { .. }
742 ),
743 "Slow finalize with insufficient notarize stake should fail verification"
744 );
745 }
746
747 {
750 let notarize_vote = Vote::new_notarization_vote(block);
752 let notarize_signing_ranks: Vec<usize> = (0..4).collect();
753 let notarize_cert_sig = build_cert_signature(
754 notarize_vote,
755 shred_version,
756 ¬arize_signing_ranks,
757 &validator_keypairs,
758 );
759
760 let finalize_vote = Vote::new_finalization_vote(block.slot);
763 let finalize_signing_ranks: Vec<usize> = (0..3).collect();
764 let finalize_cert_sig = build_cert_signature(
765 finalize_vote,
766 shred_version,
767 &finalize_signing_ranks,
768 &validator_keypairs,
769 );
770
771 let block_final_cert = BlockFinalizationCert {
772 slot: block.slot,
773 block_id: block.block_id,
774 final_aggregate: VotesAggregate::from_cert_signature(finalize_cert_sig),
775 notar_aggregate: Some(VotesAggregate::from_cert_signature(notarize_cert_sig)),
776 };
777
778 let err = ValidatedBlockFinalizationCert::try_from_footer(
779 block_final_cert,
780 &bank,
781 shred_version,
782 )
783 .unwrap_err();
784 assert!(
785 matches!(
786 err,
787 BlockFinalizationCertError::CertificateVerificationFailed { .. }
788 ),
789 "Slow finalize with insufficient finalize stake should fail verification"
790 );
791 }
792 }
793}