1use {
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#[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: BlockId::from(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: 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 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 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 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 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 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 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 {
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 {
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 ¬arize_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 ¬arize_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, ¬arize_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 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 {
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 {
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 ¬arize_signing_ranks,
697 &validator_keypairs,
698 );
699
700 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 {
735 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 ¬arize_signing_ranks,
742 &validator_keypairs,
743 );
744
745 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}