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