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