sui_types/
messages_checkpoint.rs

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