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