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. Therefore we attempt
895// to estimate its size in CheckpointBuilder in order to limit the maximum serialized
896// size of a checkpoint sent over the network. If this struct is modified,
897// CheckpointBuilder::split_checkpoint_chunks should also be updated accordingly.
898#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
899pub enum VersionedFullCheckpointContents {
900    V1(FullCheckpointContents),
901    V2(FullCheckpointContentsV2),
902}
903
904impl VersionedFullCheckpointContents {
905    pub fn from_contents_and_execution_data(
906        contents: CheckpointContents,
907        execution_data: impl Iterator<Item = ExecutionData>,
908    ) -> Self {
909        let transactions: Vec<_> = execution_data.collect();
910        match contents {
911            CheckpointContents::V1(v1) => Self::V1(FullCheckpointContents {
912                transactions,
913                user_signatures: v1.user_signatures,
914            }),
915            CheckpointContents::V2(v2) => Self::V2(FullCheckpointContentsV2 {
916                transactions,
917                user_signatures: v2
918                    .transactions
919                    .into_iter()
920                    .map(|tx| tx.user_signatures)
921                    .collect(),
922            }),
923        }
924    }
925
926    /// Verifies that this checkpoint's digest matches the given digest, and that all internal
927    /// Transaction and TransactionEffects digests are consistent.
928    pub fn verify_digests(&self, digest: CheckpointContentsDigest) -> Result<()> {
929        let self_digest = *self.checkpoint_contents().digest();
930        fp_ensure!(
931            digest == self_digest,
932            anyhow::anyhow!(
933                "checkpoint contents digest {self_digest} does not match expected digest {digest}"
934            )
935        );
936        for tx in self.iter() {
937            let transaction_digest = tx.transaction.digest();
938            fp_ensure!(
939                tx.effects.transaction_digest() == transaction_digest,
940                anyhow::anyhow!(
941                    "transaction digest {transaction_digest} does not match expected digest {}",
942                    tx.effects.transaction_digest()
943                )
944            );
945        }
946        Ok(())
947    }
948
949    pub fn into_v1(self) -> FullCheckpointContents {
950        match self {
951            Self::V1(c) => c,
952            Self::V2(c) => FullCheckpointContents {
953                transactions: c.transactions,
954                user_signatures: c
955                    .user_signatures
956                    .into_iter()
957                    .map(|sigs| sigs.into_iter().map(|(sig, _)| sig).collect())
958                    .collect(),
959            },
960        }
961    }
962
963    pub fn into_checkpoint_contents(self) -> CheckpointContents {
964        match self {
965            Self::V1(c) => c.into_checkpoint_contents(),
966            Self::V2(c) => c.into_checkpoint_contents(),
967        }
968    }
969
970    pub fn checkpoint_contents(&self) -> CheckpointContents {
971        match self {
972            Self::V1(c) => c.checkpoint_contents(),
973            Self::V2(c) => c.checkpoint_contents(),
974        }
975    }
976
977    pub fn iter(&self) -> Iter<'_, ExecutionData> {
978        match self {
979            Self::V1(c) => c.iter(),
980            Self::V2(c) => c.transactions.iter(),
981        }
982    }
983
984    pub fn size(&self) -> usize {
985        match self {
986            Self::V1(c) => c.transactions.len(),
987            Self::V2(c) => c.transactions.len(),
988        }
989    }
990}
991
992#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
993pub struct FullCheckpointContentsV2 {
994    transactions: Vec<ExecutionData>,
995    user_signatures: Vec<Vec<(GenericSignature, Option<SequenceNumber>)>>,
996}
997
998impl FullCheckpointContentsV2 {
999    pub fn checkpoint_contents(&self) -> CheckpointContents {
1000        CheckpointContents::V2(CheckpointContentsV2 {
1001            digest: Default::default(),
1002            transactions: self
1003                .transactions
1004                .iter()
1005                .zip_debug_eq(&self.user_signatures)
1006                .map(|(tx, sigs)| CheckpointTransactionContents {
1007                    digest: tx.digests(),
1008                    user_signatures: sigs.clone(),
1009                })
1010                .collect(),
1011        })
1012    }
1013
1014    pub fn into_checkpoint_contents(self) -> CheckpointContents {
1015        CheckpointContents::V2(CheckpointContentsV2 {
1016            digest: Default::default(),
1017            transactions: self
1018                .transactions
1019                .into_iter()
1020                .zip_debug_eq(self.user_signatures)
1021                .map(|(tx, sigs)| CheckpointTransactionContents {
1022                    digest: tx.digests(),
1023                    user_signatures: sigs,
1024                })
1025                .collect(),
1026        })
1027    }
1028}
1029
1030/// Deprecated version of full checkpoint contents corresponding to CheckpointContentsV1.
1031#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1032pub struct FullCheckpointContents {
1033    transactions: Vec<ExecutionData>,
1034    /// This field 'pins' user signatures for the checkpoint
1035    /// The length of this vector is same as length of transactions vector
1036    /// System transactions has empty signatures
1037    user_signatures: Vec<Vec<GenericSignature>>,
1038}
1039
1040impl FullCheckpointContents {
1041    pub fn new_with_causally_ordered_transactions<T>(contents: T) -> Self
1042    where
1043        T: IntoIterator<Item = ExecutionData>,
1044    {
1045        let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
1046            .into_iter()
1047            .map(|data| {
1048                let sig = data.transaction.data().tx_signatures().to_owned();
1049                (data, sig)
1050            })
1051            .unzip();
1052        assert_eq!(transactions.len(), user_signatures.len());
1053        Self {
1054            transactions,
1055            user_signatures,
1056        }
1057    }
1058
1059    pub fn iter(&self) -> Iter<'_, ExecutionData> {
1060        self.transactions.iter()
1061    }
1062
1063    pub fn checkpoint_contents(&self) -> CheckpointContents {
1064        CheckpointContents::V1(CheckpointContentsV1 {
1065            digest: Default::default(),
1066            transactions: self.transactions.iter().map(|tx| tx.digests()).collect(),
1067            user_signatures: self.user_signatures.clone(),
1068        })
1069    }
1070
1071    pub fn into_checkpoint_contents(self) -> CheckpointContents {
1072        CheckpointContents::V1(CheckpointContentsV1 {
1073            digest: Default::default(),
1074            transactions: self
1075                .transactions
1076                .into_iter()
1077                .map(|tx| tx.digests())
1078                .collect(),
1079            user_signatures: self.user_signatures,
1080        })
1081    }
1082
1083    pub fn random_for_testing() -> Self {
1084        let (a, key): (_, AccountKeyPair) = get_key_pair();
1085        let transaction = Transaction::from_data_and_signer(
1086            TransactionData::new_transfer(
1087                a,
1088                FullObjectRef::from_fastpath_ref(random_object_ref()),
1089                a,
1090                random_object_ref(),
1091                100000000000,
1092                100,
1093            ),
1094            vec![&key],
1095        );
1096        let effects = TestEffectsBuilder::new(transaction.data()).build();
1097        let exe_data = ExecutionData {
1098            transaction,
1099            effects,
1100        };
1101        FullCheckpointContents::new_with_causally_ordered_transactions(vec![exe_data])
1102    }
1103}
1104
1105impl IntoIterator for VersionedFullCheckpointContents {
1106    type Item = ExecutionData;
1107    type IntoIter = std::vec::IntoIter<Self::Item>;
1108
1109    fn into_iter(self) -> Self::IntoIter {
1110        match self {
1111            Self::V1(c) => c.transactions.into_iter(),
1112            Self::V2(c) => c.transactions.into_iter(),
1113        }
1114    }
1115}
1116
1117#[derive(Clone, Debug, PartialEq, Eq)]
1118pub enum VerifiedUserSignatures {
1119    V1(Vec<Vec<GenericSignature>>),
1120    V2(Vec<Vec<(GenericSignature, Option<SequenceNumber>)>>),
1121}
1122
1123#[derive(Clone, Debug, PartialEq, Eq)]
1124pub struct VerifiedCheckpointContents {
1125    transactions: Vec<VerifiedExecutionData>,
1126    /// This field 'pins' user signatures for the checkpoint
1127    /// The length of this vector is same as length of transactions vector
1128    /// System transactions has empty signatures
1129    user_signatures: VerifiedUserSignatures,
1130}
1131
1132impl VerifiedCheckpointContents {
1133    pub fn new_unchecked(contents: VersionedFullCheckpointContents) -> Self {
1134        match contents {
1135            VersionedFullCheckpointContents::V1(c) => Self {
1136                transactions: c
1137                    .transactions
1138                    .into_iter()
1139                    .map(VerifiedExecutionData::new_unchecked)
1140                    .collect(),
1141                user_signatures: VerifiedUserSignatures::V1(c.user_signatures),
1142            },
1143            VersionedFullCheckpointContents::V2(c) => Self {
1144                transactions: c
1145                    .transactions
1146                    .into_iter()
1147                    .map(VerifiedExecutionData::new_unchecked)
1148                    .collect(),
1149                user_signatures: VerifiedUserSignatures::V2(c.user_signatures),
1150            },
1151        }
1152    }
1153
1154    pub fn iter(&self) -> Iter<'_, VerifiedExecutionData> {
1155        self.transactions.iter()
1156    }
1157
1158    pub fn transactions(&self) -> &[VerifiedExecutionData] {
1159        &self.transactions
1160    }
1161
1162    pub fn into_inner(self) -> VersionedFullCheckpointContents {
1163        let transactions: Vec<_> = self
1164            .transactions
1165            .into_iter()
1166            .map(|tx| tx.into_inner())
1167            .collect();
1168
1169        match self.user_signatures {
1170            VerifiedUserSignatures::V1(user_signatures) => {
1171                VersionedFullCheckpointContents::V1(FullCheckpointContents {
1172                    transactions,
1173                    user_signatures,
1174                })
1175            }
1176            VerifiedUserSignatures::V2(user_signatures) => {
1177                VersionedFullCheckpointContents::V2(FullCheckpointContentsV2 {
1178                    transactions,
1179                    user_signatures,
1180                })
1181            }
1182        }
1183    }
1184
1185    pub fn into_checkpoint_contents(self) -> CheckpointContents {
1186        self.into_inner().into_checkpoint_contents()
1187    }
1188
1189    pub fn into_checkpoint_contents_digest(self) -> CheckpointContentsDigest {
1190        *self.into_inner().into_checkpoint_contents().digest()
1191    }
1192
1193    pub fn num_of_transactions(&self) -> usize {
1194        self.transactions.len()
1195    }
1196}
1197
1198/// Holds data in CheckpointSummary that is serialized into the `version_specific_data` field.
1199#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1200pub enum CheckpointVersionSpecificData {
1201    V1(CheckpointVersionSpecificDataV1),
1202}
1203
1204impl CheckpointVersionSpecificData {
1205    pub fn as_v1(&self) -> &CheckpointVersionSpecificDataV1 {
1206        match self {
1207            Self::V1(v) => v,
1208        }
1209    }
1210
1211    pub fn into_v1(self) -> CheckpointVersionSpecificDataV1 {
1212        match self {
1213            Self::V1(v) => v,
1214        }
1215    }
1216
1217    pub fn empty_for_tests() -> CheckpointVersionSpecificData {
1218        CheckpointVersionSpecificData::V1(CheckpointVersionSpecificDataV1 {
1219            randomness_rounds: Vec::new(),
1220        })
1221    }
1222}
1223
1224#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1225pub struct CheckpointVersionSpecificDataV1 {
1226    /// Lists the rounds for which RandomnessStateUpdate transactions are present in the checkpoint.
1227    pub randomness_rounds: Vec<RandomnessRound>,
1228}
1229
1230#[cfg(test)]
1231mod tests {
1232    use crate::digests::{ConsensusCommitDigest, TransactionDigest, TransactionEffectsDigest};
1233    use crate::messages_consensus::ConsensusDeterminedVersionAssignments;
1234    use crate::transaction::VerifiedTransaction;
1235    use fastcrypto::traits::KeyPair;
1236    use rand::SeedableRng;
1237    use rand::prelude::StdRng;
1238
1239    use super::*;
1240    use crate::utils::make_committee_key;
1241
1242    // TODO use the file name as a seed
1243    const RNG_SEED: [u8; 32] = [
1244        21, 23, 199, 200, 234, 250, 252, 178, 94, 15, 202, 178, 62, 186, 88, 137, 233, 192, 130,
1245        157, 179, 179, 65, 9, 31, 249, 221, 123, 225, 112, 199, 247,
1246    ];
1247
1248    #[test]
1249    fn test_signed_checkpoint() {
1250        let mut rng = StdRng::from_seed(RNG_SEED);
1251        let (keys, committee) = make_committee_key(&mut rng);
1252        let (_, committee2) = make_committee_key(&mut rng);
1253
1254        let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
1255
1256        // TODO: duplicated in a test below.
1257
1258        let signed_checkpoints: Vec<_> = keys
1259            .iter()
1260            .map(|k| {
1261                let name = k.public().into();
1262
1263                SignedCheckpointSummary::new(
1264                    committee.epoch,
1265                    CheckpointSummary::new(
1266                        &ProtocolConfig::get_for_max_version_UNSAFE(),
1267                        committee.epoch,
1268                        1,
1269                        0,
1270                        &set,
1271                        None,
1272                        GasCostSummary::default(),
1273                        None,
1274                        0,
1275                        Vec::new(),
1276                        Vec::new(),
1277                    ),
1278                    k,
1279                    name,
1280                )
1281            })
1282            .collect();
1283
1284        signed_checkpoints.iter().for_each(|c| {
1285            c.verify_authority_signatures(&committee)
1286                .expect("signature ok")
1287        });
1288
1289        // fails when not signed by member of committee
1290        signed_checkpoints
1291            .iter()
1292            .for_each(|c| assert!(c.verify_authority_signatures(&committee2).is_err()));
1293    }
1294
1295    #[test]
1296    fn test_certified_checkpoint() {
1297        let mut rng = StdRng::from_seed(RNG_SEED);
1298        let (keys, committee) = make_committee_key(&mut rng);
1299
1300        let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
1301
1302        let summary = CheckpointSummary::new(
1303            &ProtocolConfig::get_for_max_version_UNSAFE(),
1304            committee.epoch,
1305            1,
1306            0,
1307            &set,
1308            None,
1309            GasCostSummary::default(),
1310            None,
1311            0,
1312            Vec::new(),
1313            Vec::new(),
1314        );
1315
1316        let sign_infos: Vec<_> = keys
1317            .iter()
1318            .map(|k| {
1319                let name = k.public().into();
1320
1321                SignedCheckpointSummary::sign(committee.epoch, &summary, k, name)
1322            })
1323            .collect();
1324
1325        let checkpoint_cert =
1326            CertifiedCheckpointSummary::new(summary, sign_infos, &committee).expect("Cert is OK");
1327
1328        // Signature is correct on proposal, and with same transactions
1329        assert!(
1330            checkpoint_cert
1331                .verify_with_contents(&committee, Some(&set))
1332                .is_ok()
1333        );
1334
1335        // Make a bad proposal
1336        let signed_checkpoints: Vec<_> = keys
1337            .iter()
1338            .map(|k| {
1339                let name = k.public().into();
1340                let set = CheckpointContents::new_with_digests_only_for_tests([
1341                    ExecutionDigests::random(),
1342                ]);
1343
1344                SignedCheckpointSummary::new(
1345                    committee.epoch,
1346                    CheckpointSummary::new(
1347                        &ProtocolConfig::get_for_max_version_UNSAFE(),
1348                        committee.epoch,
1349                        1,
1350                        0,
1351                        &set,
1352                        None,
1353                        GasCostSummary::default(),
1354                        None,
1355                        0,
1356                        Vec::new(),
1357                        Vec::new(),
1358                    ),
1359                    k,
1360                    name,
1361                )
1362            })
1363            .collect();
1364
1365        let summary = signed_checkpoints[0].data().clone();
1366        let sign_infos = signed_checkpoints
1367            .into_iter()
1368            .map(|v| v.into_sig())
1369            .collect();
1370        assert!(
1371            CertifiedCheckpointSummary::new(summary, sign_infos, &committee)
1372                .unwrap()
1373                .verify_authority_signatures(&committee)
1374                .is_err()
1375        )
1376    }
1377
1378    // Generate a CheckpointSummary from the input transaction digest. All the other fields in the generated
1379    // CheckpointSummary will be the same. The generated CheckpointSummary can be used to test how input
1380    // transaction digest affects CheckpointSummary.
1381    fn generate_test_checkpoint_summary_from_digest(
1382        digest: TransactionDigest,
1383    ) -> CheckpointSummary {
1384        CheckpointSummary::new(
1385            &ProtocolConfig::get_for_max_version_UNSAFE(),
1386            1,
1387            2,
1388            10,
1389            &CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::new(
1390                digest,
1391                TransactionEffectsDigest::ZERO,
1392            )]),
1393            None,
1394            GasCostSummary::default(),
1395            None,
1396            100,
1397            Vec::new(),
1398            Vec::new(),
1399        )
1400    }
1401
1402    // Tests that ConsensusCommitPrologue with different consensus commit digest will result in different checkpoint content.
1403    #[test]
1404    fn test_checkpoint_summary_with_different_consensus_digest() {
1405        // First, tests that same consensus commit digest will produce the same checkpoint content.
1406        {
1407            let t1 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1408                1,
1409                2,
1410                100,
1411                ConsensusCommitDigest::default(),
1412                ConsensusDeterminedVersionAssignments::empty_for_testing(),
1413            );
1414            let t2 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1415                1,
1416                2,
1417                100,
1418                ConsensusCommitDigest::default(),
1419                ConsensusDeterminedVersionAssignments::empty_for_testing(),
1420            );
1421            let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
1422            let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
1423            assert_eq!(c1.digest(), c2.digest());
1424        }
1425
1426        // Next, tests that different consensus commit digests will produce the different checkpoint contents.
1427        {
1428            let t1 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1429                1,
1430                2,
1431                100,
1432                ConsensusCommitDigest::default(),
1433                ConsensusDeterminedVersionAssignments::empty_for_testing(),
1434            );
1435            let t2 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1436                1,
1437                2,
1438                100,
1439                ConsensusCommitDigest::random(),
1440                ConsensusDeterminedVersionAssignments::empty_for_testing(),
1441            );
1442            let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
1443            let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
1444            assert_ne!(c1.digest(), c2.digest());
1445        }
1446    }
1447
1448    #[test]
1449    fn test_artifacts() {
1450        let mut artifacts = CheckpointArtifacts::new();
1451        let o = CheckpointArtifact::ObjectStates(BTreeMap::new());
1452        assert!(artifacts.add_artifact(o.clone()).is_ok());
1453        assert!(artifacts.add_artifact(o.clone()).is_err());
1454    }
1455}