1use {
8 crate::bank::Bank,
9 agave_votor_messages::{
10 certificate::{Certificate, CertificateType},
11 consensus_message::Block,
12 finalized_slot::FinalizedSlot,
13 unverified_vote_message::UnverifiedCertificate,
14 },
15 solana_bls_signatures::BlsError,
16 solana_clock::Slot,
17 solana_entry::block_component::{BlockFinalizationCert, VotesAggregate},
18 solana_pubkey::Pubkey,
19 solana_signer_store::{DecodeError, Decoded, decode},
20 std::collections::HashSet,
21 thiserror::Error,
22};
23
24#[derive(Debug, Error, PartialEq, Eq)]
26pub enum BlockFinalizationCertError {
27 #[error("BLS signature conversion for {0:?} failed: {1}")]
29 BlsError(CertificateType, BlsError),
30
31 #[error("Certificate verification for {0:?} failed")]
33 CertificateVerificationFailed(CertificateType),
34
35 #[error("Missing epoch stakes for {0:?}")]
37 MissingEpochStakes(CertificateType),
38
39 #[error("Finalization certificate was base 3 for {0:?}")]
41 Base3Finalization(CertificateType),
42
43 #[error("Unable to decode certificate {0:?}: {1:?}")]
45 DecodeError(CertificateType, DecodeError),
46}
47
48#[derive(Clone, Debug)]
52#[allow(clippy::large_enum_variant)]
53enum ValidatedBlockFinalizationCertKind {
54 Finalize {
56 finalize_cert: Certificate,
58 notarize_cert: Certificate,
60 },
61 FastFinalize(Certificate),
63}
64
65#[derive(Clone, Debug)]
70pub struct ValidatedBlockFinalizationCert {
71 kind: ValidatedBlockFinalizationCertKind,
72 signers: HashSet<Pubkey>,
73}
74
75impl ValidatedBlockFinalizationCert {
76 pub fn try_from_footer(
87 block_final_cert: BlockFinalizationCert,
88 bank: &Bank,
89 shred_version: u16,
90 ) -> Result<Self, BlockFinalizationCertError> {
91 let block = Block {
92 slot: block_final_cert.slot,
93 block_id: block_final_cert.block_id,
94 };
95
96 if let Some(notar_aggregate) = block_final_cert.notar_aggregate {
97 let notarize_cert_type = CertificateType::Notarize(block);
99 let finalize_cert_type = CertificateType::Finalize(block.slot);
100
101 let notarize_cert = UnverifiedCertificate {
102 cert_type: notarize_cert_type,
103 signature: notar_aggregate
104 .uncompress_signature()
105 .map_err(|e| BlockFinalizationCertError::BlsError(notarize_cert_type, e))?,
106 bitmap: notar_aggregate.into_bitmap(),
107 shred_version,
108 };
109 let finalize_cert = UnverifiedCertificate {
110 cert_type: finalize_cert_type,
111 signature: block_final_cert
112 .final_aggregate
113 .uncompress_signature()
114 .map_err(|e| BlockFinalizationCertError::BlsError(finalize_cert_type, e))?,
115 bitmap: block_final_cert.final_aggregate.into_bitmap(),
116 shred_version,
117 };
118
119 let notarize_cert = Self::verify_certificate(bank, notarize_cert)?;
121 let finalize_cert = Self::verify_certificate(bank, finalize_cert)?;
122
123 let signers = Self::extract_signers(bank, &finalize_cert)?;
124 Ok(Self {
125 kind: ValidatedBlockFinalizationCertKind::Finalize {
126 finalize_cert,
127 notarize_cert,
128 },
129 signers,
130 })
131 } else {
132 let fast_finalize_cert_type = CertificateType::FinalizeFast(block);
134
135 let fast_finalize_cert = UnverifiedCertificate {
136 cert_type: fast_finalize_cert_type,
137 signature: block_final_cert
138 .final_aggregate
139 .uncompress_signature()
140 .map_err(|e| {
141 BlockFinalizationCertError::BlsError(fast_finalize_cert_type, e)
142 })?,
143 bitmap: block_final_cert.final_aggregate.into_bitmap(),
144 shred_version,
145 };
146
147 let fast_finalize_cert = Self::verify_certificate(bank, fast_finalize_cert)?;
148
149 let signers = Self::extract_signers(bank, &fast_finalize_cert)?;
150 Ok(Self {
151 kind: ValidatedBlockFinalizationCertKind::FastFinalize(fast_finalize_cert),
152 signers,
153 })
154 }
155 }
156
157 pub fn from_validated_slow(
166 finalize_cert: Certificate,
167 notarize_cert: Certificate,
168 bank: &Bank,
169 ) -> Self {
170 debug_assert!(finalize_cert.cert_type.is_slow_finalization());
171 debug_assert!(notarize_cert.cert_type.is_notarize());
172 debug_assert_eq!(
173 notarize_cert.cert_type.slot(),
174 finalize_cert.cert_type.slot()
175 );
176
177 let signers = Self::extract_signers(bank, &finalize_cert)
178 .expect("Certificate should have been validated");
179 Self {
180 kind: ValidatedBlockFinalizationCertKind::Finalize {
181 finalize_cert,
182 notarize_cert,
183 },
184 signers,
185 }
186 }
187
188 pub fn from_validated_fast(cert: Certificate, bank: &Bank) -> Self {
197 debug_assert!(cert.cert_type.is_fast_finalization());
198
199 let signers =
200 Self::extract_signers(bank, &cert).expect("Certificate should have been validated");
201 Self {
202 kind: ValidatedBlockFinalizationCertKind::FastFinalize(cert),
203 signers,
204 }
205 }
206
207 pub fn slot(&self) -> FinalizedSlot {
209 match &self.kind {
210 ValidatedBlockFinalizationCertKind::Finalize {
211 finalize_cert,
212 notarize_cert,
213 } => {
214 debug_assert_eq!(
215 finalize_cert.cert_type.slot(),
216 notarize_cert.cert_type.slot()
217 );
218 FinalizedSlot::Slow(finalize_cert.cert_type.slot())
219 }
220 ValidatedBlockFinalizationCertKind::FastFinalize(certificate) => {
221 FinalizedSlot::Fast(certificate.cert_type.slot())
222 }
223 }
224 }
225
226 pub fn block(&self) -> Block {
228 match &self.kind {
229 ValidatedBlockFinalizationCertKind::Finalize { notarize_cert, .. } => notarize_cert
230 .cert_type
231 .to_block()
232 .expect("notarize certificate has block"),
233 ValidatedBlockFinalizationCertKind::FastFinalize(cert) => cert
234 .cert_type
235 .to_block()
236 .expect("fast finalize certificate has block"),
237 }
238 }
239
240 pub fn is_fast(&self) -> bool {
242 matches!(
243 self.kind,
244 ValidatedBlockFinalizationCertKind::FastFinalize(_)
245 )
246 }
247
248 pub fn clone_certificates(&self) -> Vec<Certificate> {
253 match &self.kind {
254 ValidatedBlockFinalizationCertKind::Finalize {
255 finalize_cert,
256 notarize_cert,
257 } => vec![finalize_cert.clone(), notarize_cert.clone()],
258 ValidatedBlockFinalizationCertKind::FastFinalize(cert) => vec![cert.clone()],
259 }
260 }
261
262 pub fn vote_rewards_input(&self) -> (&HashSet<Pubkey>, Slot) {
264 (&self.signers, self.slot().slot())
265 }
266
267 pub(crate) fn into_parts(self) -> (HashSet<Pubkey>, Certificate, Option<Certificate>) {
272 let signers = self.signers;
273 let (final_cert, notar_cert) = match self.kind {
274 ValidatedBlockFinalizationCertKind::Finalize {
275 finalize_cert,
276 notarize_cert,
277 } => (finalize_cert, Some(notarize_cert)),
278 ValidatedBlockFinalizationCertKind::FastFinalize(cert) => (cert, None),
279 };
280 (signers, final_cert, notar_cert)
281 }
282
283 pub fn to_block_final_cert(&self) -> BlockFinalizationCert {
285 match &self.kind {
286 ValidatedBlockFinalizationCertKind::Finalize {
287 finalize_cert,
288 notarize_cert,
289 } => {
290 let slot = finalize_cert.cert_type.slot();
291 let block_id = notarize_cert
292 .cert_type
293 .to_block()
294 .expect("notarize certificates correspond to blocks")
295 .block_id;
296 BlockFinalizationCert {
297 slot,
298 block_id,
299 final_aggregate: VotesAggregate::from_certificate(finalize_cert),
300 notar_aggregate: Some(VotesAggregate::from_certificate(notarize_cert)),
301 }
302 }
303 ValidatedBlockFinalizationCertKind::FastFinalize(cert) => {
304 let block = cert
305 .cert_type
306 .to_block()
307 .expect("fast finalizations correspond to blocks");
308 BlockFinalizationCert {
309 slot: block.slot,
310 block_id: block.block_id,
311 final_aggregate: VotesAggregate::from_certificate(cert),
312 notar_aggregate: None,
313 }
314 }
315 }
316 }
317
318 fn verify_certificate(
320 bank: &Bank,
321 cert: UnverifiedCertificate,
322 ) -> Result<Certificate, BlockFinalizationCertError> {
323 let cert_type = cert.cert_type;
324 let cert = bank
325 .verify_certificate(cert)
326 .map_err(|_| BlockFinalizationCertError::CertificateVerificationFailed(cert_type))?;
327 Ok(cert)
328 }
329
330 fn extract_signers(
331 bank: &Bank,
332 cert: &Certificate,
333 ) -> Result<HashSet<Pubkey>, BlockFinalizationCertError> {
334 let Some(epoch_stakes) = bank.epoch_stakes_from_slot(cert.cert_type.slot()) else {
335 return Err(BlockFinalizationCertError::MissingEpochStakes(
336 cert.cert_type,
337 ));
338 };
339 let rank_map = epoch_stakes.bls_pubkey_to_rank_map();
340 let decoded = decode(&cert.bitmap, rank_map.len());
341 let ranks = match decoded {
342 Ok(Decoded::Base2(ranks)) => ranks,
343 Ok(Decoded::Base3(_, _)) => {
344 return Err(BlockFinalizationCertError::Base3Finalization(
345 cert.cert_type,
346 ));
347 }
348 Err(err) => return Err(BlockFinalizationCertError::DecodeError(cert.cert_type, err)),
349 };
350
351 Ok(ranks
352 .iter_ones()
353 .filter_map(|rank| {
354 rank_map
355 .get_pubkey_stake_entry(rank)
356 .map(|entry| entry.vote_account_pubkey)
357 })
358 .collect())
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use {
365 super::*,
366 crate::genesis_utils::{
367 ValidatorVoteKeypairs, create_genesis_config_with_alpenglow_vote_accounts,
368 },
369 agave_votor_messages::{vote::Vote, wire::get_vote_payload_to_sign},
370 bitvec::prelude::*,
371 rand::Rng,
372 solana_bls_signatures::SignatureProjective,
373 solana_entry::block_component::VotesAggregate,
374 solana_hash::Hash,
375 solana_signer_store::encode_base2,
376 std::sync::Arc,
377 };
378
379 fn create_bank_with_bls_validators(
382 num_validators: usize,
383 stakes: Vec<u64>,
384 ) -> (Arc<Bank>, Vec<ValidatorVoteKeypairs>) {
385 assert_eq!(num_validators, stakes.len());
386 let validator_keypairs: Vec<ValidatorVoteKeypairs> = (0..num_validators)
387 .map(|_| ValidatorVoteKeypairs::new_rand())
388 .collect();
389
390 let genesis_config_info = create_genesis_config_with_alpenglow_vote_accounts(
391 10_000_000,
392 &validator_keypairs,
393 stakes,
394 );
395
396 let bank = Arc::new(Bank::new_for_tests(&genesis_config_info.genesis_config));
397 (bank, validator_keypairs)
398 }
399
400 fn build_certificate_manual(
403 cert_type: CertificateType,
404 vote: Vote,
405 shred_version: u16,
406 signing_ranks: &[usize],
407 validator_keypairs: &[ValidatorVoteKeypairs],
408 ) -> Certificate {
409 let serialized_vote = get_vote_payload_to_sign(vote, shred_version);
410
411 let mut signature = SignatureProjective::identity();
413 for &rank in signing_ranks {
414 let sig = validator_keypairs[rank].bls_keypair.sign(&serialized_vote);
415 signature.aggregate_with(std::iter::once(&sig)).unwrap();
416 }
417
418 let max_rank = signing_ranks.iter().copied().max().unwrap_or(0);
420 let mut bitvec = BitVec::<u8, Lsb0>::repeat(false, max_rank.saturating_add(1));
421 for &rank in signing_ranks {
422 bitvec.set(rank, true);
423 }
424 let bitmap = encode_base2(&bitvec).expect("Failed to encode bitmap");
425
426 Certificate {
427 cert_type,
428 signature: signature.into(),
429 bitmap,
430 }
431 }
432
433 #[test]
434 fn test_verify_final_cert_valid() {
435 let num_validators = 10;
438 let stakes: Vec<u64> = (0..num_validators)
439 .map(|i| (1000u64).saturating_sub((i as u64).saturating_mul(100)))
440 .collect();
441 let (bank, validator_keypairs) = create_bank_with_bls_validators(num_validators, stakes);
442 let shred_version = rand::rng().random();
443
444 let block = Block {
445 slot: bank.slot(),
446 block_id: Hash::new_unique(),
447 };
448
449 {
452 let cert_type = CertificateType::FinalizeFast(block);
453 let vote = Vote::new_notarization_vote(block);
454 let signing_ranks: Vec<usize> = (0..6).collect();
455 let fast_finalize_cert = build_certificate_manual(
456 cert_type,
457 vote,
458 shred_version,
459 &signing_ranks,
460 &validator_keypairs,
461 );
462
463 let block_final_cert = BlockFinalizationCert {
464 slot: block.slot,
465 block_id: block.block_id,
466 final_aggregate: VotesAggregate::from_certificate(&fast_finalize_cert),
467 notar_aggregate: None,
468 };
469
470 let validated = ValidatedBlockFinalizationCert::try_from_footer(
471 block_final_cert,
472 &bank,
473 shred_version,
474 )
475 .expect("Valid fast finalize certificate should pass verification");
476 assert_eq!(validated.clone_certificates(), vec![fast_finalize_cert]);
477 }
478
479 {
482 let notarize_cert_type = CertificateType::Notarize(block);
483 let notarize_vote = Vote::new_notarization_vote(block);
484 let notarize_signing_ranks: Vec<usize> = (0..4).collect();
485 let notarize_cert = build_certificate_manual(
486 notarize_cert_type,
487 notarize_vote,
488 shred_version,
489 ¬arize_signing_ranks,
490 &validator_keypairs,
491 );
492
493 let finalize_cert_type = CertificateType::Finalize(block.slot);
494 let finalize_vote = Vote::new_finalization_vote(block.slot);
495 let finalize_signing_ranks: Vec<usize> = (0..4).collect();
496 let finalize_cert = build_certificate_manual(
497 finalize_cert_type,
498 finalize_vote,
499 shred_version,
500 &finalize_signing_ranks,
501 &validator_keypairs,
502 );
503
504 let block_final_cert = BlockFinalizationCert {
505 slot: block.slot,
506 block_id: block.block_id,
507 final_aggregate: VotesAggregate::from_certificate(&finalize_cert),
508 notar_aggregate: Some(VotesAggregate::from_certificate(¬arize_cert)),
509 };
510
511 let validated = ValidatedBlockFinalizationCert::try_from_footer(
512 block_final_cert,
513 &bank,
514 shred_version,
515 )
516 .expect("Valid slow finalize certificate should pass verification");
517 assert_eq!(
518 validated.clone_certificates(),
519 vec![finalize_cert, notarize_cert]
520 );
521 }
522 }
523
524 #[test]
525 fn test_verify_final_cert_insufficient_stake() {
526 let num_validators = 10;
529 let stakes: Vec<u64> = (0..num_validators)
530 .map(|i| (1000u64).saturating_sub((i as u64).saturating_mul(100)))
531 .collect();
532 let (bank, validator_keypairs) = create_bank_with_bls_validators(num_validators, stakes);
533 let shred_version = rand::rng().random();
534
535 let block = Block {
536 slot: bank.slot(),
537 block_id: Hash::new_unique(),
538 };
539
540 {
543 let cert_type = CertificateType::FinalizeFast(block);
544 let vote = Vote::new_notarization_vote(block);
545 let signing_ranks: Vec<usize> = (0..5).collect();
546 let fast_finalize_cert = build_certificate_manual(
547 cert_type,
548 vote,
549 shred_version,
550 &signing_ranks,
551 &validator_keypairs,
552 );
553
554 let block_final_cert = BlockFinalizationCert {
555 slot: block.slot,
556 block_id: block.block_id,
557 final_aggregate: VotesAggregate::from_certificate(&fast_finalize_cert),
558 notar_aggregate: None,
559 };
560
561 let err = ValidatedBlockFinalizationCert::try_from_footer(
562 block_final_cert,
563 &bank,
564 shred_version,
565 )
566 .unwrap_err();
567 assert!(
568 matches!(
569 err,
570 BlockFinalizationCertError::CertificateVerificationFailed { .. }
571 ),
572 "Fast finalize with insufficient stake should fail verification. {err:?}"
573 );
574 }
575
576 {
579 let notarize_cert_type = CertificateType::Notarize(block);
580 let notarize_vote = Vote::new_notarization_vote(block);
581 let notarize_signing_ranks: Vec<usize> = (0..3).collect();
582 let notarize_cert = build_certificate_manual(
583 notarize_cert_type,
584 notarize_vote,
585 shred_version,
586 ¬arize_signing_ranks,
587 &validator_keypairs,
588 );
589
590 let finalize_cert_type = CertificateType::Finalize(block.slot);
592 let finalize_vote = Vote::new_finalization_vote(block.slot);
593 let finalize_signing_ranks: Vec<usize> = (0..4).collect();
594 let finalize_cert = build_certificate_manual(
595 finalize_cert_type,
596 finalize_vote,
597 shred_version,
598 &finalize_signing_ranks,
599 &validator_keypairs,
600 );
601
602 let block_final_cert = BlockFinalizationCert {
603 slot: block.slot,
604 block_id: block.block_id,
605 final_aggregate: VotesAggregate::from_certificate(&finalize_cert),
606 notar_aggregate: Some(VotesAggregate::from_certificate(¬arize_cert)),
607 };
608
609 let err = ValidatedBlockFinalizationCert::try_from_footer(
610 block_final_cert,
611 &bank,
612 shred_version,
613 )
614 .unwrap_err();
615 assert!(
616 matches!(
617 err,
618 BlockFinalizationCertError::CertificateVerificationFailed { .. }
619 ),
620 "Slow finalize with insufficient notarize stake should fail verification"
621 );
622 }
623
624 {
627 let notarize_cert_type = CertificateType::Notarize(block);
629 let notarize_vote = Vote::new_notarization_vote(block);
630 let notarize_signing_ranks: Vec<usize> = (0..4).collect();
631 let notarize_cert = build_certificate_manual(
632 notarize_cert_type,
633 notarize_vote,
634 shred_version,
635 ¬arize_signing_ranks,
636 &validator_keypairs,
637 );
638
639 let finalize_cert_type = CertificateType::Finalize(block.slot);
642 let finalize_vote = Vote::new_finalization_vote(block.slot);
643 let finalize_signing_ranks: Vec<usize> = (0..3).collect();
644 let finalize_cert = build_certificate_manual(
645 finalize_cert_type,
646 finalize_vote,
647 shred_version,
648 &finalize_signing_ranks,
649 &validator_keypairs,
650 );
651
652 let block_final_cert = BlockFinalizationCert {
653 slot: block.slot,
654 block_id: block.block_id,
655 final_aggregate: VotesAggregate::from_certificate(&finalize_cert),
656 notar_aggregate: Some(VotesAggregate::from_certificate(¬arize_cert)),
657 };
658
659 let err = ValidatedBlockFinalizationCert::try_from_footer(
660 block_final_cert,
661 &bank,
662 shred_version,
663 )
664 .unwrap_err();
665 assert!(
666 matches!(
667 err,
668 BlockFinalizationCertError::CertificateVerificationFailed { .. }
669 ),
670 "Slow finalize with insufficient finalize stake should fail verification"
671 );
672 }
673 }
674}