1use std::{
6 slice::Iter,
7 time::{Duration, SystemTime, UNIX_EPOCH},
8};
9
10use anyhow::Result;
11use fastcrypto::hash::MultisetHash;
12use iota_protocol_config::ProtocolConfig;
13use iota_sdk_types::{
14 CheckpointContentsDigest, CheckpointContentsV1, CheckpointDigest, Digest, RandomnessRound,
15 Transaction,
16 checkpoint::{
17 CheckpointContents, CheckpointSummary, CheckpointTransactionInfo, EndOfEpochData,
18 },
19 crypto::{Intent, IntentScope, UserSignature},
20 gas::GasCostSummary,
21};
22#[cfg(not(target_arch = "wasm32"))]
23use prometheus_filtered::Histogram;
24use serde::{Deserialize, Serialize};
25#[cfg(not(target_arch = "wasm32"))]
26use tap::TapFallible;
27use tracing::instrument;
28#[cfg(not(target_arch = "wasm32"))]
29use tracing::warn;
30
31use crate::{
32 base_types::{ExecutionData, ExecutionDigests, VerifiedExecutionData, random_object_ref},
33 committee::{Committee, EpochId},
34 crypto::{
35 AccountKeyPair, AggregateAuthoritySignature, AuthoritySignInfo, AuthoritySignInfoTrait,
36 AuthorityStrongQuorumSignInfo, VerificationObligation, default_hash, get_key_pair,
37 },
38 effects::{TestEffectsBuilder, TransactionEffectsAPI},
39 error::{IotaError, IotaResult},
40 global_state_hash::GlobalStateHash,
41 message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope},
42 storage::ReadStore,
43 transaction::{TransactionAPI, TransactionEnvelope},
44};
45
46pub type CheckpointSequenceNumber = u64;
47pub type CheckpointTimestamp = u64;
48
49#[derive(Clone, Debug, Serialize, Deserialize)]
50pub struct CheckpointRequest {
51 pub sequence_number: Option<CheckpointSequenceNumber>,
56 pub request_content: bool,
59 pub certified: bool,
61}
62
63#[expect(clippy::large_enum_variant)]
64#[derive(Clone, Debug, Serialize, Deserialize)]
65pub enum CheckpointSummaryResponse {
66 Certified(CertifiedCheckpointSummary),
67 Pending(CheckpointSummary),
68}
69
70impl CheckpointSummaryResponse {
71 pub fn contents_digest(&self) -> CheckpointContentsDigest {
72 match self {
73 Self::Certified(s) => s.contents_digest,
74 Self::Pending(s) => s.contents_digest,
75 }
76 }
77}
78
79#[derive(Clone, Debug, Serialize, Deserialize)]
80pub struct CheckpointResponse {
81 pub checkpoint: Option<CheckpointSummaryResponse>,
82 pub contents: Option<CheckpointContents>,
83}
84
85#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
90pub struct ECMHLiveObjectSetDigest {
91 pub digest: Digest,
92}
93
94impl From<fastcrypto::hash::Digest<32>> for ECMHLiveObjectSetDigest {
95 fn from(digest: fastcrypto::hash::Digest<32>) -> Self {
96 Self {
97 digest: Digest::new(digest.digest),
98 }
99 }
100}
101
102impl Default for ECMHLiveObjectSetDigest {
103 fn default() -> Self {
104 GlobalStateHash::default().digest().into()
105 }
106}
107
108impl Message for CheckpointSummary {
109 type DigestType = CheckpointDigest;
110 const SCOPE: IntentScope = IntentScope::CheckpointSummary;
111
112 fn digest(&self) -> Self::DigestType {
113 CheckpointDigest::new(default_hash(self))
114 }
115}
116
117mod checkpoint_summary_ext {
118 pub trait Sealed {}
119 impl Sealed for super::CheckpointSummary {}
120}
121
122pub trait CheckpointSummaryExt: Sized + checkpoint_summary_ext::Sealed {
126 fn new_with_protocol_config(
127 protocol_config: &ProtocolConfig,
128 epoch: EpochId,
129 sequence_number: CheckpointSequenceNumber,
130 network_total_transactions: u64,
131 transactions: &CheckpointContents,
132 previous_digest: Option<CheckpointDigest>,
133 epoch_rolling_gas_cost_summary: GasCostSummary,
134 end_of_epoch_data: Option<EndOfEpochData>,
135 timestamp_ms: CheckpointTimestamp,
136 randomness_rounds: Vec<RandomnessRound>,
137 ) -> Self;
138
139 fn verify_epoch(&self, epoch: EpochId) -> IotaResult;
140
141 fn timestamp(&self) -> SystemTime;
142
143 #[cfg(not(target_arch = "wasm32"))]
144 fn report_checkpoint_age(&self, metrics: &Histogram);
145
146 fn parse_version_specific_data(
147 &self,
148 config: &ProtocolConfig,
149 ) -> Result<Option<CheckpointVersionSpecificData>>;
150}
151
152impl CheckpointSummaryExt for CheckpointSummary {
153 fn new_with_protocol_config(
154 protocol_config: &ProtocolConfig,
155 epoch: EpochId,
156 sequence_number: CheckpointSequenceNumber,
157 network_total_transactions: u64,
158 transactions: &CheckpointContents,
159 previous_digest: Option<CheckpointDigest>,
160 epoch_rolling_gas_cost_summary: GasCostSummary,
161 end_of_epoch_data: Option<EndOfEpochData>,
162 timestamp_ms: CheckpointTimestamp,
163 randomness_rounds: Vec<RandomnessRound>,
164 ) -> Self {
165 let contents_digest = transactions.digest();
166
167 let version_specific_data =
168 match protocol_config.checkpoint_summary_version_specific_data_as_option() {
169 None | Some(0) => Vec::new(),
170 Some(1) => bcs::to_bytes(&CheckpointVersionSpecificData::V1(
171 CheckpointVersionSpecificDataV1 { randomness_rounds },
172 ))
173 .expect("version specific data should serialize"),
174 _ => unimplemented!(
175 "unrecognized version_specific_data version for
176 CheckpointSummary"
177 ),
178 };
179
180 Self {
181 epoch,
182 sequence_number,
183 network_total_transactions,
184 contents_digest,
185 previous_digest,
186 epoch_rolling_gas_cost_summary,
187 end_of_epoch_data,
188 timestamp_ms,
189 version_specific_data,
190 checkpoint_commitments: Default::default(),
191 }
192 }
193
194 fn verify_epoch(&self, epoch: EpochId) -> IotaResult {
195 fp_ensure!(
196 self.epoch == epoch,
197 IotaError::WrongEpoch {
198 expected_epoch: epoch,
199 actual_epoch: self.epoch,
200 }
201 );
202 Ok(())
203 }
204
205 fn timestamp(&self) -> SystemTime {
206 UNIX_EPOCH + Duration::from_millis(self.timestamp_ms)
207 }
208
209 #[cfg(not(target_arch = "wasm32"))]
210 fn report_checkpoint_age(&self, metrics: &Histogram) {
211 SystemTime::now()
212 .duration_since(self.timestamp())
213 .map(|latency| {
214 metrics.observe(latency.as_secs_f64());
215 })
216 .tap_err(|err| {
217 warn!(
218 checkpoint_seq = self.sequence_number,
219 "unable to compute checkpoint age: {}", err
220 )
221 })
222 .ok();
223 }
224
225 fn parse_version_specific_data(
226 &self,
227 config: &ProtocolConfig,
228 ) -> Result<Option<CheckpointVersionSpecificData>> {
229 match config.checkpoint_summary_version_specific_data_as_option() {
230 None | Some(0) => Ok(None),
231 Some(1) => Ok(Some(bcs::from_bytes(&self.version_specific_data)?)),
232 _ => unimplemented!("unrecognized version_specific_data version in CheckpointSummary"),
233 }
234 }
235}
236
237pub type CheckpointSummaryEnvelope<S> = Envelope<CheckpointSummary, S>;
246pub type CertifiedCheckpointSummary = CheckpointSummaryEnvelope<AuthorityStrongQuorumSignInfo>;
247pub type SignedCheckpointSummary = CheckpointSummaryEnvelope<AuthoritySignInfo>;
248
249pub type VerifiedCheckpoint = VerifiedEnvelope<CheckpointSummary, AuthorityStrongQuorumSignInfo>;
250pub type TrustedCheckpoint = TrustedEnvelope<CheckpointSummary, AuthorityStrongQuorumSignInfo>;
251
252impl CertifiedCheckpointSummary {
253 #[instrument(level = "trace", skip_all)]
254 pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
255 self.data().verify_epoch(self.auth_sig().epoch)?;
256 self.auth_sig().verify_secure(
257 self.data(),
258 Intent::iota_app(IntentScope::CheckpointSummary),
259 committee,
260 )
261 }
262
263 #[instrument(level = "trace", skip_all)]
269 pub fn batch_verify_authority_signatures(
270 summaries: &[Self],
271 committee: &Committee,
272 ) -> IotaResult {
273 let mut obligation = VerificationObligation::default();
274 for summary in summaries {
275 summary.data().verify_epoch(summary.auth_sig().epoch)?;
276 let idx = obligation.add_message(
277 summary.data(),
278 summary.auth_sig().epoch,
279 Intent::iota_app(IntentScope::CheckpointSummary),
280 );
281 summary
282 .auth_sig()
283 .add_to_verification_obligation(committee, &mut obligation, idx)?;
284 }
285 obligation.verify_all()
286 }
287
288 pub fn try_into_verified(self, committee: &Committee) -> IotaResult<VerifiedCheckpoint> {
289 self.verify_authority_signatures(committee)?;
290 Ok(VerifiedCheckpoint::new_from_verified(self))
291 }
292
293 pub fn verify_with_contents(
294 &self,
295 committee: &Committee,
296 contents: Option<&CheckpointContents>,
297 ) -> IotaResult {
298 self.verify_authority_signatures(committee)?;
299
300 if let Some(contents) = contents {
301 let contents_digest = contents.digest();
302 fp_ensure!(
303 contents_digest == self.data().contents_digest,
304 IotaError::GenericAuthority {
305 error: format!(
306 "Checkpoint contents digest mismatch: summary={:?}, received content digest {:?}, received {} transactions",
307 self.data(),
308 contents_digest,
309 contents.len()
310 )
311 }
312 );
313 }
314
315 Ok(())
316 }
317
318 pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
319 let summary = self.into_data();
320 (summary.sequence_number, summary)
321 }
322
323 pub fn get_validator_signature(self) -> AggregateAuthoritySignature {
324 self.auth_sig().signature.clone()
325 }
326}
327
328impl SignedCheckpointSummary {
329 #[instrument(level = "trace", skip_all)]
330 pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
331 self.data().verify_epoch(self.auth_sig().epoch)?;
332 self.auth_sig().verify_secure(
333 self.data(),
334 Intent::iota_app(IntentScope::CheckpointSummary),
335 committee,
336 )
337 }
338
339 pub fn try_into_verified(
340 self,
341 committee: &Committee,
342 ) -> IotaResult<VerifiedEnvelope<CheckpointSummary, AuthoritySignInfo>> {
343 self.verify_authority_signatures(committee)?;
344 Ok(VerifiedEnvelope::<CheckpointSummary, AuthoritySignInfo>::new_from_verified(self))
345 }
346}
347
348impl VerifiedCheckpoint {
349 pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
350 self.into_inner().into_summary_and_sequence()
351 }
352}
353
354#[derive(Clone, Debug, Serialize, Deserialize)]
357pub struct CheckpointSignatureMessage {
358 pub summary: SignedCheckpointSummary,
359}
360
361impl CheckpointSignatureMessage {
362 pub fn verify(&self, committee: &Committee) -> IotaResult {
363 self.summary.verify_authority_signatures(committee)
364 }
365}
366
367fn execution_digests(info: &CheckpointTransactionInfo) -> ExecutionDigests {
368 ExecutionDigests {
369 transaction: info.transaction,
370 effects: info.effects,
371 }
372}
373
374mod checkpoint_contents_ext {
375 pub trait Sealed {}
376 impl Sealed for super::CheckpointContents {}
377}
378
379pub trait CheckpointContentsExt: Sized + checkpoint_contents_ext::Sealed {
383 fn new_with_digests_and_signatures(
384 contents: impl IntoIterator<Item = ExecutionDigests>,
385 user_signatures: Vec<Vec<UserSignature>>,
386 ) -> Self;
387
388 fn new_with_causally_ordered_execution_data<'a>(
389 contents: impl IntoIterator<Item = &'a VerifiedExecutionData>,
390 ) -> Self;
391
392 fn new_with_digests_only_for_tests(
393 contents: impl IntoIterator<Item = ExecutionDigests>,
394 ) -> Self;
395
396 fn iter(&self) -> impl DoubleEndedIterator<Item = ExecutionDigests> + ExactSizeIterator + '_;
397
398 fn into_iter_with_signatures(
399 self,
400 ) -> impl Iterator<Item = (ExecutionDigests, Vec<UserSignature>)>;
401
402 fn enumerate_transactions(
405 &self,
406 ckpt: &CheckpointSummary,
407 ) -> impl Iterator<Item = (u64, ExecutionDigests)> + '_;
408}
409
410impl CheckpointContentsExt for CheckpointContents {
411 fn new_with_digests_and_signatures(
412 contents: impl IntoIterator<Item = ExecutionDigests>,
413 user_signatures: Vec<Vec<UserSignature>>,
414 ) -> Self {
415 let transactions: Vec<_> = contents.into_iter().collect();
416 assert_eq!(transactions.len(), user_signatures.len());
417 Self::new_v1(CheckpointContentsV1::new(
418 transactions
419 .into_iter()
420 .zip(user_signatures)
421 .map(|(digests, signatures)| CheckpointTransactionInfo {
422 transaction: digests.transaction,
423 effects: digests.effects,
424 signatures,
425 })
426 .collect(),
427 ))
428 }
429
430 fn new_with_causally_ordered_execution_data<'a>(
431 contents: impl IntoIterator<Item = &'a VerifiedExecutionData>,
432 ) -> Self {
433 Self::new_v1(CheckpointContentsV1::new(
434 contents
435 .into_iter()
436 .map(|data| {
437 let digests = data.digests();
438 CheckpointTransactionInfo {
439 transaction: digests.transaction,
440 effects: digests.effects,
441 signatures: data.transaction.inner().data().signatures().to_owned(),
442 }
443 })
444 .collect(),
445 ))
446 }
447
448 fn new_with_digests_only_for_tests(
449 contents: impl IntoIterator<Item = ExecutionDigests>,
450 ) -> Self {
451 Self::new_v1(CheckpointContentsV1::new(
452 contents
453 .into_iter()
454 .map(|digests| CheckpointTransactionInfo {
455 transaction: digests.transaction,
456 effects: digests.effects,
457 signatures: Vec::new(),
458 })
459 .collect(),
460 ))
461 }
462
463 fn iter(&self) -> impl DoubleEndedIterator<Item = ExecutionDigests> + ExactSizeIterator + '_ {
464 self.transactions().iter().map(execution_digests)
465 }
466
467 fn into_iter_with_signatures(
468 self,
469 ) -> impl Iterator<Item = (ExecutionDigests, Vec<UserSignature>)> {
470 match self {
471 CheckpointContents::V1(v1) => v1.into_transactions().into_iter().map(|info| {
472 let digests = execution_digests(&info);
473 (digests, info.signatures)
474 }),
475 _ => unimplemented!("a new CheckpointContents variant was added and must be handled"),
476 }
477 }
478
479 fn enumerate_transactions(
480 &self,
481 ckpt: &CheckpointSummary,
482 ) -> impl Iterator<Item = (u64, ExecutionDigests)> + '_ {
483 let start = ckpt.network_total_transactions - self.len() as u64;
484
485 (0u64..)
486 .zip(self.iter())
487 .map(move |(i, digests)| (i + start, digests))
488 }
489}
490
491#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
498pub struct FullCheckpointContents {
499 transactions: Vec<ExecutionData>,
500 user_signatures: Vec<Vec<UserSignature>>,
504}
505
506impl FullCheckpointContents {
507 pub fn new_with_causally_ordered_transactions<T>(contents: T) -> Self
508 where
509 T: IntoIterator<Item = ExecutionData>,
510 {
511 let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
512 .into_iter()
513 .map(|data| {
514 let sig = data.transaction.data().signatures().to_owned();
515 (data, sig)
516 })
517 .unzip();
518 assert_eq!(transactions.len(), user_signatures.len());
519 Self {
520 transactions,
521 user_signatures,
522 }
523 }
524
525 pub fn from_contents_and_execution_data(
526 contents: CheckpointContents,
527 execution_data: impl Iterator<Item = ExecutionData>,
528 ) -> Self {
529 let transactions: Vec<_> = execution_data.collect();
530 let user_signatures = contents
531 .into_iter_with_signatures()
532 .map(|(_, signatures)| signatures)
533 .collect();
534 Self {
535 transactions,
536 user_signatures,
537 }
538 }
539
540 pub fn try_from_checkpoint_contents<S>(
541 store: S,
542 contents: CheckpointContents,
543 ) -> Result<Option<Self>, crate::storage::error::Error>
544 where
545 S: ReadStore,
546 {
547 let (digests, user_signatures): (Vec<_>, Vec<_>) =
548 contents.into_iter_with_signatures().unzip();
549 let mut transactions = Vec::with_capacity(digests.len());
550 for tx in &digests {
551 if let (Some(t), Some(e)) = (
552 store.try_get_transaction(&tx.transaction)?,
553 store.try_get_transaction_effects(&tx.transaction)?,
554 ) {
555 transactions.push(ExecutionData::new((*t).clone().into_inner(), e))
556 } else {
557 return Ok(None);
558 }
559 }
560 Ok(Some(Self {
561 transactions,
562 user_signatures,
563 }))
564 }
565
566 pub fn iter(&self) -> Iter<'_, ExecutionData> {
567 self.transactions.iter()
568 }
569
570 pub fn verify_digests(&self, digest: CheckpointContentsDigest) -> Result<()> {
574 let self_digest = self.checkpoint_contents().digest();
575 fp_ensure!(
576 digest == self_digest,
577 anyhow::anyhow!(
578 "checkpoint contents digest {self_digest} does not match expected digest {digest}"
579 )
580 );
581 for tx in self.iter() {
582 let transaction_digest = tx.transaction.digest();
583 fp_ensure!(
584 tx.effects.transaction_digest() == transaction_digest,
585 anyhow::anyhow!(
586 "transaction digest {transaction_digest} does not match expected digest {}",
587 tx.effects.transaction_digest()
588 )
589 );
590 }
591 Ok(())
592 }
593
594 pub fn checkpoint_contents(&self) -> CheckpointContents {
595 CheckpointContents::new_with_digests_and_signatures(
596 self.transactions.iter().map(|tx| tx.digests()),
597 self.user_signatures.clone(),
598 )
599 }
600
601 pub fn into_checkpoint_contents(self) -> CheckpointContents {
602 let digests: Vec<_> = self.transactions.iter().map(|tx| tx.digests()).collect();
603 CheckpointContents::new_with_digests_and_signatures(digests, self.user_signatures)
604 }
605
606 pub fn size(&self) -> usize {
607 self.transactions.len()
608 }
609
610 pub fn random_for_testing() -> Self {
611 let (a, key): (_, AccountKeyPair) = get_key_pair();
612 let transaction = TransactionEnvelope::from_data_and_signer(
613 Transaction::new_transfer(
614 a,
615 random_object_ref(),
616 a,
617 random_object_ref(),
618 100000000000,
619 100,
620 ),
621 vec![&key],
622 );
623 let effects = TestEffectsBuilder::new(transaction.data()).build();
624 let exe_data = ExecutionData {
625 transaction,
626 effects,
627 };
628 FullCheckpointContents::new_with_causally_ordered_transactions(vec![exe_data])
629 }
630}
631
632impl IntoIterator for FullCheckpointContents {
633 type Item = ExecutionData;
634 type IntoIter = std::vec::IntoIter<Self::Item>;
635
636 fn into_iter(self) -> Self::IntoIter {
637 self.transactions.into_iter()
638 }
639}
640
641#[derive(Clone, Debug, PartialEq, Eq)]
642pub struct VerifiedCheckpointContents {
643 transactions: Vec<VerifiedExecutionData>,
644 user_signatures: Vec<Vec<UserSignature>>,
648}
649
650impl VerifiedCheckpointContents {
651 pub fn new_unchecked(contents: FullCheckpointContents) -> Self {
652 Self {
653 transactions: contents
654 .transactions
655 .into_iter()
656 .map(VerifiedExecutionData::new_unchecked)
657 .collect(),
658 user_signatures: contents.user_signatures,
659 }
660 }
661
662 pub fn iter(&self) -> Iter<'_, VerifiedExecutionData> {
663 self.transactions.iter()
664 }
665
666 pub fn transactions(&self) -> &[VerifiedExecutionData] {
667 &self.transactions
668 }
669
670 pub fn into_inner(self) -> FullCheckpointContents {
671 FullCheckpointContents {
672 transactions: self
673 .transactions
674 .into_iter()
675 .map(|tx| tx.into_inner())
676 .collect(),
677 user_signatures: self.user_signatures,
678 }
679 }
680
681 pub fn into_checkpoint_contents(self) -> CheckpointContents {
682 self.into_inner().into_checkpoint_contents()
683 }
684
685 pub fn into_checkpoint_contents_digest(self) -> CheckpointContentsDigest {
686 self.into_inner().into_checkpoint_contents().digest()
687 }
688
689 pub fn num_of_transactions(&self) -> usize {
690 self.transactions.len()
691 }
692}
693
694#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
697pub enum CheckpointVersionSpecificData {
698 V1(CheckpointVersionSpecificDataV1),
699}
700
701impl CheckpointVersionSpecificData {
702 pub fn as_v1(&self) -> &CheckpointVersionSpecificDataV1 {
703 match self {
704 Self::V1(v) => v,
705 }
706 }
707
708 pub fn into_v1(self) -> CheckpointVersionSpecificDataV1 {
709 match self {
710 Self::V1(v) => v,
711 }
712 }
713
714 pub fn empty_for_tests() -> CheckpointVersionSpecificData {
715 CheckpointVersionSpecificData::V1(CheckpointVersionSpecificDataV1 {
716 randomness_rounds: Vec::new(),
717 })
718 }
719}
720
721#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
722pub struct CheckpointVersionSpecificDataV1 {
723 pub randomness_rounds: Vec<RandomnessRound>,
726}
727
728#[cfg(test)]
729mod tests {
730 use fastcrypto::traits::KeyPair;
731 use iota_sdk_types::{ConsensusCommitDigest, TransactionDigest, TransactionEffectsDigest};
732 use rand::{SeedableRng, prelude::StdRng};
733
734 use super::*;
735 use crate::{transaction::VerifiedTransaction, utils::make_committee_key};
736
737 const RNG_SEED: [u8; 32] = [
739 21, 23, 199, 200, 234, 250, 252, 178, 94, 15, 202, 178, 62, 186, 88, 137, 233, 192, 130,
740 157, 179, 179, 65, 9, 31, 249, 221, 123, 225, 112, 199, 247,
741 ];
742
743 #[test]
744 fn test_signed_checkpoint() {
745 let mut rng = StdRng::from_seed(RNG_SEED);
746 let (keys, committee) = make_committee_key(&mut rng);
747 let (_, committee2) = make_committee_key(&mut rng);
748
749 let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
750
751 let signed_checkpoints: Vec<_> = keys
754 .iter()
755 .map(|k| {
756 let name = k.public().into();
757
758 SignedCheckpointSummary::new(
759 committee.epoch,
760 CheckpointSummary::new_with_protocol_config(
761 &ProtocolConfig::get_for_max_version_UNSAFE(),
762 committee.epoch,
763 1,
764 0,
765 &set,
766 None,
767 GasCostSummary::default(),
768 None,
769 0,
770 Vec::new(),
771 ),
772 k,
773 name,
774 )
775 })
776 .collect();
777
778 signed_checkpoints.iter().for_each(|c| {
779 c.verify_authority_signatures(&committee)
780 .expect("signature ok")
781 });
782
783 signed_checkpoints
785 .iter()
786 .for_each(|c| assert!(c.verify_authority_signatures(&committee2).is_err()));
787 }
788
789 #[test]
790 fn test_certified_checkpoint() {
791 let mut rng = StdRng::from_seed(RNG_SEED);
792 let (keys, committee) = make_committee_key(&mut rng);
793
794 let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
795
796 let summary = CheckpointSummary::new_with_protocol_config(
797 &ProtocolConfig::get_for_max_version_UNSAFE(),
798 committee.epoch,
799 1,
800 0,
801 &set,
802 None,
803 GasCostSummary::default(),
804 None,
805 0,
806 Vec::new(),
807 );
808
809 let sign_infos: Vec<_> = keys
810 .iter()
811 .map(|k| {
812 let name = k.public().into();
813
814 SignedCheckpointSummary::sign(committee.epoch, &summary, k, name)
815 })
816 .collect();
817
818 let checkpoint_cert =
819 CertifiedCheckpointSummary::new(summary, sign_infos, &committee).expect("Cert is OK");
820
821 assert!(
823 checkpoint_cert
824 .verify_with_contents(&committee, Some(&set))
825 .is_ok()
826 );
827
828 let signed_checkpoints: Vec<_> = keys
830 .iter()
831 .map(|k| {
832 let name = k.public().into();
833 let set = CheckpointContents::new_with_digests_only_for_tests([
834 ExecutionDigests::random(),
835 ]);
836
837 SignedCheckpointSummary::new(
838 committee.epoch,
839 CheckpointSummary::new_with_protocol_config(
840 &ProtocolConfig::get_for_max_version_UNSAFE(),
841 committee.epoch,
842 1,
843 0,
844 &set,
845 None,
846 GasCostSummary::default(),
847 None,
848 0,
849 Vec::new(),
850 ),
851 k,
852 name,
853 )
854 })
855 .collect();
856
857 let summary = signed_checkpoints[0].data().clone();
858 let sign_infos = signed_checkpoints
859 .into_iter()
860 .map(|v| v.into_sig())
861 .collect();
862 assert!(
863 CertifiedCheckpointSummary::new(summary, sign_infos, &committee)
864 .unwrap()
865 .verify_authority_signatures(&committee)
866 .is_err()
867 )
868 }
869
870 #[test]
871 fn test_batch_verify_certified_checkpoints() {
872 let mut rng = StdRng::from_seed(RNG_SEED);
873 let (keys, committee) = make_committee_key(&mut rng);
874
875 let summary_for_sequence_number = |sequence_number| {
876 let set =
877 CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
878 CheckpointSummary::new_with_protocol_config(
879 &ProtocolConfig::get_for_max_version_UNSAFE(),
880 committee.epoch,
881 sequence_number,
882 0,
883 &set,
884 None,
885 GasCostSummary::default(),
886 None,
887 0,
888 Vec::new(),
889 )
890 };
891 let certify = |summary: CheckpointSummary| {
892 let sign_infos = keys
893 .iter()
894 .map(|k| {
895 SignedCheckpointSummary::sign(committee.epoch, &summary, k, k.public().into())
896 })
897 .collect();
898 CertifiedCheckpointSummary::new(summary, sign_infos, &committee).expect("cert is OK")
899 };
900
901 let summaries: Vec<_> = (1..=3)
902 .map(|seq| certify(summary_for_sequence_number(seq)))
903 .collect();
904 CertifiedCheckpointSummary::batch_verify_authority_signatures(&summaries, &committee)
905 .expect("signatures ok");
906
907 let mismatched = {
910 let summary = summary_for_sequence_number(4);
911 let sign_infos = keys
912 .iter()
913 .map(|k| {
914 SignedCheckpointSummary::sign(
915 committee.epoch,
916 &summary_for_sequence_number(4),
917 k,
918 k.public().into(),
919 )
920 })
921 .collect();
922 CertifiedCheckpointSummary::new(summary, sign_infos, &committee).expect("cert is OK")
923 };
924 let with_mismatched: Vec<_> = summaries.into_iter().chain([mismatched]).collect();
925 assert!(
926 CertifiedCheckpointSummary::batch_verify_authority_signatures(
927 &with_mismatched,
928 &committee
929 )
930 .is_err()
931 );
932 }
933
934 fn generate_test_checkpoint_summary_from_digest(
939 digest: TransactionDigest,
940 ) -> CheckpointSummary {
941 CheckpointSummary::new_with_protocol_config(
942 &ProtocolConfig::get_for_max_version_UNSAFE(),
943 1,
944 2,
945 10,
946 &CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::new(
947 digest,
948 TransactionEffectsDigest::ZERO,
949 )]),
950 None,
951 GasCostSummary::default(),
952 None,
953 100,
954 Vec::new(),
955 )
956 }
957
958 #[test]
961 fn test_checkpoint_summary_with_different_consensus_digest() {
962 {
965 let t1 = VerifiedTransaction::new_consensus_commit_prologue_v1(
966 1,
967 2,
968 100,
969 ConsensusCommitDigest::default(),
970 Vec::new(),
971 );
972 let t2 = VerifiedTransaction::new_consensus_commit_prologue_v1(
973 1,
974 2,
975 100,
976 ConsensusCommitDigest::default(),
977 Vec::new(),
978 );
979 let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
980 let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
981 assert_eq!(c1.digest(), c2.digest());
982 }
983
984 {
987 let t1 = VerifiedTransaction::new_consensus_commit_prologue_v1(
988 1,
989 2,
990 100,
991 ConsensusCommitDigest::default(),
992 Vec::new(),
993 );
994 let t2 = VerifiedTransaction::new_consensus_commit_prologue_v1(
995 1,
996 2,
997 100,
998 ConsensusCommitDigest::random(),
999 Vec::new(),
1000 );
1001 let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
1002 let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
1003 assert_ne!(c1.digest(), c2.digest());
1004 }
1005 }
1006}