Skip to main content

iota_types/
messages_checkpoint.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use 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    checkpoint::{
16        CheckpointContents, CheckpointSummary, CheckpointTransactionInfo, EndOfEpochData,
17    },
18    crypto::{Intent, IntentScope, UserSignature},
19    gas::GasCostSummary,
20};
21#[cfg(not(target_arch = "wasm32"))]
22use prometheus_filtered::Histogram;
23use serde::{Deserialize, Serialize};
24#[cfg(not(target_arch = "wasm32"))]
25use tap::TapFallible;
26use tracing::instrument;
27#[cfg(not(target_arch = "wasm32"))]
28use tracing::warn;
29
30use crate::{
31    base_types::{ExecutionData, ExecutionDigests, VerifiedExecutionData, random_object_ref},
32    committee::{Committee, EpochId},
33    crypto::{
34        AccountKeyPair, AggregateAuthoritySignature, AuthoritySignInfo, AuthoritySignInfoTrait,
35        AuthorityStrongQuorumSignInfo, default_hash, get_key_pair,
36    },
37    effects::{TestEffectsBuilder, TransactionEffectsAPI},
38    error::{IotaError, IotaResult},
39    global_state_hash::GlobalStateHash,
40    message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope},
41    storage::ReadStore,
42    transaction::{Transaction, TransactionData, TransactionDataAPI},
43};
44
45pub type CheckpointSequenceNumber = u64;
46pub type CheckpointTimestamp = u64;
47
48#[derive(Clone, Debug, Serialize, Deserialize)]
49pub struct CheckpointRequest {
50    /// if a sequence number is specified, return the checkpoint with that
51    /// sequence number; otherwise if None returns the latest checkpoint
52    /// stored (authenticated or pending, depending on the value of
53    /// `certified` flag)
54    pub sequence_number: Option<CheckpointSequenceNumber>,
55    // A flag, if true also return the contents of the
56    // checkpoint besides the meta-data.
57    pub request_content: bool,
58    // If true, returns certified checkpoint, otherwise returns pending checkpoint
59    pub certified: bool,
60}
61
62#[expect(clippy::large_enum_variant)]
63#[derive(Clone, Debug, Serialize, Deserialize)]
64pub enum CheckpointSummaryResponse {
65    Certified(CertifiedCheckpointSummary),
66    Pending(CheckpointSummary),
67}
68
69impl CheckpointSummaryResponse {
70    pub fn content_digest(&self) -> CheckpointContentsDigest {
71        match self {
72            Self::Certified(s) => s.content_digest,
73            Self::Pending(s) => s.content_digest,
74        }
75    }
76}
77
78#[derive(Clone, Debug, Serialize, Deserialize)]
79pub struct CheckpointResponse {
80    pub checkpoint: Option<CheckpointSummaryResponse>,
81    pub contents: Option<CheckpointContents>,
82}
83
84// The constituent parts of checkpoints, signed and certified
85
86/// The Sha256 digest of an EllipticCurveMultisetHash committing to the live
87/// object set.
88#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
89pub struct ECMHLiveObjectSetDigest {
90    pub digest: Digest,
91}
92
93impl From<fastcrypto::hash::Digest<32>> for ECMHLiveObjectSetDigest {
94    fn from(digest: fastcrypto::hash::Digest<32>) -> Self {
95        Self {
96            digest: Digest::new(digest.digest),
97        }
98    }
99}
100
101impl Default for ECMHLiveObjectSetDigest {
102    fn default() -> Self {
103        GlobalStateHash::default().digest().into()
104    }
105}
106
107impl Message for CheckpointSummary {
108    type DigestType = CheckpointDigest;
109    const SCOPE: IntentScope = IntentScope::CheckpointSummary;
110
111    fn digest(&self) -> Self::DigestType {
112        CheckpointDigest::new(default_hash(self))
113    }
114}
115
116mod checkpoint_summary_ext {
117    pub trait Sealed {}
118    impl Sealed for super::CheckpointSummary {}
119}
120
121/// Node-only helpers for [`CheckpointSummary`], which is defined in
122/// `iota_sdk_types`. These live on an extension trait because inherent methods
123/// cannot be added to a type that is foreign to this crate.
124pub trait CheckpointSummaryExt: Sized + checkpoint_summary_ext::Sealed {
125    fn new_with_protocol_config(
126        protocol_config: &ProtocolConfig,
127        epoch: EpochId,
128        sequence_number: CheckpointSequenceNumber,
129        network_total_transactions: u64,
130        transactions: &CheckpointContents,
131        previous_digest: Option<CheckpointDigest>,
132        epoch_rolling_gas_cost_summary: GasCostSummary,
133        end_of_epoch_data: Option<EndOfEpochData>,
134        timestamp_ms: CheckpointTimestamp,
135        randomness_rounds: Vec<RandomnessRound>,
136    ) -> Self;
137
138    fn verify_epoch(&self, epoch: EpochId) -> IotaResult;
139
140    fn timestamp(&self) -> SystemTime;
141
142    #[cfg(not(target_arch = "wasm32"))]
143    fn report_checkpoint_age(&self, metrics: &Histogram);
144
145    fn parse_version_specific_data(
146        &self,
147        config: &ProtocolConfig,
148    ) -> Result<Option<CheckpointVersionSpecificData>>;
149}
150
151impl CheckpointSummaryExt for CheckpointSummary {
152    fn new_with_protocol_config(
153        protocol_config: &ProtocolConfig,
154        epoch: EpochId,
155        sequence_number: CheckpointSequenceNumber,
156        network_total_transactions: u64,
157        transactions: &CheckpointContents,
158        previous_digest: Option<CheckpointDigest>,
159        epoch_rolling_gas_cost_summary: GasCostSummary,
160        end_of_epoch_data: Option<EndOfEpochData>,
161        timestamp_ms: CheckpointTimestamp,
162        randomness_rounds: Vec<RandomnessRound>,
163    ) -> Self {
164        let content_digest = transactions.digest();
165
166        let version_specific_data =
167            match protocol_config.checkpoint_summary_version_specific_data_as_option() {
168                None | Some(0) => Vec::new(),
169                Some(1) => bcs::to_bytes(&CheckpointVersionSpecificData::V1(
170                    CheckpointVersionSpecificDataV1 { randomness_rounds },
171                ))
172                .expect("version specific data should serialize"),
173                _ => unimplemented!(
174                    "unrecognized version_specific_data version for
175    CheckpointSummary"
176                ),
177            };
178
179        Self {
180            epoch,
181            sequence_number,
182            network_total_transactions,
183            content_digest,
184            previous_digest,
185            epoch_rolling_gas_cost_summary,
186            end_of_epoch_data,
187            timestamp_ms,
188            version_specific_data,
189            checkpoint_commitments: Default::default(),
190        }
191    }
192
193    fn verify_epoch(&self, epoch: EpochId) -> IotaResult {
194        fp_ensure!(
195            self.epoch == epoch,
196            IotaError::WrongEpoch {
197                expected_epoch: epoch,
198                actual_epoch: self.epoch,
199            }
200        );
201        Ok(())
202    }
203
204    fn timestamp(&self) -> SystemTime {
205        UNIX_EPOCH + Duration::from_millis(self.timestamp_ms)
206    }
207
208    #[cfg(not(target_arch = "wasm32"))]
209    fn report_checkpoint_age(&self, metrics: &Histogram) {
210        SystemTime::now()
211            .duration_since(self.timestamp())
212            .map(|latency| {
213                metrics.observe(latency.as_secs_f64());
214            })
215            .tap_err(|err| {
216                warn!(
217                    checkpoint_seq = self.sequence_number,
218                    "unable to compute checkpoint age: {}", err
219                )
220            })
221            .ok();
222    }
223
224    fn parse_version_specific_data(
225        &self,
226        config: &ProtocolConfig,
227    ) -> Result<Option<CheckpointVersionSpecificData>> {
228        match config.checkpoint_summary_version_specific_data_as_option() {
229            None | Some(0) => Ok(None),
230            Some(1) => Ok(Some(bcs::from_bytes(&self.version_specific_data)?)),
231            _ => unimplemented!("unrecognized version_specific_data version in CheckpointSummary"),
232        }
233    }
234}
235
236// Checkpoints are signed by an authority and 2f+1 form a
237// certificate that others can use to catch up. The actual
238// content of the digest must at the very least commit to
239// the set of transactions contained in the certificate but
240// we might extend this to contain roots of merkle trees,
241// or other authenticated data structures to support light
242// clients and more efficient sync protocols.
243
244pub type CheckpointSummaryEnvelope<S> = Envelope<CheckpointSummary, S>;
245pub type CertifiedCheckpointSummary = CheckpointSummaryEnvelope<AuthorityStrongQuorumSignInfo>;
246pub type SignedCheckpointSummary = CheckpointSummaryEnvelope<AuthoritySignInfo>;
247
248pub type VerifiedCheckpoint = VerifiedEnvelope<CheckpointSummary, AuthorityStrongQuorumSignInfo>;
249pub type TrustedCheckpoint = TrustedEnvelope<CheckpointSummary, AuthorityStrongQuorumSignInfo>;
250
251impl CertifiedCheckpointSummary {
252    #[instrument(level = "trace", skip_all)]
253    pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
254        self.data().verify_epoch(self.auth_sig().epoch)?;
255        self.auth_sig().verify_secure(
256            self.data(),
257            Intent::iota_app(IntentScope::CheckpointSummary),
258            committee,
259        )
260    }
261
262    pub fn try_into_verified(self, committee: &Committee) -> IotaResult<VerifiedCheckpoint> {
263        self.verify_authority_signatures(committee)?;
264        Ok(VerifiedCheckpoint::new_from_verified(self))
265    }
266
267    pub fn verify_with_contents(
268        &self,
269        committee: &Committee,
270        contents: Option<&CheckpointContents>,
271    ) -> IotaResult {
272        self.verify_authority_signatures(committee)?;
273
274        if let Some(contents) = contents {
275            let content_digest = contents.digest();
276            fp_ensure!(
277                content_digest == self.data().content_digest,
278                IotaError::GenericAuthority {
279                    error: format!(
280                        "Checkpoint contents digest mismatch: summary={:?}, received content digest {:?}, received {} transactions",
281                        self.data(),
282                        content_digest,
283                        contents.len()
284                    )
285                }
286            );
287        }
288
289        Ok(())
290    }
291
292    pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
293        let summary = self.into_data();
294        (summary.sequence_number, summary)
295    }
296
297    pub fn get_validator_signature(self) -> AggregateAuthoritySignature {
298        self.auth_sig().signature.clone()
299    }
300}
301
302impl SignedCheckpointSummary {
303    #[instrument(level = "trace", skip_all)]
304    pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
305        self.data().verify_epoch(self.auth_sig().epoch)?;
306        self.auth_sig().verify_secure(
307            self.data(),
308            Intent::iota_app(IntentScope::CheckpointSummary),
309            committee,
310        )
311    }
312
313    pub fn try_into_verified(
314        self,
315        committee: &Committee,
316    ) -> IotaResult<VerifiedEnvelope<CheckpointSummary, AuthoritySignInfo>> {
317        self.verify_authority_signatures(committee)?;
318        Ok(VerifiedEnvelope::<CheckpointSummary, AuthoritySignInfo>::new_from_verified(self))
319    }
320}
321
322impl VerifiedCheckpoint {
323    pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
324        self.into_inner().into_summary_and_sequence()
325    }
326}
327
328/// This is a message validators publish to consensus in order to sign
329/// checkpoint
330#[derive(Clone, Debug, Serialize, Deserialize)]
331pub struct CheckpointSignatureMessage {
332    pub summary: SignedCheckpointSummary,
333}
334
335impl CheckpointSignatureMessage {
336    pub fn verify(&self, committee: &Committee) -> IotaResult {
337        self.summary.verify_authority_signatures(committee)
338    }
339}
340
341fn execution_digests(info: &CheckpointTransactionInfo) -> ExecutionDigests {
342    ExecutionDigests {
343        transaction: info.transaction,
344        effects: info.effects,
345    }
346}
347
348mod checkpoint_contents_ext {
349    pub trait Sealed {}
350    impl Sealed for super::CheckpointContents {}
351}
352
353/// Node-only helpers for [`CheckpointContents`], which is defined in
354/// `iota_sdk_types`. They bridge the node's `ExecutionDigests` representation
355/// to the SDK type's parallel [`CheckpointTransactionInfo`] form.
356pub trait CheckpointContentsExt: Sized + checkpoint_contents_ext::Sealed {
357    fn new_with_digests_and_signatures(
358        contents: impl IntoIterator<Item = ExecutionDigests>,
359        user_signatures: Vec<Vec<UserSignature>>,
360    ) -> Self;
361
362    fn new_with_causally_ordered_execution_data<'a>(
363        contents: impl IntoIterator<Item = &'a VerifiedExecutionData>,
364    ) -> Self;
365
366    fn new_with_digests_only_for_tests(
367        contents: impl IntoIterator<Item = ExecutionDigests>,
368    ) -> Self;
369
370    fn iter(&self) -> impl DoubleEndedIterator<Item = ExecutionDigests> + ExactSizeIterator + '_;
371
372    fn into_iter_with_signatures(
373        self,
374    ) -> impl Iterator<Item = (ExecutionDigests, Vec<UserSignature>)>;
375
376    /// Enumerate the transactions in the contents, pairing each with its index
377    /// in the global ordering of executed transactions since genesis.
378    fn enumerate_transactions(
379        &self,
380        ckpt: &CheckpointSummary,
381    ) -> impl Iterator<Item = (u64, ExecutionDigests)> + '_;
382}
383
384impl CheckpointContentsExt for CheckpointContents {
385    fn new_with_digests_and_signatures(
386        contents: impl IntoIterator<Item = ExecutionDigests>,
387        user_signatures: Vec<Vec<UserSignature>>,
388    ) -> Self {
389        let transactions: Vec<_> = contents.into_iter().collect();
390        assert_eq!(transactions.len(), user_signatures.len());
391        Self::new_v1(CheckpointContentsV1::new(
392            transactions
393                .into_iter()
394                .zip(user_signatures)
395                .map(|(digests, signatures)| CheckpointTransactionInfo {
396                    transaction: digests.transaction,
397                    effects: digests.effects,
398                    signatures,
399                })
400                .collect(),
401        ))
402    }
403
404    fn new_with_causally_ordered_execution_data<'a>(
405        contents: impl IntoIterator<Item = &'a VerifiedExecutionData>,
406    ) -> Self {
407        Self::new_v1(CheckpointContentsV1::new(
408            contents
409                .into_iter()
410                .map(|data| {
411                    let digests = data.digests();
412                    CheckpointTransactionInfo {
413                        transaction: digests.transaction,
414                        effects: digests.effects,
415                        signatures: data.transaction.inner().data().signatures().to_owned(),
416                    }
417                })
418                .collect(),
419        ))
420    }
421
422    fn new_with_digests_only_for_tests(
423        contents: impl IntoIterator<Item = ExecutionDigests>,
424    ) -> Self {
425        Self::new_v1(CheckpointContentsV1::new(
426            contents
427                .into_iter()
428                .map(|digests| CheckpointTransactionInfo {
429                    transaction: digests.transaction,
430                    effects: digests.effects,
431                    signatures: Vec::new(),
432                })
433                .collect(),
434        ))
435    }
436
437    fn iter(&self) -> impl DoubleEndedIterator<Item = ExecutionDigests> + ExactSizeIterator + '_ {
438        self.transactions().iter().map(execution_digests)
439    }
440
441    fn into_iter_with_signatures(
442        self,
443    ) -> impl Iterator<Item = (ExecutionDigests, Vec<UserSignature>)> {
444        match self {
445            CheckpointContents::V1(v1) => v1.into_transactions().into_iter().map(|info| {
446                let digests = execution_digests(&info);
447                (digests, info.signatures)
448            }),
449            _ => unimplemented!("a new CheckpointContents variant was added and must be handled"),
450        }
451    }
452
453    fn enumerate_transactions(
454        &self,
455        ckpt: &CheckpointSummary,
456    ) -> impl Iterator<Item = (u64, ExecutionDigests)> + '_ {
457        let start = ckpt.network_total_transactions - self.len() as u64;
458
459        (0u64..)
460            .zip(self.iter())
461            .map(move |(i, digests)| (i + start, digests))
462    }
463}
464
465/// Same as CheckpointContents, but contains full contents of all Transactions
466/// and TransactionEffects associated with the checkpoint.
467// NOTE: This data structure is used for state sync of checkpoints. Therefore we attempt
468// to estimate its size in CheckpointBuilder in order to limit the maximum serialized
469// size of a checkpoint sent over the network. If this struct is modified,
470// CheckpointBuilder::split_checkpoint_chunks should also be updated accordingly.
471#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
472pub struct FullCheckpointContents {
473    transactions: Vec<ExecutionData>,
474    /// This field 'pins' user signatures for the checkpoint
475    /// The length of this vector is same as length of transactions vector
476    /// System transactions has empty signatures
477    user_signatures: Vec<Vec<UserSignature>>,
478}
479
480impl FullCheckpointContents {
481    pub fn new_with_causally_ordered_transactions<T>(contents: T) -> Self
482    where
483        T: IntoIterator<Item = ExecutionData>,
484    {
485        let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
486            .into_iter()
487            .map(|data| {
488                let sig = data.transaction.data().signatures().to_owned();
489                (data, sig)
490            })
491            .unzip();
492        assert_eq!(transactions.len(), user_signatures.len());
493        Self {
494            transactions,
495            user_signatures,
496        }
497    }
498
499    pub fn from_contents_and_execution_data(
500        contents: CheckpointContents,
501        execution_data: impl Iterator<Item = ExecutionData>,
502    ) -> Self {
503        let transactions: Vec<_> = execution_data.collect();
504        let user_signatures = contents
505            .into_iter_with_signatures()
506            .map(|(_, signatures)| signatures)
507            .collect();
508        Self {
509            transactions,
510            user_signatures,
511        }
512    }
513
514    pub fn try_from_checkpoint_contents<S>(
515        store: S,
516        contents: CheckpointContents,
517    ) -> Result<Option<Self>, crate::storage::error::Error>
518    where
519        S: ReadStore,
520    {
521        let (digests, user_signatures): (Vec<_>, Vec<_>) =
522            contents.into_iter_with_signatures().unzip();
523        let mut transactions = Vec::with_capacity(digests.len());
524        for tx in &digests {
525            if let (Some(t), Some(e)) = (
526                store.try_get_transaction(&tx.transaction)?,
527                store.try_get_transaction_effects(&tx.transaction)?,
528            ) {
529                transactions.push(ExecutionData::new((*t).clone().into_inner(), e))
530            } else {
531                return Ok(None);
532            }
533        }
534        Ok(Some(Self {
535            transactions,
536            user_signatures,
537        }))
538    }
539
540    pub fn iter(&self) -> Iter<'_, ExecutionData> {
541        self.transactions.iter()
542    }
543
544    /// Verifies that this checkpoint's digest matches the given digest, and
545    /// that all internal Transaction and TransactionEffects digests are
546    /// consistent.
547    pub fn verify_digests(&self, digest: CheckpointContentsDigest) -> Result<()> {
548        let self_digest = self.checkpoint_contents().digest();
549        fp_ensure!(
550            digest == self_digest,
551            anyhow::anyhow!(
552                "checkpoint contents digest {self_digest} does not match expected digest {digest}"
553            )
554        );
555        for tx in self.iter() {
556            let transaction_digest = tx.transaction.digest();
557            fp_ensure!(
558                tx.effects.transaction_digest() == transaction_digest,
559                anyhow::anyhow!(
560                    "transaction digest {transaction_digest} does not match expected digest {}",
561                    tx.effects.transaction_digest()
562                )
563            );
564        }
565        Ok(())
566    }
567
568    pub fn checkpoint_contents(&self) -> CheckpointContents {
569        CheckpointContents::new_with_digests_and_signatures(
570            self.transactions.iter().map(|tx| tx.digests()),
571            self.user_signatures.clone(),
572        )
573    }
574
575    pub fn into_checkpoint_contents(self) -> CheckpointContents {
576        let digests: Vec<_> = self.transactions.iter().map(|tx| tx.digests()).collect();
577        CheckpointContents::new_with_digests_and_signatures(digests, self.user_signatures)
578    }
579
580    pub fn size(&self) -> usize {
581        self.transactions.len()
582    }
583
584    pub fn random_for_testing() -> Self {
585        let (a, key): (_, AccountKeyPair) = get_key_pair();
586        let transaction = Transaction::from_data_and_signer(
587            TransactionData::new_transfer(
588                a,
589                random_object_ref(),
590                a,
591                random_object_ref(),
592                100000000000,
593                100,
594            ),
595            vec![&key],
596        );
597        let effects = TestEffectsBuilder::new(transaction.data()).build();
598        let exe_data = ExecutionData {
599            transaction,
600            effects,
601        };
602        FullCheckpointContents::new_with_causally_ordered_transactions(vec![exe_data])
603    }
604}
605
606impl IntoIterator for FullCheckpointContents {
607    type Item = ExecutionData;
608    type IntoIter = std::vec::IntoIter<Self::Item>;
609
610    fn into_iter(self) -> Self::IntoIter {
611        self.transactions.into_iter()
612    }
613}
614
615#[derive(Clone, Debug, PartialEq, Eq)]
616pub struct VerifiedCheckpointContents {
617    transactions: Vec<VerifiedExecutionData>,
618    /// This field 'pins' user signatures for the checkpoint
619    /// The length of this vector is same as length of transactions vector
620    /// System transactions has empty signatures
621    user_signatures: Vec<Vec<UserSignature>>,
622}
623
624impl VerifiedCheckpointContents {
625    pub fn new_unchecked(contents: FullCheckpointContents) -> Self {
626        Self {
627            transactions: contents
628                .transactions
629                .into_iter()
630                .map(VerifiedExecutionData::new_unchecked)
631                .collect(),
632            user_signatures: contents.user_signatures,
633        }
634    }
635
636    pub fn iter(&self) -> Iter<'_, VerifiedExecutionData> {
637        self.transactions.iter()
638    }
639
640    pub fn transactions(&self) -> &[VerifiedExecutionData] {
641        &self.transactions
642    }
643
644    pub fn into_inner(self) -> FullCheckpointContents {
645        FullCheckpointContents {
646            transactions: self
647                .transactions
648                .into_iter()
649                .map(|tx| tx.into_inner())
650                .collect(),
651            user_signatures: self.user_signatures,
652        }
653    }
654
655    pub fn into_checkpoint_contents(self) -> CheckpointContents {
656        self.into_inner().into_checkpoint_contents()
657    }
658
659    pub fn into_checkpoint_contents_digest(self) -> CheckpointContentsDigest {
660        self.into_inner().into_checkpoint_contents().digest()
661    }
662
663    pub fn num_of_transactions(&self) -> usize {
664        self.transactions.len()
665    }
666}
667
668/// Holds data in CheckpointSummary that is serialized into the
669/// `version_specific_data` field.
670#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
671pub enum CheckpointVersionSpecificData {
672    V1(CheckpointVersionSpecificDataV1),
673}
674
675impl CheckpointVersionSpecificData {
676    pub fn as_v1(&self) -> &CheckpointVersionSpecificDataV1 {
677        match self {
678            Self::V1(v) => v,
679        }
680    }
681
682    pub fn into_v1(self) -> CheckpointVersionSpecificDataV1 {
683        match self {
684            Self::V1(v) => v,
685        }
686    }
687
688    pub fn empty_for_tests() -> CheckpointVersionSpecificData {
689        CheckpointVersionSpecificData::V1(CheckpointVersionSpecificDataV1 {
690            randomness_rounds: Vec::new(),
691        })
692    }
693}
694
695#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
696pub struct CheckpointVersionSpecificDataV1 {
697    /// Lists the rounds for which RandomnessStateUpdate transactions are
698    /// present in the checkpoint.
699    pub randomness_rounds: Vec<RandomnessRound>,
700}
701
702#[cfg(test)]
703mod tests {
704    use fastcrypto::traits::KeyPair;
705    use iota_sdk_types::{ConsensusCommitDigest, TransactionDigest, TransactionEffectsDigest};
706    use rand::{SeedableRng, prelude::StdRng};
707
708    use super::*;
709    use crate::{transaction::VerifiedTransaction, utils::make_committee_key};
710
711    // TODO use the file name as a seed
712    const RNG_SEED: [u8; 32] = [
713        21, 23, 199, 200, 234, 250, 252, 178, 94, 15, 202, 178, 62, 186, 88, 137, 233, 192, 130,
714        157, 179, 179, 65, 9, 31, 249, 221, 123, 225, 112, 199, 247,
715    ];
716
717    #[test]
718    fn test_signed_checkpoint() {
719        let mut rng = StdRng::from_seed(RNG_SEED);
720        let (keys, committee) = make_committee_key(&mut rng);
721        let (_, committee2) = make_committee_key(&mut rng);
722
723        let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
724
725        // TODO: duplicated in a test below.
726
727        let signed_checkpoints: Vec<_> = keys
728            .iter()
729            .map(|k| {
730                let name = k.public().into();
731
732                SignedCheckpointSummary::new(
733                    committee.epoch,
734                    CheckpointSummary::new_with_protocol_config(
735                        &ProtocolConfig::get_for_max_version_UNSAFE(),
736                        committee.epoch,
737                        1,
738                        0,
739                        &set,
740                        None,
741                        GasCostSummary::default(),
742                        None,
743                        0,
744                        Vec::new(),
745                    ),
746                    k,
747                    name,
748                )
749            })
750            .collect();
751
752        signed_checkpoints.iter().for_each(|c| {
753            c.verify_authority_signatures(&committee)
754                .expect("signature ok")
755        });
756
757        // fails when not signed by member of committee
758        signed_checkpoints
759            .iter()
760            .for_each(|c| assert!(c.verify_authority_signatures(&committee2).is_err()));
761    }
762
763    #[test]
764    fn test_certified_checkpoint() {
765        let mut rng = StdRng::from_seed(RNG_SEED);
766        let (keys, committee) = make_committee_key(&mut rng);
767
768        let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
769
770        let summary = CheckpointSummary::new_with_protocol_config(
771            &ProtocolConfig::get_for_max_version_UNSAFE(),
772            committee.epoch,
773            1,
774            0,
775            &set,
776            None,
777            GasCostSummary::default(),
778            None,
779            0,
780            Vec::new(),
781        );
782
783        let sign_infos: Vec<_> = keys
784            .iter()
785            .map(|k| {
786                let name = k.public().into();
787
788                SignedCheckpointSummary::sign(committee.epoch, &summary, k, name)
789            })
790            .collect();
791
792        let checkpoint_cert =
793            CertifiedCheckpointSummary::new(summary, sign_infos, &committee).expect("Cert is OK");
794
795        // Signature is correct on proposal, and with same transactions
796        assert!(
797            checkpoint_cert
798                .verify_with_contents(&committee, Some(&set))
799                .is_ok()
800        );
801
802        // Make a bad proposal
803        let signed_checkpoints: Vec<_> = keys
804            .iter()
805            .map(|k| {
806                let name = k.public().into();
807                let set = CheckpointContents::new_with_digests_only_for_tests([
808                    ExecutionDigests::random(),
809                ]);
810
811                SignedCheckpointSummary::new(
812                    committee.epoch,
813                    CheckpointSummary::new_with_protocol_config(
814                        &ProtocolConfig::get_for_max_version_UNSAFE(),
815                        committee.epoch,
816                        1,
817                        0,
818                        &set,
819                        None,
820                        GasCostSummary::default(),
821                        None,
822                        0,
823                        Vec::new(),
824                    ),
825                    k,
826                    name,
827                )
828            })
829            .collect();
830
831        let summary = signed_checkpoints[0].data().clone();
832        let sign_infos = signed_checkpoints
833            .into_iter()
834            .map(|v| v.into_sig())
835            .collect();
836        assert!(
837            CertifiedCheckpointSummary::new(summary, sign_infos, &committee)
838                .unwrap()
839                .verify_authority_signatures(&committee)
840                .is_err()
841        )
842    }
843
844    // Generate a CheckpointSummary from the input transaction digest. All the other
845    // fields in the generated CheckpointSummary will be the same. The generated
846    // CheckpointSummary can be used to test how input transaction digest
847    // affects CheckpointSummary.
848    fn generate_test_checkpoint_summary_from_digest(
849        digest: TransactionDigest,
850    ) -> CheckpointSummary {
851        CheckpointSummary::new_with_protocol_config(
852            &ProtocolConfig::get_for_max_version_UNSAFE(),
853            1,
854            2,
855            10,
856            &CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::new(
857                digest,
858                TransactionEffectsDigest::ZERO,
859            )]),
860            None,
861            GasCostSummary::default(),
862            None,
863            100,
864            Vec::new(),
865        )
866    }
867
868    // Tests that ConsensusCommitPrologue with different consensus commit digest
869    // will result in different checkpoint content.
870    #[test]
871    fn test_checkpoint_summary_with_different_consensus_digest() {
872        // First, tests that same consensus commit digest will produce the same
873        // checkpoint content.
874        {
875            let t1 = VerifiedTransaction::new_consensus_commit_prologue_v1(
876                1,
877                2,
878                100,
879                ConsensusCommitDigest::default(),
880                Vec::new(),
881            );
882            let t2 = VerifiedTransaction::new_consensus_commit_prologue_v1(
883                1,
884                2,
885                100,
886                ConsensusCommitDigest::default(),
887                Vec::new(),
888            );
889            let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
890            let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
891            assert_eq!(c1.digest(), c2.digest());
892        }
893
894        // Next, tests that different consensus commit digests will produce the
895        // different checkpoint contents.
896        {
897            let t1 = VerifiedTransaction::new_consensus_commit_prologue_v1(
898                1,
899                2,
900                100,
901                ConsensusCommitDigest::default(),
902                Vec::new(),
903            );
904            let t2 = VerifiedTransaction::new_consensus_commit_prologue_v1(
905                1,
906                2,
907                100,
908                ConsensusCommitDigest::random(),
909                Vec::new(),
910            );
911            let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
912            let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
913            assert_ne!(c1.digest(), c2.digest());
914        }
915    }
916}