Skip to main content

iota_types/
messages_checkpoint.rs

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