sui_types/
messages_checkpoint.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::base_types::{
5    ExecutionData, ExecutionDigests, FullObjectRef, VerifiedExecutionData, random_object_ref,
6};
7use crate::base_types::{ObjectID, SequenceNumber};
8use crate::committee::{EpochId, ProtocolVersion, StakeUnit};
9use crate::crypto::{
10    AccountKeyPair, AggregateAuthoritySignature, AuthoritySignInfo, AuthoritySignInfoTrait,
11    AuthorityStrongQuorumSignInfo, RandomnessRound, default_hash, get_key_pair,
12};
13use crate::digests::{CheckpointArtifactsDigest, Digest, ObjectDigest};
14use crate::effects::{TestEffectsBuilder, TransactionEffects, TransactionEffectsAPI};
15use crate::error::SuiResult;
16use crate::full_checkpoint_content::{Checkpoint, CheckpointData};
17use crate::gas::GasCostSummary;
18use crate::global_state_hash::GlobalStateHash;
19use crate::message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope};
20use crate::signature::GenericSignature;
21use crate::sui_serde::AsProtocolVersion;
22use crate::sui_serde::BigInt;
23use crate::sui_serde::Readable;
24use crate::transaction::{Transaction, TransactionData};
25use crate::{base_types::AuthorityName, committee::Committee, error::SuiErrorKind};
26use anyhow::Result;
27use fastcrypto::hash::Blake2b256;
28use fastcrypto::hash::MultisetHash;
29use fastcrypto::merkle::MerkleTree;
30use mysten_metrics::histogram::Histogram as MystenHistogram;
31use once_cell::sync::OnceCell;
32use prometheus::Histogram;
33use schemars::JsonSchema;
34use serde::{Deserialize, Serialize};
35use serde_with::serde_as;
36use shared_crypto::intent::{Intent, IntentScope};
37use std::collections::{BTreeMap, BTreeSet};
38use std::fmt::{Debug, Display, Formatter};
39use std::slice::Iter;
40use std::time::{Duration, SystemTime, UNIX_EPOCH};
41use sui_protocol_config::ProtocolConfig;
42use tap::TapFallible;
43use tracing::warn;
44
45pub use crate::digests::CheckpointContentsDigest;
46pub use crate::digests::CheckpointDigest;
47
48pub type CheckpointSequenceNumber = u64;
49pub type CheckpointTimestamp = u64;
50
51#[derive(Clone, Debug, Serialize, Deserialize)]
52pub struct CheckpointRequest {
53    /// if a sequence number is specified, return the checkpoint with that sequence number;
54    /// otherwise if None returns the latest authenticated checkpoint stored.
55    pub sequence_number: Option<CheckpointSequenceNumber>,
56    // A flag, if true also return the contents of the
57    // checkpoint besides the meta-data.
58    pub request_content: bool,
59}
60
61#[derive(Clone, Debug, Serialize, Deserialize)]
62pub struct CheckpointRequestV2 {
63    /// if a sequence number is specified, return the checkpoint with that sequence number;
64    /// otherwise if None returns the latest checkpoint stored (authenticated or pending,
65    /// depending on the value of `certified` flag)
66    pub sequence_number: Option<CheckpointSequenceNumber>,
67    // A flag, if true also return the contents of the
68    // checkpoint besides the meta-data.
69    pub request_content: bool,
70    // If true, returns certified checkpoint, otherwise returns pending checkpoint
71    pub certified: bool,
72}
73
74#[allow(clippy::large_enum_variant)]
75#[derive(Clone, Debug, Serialize, Deserialize)]
76pub enum CheckpointSummaryResponse {
77    Certified(CertifiedCheckpointSummary),
78    Pending(CheckpointSummary),
79}
80
81impl CheckpointSummaryResponse {
82    pub fn content_digest(&self) -> CheckpointContentsDigest {
83        match self {
84            Self::Certified(s) => s.content_digest,
85            Self::Pending(s) => s.content_digest,
86        }
87    }
88}
89
90#[allow(clippy::large_enum_variant)]
91#[derive(Clone, Debug, Serialize, Deserialize)]
92pub struct CheckpointResponse {
93    pub checkpoint: Option<CertifiedCheckpointSummary>,
94    pub contents: Option<CheckpointContents>,
95}
96
97#[allow(clippy::large_enum_variant)]
98#[derive(Clone, Debug, Serialize, Deserialize)]
99pub struct CheckpointResponseV2 {
100    pub checkpoint: Option<CheckpointSummaryResponse>,
101    pub contents: Option<CheckpointContents>,
102}
103
104// The constituent parts of checkpoints, signed and certified
105
106/// The Sha256 digest of an EllipticCurveMultisetHash committing to the live object set.
107#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
108pub struct ECMHLiveObjectSetDigest {
109    #[schemars(with = "[u8; 32]")]
110    pub digest: Digest,
111}
112
113impl From<fastcrypto::hash::Digest<32>> for ECMHLiveObjectSetDigest {
114    fn from(digest: fastcrypto::hash::Digest<32>) -> Self {
115        Self {
116            digest: Digest::new(digest.digest),
117        }
118    }
119}
120
121impl Default for ECMHLiveObjectSetDigest {
122    fn default() -> Self {
123        GlobalStateHash::default().digest().into()
124    }
125}
126
127/// CheckpointArtifact is a type that represents various artifacts of a checkpoint.
128/// We hash all the artifacts together to get the checkpoint artifacts digest
129/// that is included in the checkpoint summary.
130#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
131pub enum CheckpointArtifact {
132    /// The post-checkpoint state of all objects modified in the checkpoint.
133    /// It also includes objects that were deleted or wrapped in the checkpoint.
134    ObjectStates(BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>),
135    // In the future, we can add more artifacts e.g., execution digests, events, etc.
136}
137
138impl CheckpointArtifact {
139    pub fn digest(&self) -> SuiResult<Digest> {
140        match self {
141            Self::ObjectStates(object_states) => {
142                let tree = MerkleTree::<Blake2b256>::build_from_unserialized(
143                    object_states
144                        .iter()
145                        .map(|(id, (seq, digest))| (id, seq, digest)),
146                )
147                .map_err(|e| SuiErrorKind::GenericAuthorityError {
148                    error: format!("Failed to build Merkle tree: {}", e),
149                })?;
150                let root = tree.root().bytes();
151                Ok(Digest::new(root))
152            }
153        }
154    }
155
156    pub fn artifact_type(&self) -> &'static str {
157        match self {
158            Self::ObjectStates(_) => "ObjectStates",
159            // Future variants...
160        }
161    }
162}
163
164#[derive(Debug)]
165pub struct CheckpointArtifacts {
166    /// An ordered list of artifacts.
167    artifacts: BTreeSet<CheckpointArtifact>,
168}
169
170impl CheckpointArtifacts {
171    pub fn new() -> Self {
172        Self {
173            artifacts: BTreeSet::new(),
174        }
175    }
176
177    pub fn add_artifact(&mut self, artifact: CheckpointArtifact) -> SuiResult<()> {
178        if self
179            .artifacts
180            .iter()
181            .any(|existing| existing.artifact_type() == artifact.artifact_type())
182        {
183            return Err(SuiErrorKind::GenericAuthorityError {
184                error: format!("Artifact {} already exists", artifact.artifact_type()),
185            }
186            .into());
187        }
188        self.artifacts.insert(artifact);
189        Ok(())
190    }
191
192    pub fn from_object_states(
193        object_states: BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>,
194    ) -> Self {
195        CheckpointArtifacts {
196            artifacts: BTreeSet::from([CheckpointArtifact::ObjectStates(object_states)]),
197        }
198    }
199
200    /// Get the object states if present
201    pub fn object_states(&self) -> SuiResult<&BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>> {
202        self.artifacts
203            .iter()
204            .find(|artifact| matches!(artifact, CheckpointArtifact::ObjectStates(_)))
205            .map(|artifact| match artifact {
206                CheckpointArtifact::ObjectStates(states) => states,
207            })
208            .ok_or(
209                SuiErrorKind::GenericAuthorityError {
210                    error: "Object states not found in checkpoint artifacts".to_string(),
211                }
212                .into(),
213            )
214    }
215
216    pub fn digest(&self) -> SuiResult<CheckpointArtifactsDigest> {
217        // Already sorted by BTreeSet!
218        let digests = self
219            .artifacts
220            .iter()
221            .map(|a| a.digest())
222            .collect::<Result<Vec<_>, _>>()?;
223
224        CheckpointArtifactsDigest::from_artifact_digests(digests)
225    }
226}
227
228impl Default for CheckpointArtifacts {
229    fn default() -> Self {
230        Self::new()
231    }
232}
233
234impl From<&[&TransactionEffects]> for CheckpointArtifacts {
235    fn from(effects: &[&TransactionEffects]) -> Self {
236        let mut latest_object_states = BTreeMap::new();
237        for e in effects {
238            for (id, seq, digest) in e.written() {
239                if let Some((old_seq, _)) = latest_object_states.insert(id, (seq, digest)) {
240                    assert!(
241                        old_seq < seq,
242                        "Object states should be monotonically increasing"
243                    );
244                }
245            }
246        }
247
248        CheckpointArtifacts::from_object_states(latest_object_states)
249    }
250}
251
252impl From<&[TransactionEffects]> for CheckpointArtifacts {
253    fn from(effects: &[TransactionEffects]) -> Self {
254        let effect_refs: Vec<&TransactionEffects> = effects.iter().collect();
255        Self::from(effect_refs.as_slice())
256    }
257}
258
259impl From<&CheckpointData> for CheckpointArtifacts {
260    fn from(checkpoint_data: &CheckpointData) -> Self {
261        let effects = checkpoint_data
262            .transactions
263            .iter()
264            .map(|tx| &tx.effects)
265            .collect::<Vec<_>>();
266
267        Self::from(effects.as_slice())
268    }
269}
270
271impl From<&Checkpoint> for CheckpointArtifacts {
272    fn from(checkpoint: &Checkpoint) -> Self {
273        let effects = checkpoint
274            .transactions
275            .iter()
276            .map(|tx| &tx.effects)
277            .collect::<Vec<_>>();
278
279        Self::from(effects.as_slice())
280    }
281}
282
283#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
284pub enum CheckpointCommitment {
285    ECMHLiveObjectSetDigest(ECMHLiveObjectSetDigest),
286    CheckpointArtifactsDigest(CheckpointArtifactsDigest),
287}
288
289impl From<ECMHLiveObjectSetDigest> for CheckpointCommitment {
290    fn from(d: ECMHLiveObjectSetDigest) -> Self {
291        Self::ECMHLiveObjectSetDigest(d)
292    }
293}
294
295impl From<CheckpointArtifactsDigest> for CheckpointCommitment {
296    fn from(d: CheckpointArtifactsDigest) -> Self {
297        Self::CheckpointArtifactsDigest(d)
298    }
299}
300
301#[serde_as]
302#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
303#[serde(rename_all = "camelCase")]
304pub struct EndOfEpochData {
305    /// next_epoch_committee is `Some` if and only if the current checkpoint is
306    /// the last checkpoint of an epoch.
307    /// Therefore next_epoch_committee can be used to pick the last checkpoint of an epoch,
308    /// which is often useful to get epoch level summary stats like total gas cost of an epoch,
309    /// or the total number of transactions from genesis to the end of an epoch.
310    /// The committee is stored as a vector of validator pub key and stake pairs. The vector
311    /// should be sorted based on the Committee data structure.
312    #[schemars(with = "Vec<(AuthorityName, BigInt<u64>)>")]
313    #[serde_as(as = "Vec<(_, Readable<BigInt<u64>, _>)>")]
314    pub next_epoch_committee: Vec<(AuthorityName, StakeUnit)>,
315
316    /// The protocol version that is in effect during the epoch that starts immediately after this
317    /// checkpoint.
318    #[schemars(with = "AsProtocolVersion")]
319    #[serde_as(as = "Readable<AsProtocolVersion, _>")]
320    pub next_epoch_protocol_version: ProtocolVersion,
321
322    /// Commitments to epoch specific state (e.g. live object set)
323    pub epoch_commitments: Vec<CheckpointCommitment>,
324}
325
326#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
327pub struct CheckpointSummary {
328    pub epoch: EpochId,
329    pub sequence_number: CheckpointSequenceNumber,
330    /// Total number of transactions committed since genesis, including those in this
331    /// checkpoint.
332    pub network_total_transactions: u64,
333    pub content_digest: CheckpointContentsDigest,
334    pub previous_digest: Option<CheckpointDigest>,
335    /// The running total gas costs of all transactions included in the current epoch so far
336    /// until this checkpoint.
337    pub epoch_rolling_gas_cost_summary: GasCostSummary,
338
339    /// Timestamp of the checkpoint - number of milliseconds from the Unix epoch
340    /// Checkpoint timestamps are monotonic, but not strongly monotonic - subsequent
341    /// checkpoints can have same timestamp if they originate from the same underlining consensus commit
342    pub timestamp_ms: CheckpointTimestamp,
343
344    /// Commitments to checkpoint-specific state (e.g. txns in checkpoint, objects read/written in
345    /// checkpoint).
346    pub checkpoint_commitments: Vec<CheckpointCommitment>,
347
348    /// Present only on the final checkpoint of the epoch.
349    pub end_of_epoch_data: Option<EndOfEpochData>,
350
351    /// CheckpointSummary is not an evolvable structure - it must be readable by any version of the
352    /// code. Therefore, in order to allow extensions to be added to CheckpointSummary, we allow
353    /// opaque data to be added to checkpoints which can be deserialized based on the current
354    /// protocol version.
355    ///
356    /// This is implemented with BCS-serialized `CheckpointVersionSpecificData`.
357    pub version_specific_data: Vec<u8>,
358}
359
360impl Message for CheckpointSummary {
361    type DigestType = CheckpointDigest;
362    const SCOPE: IntentScope = IntentScope::CheckpointSummary;
363
364    fn digest(&self) -> Self::DigestType {
365        CheckpointDigest::new(default_hash(self))
366    }
367}
368
369impl CheckpointSummary {
370    pub fn new(
371        protocol_config: &ProtocolConfig,
372        epoch: EpochId,
373        sequence_number: CheckpointSequenceNumber,
374        network_total_transactions: u64,
375        transactions: &CheckpointContents,
376        previous_digest: Option<CheckpointDigest>,
377        epoch_rolling_gas_cost_summary: GasCostSummary,
378        end_of_epoch_data: Option<EndOfEpochData>,
379        timestamp_ms: CheckpointTimestamp,
380        randomness_rounds: Vec<RandomnessRound>,
381        checkpoint_commitments: Vec<CheckpointCommitment>,
382    ) -> CheckpointSummary {
383        let content_digest = *transactions.digest();
384
385        let version_specific_data = match protocol_config
386            .checkpoint_summary_version_specific_data_as_option()
387        {
388            None | Some(0) => Vec::new(),
389            Some(1) => bcs::to_bytes(&CheckpointVersionSpecificData::V1(
390                CheckpointVersionSpecificDataV1 { randomness_rounds },
391            ))
392            .expect("version specific data should serialize"),
393            _ => unimplemented!("unrecognized version_specific_data version for CheckpointSummary"),
394        };
395
396        Self {
397            epoch,
398            sequence_number,
399            network_total_transactions,
400            content_digest,
401            previous_digest,
402            epoch_rolling_gas_cost_summary,
403            end_of_epoch_data,
404            timestamp_ms,
405            version_specific_data,
406            checkpoint_commitments,
407        }
408    }
409
410    pub fn verify_epoch(&self, epoch: EpochId) -> SuiResult {
411        fp_ensure!(
412            self.epoch == epoch,
413            SuiErrorKind::WrongEpoch {
414                expected_epoch: epoch,
415                actual_epoch: self.epoch,
416            }
417            .into()
418        );
419        Ok(())
420    }
421
422    pub fn sequence_number(&self) -> &CheckpointSequenceNumber {
423        &self.sequence_number
424    }
425
426    pub fn timestamp(&self) -> SystemTime {
427        UNIX_EPOCH + Duration::from_millis(self.timestamp_ms)
428    }
429
430    pub fn next_epoch_committee(&self) -> Option<&[(AuthorityName, StakeUnit)]> {
431        self.end_of_epoch_data
432            .as_ref()
433            .map(|e| e.next_epoch_committee.as_slice())
434    }
435
436    pub fn report_checkpoint_age(&self, metrics: &Histogram, metrics_deprecated: &MystenHistogram) {
437        SystemTime::now()
438            .duration_since(self.timestamp())
439            .map(|latency| {
440                metrics.observe(latency.as_secs_f64());
441                metrics_deprecated.report(latency.as_millis() as u64);
442            })
443            .tap_err(|err| {
444                warn!(
445                    checkpoint_seq = self.sequence_number,
446                    "unable to compute checkpoint age: {}", err
447                )
448            })
449            .ok();
450    }
451
452    pub fn is_last_checkpoint_of_epoch(&self) -> bool {
453        self.end_of_epoch_data.is_some()
454    }
455
456    pub fn version_specific_data(
457        &self,
458        config: &ProtocolConfig,
459    ) -> Result<Option<CheckpointVersionSpecificData>> {
460        match config.checkpoint_summary_version_specific_data_as_option() {
461            None | Some(0) => Ok(None),
462            Some(1) => Ok(Some(bcs::from_bytes(&self.version_specific_data)?)),
463            _ => unimplemented!("unrecognized version_specific_data version in CheckpointSummary"),
464        }
465    }
466
467    pub fn checkpoint_artifacts_digest(&self) -> SuiResult<&CheckpointArtifactsDigest> {
468        self.checkpoint_commitments
469            .iter()
470            .find_map(|c| match c {
471                CheckpointCommitment::CheckpointArtifactsDigest(digest) => Some(digest),
472                _ => None,
473            })
474            .ok_or(
475                SuiErrorKind::GenericAuthorityError {
476                    error: "Checkpoint artifacts digest not found in checkpoint commitments"
477                        .to_string(),
478                }
479                .into(),
480            )
481    }
482}
483
484impl Display for CheckpointSummary {
485    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
486        write!(
487            f,
488            "CheckpointSummary {{ epoch: {:?}, seq: {:?}, content_digest: {},
489            epoch_rolling_gas_cost_summary: {:?}}}",
490            self.epoch,
491            self.sequence_number,
492            self.content_digest,
493            self.epoch_rolling_gas_cost_summary,
494        )
495    }
496}
497
498// Checkpoints are signed by an authority and 2f+1 form a
499// certificate that others can use to catch up. The actual
500// content of the digest must at the very least commit to
501// the set of transactions contained in the certificate but
502// we might extend this to contain roots of merkle trees,
503// or other authenticated data structures to support light
504// clients and more efficient sync protocols.
505
506pub type CheckpointSummaryEnvelope<S> = Envelope<CheckpointSummary, S>;
507pub type CertifiedCheckpointSummary = CheckpointSummaryEnvelope<AuthorityStrongQuorumSignInfo>;
508pub type SignedCheckpointSummary = CheckpointSummaryEnvelope<AuthoritySignInfo>;
509
510pub type VerifiedCheckpoint = VerifiedEnvelope<CheckpointSummary, AuthorityStrongQuorumSignInfo>;
511pub type TrustedCheckpoint = TrustedEnvelope<CheckpointSummary, AuthorityStrongQuorumSignInfo>;
512
513impl CertifiedCheckpointSummary {
514    pub fn verify_authority_signatures(&self, committee: &Committee) -> SuiResult {
515        self.data().verify_epoch(self.auth_sig().epoch)?;
516        self.auth_sig().verify_secure(
517            self.data(),
518            Intent::sui_app(IntentScope::CheckpointSummary),
519            committee,
520        )
521    }
522
523    pub fn try_into_verified(self, committee: &Committee) -> SuiResult<VerifiedCheckpoint> {
524        self.verify_authority_signatures(committee)?;
525        Ok(VerifiedCheckpoint::new_from_verified(self))
526    }
527
528    pub fn verify_with_contents(
529        &self,
530        committee: &Committee,
531        contents: Option<&CheckpointContents>,
532    ) -> SuiResult {
533        self.verify_authority_signatures(committee)?;
534
535        if let Some(contents) = contents {
536            let content_digest = *contents.digest();
537            fp_ensure!(
538                content_digest == self.data().content_digest,
539                SuiErrorKind::GenericAuthorityError{error:format!("Checkpoint contents digest mismatch: summary={:?}, received content digest {:?}, received {} transactions", self.data(), content_digest, contents.size())}.into()
540            );
541        }
542
543        Ok(())
544    }
545
546    pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
547        let summary = self.into_data();
548        (summary.sequence_number, summary)
549    }
550
551    pub fn get_validator_signature(self) -> AggregateAuthoritySignature {
552        self.auth_sig().signature.clone()
553    }
554}
555
556impl SignedCheckpointSummary {
557    pub fn verify_authority_signatures(&self, committee: &Committee) -> SuiResult {
558        self.data().verify_epoch(self.auth_sig().epoch)?;
559        self.auth_sig().verify_secure(
560            self.data(),
561            Intent::sui_app(IntentScope::CheckpointSummary),
562            committee,
563        )
564    }
565
566    pub fn try_into_verified(
567        self,
568        committee: &Committee,
569    ) -> SuiResult<VerifiedEnvelope<CheckpointSummary, AuthoritySignInfo>> {
570        self.verify_authority_signatures(committee)?;
571        Ok(VerifiedEnvelope::<CheckpointSummary, AuthoritySignInfo>::new_from_verified(self))
572    }
573}
574
575impl VerifiedCheckpoint {
576    pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
577        self.into_inner().into_summary_and_sequence()
578    }
579}
580
581/// This is a message validators publish to consensus in order to sign checkpoint
582#[derive(Clone, Debug, Serialize, Deserialize)]
583pub struct CheckpointSignatureMessage {
584    pub summary: SignedCheckpointSummary,
585}
586
587impl CheckpointSignatureMessage {
588    pub fn verify(&self, committee: &Committee) -> SuiResult {
589        self.summary.verify_authority_signatures(committee)
590    }
591}
592
593#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
594pub enum CheckpointContents {
595    V1(CheckpointContentsV1),
596    V2(CheckpointContentsV2),
597}
598
599/// CheckpointContents are the transactions included in an upcoming checkpoint.
600/// They must have already been causally ordered. Since the causal order algorithm
601/// is the same among validators, we expect all honest validators to come up with
602/// the same order for each checkpoint content.
603#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
604pub struct CheckpointContentsV1 {
605    #[serde(skip)]
606    digest: OnceCell<CheckpointContentsDigest>,
607
608    transactions: Vec<ExecutionDigests>,
609    /// This field 'pins' user signatures for the checkpoint
610    /// The length of this vector is same as length of transactions vector
611    /// System transactions has empty signatures
612    user_signatures: Vec<Vec<GenericSignature>>,
613}
614
615#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
616pub struct CheckpointContentsV2 {
617    #[serde(skip)]
618    digest: OnceCell<CheckpointContentsDigest>,
619
620    transactions: Vec<CheckpointTransactionContents>,
621}
622
623#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
624pub struct CheckpointTransactionContents {
625    pub digest: ExecutionDigests,
626
627    /// Each signature is paired with the version of the AddressAliases object
628    /// that was used to verify it. Signatures always appear here in the same
629    /// order as the `required_signers` of the input `Transaction`.
630    pub user_signatures: Vec<(GenericSignature, Option<SequenceNumber>)>,
631}
632
633impl CheckpointContents {
634    pub fn new_with_digests_and_signatures<T>(
635        contents: T,
636        user_signatures: Vec<Vec<GenericSignature>>,
637    ) -> Self
638    where
639        T: IntoIterator<Item = ExecutionDigests>,
640    {
641        let transactions: Vec<_> = contents.into_iter().collect();
642        assert_eq!(transactions.len(), user_signatures.len());
643        Self::V1(CheckpointContentsV1 {
644            digest: Default::default(),
645            transactions,
646            user_signatures,
647        })
648    }
649
650    pub fn new_v2(
651        effects: &[TransactionEffects],
652        signatures: Vec<Vec<(GenericSignature, Option<SequenceNumber>)>>,
653    ) -> Self {
654        assert_eq!(effects.len(), signatures.len());
655        Self::V2(CheckpointContentsV2 {
656            digest: Default::default(),
657            transactions: effects
658                .iter()
659                .zip(signatures)
660                .map(|(e, s)| CheckpointTransactionContents {
661                    digest: e.execution_digests(),
662                    user_signatures: s,
663                })
664                .collect(),
665        })
666    }
667
668    pub fn new_with_causally_ordered_execution_data<'a, T>(contents: T) -> Self
669    where
670        T: IntoIterator<Item = &'a VerifiedExecutionData>,
671    {
672        let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
673            .into_iter()
674            .map(|data| {
675                (
676                    data.digests(),
677                    data.transaction.inner().data().tx_signatures().to_owned(),
678                )
679            })
680            .unzip();
681        assert_eq!(transactions.len(), user_signatures.len());
682        Self::V1(CheckpointContentsV1 {
683            digest: Default::default(),
684            transactions,
685            user_signatures,
686        })
687    }
688
689    pub fn new_with_digests_only_for_tests<T>(contents: T) -> Self
690    where
691        T: IntoIterator<Item = ExecutionDigests>,
692    {
693        let transactions: Vec<_> = contents.into_iter().collect();
694        let user_signatures = transactions.iter().map(|_| vec![]).collect();
695        Self::V1(CheckpointContentsV1 {
696            digest: Default::default(),
697            transactions,
698            user_signatures,
699        })
700    }
701
702    fn into_v1(self) -> CheckpointContentsV1 {
703        let digest = *self.digest();
704        match self {
705            Self::V1(c) => c,
706            Self::V2(c) => CheckpointContentsV1 {
707                // Preserve V2 digest when generating a V1 view of a CheckpointContentsV2.
708                digest: OnceCell::with_value(digest),
709                transactions: c.transactions.iter().map(|t| t.digest).collect(),
710                user_signatures: c
711                    .transactions
712                    .iter()
713                    .map(|t| {
714                        t.user_signatures
715                            .iter()
716                            .map(|(s, _)| s.to_owned())
717                            .collect()
718                    })
719                    .collect(),
720            },
721        }
722    }
723
724    pub fn iter(&self) -> impl DoubleEndedIterator<Item = &ExecutionDigests> + '_ {
725        match self {
726            Self::V1(v) => itertools::Either::Left(v.transactions.iter()),
727            Self::V2(v) => itertools::Either::Right(v.transactions.iter().map(|t| &t.digest)),
728        }
729    }
730
731    pub fn into_iter_with_signatures(
732        self,
733    ) -> impl Iterator<Item = (ExecutionDigests, Vec<GenericSignature>)> {
734        let CheckpointContentsV1 {
735            transactions,
736            user_signatures,
737            ..
738        } = self.into_v1();
739
740        transactions.into_iter().zip(user_signatures)
741    }
742
743    /// Return an iterator that enumerates the transactions in the contents.
744    /// The iterator item is a tuple of (sequence_number, &ExecutionDigests),
745    /// where the sequence_number indicates the index of the transaction in the
746    /// global ordering of executed transactions since genesis.
747    pub fn enumerate_transactions(
748        &self,
749        ckpt: &CheckpointSummary,
750    ) -> impl Iterator<Item = (u64, &ExecutionDigests)> {
751        let start = ckpt.network_total_transactions - self.size() as u64;
752
753        (0u64..)
754            .zip(self.iter())
755            .map(move |(i, digests)| (i + start, digests))
756    }
757
758    pub fn into_inner(self) -> Vec<ExecutionDigests> {
759        self.into_v1().transactions
760    }
761
762    pub fn inner(&self) -> CheckpointContentsView<'_> {
763        match self {
764            Self::V1(c) => CheckpointContentsView::V1 {
765                transactions: &c.transactions,
766                user_signatures: &c.user_signatures,
767            },
768            Self::V2(c) => CheckpointContentsView::V2(&c.transactions),
769        }
770    }
771
772    pub fn size(&self) -> usize {
773        match self {
774            Self::V1(c) => c.transactions.len(),
775            Self::V2(c) => c.transactions.len(),
776        }
777    }
778
779    pub fn digest(&self) -> &CheckpointContentsDigest {
780        match self {
781            Self::V1(c) => c
782                .digest
783                .get_or_init(|| CheckpointContentsDigest::new(default_hash(self))),
784            Self::V2(c) => c
785                .digest
786                .get_or_init(|| CheckpointContentsDigest::new(default_hash(self))),
787        }
788    }
789}
790
791// Enables slice-style access to CheckpointContents tx digests without extra clones.
792pub enum CheckpointContentsView<'a> {
793    V1 {
794        transactions: &'a [ExecutionDigests],
795        user_signatures: &'a [Vec<GenericSignature>],
796    },
797    V2(&'a [CheckpointTransactionContents]),
798}
799
800impl CheckpointContentsView<'_> {
801    pub fn len(&self) -> usize {
802        match self {
803            Self::V1 { transactions, .. } => transactions.len(),
804            Self::V2(v) => v.len(),
805        }
806    }
807
808    pub fn is_empty(&self) -> bool {
809        self.len() == 0
810    }
811
812    pub fn get_digests(&self, index: usize) -> Option<&ExecutionDigests> {
813        match self {
814            Self::V1 { transactions, .. } => transactions.get(index),
815            Self::V2(v) => v.get(index).map(|t| &t.digest),
816        }
817    }
818
819    pub fn first_digests(&self) -> Option<&ExecutionDigests> {
820        self.get_digests(0)
821    }
822
823    pub fn digests_iter(
824        &self,
825    ) -> impl DoubleEndedIterator<Item = &ExecutionDigests> + ExactSizeIterator {
826        match self {
827            Self::V1 { transactions, .. } => itertools::Either::Left(transactions.iter()),
828            Self::V2(v) => itertools::Either::Right(v.iter().map(|t| &t.digest)),
829        }
830    }
831
832    /// Returns the user_signatures for a transaction at the given index along with
833    /// the version of the AddressAliases object that was used to verify it.
834    pub fn user_signatures(
835        &self,
836        index: usize,
837    ) -> Option<Vec<(GenericSignature, Option<SequenceNumber>)>> {
838        match self {
839            Self::V1 {
840                user_signatures, ..
841            } => user_signatures
842                .get(index)
843                .map(|sigs| sigs.iter().map(|sig| (sig.clone(), None)).collect()),
844            Self::V2(v) => v.get(index).map(|t| t.user_signatures.clone()),
845        }
846    }
847
848    pub fn iter(
849        &self,
850    ) -> impl Iterator<
851        Item = (
852            &ExecutionDigests,
853            impl Iterator<Item = (&GenericSignature, Option<SequenceNumber>)>,
854        ),
855    > {
856        match self {
857            Self::V1 {
858                transactions,
859                user_signatures,
860            } => itertools::Either::Left(transactions.iter().zip(user_signatures.iter()).map(
861                |(digests, signatures)| {
862                    let signatures_iter =
863                        itertools::Either::Left(signatures.iter().map(|s| (s, None)));
864                    (digests, signatures_iter)
865                },
866            )),
867            Self::V2(v) => itertools::Either::Right(v.iter().map(|t| {
868                (
869                    &t.digest,
870                    itertools::Either::Right(t.user_signatures.iter().map(|(s, v)| (s, *v))),
871                )
872            })),
873        }
874    }
875}
876
877impl std::ops::Index<usize> for CheckpointContentsView<'_> {
878    type Output = ExecutionDigests;
879
880    fn index(&self, index: usize) -> &Self::Output {
881        match self {
882            Self::V1 { transactions, .. } => &transactions[index],
883            Self::V2(v) => &v[index].digest,
884        }
885    }
886}
887
888/// Same as CheckpointContents, but contains full contents of all transactions, effects,
889/// and user signatures associated with the checkpoint.
890// NOTE: This data structure is used for state sync of checkpoints. Therefore we attempt
891// to estimate its size in CheckpointBuilder in order to limit the maximum serialized
892// size of a checkpoint sent over the network. If this struct is modified,
893// CheckpointBuilder::split_checkpoint_chunks should also be updated accordingly.
894#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
895pub enum VersionedFullCheckpointContents {
896    V1(FullCheckpointContents),
897    V2(FullCheckpointContentsV2),
898}
899
900impl VersionedFullCheckpointContents {
901    pub fn from_contents_and_execution_data(
902        contents: CheckpointContents,
903        execution_data: impl Iterator<Item = ExecutionData>,
904    ) -> Self {
905        let transactions: Vec<_> = execution_data.collect();
906        match contents {
907            CheckpointContents::V1(v1) => Self::V1(FullCheckpointContents {
908                transactions,
909                user_signatures: v1.user_signatures,
910            }),
911            CheckpointContents::V2(v2) => Self::V2(FullCheckpointContentsV2 {
912                transactions,
913                user_signatures: v2
914                    .transactions
915                    .into_iter()
916                    .map(|tx| tx.user_signatures)
917                    .collect(),
918            }),
919        }
920    }
921
922    /// Verifies that this checkpoint's digest matches the given digest, and that all internal
923    /// Transaction and TransactionEffects digests are consistent.
924    pub fn verify_digests(&self, digest: CheckpointContentsDigest) -> Result<()> {
925        let self_digest = *self.checkpoint_contents().digest();
926        fp_ensure!(
927            digest == self_digest,
928            anyhow::anyhow!(
929                "checkpoint contents digest {self_digest} does not match expected digest {digest}"
930            )
931        );
932        for tx in self.iter() {
933            let transaction_digest = tx.transaction.digest();
934            fp_ensure!(
935                tx.effects.transaction_digest() == transaction_digest,
936                anyhow::anyhow!(
937                    "transaction digest {transaction_digest} does not match expected digest {}",
938                    tx.effects.transaction_digest()
939                )
940            );
941        }
942        Ok(())
943    }
944
945    pub fn into_v1(self) -> FullCheckpointContents {
946        match self {
947            Self::V1(c) => c,
948            Self::V2(c) => FullCheckpointContents {
949                transactions: c.transactions,
950                user_signatures: c
951                    .user_signatures
952                    .into_iter()
953                    .map(|sigs| sigs.into_iter().map(|(sig, _)| sig).collect())
954                    .collect(),
955            },
956        }
957    }
958
959    pub fn into_checkpoint_contents(self) -> CheckpointContents {
960        match self {
961            Self::V1(c) => c.into_checkpoint_contents(),
962            Self::V2(c) => c.into_checkpoint_contents(),
963        }
964    }
965
966    pub fn checkpoint_contents(&self) -> CheckpointContents {
967        match self {
968            Self::V1(c) => c.checkpoint_contents(),
969            Self::V2(c) => c.checkpoint_contents(),
970        }
971    }
972
973    pub fn iter(&self) -> Iter<'_, ExecutionData> {
974        match self {
975            Self::V1(c) => c.iter(),
976            Self::V2(c) => c.transactions.iter(),
977        }
978    }
979
980    pub fn size(&self) -> usize {
981        match self {
982            Self::V1(c) => c.transactions.len(),
983            Self::V2(c) => c.transactions.len(),
984        }
985    }
986}
987
988#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
989pub struct FullCheckpointContentsV2 {
990    transactions: Vec<ExecutionData>,
991    user_signatures: Vec<Vec<(GenericSignature, Option<SequenceNumber>)>>,
992}
993
994impl FullCheckpointContentsV2 {
995    pub fn checkpoint_contents(&self) -> CheckpointContents {
996        CheckpointContents::V2(CheckpointContentsV2 {
997            digest: Default::default(),
998            transactions: self
999                .transactions
1000                .iter()
1001                .zip(&self.user_signatures)
1002                .map(|(tx, sigs)| CheckpointTransactionContents {
1003                    digest: tx.digests(),
1004                    user_signatures: sigs.clone(),
1005                })
1006                .collect(),
1007        })
1008    }
1009
1010    pub fn into_checkpoint_contents(self) -> CheckpointContents {
1011        CheckpointContents::V2(CheckpointContentsV2 {
1012            digest: Default::default(),
1013            transactions: self
1014                .transactions
1015                .into_iter()
1016                .zip(self.user_signatures)
1017                .map(|(tx, sigs)| CheckpointTransactionContents {
1018                    digest: tx.digests(),
1019                    user_signatures: sigs,
1020                })
1021                .collect(),
1022        })
1023    }
1024}
1025
1026/// Deprecated version of full checkpoint contents corresponding to CheckpointContentsV1.
1027#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1028pub struct FullCheckpointContents {
1029    transactions: Vec<ExecutionData>,
1030    /// This field 'pins' user signatures for the checkpoint
1031    /// The length of this vector is same as length of transactions vector
1032    /// System transactions has empty signatures
1033    user_signatures: Vec<Vec<GenericSignature>>,
1034}
1035
1036impl FullCheckpointContents {
1037    pub fn new_with_causally_ordered_transactions<T>(contents: T) -> Self
1038    where
1039        T: IntoIterator<Item = ExecutionData>,
1040    {
1041        let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
1042            .into_iter()
1043            .map(|data| {
1044                let sig = data.transaction.data().tx_signatures().to_owned();
1045                (data, sig)
1046            })
1047            .unzip();
1048        assert_eq!(transactions.len(), user_signatures.len());
1049        Self {
1050            transactions,
1051            user_signatures,
1052        }
1053    }
1054
1055    pub fn iter(&self) -> Iter<'_, ExecutionData> {
1056        self.transactions.iter()
1057    }
1058
1059    pub fn checkpoint_contents(&self) -> CheckpointContents {
1060        CheckpointContents::V1(CheckpointContentsV1 {
1061            digest: Default::default(),
1062            transactions: self.transactions.iter().map(|tx| tx.digests()).collect(),
1063            user_signatures: self.user_signatures.clone(),
1064        })
1065    }
1066
1067    pub fn into_checkpoint_contents(self) -> CheckpointContents {
1068        CheckpointContents::V1(CheckpointContentsV1 {
1069            digest: Default::default(),
1070            transactions: self
1071                .transactions
1072                .into_iter()
1073                .map(|tx| tx.digests())
1074                .collect(),
1075            user_signatures: self.user_signatures,
1076        })
1077    }
1078
1079    pub fn random_for_testing() -> Self {
1080        let (a, key): (_, AccountKeyPair) = get_key_pair();
1081        let transaction = Transaction::from_data_and_signer(
1082            TransactionData::new_transfer(
1083                a,
1084                FullObjectRef::from_fastpath_ref(random_object_ref()),
1085                a,
1086                random_object_ref(),
1087                100000000000,
1088                100,
1089            ),
1090            vec![&key],
1091        );
1092        let effects = TestEffectsBuilder::new(transaction.data()).build();
1093        let exe_data = ExecutionData {
1094            transaction,
1095            effects,
1096        };
1097        FullCheckpointContents::new_with_causally_ordered_transactions(vec![exe_data])
1098    }
1099}
1100
1101impl IntoIterator for VersionedFullCheckpointContents {
1102    type Item = ExecutionData;
1103    type IntoIter = std::vec::IntoIter<Self::Item>;
1104
1105    fn into_iter(self) -> Self::IntoIter {
1106        match self {
1107            Self::V1(c) => c.transactions.into_iter(),
1108            Self::V2(c) => c.transactions.into_iter(),
1109        }
1110    }
1111}
1112
1113#[derive(Clone, Debug, PartialEq, Eq)]
1114pub enum VerifiedUserSignatures {
1115    V1(Vec<Vec<GenericSignature>>),
1116    V2(Vec<Vec<(GenericSignature, Option<SequenceNumber>)>>),
1117}
1118
1119#[derive(Clone, Debug, PartialEq, Eq)]
1120pub struct VerifiedCheckpointContents {
1121    transactions: Vec<VerifiedExecutionData>,
1122    /// This field 'pins' user signatures for the checkpoint
1123    /// The length of this vector is same as length of transactions vector
1124    /// System transactions has empty signatures
1125    user_signatures: VerifiedUserSignatures,
1126}
1127
1128impl VerifiedCheckpointContents {
1129    pub fn new_unchecked(contents: VersionedFullCheckpointContents) -> Self {
1130        match contents {
1131            VersionedFullCheckpointContents::V1(c) => Self {
1132                transactions: c
1133                    .transactions
1134                    .into_iter()
1135                    .map(VerifiedExecutionData::new_unchecked)
1136                    .collect(),
1137                user_signatures: VerifiedUserSignatures::V1(c.user_signatures),
1138            },
1139            VersionedFullCheckpointContents::V2(c) => Self {
1140                transactions: c
1141                    .transactions
1142                    .into_iter()
1143                    .map(VerifiedExecutionData::new_unchecked)
1144                    .collect(),
1145                user_signatures: VerifiedUserSignatures::V2(c.user_signatures),
1146            },
1147        }
1148    }
1149
1150    pub fn iter(&self) -> Iter<'_, VerifiedExecutionData> {
1151        self.transactions.iter()
1152    }
1153
1154    pub fn transactions(&self) -> &[VerifiedExecutionData] {
1155        &self.transactions
1156    }
1157
1158    pub fn into_inner(self) -> VersionedFullCheckpointContents {
1159        let transactions: Vec<_> = self
1160            .transactions
1161            .into_iter()
1162            .map(|tx| tx.into_inner())
1163            .collect();
1164
1165        match self.user_signatures {
1166            VerifiedUserSignatures::V1(user_signatures) => {
1167                VersionedFullCheckpointContents::V1(FullCheckpointContents {
1168                    transactions,
1169                    user_signatures,
1170                })
1171            }
1172            VerifiedUserSignatures::V2(user_signatures) => {
1173                VersionedFullCheckpointContents::V2(FullCheckpointContentsV2 {
1174                    transactions,
1175                    user_signatures,
1176                })
1177            }
1178        }
1179    }
1180
1181    pub fn into_checkpoint_contents(self) -> CheckpointContents {
1182        self.into_inner().into_checkpoint_contents()
1183    }
1184
1185    pub fn into_checkpoint_contents_digest(self) -> CheckpointContentsDigest {
1186        *self.into_inner().into_checkpoint_contents().digest()
1187    }
1188
1189    pub fn num_of_transactions(&self) -> usize {
1190        self.transactions.len()
1191    }
1192}
1193
1194/// Holds data in CheckpointSummary that is serialized into the `version_specific_data` field.
1195#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1196pub enum CheckpointVersionSpecificData {
1197    V1(CheckpointVersionSpecificDataV1),
1198}
1199
1200impl CheckpointVersionSpecificData {
1201    pub fn as_v1(&self) -> &CheckpointVersionSpecificDataV1 {
1202        match self {
1203            Self::V1(v) => v,
1204        }
1205    }
1206
1207    pub fn into_v1(self) -> CheckpointVersionSpecificDataV1 {
1208        match self {
1209            Self::V1(v) => v,
1210        }
1211    }
1212
1213    pub fn empty_for_tests() -> CheckpointVersionSpecificData {
1214        CheckpointVersionSpecificData::V1(CheckpointVersionSpecificDataV1 {
1215            randomness_rounds: Vec::new(),
1216        })
1217    }
1218}
1219
1220#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1221pub struct CheckpointVersionSpecificDataV1 {
1222    /// Lists the rounds for which RandomnessStateUpdate transactions are present in the checkpoint.
1223    pub randomness_rounds: Vec<RandomnessRound>,
1224}
1225
1226#[cfg(test)]
1227mod tests {
1228    use crate::digests::{ConsensusCommitDigest, TransactionDigest, TransactionEffectsDigest};
1229    use crate::messages_consensus::ConsensusDeterminedVersionAssignments;
1230    use crate::transaction::VerifiedTransaction;
1231    use fastcrypto::traits::KeyPair;
1232    use rand::SeedableRng;
1233    use rand::prelude::StdRng;
1234
1235    use super::*;
1236    use crate::utils::make_committee_key;
1237
1238    // TODO use the file name as a seed
1239    const RNG_SEED: [u8; 32] = [
1240        21, 23, 199, 200, 234, 250, 252, 178, 94, 15, 202, 178, 62, 186, 88, 137, 233, 192, 130,
1241        157, 179, 179, 65, 9, 31, 249, 221, 123, 225, 112, 199, 247,
1242    ];
1243
1244    #[test]
1245    fn test_signed_checkpoint() {
1246        let mut rng = StdRng::from_seed(RNG_SEED);
1247        let (keys, committee) = make_committee_key(&mut rng);
1248        let (_, committee2) = make_committee_key(&mut rng);
1249
1250        let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
1251
1252        // TODO: duplicated in a test below.
1253
1254        let signed_checkpoints: Vec<_> = keys
1255            .iter()
1256            .map(|k| {
1257                let name = k.public().into();
1258
1259                SignedCheckpointSummary::new(
1260                    committee.epoch,
1261                    CheckpointSummary::new(
1262                        &ProtocolConfig::get_for_max_version_UNSAFE(),
1263                        committee.epoch,
1264                        1,
1265                        0,
1266                        &set,
1267                        None,
1268                        GasCostSummary::default(),
1269                        None,
1270                        0,
1271                        Vec::new(),
1272                        Vec::new(),
1273                    ),
1274                    k,
1275                    name,
1276                )
1277            })
1278            .collect();
1279
1280        signed_checkpoints.iter().for_each(|c| {
1281            c.verify_authority_signatures(&committee)
1282                .expect("signature ok")
1283        });
1284
1285        // fails when not signed by member of committee
1286        signed_checkpoints
1287            .iter()
1288            .for_each(|c| assert!(c.verify_authority_signatures(&committee2).is_err()));
1289    }
1290
1291    #[test]
1292    fn test_certified_checkpoint() {
1293        let mut rng = StdRng::from_seed(RNG_SEED);
1294        let (keys, committee) = make_committee_key(&mut rng);
1295
1296        let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
1297
1298        let summary = CheckpointSummary::new(
1299            &ProtocolConfig::get_for_max_version_UNSAFE(),
1300            committee.epoch,
1301            1,
1302            0,
1303            &set,
1304            None,
1305            GasCostSummary::default(),
1306            None,
1307            0,
1308            Vec::new(),
1309            Vec::new(),
1310        );
1311
1312        let sign_infos: Vec<_> = keys
1313            .iter()
1314            .map(|k| {
1315                let name = k.public().into();
1316
1317                SignedCheckpointSummary::sign(committee.epoch, &summary, k, name)
1318            })
1319            .collect();
1320
1321        let checkpoint_cert =
1322            CertifiedCheckpointSummary::new(summary, sign_infos, &committee).expect("Cert is OK");
1323
1324        // Signature is correct on proposal, and with same transactions
1325        assert!(
1326            checkpoint_cert
1327                .verify_with_contents(&committee, Some(&set))
1328                .is_ok()
1329        );
1330
1331        // Make a bad proposal
1332        let signed_checkpoints: Vec<_> = keys
1333            .iter()
1334            .map(|k| {
1335                let name = k.public().into();
1336                let set = CheckpointContents::new_with_digests_only_for_tests([
1337                    ExecutionDigests::random(),
1338                ]);
1339
1340                SignedCheckpointSummary::new(
1341                    committee.epoch,
1342                    CheckpointSummary::new(
1343                        &ProtocolConfig::get_for_max_version_UNSAFE(),
1344                        committee.epoch,
1345                        1,
1346                        0,
1347                        &set,
1348                        None,
1349                        GasCostSummary::default(),
1350                        None,
1351                        0,
1352                        Vec::new(),
1353                        Vec::new(),
1354                    ),
1355                    k,
1356                    name,
1357                )
1358            })
1359            .collect();
1360
1361        let summary = signed_checkpoints[0].data().clone();
1362        let sign_infos = signed_checkpoints
1363            .into_iter()
1364            .map(|v| v.into_sig())
1365            .collect();
1366        assert!(
1367            CertifiedCheckpointSummary::new(summary, sign_infos, &committee)
1368                .unwrap()
1369                .verify_authority_signatures(&committee)
1370                .is_err()
1371        )
1372    }
1373
1374    // Generate a CheckpointSummary from the input transaction digest. All the other fields in the generated
1375    // CheckpointSummary will be the same. The generated CheckpointSummary can be used to test how input
1376    // transaction digest affects CheckpointSummary.
1377    fn generate_test_checkpoint_summary_from_digest(
1378        digest: TransactionDigest,
1379    ) -> CheckpointSummary {
1380        CheckpointSummary::new(
1381            &ProtocolConfig::get_for_max_version_UNSAFE(),
1382            1,
1383            2,
1384            10,
1385            &CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::new(
1386                digest,
1387                TransactionEffectsDigest::ZERO,
1388            )]),
1389            None,
1390            GasCostSummary::default(),
1391            None,
1392            100,
1393            Vec::new(),
1394            Vec::new(),
1395        )
1396    }
1397
1398    // Tests that ConsensusCommitPrologue with different consensus commit digest will result in different checkpoint content.
1399    #[test]
1400    fn test_checkpoint_summary_with_different_consensus_digest() {
1401        // First, tests that same consensus commit digest will produce the same checkpoint content.
1402        {
1403            let t1 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1404                1,
1405                2,
1406                100,
1407                ConsensusCommitDigest::default(),
1408                ConsensusDeterminedVersionAssignments::empty_for_testing(),
1409            );
1410            let t2 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1411                1,
1412                2,
1413                100,
1414                ConsensusCommitDigest::default(),
1415                ConsensusDeterminedVersionAssignments::empty_for_testing(),
1416            );
1417            let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
1418            let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
1419            assert_eq!(c1.digest(), c2.digest());
1420        }
1421
1422        // Next, tests that different consensus commit digests will produce the different checkpoint contents.
1423        {
1424            let t1 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1425                1,
1426                2,
1427                100,
1428                ConsensusCommitDigest::default(),
1429                ConsensusDeterminedVersionAssignments::empty_for_testing(),
1430            );
1431            let t2 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1432                1,
1433                2,
1434                100,
1435                ConsensusCommitDigest::random(),
1436                ConsensusDeterminedVersionAssignments::empty_for_testing(),
1437            );
1438            let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
1439            let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
1440            assert_ne!(c1.digest(), c2.digest());
1441        }
1442    }
1443
1444    #[test]
1445    fn test_artifacts() {
1446        let mut artifacts = CheckpointArtifacts::new();
1447        let o = CheckpointArtifact::ObjectStates(BTreeMap::new());
1448        assert!(artifacts.add_artifact(o.clone()).is_ok());
1449        assert!(artifacts.add_artifact(o.clone()).is_err());
1450    }
1451}