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 once_cell::sync::OnceCell;
15use prometheus::Histogram;
16use schemars::JsonSchema;
17use serde::{Deserialize, Serialize};
18use serde_with::serde_as;
19use shared_crypto::intent::{Intent, IntentScope};
20use tap::TapFallible;
21use tracing::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    pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
322        self.data().verify_epoch(self.auth_sig().epoch)?;
323        self.auth_sig().verify_secure(
324            self.data(),
325            Intent::iota_app(IntentScope::CheckpointSummary),
326            committee,
327        )
328    }
329
330    pub fn try_into_verified(self, committee: &Committee) -> IotaResult<VerifiedCheckpoint> {
331        self.verify_authority_signatures(committee)?;
332        Ok(VerifiedCheckpoint::new_from_verified(self))
333    }
334
335    pub fn verify_with_contents(
336        &self,
337        committee: &Committee,
338        contents: Option<&CheckpointContents>,
339    ) -> IotaResult {
340        self.verify_authority_signatures(committee)?;
341
342        if let Some(contents) = contents {
343            let content_digest = *contents.digest();
344            fp_ensure!(
345                content_digest == self.data().content_digest,
346                IotaError::GenericAuthority {
347                    error: format!(
348                        "Checkpoint contents digest mismatch: summary={:?}, received content digest {:?}, received {} transactions",
349                        self.data(),
350                        content_digest,
351                        contents.size()
352                    )
353                }
354            );
355        }
356
357        Ok(())
358    }
359
360    pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
361        let summary = self.into_data();
362        (summary.sequence_number, summary)
363    }
364
365    pub fn get_validator_signature(self) -> AggregateAuthoritySignature {
366        self.auth_sig().signature.clone()
367    }
368}
369
370impl SignedCheckpointSummary {
371    pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
372        self.data().verify_epoch(self.auth_sig().epoch)?;
373        self.auth_sig().verify_secure(
374            self.data(),
375            Intent::iota_app(IntentScope::CheckpointSummary),
376            committee,
377        )
378    }
379
380    pub fn try_into_verified(
381        self,
382        committee: &Committee,
383    ) -> IotaResult<VerifiedEnvelope<CheckpointSummary, AuthoritySignInfo>> {
384        self.verify_authority_signatures(committee)?;
385        Ok(VerifiedEnvelope::<CheckpointSummary, AuthoritySignInfo>::new_from_verified(self))
386    }
387}
388
389impl VerifiedCheckpoint {
390    pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
391        self.into_inner().into_summary_and_sequence()
392    }
393}
394
395/// This is a message validators publish to consensus in order to sign
396/// checkpoint
397#[derive(Clone, Debug, Serialize, Deserialize)]
398pub struct CheckpointSignatureMessage {
399    pub summary: SignedCheckpointSummary,
400}
401
402impl CheckpointSignatureMessage {
403    pub fn verify(&self, committee: &Committee) -> IotaResult {
404        self.summary.verify_authority_signatures(committee)
405    }
406}
407
408#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
409pub enum CheckpointContents {
410    V1(CheckpointContentsV1),
411}
412
413/// CheckpointContents are the transactions included in an upcoming checkpoint.
414/// They must have already been causally ordered. Since the causal order
415/// algorithm is the same among validators, we expect all honest validators to
416/// come up with the same order for each checkpoint content.
417#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
418pub struct CheckpointContentsV1 {
419    #[serde(skip)]
420    digest: OnceCell<CheckpointContentsDigest>,
421
422    transactions: Vec<ExecutionDigests>,
423    /// This field 'pins' user signatures for the checkpoint
424    /// The length of this vector is same as length of transactions vector
425    /// System transactions has empty signatures
426    user_signatures: Vec<Vec<GenericSignature>>,
427}
428
429impl CheckpointContents {
430    pub fn new_with_digests_and_signatures(
431        contents: impl IntoIterator<Item = ExecutionDigests>,
432        user_signatures: Vec<Vec<GenericSignature>>,
433    ) -> Self {
434        let transactions: Vec<_> = contents.into_iter().collect();
435        assert_eq!(transactions.len(), user_signatures.len());
436        Self::V1(CheckpointContentsV1 {
437            digest: Default::default(),
438            transactions,
439            user_signatures,
440        })
441    }
442
443    pub fn new_with_causally_ordered_execution_data<'a>(
444        contents: impl IntoIterator<Item = &'a VerifiedExecutionData>,
445    ) -> Self {
446        let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
447            .into_iter()
448            .map(|data| {
449                (
450                    data.digests(),
451                    data.transaction.inner().data().tx_signatures().to_owned(),
452                )
453            })
454            .unzip();
455        assert_eq!(transactions.len(), user_signatures.len());
456        Self::V1(CheckpointContentsV1 {
457            digest: Default::default(),
458            transactions,
459            user_signatures,
460        })
461    }
462
463    pub fn new_with_digests_only_for_tests(
464        contents: impl IntoIterator<Item = ExecutionDigests>,
465    ) -> Self {
466        let transactions: Vec<_> = contents.into_iter().collect();
467        let user_signatures = transactions.iter().map(|_| vec![]).collect();
468        Self::V1(CheckpointContentsV1 {
469            digest: Default::default(),
470            transactions,
471            user_signatures,
472        })
473    }
474
475    fn as_v1(&self) -> &CheckpointContentsV1 {
476        match self {
477            Self::V1(v) => v,
478        }
479    }
480
481    fn into_v1(self) -> CheckpointContentsV1 {
482        match self {
483            Self::V1(v) => v,
484        }
485    }
486
487    pub fn iter(&self) -> Iter<'_, ExecutionDigests> {
488        self.as_v1().transactions.iter()
489    }
490
491    pub fn into_iter_with_signatures(
492        self,
493    ) -> impl Iterator<Item = (ExecutionDigests, Vec<GenericSignature>)> {
494        let CheckpointContentsV1 {
495            transactions,
496            user_signatures,
497            ..
498        } = self.into_v1();
499
500        transactions.into_iter().zip(user_signatures)
501    }
502
503    /// Return an iterator that enumerates the transactions in the contents.
504    /// The iterator item is a tuple of (sequence_number, &ExecutionDigests),
505    /// where the sequence_number indicates the index of the transaction in the
506    /// global ordering of executed transactions since genesis.
507    pub fn enumerate_transactions(
508        &self,
509        ckpt: &CheckpointSummary,
510    ) -> impl Iterator<Item = (u64, &ExecutionDigests)> {
511        let start = ckpt.network_total_transactions - self.size() as u64;
512
513        (0u64..)
514            .zip(self.iter())
515            .map(move |(i, digests)| (i + start, digests))
516    }
517
518    pub fn into_inner(self) -> Vec<ExecutionDigests> {
519        self.into_v1().transactions
520    }
521
522    pub fn inner(&self) -> &[ExecutionDigests] {
523        &self.as_v1().transactions
524    }
525
526    pub fn size(&self) -> usize {
527        self.as_v1().transactions.len()
528    }
529
530    pub fn digest(&self) -> &CheckpointContentsDigest {
531        self.as_v1()
532            .digest
533            .get_or_init(|| CheckpointContentsDigest::new(default_hash(self)))
534    }
535}
536
537/// Same as CheckpointContents, but contains full contents of all Transactions
538/// and TransactionEffects associated with the checkpoint.
539// NOTE: This data structure is used for state sync of checkpoints. Therefore we attempt
540// to estimate its size in CheckpointBuilder in order to limit the maximum serialized
541// size of a checkpoint sent over the network. If this struct is modified,
542// CheckpointBuilder::split_checkpoint_chunks should also be updated accordingly.
543#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
544pub struct FullCheckpointContents {
545    transactions: Vec<ExecutionData>,
546    /// This field 'pins' user signatures for the checkpoint
547    /// The length of this vector is same as length of transactions vector
548    /// System transactions has empty signatures
549    user_signatures: Vec<Vec<GenericSignature>>,
550}
551
552impl FullCheckpointContents {
553    pub fn new_with_causally_ordered_transactions<T>(contents: T) -> Self
554    where
555        T: IntoIterator<Item = ExecutionData>,
556    {
557        let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
558            .into_iter()
559            .map(|data| {
560                let sig = data.transaction.data().tx_signatures().to_owned();
561                (data, sig)
562            })
563            .unzip();
564        assert_eq!(transactions.len(), user_signatures.len());
565        Self {
566            transactions,
567            user_signatures,
568        }
569    }
570
571    pub fn from_contents_and_execution_data(
572        contents: CheckpointContents,
573        execution_data: impl Iterator<Item = ExecutionData>,
574    ) -> Self {
575        let transactions: Vec<_> = execution_data.collect();
576        Self {
577            transactions,
578            user_signatures: contents.into_v1().user_signatures,
579        }
580    }
581
582    pub fn try_from_checkpoint_contents<S>(
583        store: S,
584        contents: CheckpointContents,
585    ) -> Result<Option<Self>, crate::storage::error::Error>
586    where
587        S: ReadStore,
588    {
589        let mut transactions = Vec::with_capacity(contents.size());
590        for tx in contents.iter() {
591            if let (Some(t), Some(e)) = (
592                store.try_get_transaction(&tx.transaction)?,
593                store.try_get_transaction_effects(&tx.transaction)?,
594            ) {
595                transactions.push(ExecutionData::new((*t).clone().into_inner(), e))
596            } else {
597                return Ok(None);
598            }
599        }
600        Ok(Some(Self {
601            transactions,
602            user_signatures: contents.into_v1().user_signatures,
603        }))
604    }
605
606    pub fn iter(&self) -> Iter<'_, ExecutionData> {
607        self.transactions.iter()
608    }
609
610    /// Verifies that this checkpoint's digest matches the given digest, and
611    /// that all internal Transaction and TransactionEffects digests are
612    /// consistent.
613    pub fn verify_digests(&self, digest: CheckpointContentsDigest) -> Result<()> {
614        let self_digest = *self.checkpoint_contents().digest();
615        fp_ensure!(
616            digest == self_digest,
617            anyhow::anyhow!(
618                "checkpoint contents digest {self_digest} does not match expected digest {digest}"
619            )
620        );
621        for tx in self.iter() {
622            let transaction_digest = tx.transaction.digest();
623            fp_ensure!(
624                tx.effects.transaction_digest() == transaction_digest,
625                anyhow::anyhow!(
626                    "transaction digest {transaction_digest} does not match expected digest {}",
627                    tx.effects.transaction_digest()
628                )
629            );
630        }
631        Ok(())
632    }
633
634    pub fn checkpoint_contents(&self) -> CheckpointContents {
635        CheckpointContents::V1(CheckpointContentsV1 {
636            digest: Default::default(),
637            transactions: self.transactions.iter().map(|tx| tx.digests()).collect(),
638            user_signatures: self.user_signatures.clone(),
639        })
640    }
641
642    pub fn into_checkpoint_contents(self) -> CheckpointContents {
643        CheckpointContents::V1(CheckpointContentsV1 {
644            digest: Default::default(),
645            transactions: self
646                .transactions
647                .into_iter()
648                .map(|tx| tx.digests())
649                .collect(),
650            user_signatures: self.user_signatures,
651        })
652    }
653
654    pub fn size(&self) -> usize {
655        self.transactions.len()
656    }
657
658    pub fn random_for_testing() -> Self {
659        let (a, key): (_, AccountKeyPair) = get_key_pair();
660        let transaction = Transaction::from_data_and_signer(
661            TransactionData::new_transfer(
662                a,
663                random_object_ref(),
664                a,
665                random_object_ref(),
666                100000000000,
667                100,
668            ),
669            vec![&key],
670        );
671        let effects = TestEffectsBuilder::new(transaction.data()).build();
672        let exe_data = ExecutionData {
673            transaction,
674            effects,
675        };
676        FullCheckpointContents::new_with_causally_ordered_transactions(vec![exe_data])
677    }
678}
679
680impl IntoIterator for FullCheckpointContents {
681    type Item = ExecutionData;
682    type IntoIter = std::vec::IntoIter<Self::Item>;
683
684    fn into_iter(self) -> Self::IntoIter {
685        self.transactions.into_iter()
686    }
687}
688
689#[derive(Clone, Debug, PartialEq, Eq)]
690pub struct VerifiedCheckpointContents {
691    transactions: Vec<VerifiedExecutionData>,
692    /// This field 'pins' user signatures for the checkpoint
693    /// The length of this vector is same as length of transactions vector
694    /// System transactions has empty signatures
695    user_signatures: Vec<Vec<GenericSignature>>,
696}
697
698impl VerifiedCheckpointContents {
699    pub fn new_unchecked(contents: FullCheckpointContents) -> Self {
700        Self {
701            transactions: contents
702                .transactions
703                .into_iter()
704                .map(VerifiedExecutionData::new_unchecked)
705                .collect(),
706            user_signatures: contents.user_signatures,
707        }
708    }
709
710    pub fn iter(&self) -> Iter<'_, VerifiedExecutionData> {
711        self.transactions.iter()
712    }
713
714    pub fn transactions(&self) -> &[VerifiedExecutionData] {
715        &self.transactions
716    }
717
718    pub fn into_inner(self) -> FullCheckpointContents {
719        FullCheckpointContents {
720            transactions: self
721                .transactions
722                .into_iter()
723                .map(|tx| tx.into_inner())
724                .collect(),
725            user_signatures: self.user_signatures,
726        }
727    }
728
729    pub fn into_checkpoint_contents(self) -> CheckpointContents {
730        self.into_inner().into_checkpoint_contents()
731    }
732
733    pub fn into_checkpoint_contents_digest(self) -> CheckpointContentsDigest {
734        *self.into_inner().into_checkpoint_contents().digest()
735    }
736
737    pub fn num_of_transactions(&self) -> usize {
738        self.transactions.len()
739    }
740}
741
742/// Holds data in CheckpointSummary that is serialized into the
743/// `version_specific_data` field.
744#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
745pub enum CheckpointVersionSpecificData {
746    V1(CheckpointVersionSpecificDataV1),
747}
748
749impl CheckpointVersionSpecificData {
750    pub fn as_v1(&self) -> &CheckpointVersionSpecificDataV1 {
751        match self {
752            Self::V1(v) => v,
753        }
754    }
755
756    pub fn into_v1(self) -> CheckpointVersionSpecificDataV1 {
757        match self {
758            Self::V1(v) => v,
759        }
760    }
761
762    pub fn empty_for_tests() -> CheckpointVersionSpecificData {
763        CheckpointVersionSpecificData::V1(CheckpointVersionSpecificDataV1 {
764            randomness_rounds: Vec::new(),
765        })
766    }
767}
768
769#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
770pub struct CheckpointVersionSpecificDataV1 {
771    /// Lists the rounds for which RandomnessStateUpdate transactions are
772    /// present in the checkpoint.
773    pub randomness_rounds: Vec<RandomnessRound>,
774}
775
776#[cfg(test)]
777mod tests {
778    use fastcrypto::traits::KeyPair;
779    use rand::{SeedableRng, prelude::StdRng};
780
781    use super::*;
782    use crate::{
783        digests::{ConsensusCommitDigest, TransactionDigest, TransactionEffectsDigest},
784        transaction::VerifiedTransaction,
785        utils::make_committee_key,
786    };
787
788    // TODO use the file name as a seed
789    const RNG_SEED: [u8; 32] = [
790        21, 23, 199, 200, 234, 250, 252, 178, 94, 15, 202, 178, 62, 186, 88, 137, 233, 192, 130,
791        157, 179, 179, 65, 9, 31, 249, 221, 123, 225, 112, 199, 247,
792    ];
793
794    #[test]
795    fn test_signed_checkpoint() {
796        let mut rng = StdRng::from_seed(RNG_SEED);
797        let (keys, committee) = make_committee_key(&mut rng);
798        let (_, committee2) = make_committee_key(&mut rng);
799
800        let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
801
802        // TODO: duplicated in a test below.
803
804        let signed_checkpoints: Vec<_> = keys
805            .iter()
806            .map(|k| {
807                let name = k.public().into();
808
809                SignedCheckpointSummary::new(
810                    committee.epoch,
811                    CheckpointSummary::new(
812                        &ProtocolConfig::get_for_max_version_UNSAFE(),
813                        committee.epoch,
814                        1,
815                        0,
816                        &set,
817                        None,
818                        GasCostSummary::default(),
819                        None,
820                        0,
821                        Vec::new(),
822                    ),
823                    k,
824                    name,
825                )
826            })
827            .collect();
828
829        signed_checkpoints.iter().for_each(|c| {
830            c.verify_authority_signatures(&committee)
831                .expect("signature ok")
832        });
833
834        // fails when not signed by member of committee
835        signed_checkpoints
836            .iter()
837            .for_each(|c| assert!(c.verify_authority_signatures(&committee2).is_err()));
838    }
839
840    #[test]
841    fn test_certified_checkpoint() {
842        let mut rng = StdRng::from_seed(RNG_SEED);
843        let (keys, committee) = make_committee_key(&mut rng);
844
845        let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
846
847        let summary = CheckpointSummary::new(
848            &ProtocolConfig::get_for_max_version_UNSAFE(),
849            committee.epoch,
850            1,
851            0,
852            &set,
853            None,
854            GasCostSummary::default(),
855            None,
856            0,
857            Vec::new(),
858        );
859
860        let sign_infos: Vec<_> = keys
861            .iter()
862            .map(|k| {
863                let name = k.public().into();
864
865                SignedCheckpointSummary::sign(committee.epoch, &summary, k, name)
866            })
867            .collect();
868
869        let checkpoint_cert =
870            CertifiedCheckpointSummary::new(summary, sign_infos, &committee).expect("Cert is OK");
871
872        // Signature is correct on proposal, and with same transactions
873        assert!(
874            checkpoint_cert
875                .verify_with_contents(&committee, Some(&set))
876                .is_ok()
877        );
878
879        // Make a bad proposal
880        let signed_checkpoints: Vec<_> = keys
881            .iter()
882            .map(|k| {
883                let name = k.public().into();
884                let set = CheckpointContents::new_with_digests_only_for_tests([
885                    ExecutionDigests::random(),
886                ]);
887
888                SignedCheckpointSummary::new(
889                    committee.epoch,
890                    CheckpointSummary::new(
891                        &ProtocolConfig::get_for_max_version_UNSAFE(),
892                        committee.epoch,
893                        1,
894                        0,
895                        &set,
896                        None,
897                        GasCostSummary::default(),
898                        None,
899                        0,
900                        Vec::new(),
901                    ),
902                    k,
903                    name,
904                )
905            })
906            .collect();
907
908        let summary = signed_checkpoints[0].data().clone();
909        let sign_infos = signed_checkpoints
910            .into_iter()
911            .map(|v| v.into_sig())
912            .collect();
913        assert!(
914            CertifiedCheckpointSummary::new(summary, sign_infos, &committee)
915                .unwrap()
916                .verify_authority_signatures(&committee)
917                .is_err()
918        )
919    }
920
921    // Generate a CheckpointSummary from the input transaction digest. All the other
922    // fields in the generated CheckpointSummary will be the same. The generated
923    // CheckpointSummary can be used to test how input transaction digest
924    // affects CheckpointSummary.
925    fn generate_test_checkpoint_summary_from_digest(
926        digest: TransactionDigest,
927    ) -> CheckpointSummary {
928        CheckpointSummary::new(
929            &ProtocolConfig::get_for_max_version_UNSAFE(),
930            1,
931            2,
932            10,
933            &CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::new(
934                digest,
935                TransactionEffectsDigest::ZERO,
936            )]),
937            None,
938            GasCostSummary::default(),
939            None,
940            100,
941            Vec::new(),
942        )
943    }
944
945    // Tests that ConsensusCommitPrologue with different consensus commit digest
946    // will result in different checkpoint content.
947    #[test]
948    fn test_checkpoint_summary_with_different_consensus_digest() {
949        // First, tests that same consensus commit digest will produce the same
950        // checkpoint content.
951        {
952            let t1 = VerifiedTransaction::new_consensus_commit_prologue_v1(
953                1,
954                2,
955                100,
956                ConsensusCommitDigest::default(),
957                Vec::new(),
958            );
959            let t2 = VerifiedTransaction::new_consensus_commit_prologue_v1(
960                1,
961                2,
962                100,
963                ConsensusCommitDigest::default(),
964                Vec::new(),
965            );
966            let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
967            let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
968            assert_eq!(c1.digest(), c2.digest());
969        }
970
971        // Next, tests that different consensus commit digests will produce the
972        // different checkpoint contents.
973        {
974            let t1 = VerifiedTransaction::new_consensus_commit_prologue_v1(
975                1,
976                2,
977                100,
978                ConsensusCommitDigest::default(),
979                Vec::new(),
980            );
981            let t2 = VerifiedTransaction::new_consensus_commit_prologue_v1(
982                1,
983                2,
984                100,
985                ConsensusCommitDigest::random(),
986                Vec::new(),
987            );
988            let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
989            let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
990            assert_ne!(c1.digest(), c2.digest());
991        }
992    }
993}