sui_sdk_types/
checkpoint.rs

1use super::Digest;
2use super::GasCostSummary;
3use super::Object;
4use super::SignedTransaction;
5use super::TransactionEffects;
6use super::TransactionEvents;
7use super::UserSignature;
8use super::ValidatorAggregatedSignature;
9use super::ValidatorCommitteeMember;
10
11pub type CheckpointSequenceNumber = u64;
12pub type CheckpointTimestamp = u64;
13pub type EpochId = u64;
14pub type StakeUnit = u64;
15pub type ProtocolVersion = u64;
16
17/// A commitment made by a checkpoint.
18///
19/// # BCS
20///
21/// The BCS serialized form for this type is defined by the following ABNF:
22///
23/// ```text
24/// ; CheckpointCommitment is an enum and each variant is prefixed with its index
25/// ecmh-live-object-set = %x00 digest
26/// checkpoint-artifacts-digest = %x01 digest
27/// ```
28#[derive(Clone, Debug, PartialEq, Eq)]
29#[cfg_attr(
30    feature = "serde",
31    derive(serde_derive::Serialize, serde_derive::Deserialize)
32)]
33#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
34#[non_exhaustive]
35pub enum CheckpointCommitment {
36    /// An Elliptic Curve Multiset Hash attesting to the set of Objects that comprise the live
37    /// state of the Sui blockchain.
38    EcmhLiveObjectSet { digest: Digest },
39
40    /// Digest of the checkpoint artifacts.
41    CheckpointArtifacts { digest: Digest },
42}
43
44/// Data, which when included in a [`CheckpointSummary`], signals the end of an `Epoch`.
45///
46/// # BCS
47///
48/// The BCS serialized form for this type is defined by the following ABNF:
49///
50/// ```text
51/// end-of-epoch-data = (vector validator-committee-member) ; next_epoch_committee
52///                     u64                                 ; next_epoch_protocol_version
53///                     (vector checkpoint-commitment)      ; epoch_commitments
54/// ```
55#[derive(Clone, Debug, PartialEq, Eq)]
56#[cfg_attr(
57    feature = "serde",
58    derive(serde_derive::Serialize, serde_derive::Deserialize)
59)]
60#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
61pub struct EndOfEpochData {
62    /// The set of Validators that will be in the ValidatorCommittee for the next epoch.
63    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
64    pub next_epoch_committee: Vec<ValidatorCommitteeMember>,
65
66    /// The protocol version that is in effect during the next epoch.
67    pub next_epoch_protocol_version: ProtocolVersion,
68
69    /// Commitments to epoch specific state (e.g. live object set)
70    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
71    pub epoch_commitments: Vec<CheckpointCommitment>,
72}
73
74/// A header for a Checkpoint on the Sui blockchain.
75///
76/// On the Sui network, checkpoints define the history of the blockchain. They are quite similar to
77/// the concept of blocks used by other blockchains like Bitcoin or Ethereum. The Sui blockchain,
78/// however, forms checkpoints after transaction execution has already happened to provide a
79/// certified history of the chain, instead of being formed before execution.
80///
81/// Checkpoints commit to a variety of state including but not limited to:
82/// - The hash of the previous checkpoint.
83/// - The set of transaction digests, their corresponding effects digests, as well as the set of
84///   user signatures which authorized its execution.
85/// - The object's produced by a transaction.
86/// - The set of live objects that make up the current state of the chain.
87/// - On epoch transitions, the next validator committee.
88///
89/// `CheckpointSummary`s themselves don't directly include all of the above information but they
90/// are the top-level type by which all the above are committed to transitively via cryptographic
91/// hashes included in the summary. `CheckpointSummary`s are signed and certified by a quorum of
92/// the validator committee in a given epoch in order to allow verification of the chain's state.
93///
94/// # BCS
95///
96/// The BCS serialized form for this type is defined by the following ABNF:
97///
98/// ```text
99/// checkpoint-summary = u64                            ; epoch
100///                      u64                            ; sequence_number
101///                      u64                            ; network_total_transactions
102///                      digest                         ; content_digest
103///                      (option digest)                ; previous_digest
104///                      gas-cost-summary               ; epoch_rolling_gas_cost_summary
105///                      u64                            ; timestamp_ms
106///                      (vector checkpoint-commitment) ; checkpoint_commitments
107///                      (option end-of-epoch-data)     ; end_of_epoch_data
108///                      bytes                          ; version_specific_data
109/// ```
110#[derive(Clone, Debug, PartialEq, Eq)]
111#[cfg_attr(
112    feature = "serde",
113    derive(serde_derive::Serialize, serde_derive::Deserialize)
114)]
115#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
116pub struct CheckpointSummary {
117    /// Epoch that this checkpoint belongs to.
118    pub epoch: EpochId,
119
120    /// The height of this checkpoint.
121    pub sequence_number: CheckpointSequenceNumber,
122
123    /// Total number of transactions committed since genesis, including those in this
124    /// checkpoint.
125    pub network_total_transactions: u64,
126
127    /// The hash of the [`CheckpointContents`] for this checkpoint.
128    pub content_digest: Digest,
129
130    /// The hash of the previous `CheckpointSummary`.
131    ///
132    /// This will be only be `None` for the first, or genesis checkpoint.
133    pub previous_digest: Option<Digest>,
134
135    /// The running total gas costs of all transactions included in the current epoch so far
136    /// until this checkpoint.
137    pub epoch_rolling_gas_cost_summary: GasCostSummary,
138
139    /// Timestamp of the checkpoint - number of milliseconds from the Unix epoch
140    /// Checkpoint timestamps are monotonic, but not strongly monotonic - subsequent
141    /// checkpoints can have same timestamp if they originate from the same underlining consensus commit
142    pub timestamp_ms: CheckpointTimestamp,
143
144    /// Commitments to checkpoint-specific state.
145    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
146    pub checkpoint_commitments: Vec<CheckpointCommitment>,
147
148    /// Extra data only present in the final checkpoint of an epoch.
149    pub end_of_epoch_data: Option<EndOfEpochData>,
150
151    /// `CheckpointSummary` is not an evolvable structure - it must be readable by any version of
152    /// the code. Therefore, in order to allow extensions to be added to `CheckpointSummary`, we
153    /// allow opaque data to be added to checkpoints which can be deserialized based on the current
154    /// protocol version.
155    pub version_specific_data: Vec<u8>,
156}
157
158#[derive(Clone, Debug, PartialEq)]
159#[cfg_attr(
160    feature = "serde",
161    derive(serde_derive::Serialize, serde_derive::Deserialize)
162)]
163#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
164pub struct SignedCheckpointSummary {
165    pub checkpoint: CheckpointSummary,
166    pub signature: ValidatorAggregatedSignature,
167}
168
169/// The committed to contents of a checkpoint.
170///
171/// `CheckpointContents` contains a list of digests of Transactions, their effects, and the user
172/// signatures that authorized their execution included in a checkpoint.
173///
174/// # BCS
175///
176/// The BCS serialized form for this type is defined by the following ABNF:
177///
178/// ```text
179/// checkpoint-contents = %x00 checkpoint-contents-v1 ; variant 0
180///
181/// checkpoint-contents-v1 = (vector (digest digest)) ; vector of transaction and effect digests
182///                          (vector (vector bcs-user-signature)) ; set of user signatures for each
183///                                                               ; transaction. MUST be the same
184///                                                               ; length as the vector of digests
185/// ```
186#[derive(Clone, Debug, PartialEq, Eq)]
187pub struct CheckpointContents {
188    version: usize,
189    transactions: Vec<CheckpointTransactionInfo>,
190}
191
192impl CheckpointContents {
193    pub fn new_v1(mut transactions: Vec<CheckpointTransactionInfo>) -> Self {
194        transactions
195            .iter_mut()
196            .flat_map(|t| t.signatures.iter_mut())
197            .for_each(|(_, v)| *v = None);
198        Self {
199            version: 1,
200            transactions,
201        }
202    }
203
204    pub fn new_v2(transactions: Vec<CheckpointTransactionInfo>) -> Self {
205        Self {
206            version: 2,
207            transactions,
208        }
209    }
210
211    pub fn transactions(&self) -> &[CheckpointTransactionInfo] {
212        &self.transactions
213    }
214
215    pub fn version(&self) -> usize {
216        self.version
217    }
218}
219
220#[cfg(feature = "proptest")]
221impl proptest::arbitrary::Arbitrary for CheckpointContents {
222    type Parameters = ();
223    type Strategy = proptest::strategy::BoxedStrategy<Self>;
224
225    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
226        use proptest::collection::vec;
227        use proptest::prelude::*;
228        use proptest::strategy::Strategy;
229
230        (
231            any::<bool>(),
232            vec(any::<CheckpointTransactionInfo>(), 0..=2),
233        )
234            .prop_map(|(version, txns)| match version {
235                false => Self::new_v1(txns),
236                true => Self::new_v2(txns),
237            })
238            .boxed()
239    }
240}
241
242/// Transaction information committed to in a checkpoint
243#[derive(Clone, Debug, PartialEq, Eq)]
244#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
245pub struct CheckpointTransactionInfo {
246    transaction: Digest,
247    effects: Digest,
248    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
249    signatures: Vec<(UserSignature, Option<u64>)>,
250}
251
252impl CheckpointTransactionInfo {
253    pub fn new(transaction: Digest, effects: Digest, signatures: Vec<UserSignature>) -> Self {
254        Self {
255            transaction,
256            effects,
257            signatures: signatures.into_iter().map(|s| (s, None)).collect(),
258        }
259    }
260
261    pub fn new_with_address_aliases_versions(
262        transaction: Digest,
263        effects: Digest,
264        signatures: Vec<(UserSignature, Option<u64>)>,
265    ) -> Self {
266        Self {
267            transaction,
268            effects,
269            signatures,
270        }
271    }
272
273    pub fn transaction(&self) -> &Digest {
274        &self.transaction
275    }
276
277    pub fn effects(&self) -> &Digest {
278        &self.effects
279    }
280
281    pub fn signatures(&self) -> impl Iterator<Item = &UserSignature> {
282        self.signatures.iter().map(|(s, _)| s)
283    }
284
285    pub fn signatures_with_address_aliases_versions(
286        &self,
287    ) -> impl Iterator<Item = (&UserSignature, Option<u64>)> {
288        self.signatures.iter().map(|(s, v)| (s, *v))
289    }
290}
291
292#[derive(Clone, Debug, PartialEq)]
293#[cfg_attr(
294    feature = "serde",
295    derive(serde_derive::Serialize, serde_derive::Deserialize)
296)]
297#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
298pub struct CheckpointData {
299    pub checkpoint_summary: SignedCheckpointSummary,
300    pub checkpoint_contents: CheckpointContents,
301    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
302    pub transactions: Vec<CheckpointTransaction>,
303}
304
305#[derive(Clone, Debug, PartialEq, Eq)]
306#[cfg_attr(
307    feature = "serde",
308    derive(serde_derive::Serialize, serde_derive::Deserialize)
309)]
310#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
311pub struct CheckpointTransaction {
312    /// The input Transaction
313    #[cfg_attr(
314        feature = "serde",
315        serde(with = "::serde_with::As::<crate::_serde::SignedTransactionWithIntentMessage>")
316    )]
317    pub transaction: SignedTransaction,
318    /// The effects produced by executing this transaction
319    pub effects: TransactionEffects,
320    /// The events, if any, emitted by this transaciton during execution
321    pub events: Option<TransactionEvents>,
322    /// The state of all inputs to this transaction as they were prior to execution.
323    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
324    pub input_objects: Vec<Object>,
325    /// The state of all output objects created or mutated by this transaction.
326    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
327    pub output_objects: Vec<Object>,
328}
329
330#[cfg(feature = "serde")]
331#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
332mod serialization {
333    use super::*;
334
335    use serde::Deserialize;
336    use serde::Deserializer;
337    use serde::Serialize;
338    use serde::Serializer;
339
340    impl Serialize for CheckpointContents {
341        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
342        where
343            S: Serializer,
344        {
345            use serde::ser::SerializeSeq;
346            use serde::ser::SerializeTupleVariant;
347
348            #[derive(serde_derive::Serialize)]
349            struct Digests<'a> {
350                transaction: &'a Digest,
351                effects: &'a Digest,
352            }
353
354            match self.version() {
355                1 => {
356                    struct DigestSeq<'a>(&'a CheckpointContents);
357                    impl Serialize for DigestSeq<'_> {
358                        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
359                        where
360                            S: Serializer,
361                        {
362                            let mut seq =
363                                serializer.serialize_seq(Some(self.0.transactions.len()))?;
364                            for txn in &self.0.transactions {
365                                let digests = Digests {
366                                    transaction: &txn.transaction,
367                                    effects: &txn.effects,
368                                };
369                                seq.serialize_element(&digests)?;
370                            }
371                            seq.end()
372                        }
373                    }
374
375                    struct SignatureSeq<'a>(&'a CheckpointContents);
376                    impl Serialize for SignatureSeq<'_> {
377                        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
378                        where
379                            S: Serializer,
380                        {
381                            let mut seq =
382                                serializer.serialize_seq(Some(self.0.transactions.len()))?;
383                            for txn in &self.0.transactions {
384                                let sigs: Vec<&UserSignature> = txn.signatures().collect();
385                                seq.serialize_element(&sigs)?;
386                            }
387                            seq.end()
388                        }
389                    }
390
391                    let mut s =
392                        serializer.serialize_tuple_variant("CheckpointContents", 0, "V1", 2)?;
393                    s.serialize_field(&DigestSeq(self))?;
394                    s.serialize_field(&SignatureSeq(self))?;
395                    s.end()
396                }
397                2 => {
398                    #[derive(serde_derive::Serialize)]
399                    struct CheckpointTransactionInfoV2<'a> {
400                        digests: Digests<'a>,
401                        signatures: &'a [(UserSignature, Option<u64>)],
402                    }
403
404                    struct V2<'a>(&'a CheckpointContents);
405                    impl Serialize for V2<'_> {
406                        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
407                        where
408                            S: Serializer,
409                        {
410                            let mut seq =
411                                serializer.serialize_seq(Some(self.0.transactions.len()))?;
412                            for txn in &self.0.transactions {
413                                let txn = CheckpointTransactionInfoV2 {
414                                    digests: Digests {
415                                        transaction: &txn.transaction,
416                                        effects: &txn.effects,
417                                    },
418                                    signatures: &txn.signatures,
419                                };
420                                seq.serialize_element(&txn)?;
421                            }
422                            seq.end()
423                        }
424                    }
425
426                    let mut s =
427                        serializer.serialize_tuple_variant("CheckpointContents", 1, "V2", 1)?;
428                    s.serialize_field(&V2(self))?;
429                    s.end()
430                }
431                _ => {
432                    unreachable!("invalid checkpoint contents version");
433                }
434            }
435        }
436    }
437
438    #[derive(serde_derive::Deserialize)]
439    struct ExecutionDigests {
440        transaction: Digest,
441        effects: Digest,
442    }
443
444    #[derive(serde_derive::Deserialize)]
445    struct BinaryContentsV1 {
446        digests: Vec<ExecutionDigests>,
447        signatures: Vec<Vec<UserSignature>>,
448    }
449
450    #[derive(serde_derive::Deserialize)]
451    struct BinaryContentsV2 {
452        transactions: Vec<CheckpointTransactionContents>,
453    }
454
455    #[derive(serde_derive::Deserialize)]
456    struct CheckpointTransactionContents {
457        digests: ExecutionDigests,
458        signatures: Vec<(UserSignature, Option<u64>)>,
459    }
460
461    #[derive(serde_derive::Deserialize)]
462    enum BinaryContents {
463        V1(BinaryContentsV1),
464        V2(BinaryContentsV2),
465    }
466
467    impl<'de> Deserialize<'de> for CheckpointContents {
468        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
469        where
470            D: Deserializer<'de>,
471        {
472            match Deserialize::deserialize(deserializer)? {
473                BinaryContents::V1(BinaryContentsV1 {
474                    digests,
475                    signatures,
476                }) => {
477                    if digests.len() != signatures.len() {
478                        return Err(serde::de::Error::custom(
479                            "must have same number of signatures as transactions",
480                        ));
481                    }
482
483                    Ok(Self::new_v1(
484                        digests
485                            .into_iter()
486                            .zip(signatures)
487                            .map(
488                                |(
489                                    ExecutionDigests {
490                                        transaction,
491                                        effects,
492                                    },
493                                    signatures,
494                                )| {
495                                    CheckpointTransactionInfo::new(transaction, effects, signatures)
496                                },
497                            )
498                            .collect(),
499                    ))
500                }
501                BinaryContents::V2(v2) => Ok(Self::new_v2(
502                    v2.transactions
503                        .into_iter()
504                        .map(|info| {
505                            CheckpointTransactionInfo::new_with_address_aliases_versions(
506                                info.digests.transaction,
507                                info.digests.effects,
508                                info.signatures,
509                            )
510                        })
511                        .collect(),
512                )),
513            }
514        }
515    }
516
517    #[cfg(test)]
518    mod test {
519        use super::*;
520        use base64ct::Base64;
521        use base64ct::Encoding;
522
523        #[cfg(target_arch = "wasm32")]
524        use wasm_bindgen_test::wasm_bindgen_test as test;
525
526        #[test]
527        fn signed_checkpoint_fixture() {
528            const FIXTURES: &[&str] = &[
529                "CgAAAAAAAAAUAAAAAAAAABUAAAAAAAAAIJ6CIMG/6Un4MKNM8h+R9r8bQ6dNTk0WZxBMUQH1XFQBASCWUVucdQkje+4YbXVpvQZcg74nndL1NK7ccj1dDR04agAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAKAAAAAAAAAKOonlp6Vf8dJEjQYa/VyigZruaZwSwu3u/ZZVCsdrS1iaGPIAERZcNnfM75tOh10hI6MAAAAQAAAAAAAAAQAAAAAAA=",
530                "AgAAAAAAAAAFAAAAAAAAAAYAAAAAAAAAIINaPEm+WRQV2vGcPR9fe6fYhxl48GpqB+DqDYQqRHkuASBe+6BDLHSRCMiWqBkvVMqWXPWUsZnpc2gbOVdre3vnowAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAQFgqGJldzxWMt2CZow1QiLmDf0RdLE6udu0bVdc1xaExX37NByF27rDH5C1DF+mkpLdA6YZnXMvuUw+zoWo71qe2DTdIDU4AcNaSUE3OoEHceuT+fBa6dMib3yDkkhmOZLyECcAAAAAAAAkAAAAAAAAAAAAAgAAAAAAAACvljn+1LWFSpu3PGx4BlIlVZq7blFK+fV7SOPEU0z9nz7lgkv8a12EA9R0tGm8hEYSOjAAAAEAAAAAAAAAEAAAAAAA",
531                "AAAAAAAAAAACAAAAAAAAAAgAAAAAAAAAIJBUX7gl7mh+M/NoHcFa3oR3I+5BFublxXc33/GPUZ79ASAyWbpVsiA3AaeLJkcLPhQy4QKHM66TkJNFPJLaVqfoJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjf/gOJgBAAABASDxpIv3q2qAdsaAF16ZXzTSU8tRSJ5ylwIRyOzYsdeZigACAAAAAAAAAAAAALkSpPtV6n0lfTq6upYfSk7ZWw8avL3vaG/tU6s2ELoUKK3ucADvyjsDGNVKkhhGkhI6MAAAAQAAAAAAAAAQAAAAAAA=",
532            ];
533
534            for fixture in FIXTURES {
535                let bcs = Base64::decode_vec(fixture).unwrap();
536
537                let checkpoint: SignedCheckpointSummary = bcs::from_bytes(&bcs).unwrap();
538                let bytes = bcs::to_bytes(&checkpoint).unwrap();
539                assert_eq!(bcs, bytes);
540                let json = serde_json::to_string_pretty(&checkpoint).unwrap();
541                println!("{json}");
542            }
543        }
544
545        #[test]
546        fn contents_fixture() {
547            let fixture = "AAEgp6oAB8Qadn8+FqtdqeDIp8ViQNOZpMKs44MN0N5y7zIgqn5dKR1+8poL0pLNwRo/2knMnodwMTEDhqYL03kdewQBAWEAgpORkfH6ewjfFQYZJhmjkYq0/B3Set4mLJX/G0wUPb/V4H41gJipYu4I6ToyixnEuPQWxHKLckhNn+0UmI+pAJ9GegzEh0q2HWABmFMpFoPw0229dCfzWNOhHW5bes4H";
548
549            let bcs = Base64::decode_vec(fixture).unwrap();
550
551            let contents: CheckpointContents = bcs::from_bytes(&bcs).unwrap();
552            let bytes = bcs::to_bytes(&contents).unwrap();
553            assert_eq!(bcs, bytes);
554            let json = serde_json::to_string_pretty(&contents).unwrap();
555            println!("{json}");
556        }
557    }
558}