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