1mod error;
5
6pub use crate::error::CertsError;
7use crate::error::CertsError::UnsortedDiff;
8use ahash::HashMap;
28use filecoin_f3_gpbft::api::Verifier;
29pub use filecoin_f3_gpbft::chain::Tipset;
30use filecoin_f3_gpbft::{
31 ActorId, BitField, ECChain, Justification, NetworkName, Payload, Phase, PowerEntries,
32 PowerEntry, PubKey, Sign, StoragePower, SupplementalData, Zero, cid_from_bytes,
33 is_strong_quorum,
34};
35use std::ops::Neg;
36
37pub type Result<T> = std::result::Result<T, error::CertsError>;
38
39#[derive(Debug, Clone)]
42pub struct PowerTableDelta {
43 pub participant_id: ActorId,
45 pub power_delta: StoragePower,
47 pub signing_key: PubKey,
49}
50
51impl PowerTableDelta {
52 pub fn is_zero(&self) -> bool {
57 self.power_delta.is_zero() && self.signing_key.is_empty()
58 }
59}
60
61pub type PowerTableDiff = Vec<PowerTableDelta>;
67
68#[derive(Default, Debug, Clone)]
70pub struct FinalityCertificate {
71 pub gpbft_instance: u64,
73 pub ec_chain: ECChain,
76 pub supplemental_data: SupplementalData,
79 pub signers: BitField,
81 pub signature: Vec<u8>,
83 pub power_table_delta: PowerTableDiff,
86}
87
88impl FinalityCertificate {
89 pub fn new(power_delta: PowerTableDiff, justification: &Justification) -> Result<Self> {
98 if justification.vote.phase != Phase::Decide {
99 return Err(CertsError::InvalidJustificationVotePhase {
100 expected: Phase::Decide,
101 actual: justification.vote.phase,
102 });
103 }
104
105 if justification.vote.round != 0 {
106 return Err(CertsError::InvalidRound {
107 expected: 0,
108 actual: justification.vote.round,
109 });
110 }
111
112 if justification.vote.value.is_empty() {
113 return Err(CertsError::BottomDecision(justification.vote.instance));
114 }
115
116 Ok(FinalityCertificate {
117 gpbft_instance: justification.vote.instance,
118 supplemental_data: justification.vote.supplemental_data.clone(),
119 ec_chain: justification.vote.value.clone(),
120 signers: justification.signers.clone(),
121 signature: justification.signature.clone(),
122 power_table_delta: power_delta,
123 })
124 }
125}
126
127pub fn apply_power_table_diffs(
132 prev_power_table: &PowerEntries,
133 diffs: &[&PowerTableDiff],
134) -> Result<PowerEntries> {
135 let mut power_table_map: HashMap<ActorId, PowerEntry> = prev_power_table
136 .iter()
137 .map(|pe| (pe.id, pe.clone()))
138 .collect();
139
140 for (j, diff) in diffs.iter().enumerate() {
141 let mut last_actor_id = 0;
142 for (i, d) in diff.iter().enumerate() {
143 if i > 0 && d.participant_id <= last_actor_id {
146 return Err(UnsortedDiff(j));
147 }
148
149 if d.is_zero() {
151 return Err(CertsError::EmptyDelta(j, d.participant_id));
152 }
153
154 last_actor_id = d.participant_id;
155
156 if !power_table_map.contains_key(&d.participant_id) {
157 if d.power_delta <= StoragePower::from(0) {
159 return Err(CertsError::NonPositivePowerDeltaForNewEntry(
160 j,
161 d.participant_id,
162 ));
163 }
164 }
165
166 let pe = power_table_map
167 .entry(d.participant_id)
168 .or_insert_with(|| PowerEntry {
169 id: d.participant_id,
170 power: StoragePower::from(0),
171 pub_key: PubKey::default(),
172 });
173
174 if pe.pub_key == d.signing_key {
178 return Err(CertsError::UnchangedKey(j, pe.id));
179 }
180
181 if !d.power_delta.is_zero() {
182 pe.power += &d.power_delta;
183 }
184
185 if !d.signing_key.is_empty() {
186 if pe.power.is_zero() {
189 return Err(CertsError::RemovesAllPowerWithNewKey(j, pe.id));
190 }
191 pe.pub_key = d.signing_key.clone();
192 }
193
194 match pe.power.sign() {
195 Sign::Minus => return Err(CertsError::NegativePower(j, pe.id)),
196 Sign::NoSign => {
197 power_table_map.remove(&d.participant_id);
198 }
199 Sign::Plus => {
200 }
202 }
203 }
204 }
205
206 let mut new_power_table: PowerEntries = power_table_map.into_values().collect();
207 new_power_table.sort();
208 Ok(new_power_table)
209}
210
211pub fn make_power_table_diff(
215 old_power_table: &PowerEntries,
216 new_power_table: &PowerEntries,
217) -> PowerTableDiff {
218 let mut old_power_map: HashMap<ActorId, &PowerEntry> =
219 old_power_table.iter().map(|e| (e.id, e)).collect();
220
221 let mut diff = PowerTableDiff::new();
222
223 for new_entry in new_power_table.iter() {
224 let mut delta = PowerTableDelta {
225 participant_id: new_entry.id,
226 power_delta: StoragePower::from(0),
227 signing_key: PubKey::default(),
228 };
229
230 let delta = match old_power_map.remove(&new_entry.id) {
231 Some(old_entry) => {
232 delta.power_delta = &new_entry.power - &old_entry.power;
233 if new_entry.pub_key != old_entry.pub_key {
234 delta.signing_key = new_entry.pub_key.clone();
235 }
236 delta
237 }
238 None => {
239 delta.power_delta = new_entry.power.clone();
240 delta.signing_key = new_entry.pub_key.clone();
241 delta
242 }
243 };
244
245 if !delta.is_zero() {
246 diff.push(delta);
247 }
248 }
249
250 for old_entry in old_power_map.values() {
251 diff.push(PowerTableDelta {
252 participant_id: old_entry.id,
253 power_delta: old_entry.power.clone().neg(),
254 signing_key: PubKey::default(),
255 });
256 }
257
258 diff.sort_by_key(|delta| delta.participant_id);
259 diff
260}
261
262fn verify_signature(
264 verifier: &impl Verifier,
265 network: NetworkName,
266 cert: &FinalityCertificate,
267 power_table: &PowerEntries,
268) -> Result<()> {
269 let (scaled_powers, scaled_total) = power_table
270 .scaled()
271 .map_err(|e| CertsError::SerializationError(e.to_string().into()))?;
272
273 let signers: Vec<u64> = cert.signers.iter().collect();
274 let signer_scaled_total =
275 signer_scaled_total(cert.gpbft_instance, power_table, &scaled_powers, &signers)?;
276 if !is_strong_quorum(signer_scaled_total, scaled_total) {
277 return Err(CertsError::InsufficientPower {
278 instance: cert.gpbft_instance,
279 signer_power: signer_scaled_total,
280 total_power: scaled_total,
281 });
282 }
283
284 let payload = Payload {
286 instance: cert.gpbft_instance,
287 round: 0,
288 phase: Phase::Decide,
289 supplemental_data: cert.supplemental_data.clone(),
290 value: cert.ec_chain.clone(),
291 };
292
293 let payload_bytes = payload.serialize_for_signing(&network.to_string());
295
296 let signers_pk: Vec<PubKey> = signers
298 .iter()
299 .map(|&index| power_table[index as usize].pub_key.clone())
300 .collect();
301
302 let res = verifier
304 .verify_aggregate(&payload_bytes, &cert.signature, &signers_pk)
305 .map_err(|e| CertsError::SignatureVerificationFailed {
306 instance: cert.gpbft_instance,
307 error: e.to_string(),
308 });
309
310 if let Err(err) = res {
315 println!("WARN: {}", err);
316 }
317
318 Ok(())
319}
320
321pub fn validate_finality_certificates<'a>(
345 verifier: &impl Verifier,
346 network: NetworkName,
347 prev_power_table: PowerEntries,
348 mut next_instance: u64,
349 mut base: Option<&'a Tipset>,
350 certs: &'a [FinalityCertificate],
351) -> Result<(u64, ECChain, PowerEntries)> {
352 let mut chain: Option<ECChain> = None;
353 let mut current_power_table = prev_power_table;
354
355 for cert in certs {
356 if cert.gpbft_instance != next_instance {
357 return Err(CertsError::InstanceMismatch {
358 expected: next_instance,
359 actual: cert.gpbft_instance,
360 });
361 }
362
363 if let Err(e) = cert.ec_chain.validate() {
365 return Err(CertsError::InvalidFinalityCertificate(
366 cert.gpbft_instance,
367 e,
368 ));
369 }
370
371 if cert.ec_chain.is_empty() {
372 return Err(CertsError::EmptyFinalityCertificate(cert.gpbft_instance));
373 }
374
375 if base.is_some() && base != cert.ec_chain.base() {
377 return Err(CertsError::BaseTipsetMismatch(cert.gpbft_instance));
378 }
379
380 verify_signature(verifier, network, cert, ¤t_power_table)?;
382
383 let new_power_table =
385 apply_power_table_diffs(¤t_power_table, &[&cert.power_table_delta])?;
386
387 let bytes = new_power_table.serialize_cbor();
388 let power_table_cid = cid_from_bytes(&bytes);
389
390 if cert.supplemental_data.power_table != power_table_cid {
391 return Err(CertsError::IncorrectPowerDiff {
392 instance: cert.gpbft_instance,
393 expected: power_table_cid.into(),
394 actual: cert.supplemental_data.power_table.into(),
395 });
396 }
397
398 next_instance += 1;
399 chain = match (chain, cert.ec_chain.has_suffix()) {
400 (None, _) => {
401 Some(ECChain::new_unvalidated(cert.ec_chain.to_vec()))
403 }
404 (Some(existing), false) => {
405 Some(existing)
407 }
408 (Some(existing), true) => {
409 existing.extend(
411 &cert
412 .ec_chain
413 .suffix()
414 .iter()
415 .map(|ts| ts.key.clone())
416 .collect::<Vec<_>>(),
417 )
418 }
419 };
420
421 current_power_table = new_power_table;
422 base = cert.ec_chain.head();
423 }
424
425 Ok((
426 next_instance,
427 chain.ok_or(CertsError::EmptyChain)?,
428 current_power_table,
429 ))
430}
431
432fn signer_scaled_total(
434 instance: u64,
435 power_table: &PowerEntries,
436 scaled_power: &[i64],
437 signer_indices: &[u64],
438) -> Result<i64> {
439 let mut total_signer_power = 0;
440
441 for &index in signer_indices {
442 let idx = index as usize;
443
444 if idx >= power_table.len() {
446 return Err(CertsError::SignerIndexOutOfBounds {
447 instance,
448 signer_index: idx,
449 power_table_size: power_table.len(),
450 });
451 }
452
453 let scaled_power = scaled_power[idx];
454
455 if scaled_power == 0 {
457 return Err(CertsError::ZeroEffectivePower {
458 instance,
459 signer: power_table[idx].id,
460 });
461 }
462
463 total_signer_power += scaled_power;
464 }
465
466 Ok(total_signer_power)
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472 use filecoin_f3_gpbft::chain::Tipset;
473 use filecoin_f3_gpbft::test_utils::powertable_cid;
474 use filecoin_f3_gpbft::{Cid, Payload};
475 use std::str::FromStr;
476
477 fn create_mock_justification(phase: Phase, cid: &str) -> Justification {
478 let base_tipset = Tipset {
479 epoch: 1,
480 key: vec![1, 2, 3],
481 power_table: powertable_cid(),
482 commitments: keccak_hash::H256::zero(),
483 };
484
485 let additional_tipset = Tipset {
486 epoch: 2,
487 key: vec![4, 5, 6],
488 power_table: powertable_cid(),
489 commitments: keccak_hash::H256::zero(),
490 };
491
492 let ec_chain = ECChain::new(base_tipset, vec![additional_tipset]).unwrap();
493
494 let mut signers = BitField::new();
496 signers.set(0);
497 signers.set(1);
498
499 Justification {
500 vote: Payload {
501 instance: 1,
502 round: 0,
503 phase,
504 supplemental_data: SupplementalData {
505 commitments: keccak_hash::H256::zero(),
506 power_table: Cid::from_str(cid).unwrap(),
507 },
508 value: ec_chain,
509 },
510 signers,
511 signature: vec![7, 8, 9],
512 }
513 }
514
515 #[test]
516 fn test_power_table_delta_is_zero() {
517 let zero_delta = PowerTableDelta {
518 participant_id: 1,
519 power_delta: StoragePower::from(0),
520 signing_key: PubKey::default(),
521 };
522 assert!(zero_delta.is_zero());
523
524 let non_zero_power_delta = PowerTableDelta {
525 participant_id: 1,
526 power_delta: StoragePower::from(100),
527 signing_key: PubKey::default(),
528 };
529 assert!(!non_zero_power_delta.is_zero());
530
531 let non_zero_key_delta = PowerTableDelta {
532 participant_id: 1,
533 power_delta: StoragePower::from(0),
534 signing_key: PubKey::new(vec![1, 2, 3]),
535 };
536 assert!(!non_zero_key_delta.is_zero());
537 }
538
539 #[test]
540 fn test_finality_certificate_new_success() {
541 let power_delta = PowerTableDiff::new();
542 let justification = create_mock_justification(Phase::Decide, &powertable_cid().to_string());
543
544 let result = FinalityCertificate::new(power_delta, &justification);
545 assert!(result.is_ok());
546
547 let cert = result.unwrap();
548 assert_eq!(cert.gpbft_instance, justification.vote.instance);
549 assert_eq!(cert.ec_chain, justification.vote.value);
550 assert_eq!(cert.supplemental_data, justification.vote.supplemental_data);
551 assert_eq!(cert.signers, justification.signers);
552 assert_eq!(cert.signature, justification.signature);
553 }
554
555 #[test]
556 fn test_finality_certificate_new_wrong_phase() {
557 let power_delta = PowerTableDiff::new();
558 let justification = create_mock_justification(Phase::Commit, &powertable_cid().to_string());
559
560 let result = FinalityCertificate::new(power_delta, &justification);
561 assert!(result.is_err());
562 assert_eq!(
563 result.unwrap_err(),
564 CertsError::InvalidJustificationVotePhase {
565 expected: Phase::Decide,
566 actual: Phase::Commit
567 }
568 );
569 }
570
571 #[test]
572 fn test_finality_certificate_new_wrong_round() {
573 let power_delta = PowerTableDiff::new();
574 let mut justification =
575 create_mock_justification(Phase::Decide, &powertable_cid().to_string());
576 justification.vote.round = 1;
577
578 let result = FinalityCertificate::new(power_delta, &justification);
579 assert!(result.is_err());
580 assert_eq!(
581 result.unwrap_err(),
582 CertsError::InvalidRound {
583 expected: 0,
584 actual: 1
585 }
586 );
587 }
588
589 #[test]
591 fn test_finality_certificate_new_empty_value() {
592 let power_delta = PowerTableDiff::new();
593 let mut justification =
594 create_mock_justification(Phase::Decide, &powertable_cid().to_string());
595 justification.vote.value = ECChain::new_unvalidated(Vec::new());
596
597 let result = FinalityCertificate::new(power_delta, &justification);
598 assert!(result.is_err());
599 assert_eq!(result.unwrap_err(), CertsError::BottomDecision(1));
600 }
601
602 #[test]
603 fn test_make_power_table_diff() {
604 let old_power_table = vec![
605 PowerEntry {
606 id: 1,
607 power: StoragePower::from(100),
608 pub_key: PubKey::new(vec![1, 2, 3]),
609 },
610 PowerEntry {
611 id: 2,
612 power: StoragePower::from(200),
613 pub_key: PubKey::new(vec![4, 5, 6]),
614 },
615 ];
616 let new_power_table = vec![
617 PowerEntry {
618 id: 1,
619 power: StoragePower::from(150),
620 pub_key: PubKey::new(vec![1, 2, 3]),
621 },
622 PowerEntry {
623 id: 3,
624 power: StoragePower::from(300),
625 pub_key: PubKey::new(vec![7, 8, 9]),
626 },
627 ];
628
629 let diff = make_power_table_diff(
630 &PowerEntries(old_power_table),
631 &PowerEntries(new_power_table),
632 );
633
634 assert_eq!(diff.len(), 3);
635 assert_eq!(diff[0].participant_id, 1);
636 assert_eq!(diff[0].power_delta, StoragePower::from(50));
637 assert!(diff[0].signing_key.is_empty());
638 assert_eq!(diff[1].participant_id, 2);
639 assert_eq!(diff[1].power_delta, StoragePower::from(-200));
640 assert!(diff[1].signing_key.is_empty());
641 assert_eq!(diff[2].participant_id, 3);
642 assert_eq!(diff[2].power_delta, StoragePower::from(300));
643 assert_eq!(diff[2].signing_key, PubKey::new(vec![7, 8, 9]));
644 }
645
646 #[test]
647 fn test_apply_power_table_diffs() {
648 let prev_power_table = vec![
649 PowerEntry {
650 id: 1,
651 power: StoragePower::from(100),
652 pub_key: PubKey::new(vec![1, 2, 3]),
653 },
654 PowerEntry {
655 id: 2,
656 power: StoragePower::from(200),
657 pub_key: PubKey::new(vec![4, 5, 6]),
658 },
659 ];
660 let diff_one = vec![
661 PowerTableDelta {
662 participant_id: 1,
663 power_delta: StoragePower::from(50),
664 signing_key: PubKey::default(),
665 },
666 PowerTableDelta {
667 participant_id: 3,
668 power_delta: StoragePower::from(300),
669 signing_key: PubKey::new(vec![7, 8, 9]),
670 },
671 ];
672
673 let diff_two = vec![PowerTableDelta {
674 participant_id: 2,
675 power_delta: StoragePower::from(-200),
676 signing_key: PubKey::default(),
677 }];
678
679 let diffs = vec![&diff_one, &diff_two];
680
681 let result = apply_power_table_diffs(&PowerEntries(prev_power_table), &diffs).unwrap();
682
683 assert_eq!(result.len(), 2);
684 assert_eq!(result[0].id, 3);
685 assert_eq!(result[0].power, StoragePower::from(300));
686 assert_eq!(result[0].pub_key, PubKey::new(vec![7, 8, 9]));
687 assert_eq!(result[1].id, 1);
688 assert_eq!(result[1].power, StoragePower::from(150));
689 assert_eq!(result[1].pub_key, PubKey::new(vec![1, 2, 3]));
690 }
691
692 #[derive(Debug)]
693 struct MockVerifier;
694
695 #[derive(Debug, thiserror::Error)]
696 #[error("Mock verifier error")]
697 struct MockVerifierError;
698
699 #[allow(unused)]
700 impl Verifier for MockVerifier {
701 type Error = MockVerifierError;
702
703 fn verify(
704 &self,
705 pub_key: &PubKey,
706 msg: &[u8],
707 sig: &[u8],
708 ) -> std::result::Result<(), Self::Error> {
709 Ok(())
710 }
711
712 fn aggregate(
713 &self,
714 pub_keys: &[PubKey],
715 sigs: &[Vec<u8>],
716 ) -> std::result::Result<Vec<u8>, Self::Error> {
717 Ok(vec![])
718 }
719
720 fn verify_aggregate(
721 &self,
722 payload: &[u8],
723 agg_sig: &[u8],
724 signers: &[PubKey],
725 ) -> std::result::Result<(), Self::Error> {
726 Ok(())
727 }
728 }
729
730 #[test]
731 fn test_validate_finality_certificates_success() -> anyhow::Result<()> {
732 let initial_power_table = vec![
733 PowerEntry {
734 id: 1,
735 power: StoragePower::from(100),
736 pub_key: PubKey::new(vec![1, 2, 3]),
737 },
738 PowerEntry {
739 id: 2,
740 power: StoragePower::from(200),
741 pub_key: PubKey::new(vec![4, 5, 6]),
742 },
743 ];
744
745 let mut certs = Vec::new();
746 for i in 1..=1 {
747 let power_delta = PowerTableDiff::new();
748 let mut justification = create_mock_justification(
749 Phase::Decide,
750 "bafy2bzacearmjx2rxwdc6qkvowushj5nlkdkxmvop2ljojts2znfozxguafmg",
751 );
752 justification.vote.instance = i;
753 let cert = FinalityCertificate::new(power_delta, &justification)?;
754 certs.push(cert);
755 }
756
757 let result = validate_finality_certificates(
758 &MockVerifier,
759 NetworkName::TestnetCalibration,
760 PowerEntries(initial_power_table.clone()),
761 1,
762 None,
763 &certs,
764 )?;
765
766 let (next_instance, _, current_power_table) = result;
767
768 assert_eq!(next_instance, 2);
769 let mut initial_power_table = PowerEntries(initial_power_table);
770 initial_power_table.sort();
771 assert_eq!(current_power_table, initial_power_table);
772
773 Ok(())
774 }
775
776 #[test]
777 fn test_validate_finality_certificates_empty() -> anyhow::Result<()> {
778 let initial_power_table = vec![
779 PowerEntry {
780 id: 1,
781 power: StoragePower::from(100),
782 pub_key: PubKey::new(vec![1, 2, 3]),
783 },
784 PowerEntry {
785 id: 2,
786 power: StoragePower::from(200),
787 pub_key: PubKey::new(vec![4, 5, 6]),
788 },
789 ];
790 let base_tipset = Tipset {
791 epoch: 1,
792 key: vec![1, 2, 3],
793 power_table: powertable_cid(),
794 commitments: keccak_hash::H256::zero(),
795 };
796
797 let certs = Vec::new();
798
799 let result = validate_finality_certificates(
800 &MockVerifier,
801 NetworkName::TestnetCalibration,
802 PowerEntries(initial_power_table.clone()),
803 1,
804 Some(&base_tipset),
805 &certs,
806 );
807
808 assert!(matches!(result, Err(CertsError::EmptyChain)));
809
810 Ok(())
811 }
812
813 #[test]
814 fn test_validate_finality_certificates_non_consecutive() -> anyhow::Result<()> {
815 let initial_power_table = vec![
816 PowerEntry {
817 id: 1,
818 power: StoragePower::from(100),
819 pub_key: PubKey::new(vec![1, 2, 3]),
820 },
821 PowerEntry {
822 id: 2,
823 power: StoragePower::from(200),
824 pub_key: PubKey::new(vec![4, 5, 6]),
825 },
826 ];
827 let mut certs = Vec::new();
828 for i in [1, 3, 4] {
829 let power_delta = PowerTableDiff::new();
830 let mut justification = create_mock_justification(
831 Phase::Decide,
832 "bafy2bzacearmjx2rxwdc6qkvowushj5nlkdkxmvop2ljojts2znfozxguafmg",
833 );
834 justification.vote.instance = i;
835 let cert = FinalityCertificate::new(power_delta, &justification)?;
836 certs.push(cert);
837 }
838
839 let result = validate_finality_certificates(
840 &MockVerifier,
841 NetworkName::TestnetCalibration,
842 PowerEntries(initial_power_table),
843 1,
844 None,
845 &certs,
846 );
847
848 assert!(matches!(result, Err(CertsError::InstanceMismatch { .. })));
849
850 Ok(())
851 }
852
853 #[test]
854 fn test_validate_finality_certificates_base_mismatch() -> anyhow::Result<()> {
855 let power_delta = PowerTableDiff::new();
856 let justification = create_mock_justification(Phase::Decide, &powertable_cid().to_string());
857 let cert = FinalityCertificate::new(power_delta, &justification)?;
858
859 let mismatched_base = Tipset {
860 epoch: 2,
861 key: vec![4, 5, 6],
862 power_table: powertable_cid(),
863 commitments: keccak_hash::H256::zero(),
864 };
865
866 let result = validate_finality_certificates(
867 &MockVerifier,
868 NetworkName::TestnetCalibration,
869 PowerEntries(Vec::new()),
870 1,
871 Some(&mismatched_base),
872 &[cert],
873 );
874
875 assert!(matches!(result, Err(CertsError::BaseTipsetMismatch(_))));
876
877 Ok(())
878 }
879
880 #[test]
881 fn test_validate_finality_certificates_invalid_ec_chain() -> anyhow::Result<()> {
882 let initial_power_table = vec![
883 PowerEntry {
884 id: 1,
885 power: StoragePower::from(100),
886 pub_key: PubKey::new(vec![1, 2, 3]),
887 },
888 PowerEntry {
889 id: 2,
890 power: StoragePower::from(200),
891 pub_key: PubKey::new(vec![4, 5, 6]),
892 },
893 ];
894
895 let base_tipset = Tipset {
896 epoch: 1,
897 key: vec![1, 2, 3],
898 power_table: powertable_cid(),
899 commitments: keccak_hash::H256::zero(),
900 };
901
902 let power_delta = PowerTableDiff::new();
903 let mut justification =
904 create_mock_justification(Phase::Decide, &powertable_cid().to_string());
905
906 justification.vote.value = ECChain::new_unvalidated(vec![Tipset::default()]);
908
909 let invalid_cert = FinalityCertificate::new(power_delta, &justification)?;
910
911 let result = validate_finality_certificates(
912 &MockVerifier,
913 NetworkName::TestnetCalibration,
914 PowerEntries(initial_power_table),
915 1,
916 Some(&base_tipset),
917 &[invalid_cert],
918 );
919
920 assert!(matches!(
921 result,
922 Err(CertsError::InvalidFinalityCertificate(_, _))
923 ));
924
925 Ok(())
926 }
927
928 #[test]
929 fn test_validate_finality_certificates_empty_ec_chain() -> anyhow::Result<()> {
930 let initial_power_table = vec![
931 PowerEntry {
932 id: 1,
933 power: StoragePower::from(100),
934 pub_key: PubKey::new(vec![1, 2, 3]),
935 },
936 PowerEntry {
937 id: 2,
938 power: StoragePower::from(200),
939 pub_key: PubKey::new(vec![4, 5, 6]),
940 },
941 ];
942 let base_tipset = Tipset {
943 epoch: 1,
944 key: vec![1, 2, 3],
945 power_table: powertable_cid(),
946 commitments: keccak_hash::H256::zero(),
947 };
948
949 let cert_with_empty_ec_chain = FinalityCertificate::default();
950
951 let result = validate_finality_certificates(
952 &MockVerifier,
953 NetworkName::TestnetCalibration,
954 PowerEntries(initial_power_table),
955 0,
956 Some(&base_tipset),
957 &[cert_with_empty_ec_chain],
958 );
959
960 assert!(matches!(
962 result,
963 Err(CertsError::EmptyFinalityCertificate(_))
964 ));
965
966 Ok(())
967 }
968
969 #[test]
970 fn test_validate_finality_certificates_incorrect_power_diff() -> anyhow::Result<()> {
971 let initial_power_table = vec![
972 PowerEntry {
973 id: 1,
974 power: StoragePower::from(100),
975 pub_key: PubKey::new(vec![1, 2, 3]),
976 },
977 PowerEntry {
978 id: 2,
979 power: StoragePower::from(200),
980 pub_key: PubKey::new(vec![4, 5, 6]),
981 },
982 ];
983 let base_tipset = Tipset {
984 epoch: 1,
985 key: vec![1, 2, 3],
986 power_table: powertable_cid(),
987 commitments: keccak_hash::H256::zero(),
988 };
989
990 let incorrect_power_delta = vec![PowerTableDelta {
991 participant_id: 3, power_delta: StoragePower::from(50),
993 signing_key: PubKey::new(vec![7, 8, 9]),
994 }];
995
996 let justification = create_mock_justification(Phase::Decide, &powertable_cid().to_string());
997 let cert_with_incorrect_power_diff =
998 FinalityCertificate::new(incorrect_power_delta, &justification)?;
999
1000 let result = validate_finality_certificates(
1001 &MockVerifier,
1002 NetworkName::TestnetCalibration,
1003 PowerEntries(initial_power_table),
1004 1,
1005 Some(&base_tipset),
1006 &[cert_with_incorrect_power_diff],
1007 );
1008
1009 assert!(matches!(result, Err(CertsError::IncorrectPowerDiff { .. })));
1010
1011 Ok(())
1012 }
1013
1014 #[test]
1015 fn test_validate_finality_certificates_empty_vec() -> anyhow::Result<()> {
1016 let initial_power_table = vec![
1017 PowerEntry {
1018 id: 1,
1019 power: StoragePower::from(100),
1020 pub_key: PubKey::new(vec![1, 2, 3]),
1021 },
1022 PowerEntry {
1023 id: 2,
1024 power: StoragePower::from(200),
1025 pub_key: PubKey::new(vec![4, 5, 6]),
1026 },
1027 ];
1028 let base_tipset = Tipset {
1029 epoch: 1,
1030 key: vec![1, 2, 3],
1031 power_table: powertable_cid(),
1032 commitments: keccak_hash::H256::zero(),
1033 };
1034
1035 let certs = Vec::new();
1036
1037 let result = validate_finality_certificates(
1038 &MockVerifier,
1039 NetworkName::TestnetCalibration,
1040 PowerEntries(initial_power_table),
1041 1,
1042 Some(&base_tipset),
1043 &certs,
1044 );
1045
1046 assert!(matches!(result, Err(CertsError::EmptyChain)));
1047
1048 Ok(())
1049 }
1050}