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)]
187#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
188pub struct CheckpointContents(
189    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
190    Vec<CheckpointTransactionInfo>,
191);
192
193impl CheckpointContents {
194    pub fn new(transactions: Vec<CheckpointTransactionInfo>) -> Self {
195        Self(transactions)
196    }
197
198    pub fn transactions(&self) -> &[CheckpointTransactionInfo] {
199        &self.0
200    }
201
202    pub fn into_v1(self) -> Vec<CheckpointTransactionInfo> {
203        self.0
204    }
205}
206
207/// Transaction information committed to in a checkpoint
208#[derive(Clone, Debug, PartialEq, Eq)]
209#[cfg_attr(
210    feature = "serde",
211    derive(serde_derive::Serialize, serde_derive::Deserialize)
212)]
213#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
214pub struct CheckpointTransactionInfo {
215    pub transaction: Digest,
216    pub effects: Digest,
217    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
218    pub signatures: Vec<UserSignature>,
219}
220
221#[derive(Clone, Debug, PartialEq)]
222#[cfg_attr(
223    feature = "serde",
224    derive(serde_derive::Serialize, serde_derive::Deserialize)
225)]
226#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
227pub struct CheckpointData {
228    pub checkpoint_summary: SignedCheckpointSummary,
229    pub checkpoint_contents: CheckpointContents,
230    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
231    pub transactions: Vec<CheckpointTransaction>,
232}
233
234#[derive(Clone, Debug, PartialEq, Eq)]
235#[cfg_attr(
236    feature = "serde",
237    derive(serde_derive::Serialize, serde_derive::Deserialize)
238)]
239#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
240pub struct CheckpointTransaction {
241    /// The input Transaction
242    #[cfg_attr(
243        feature = "serde",
244        serde(with = "::serde_with::As::<crate::_serde::SignedTransactionWithIntentMessage>")
245    )]
246    pub transaction: SignedTransaction,
247    /// The effects produced by executing this transaction
248    pub effects: TransactionEffects,
249    /// The events, if any, emitted by this transaciton during execution
250    pub events: Option<TransactionEvents>,
251    /// The state of all inputs to this transaction as they were prior to execution.
252    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
253    pub input_objects: Vec<Object>,
254    /// The state of all output objects created or mutated by this transaction.
255    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
256    pub output_objects: Vec<Object>,
257}
258
259#[cfg(feature = "serde")]
260#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
261mod serialization {
262    use super::*;
263
264    use serde::Deserialize;
265    use serde::Deserializer;
266    use serde::Serialize;
267    use serde::Serializer;
268
269    impl Serialize for CheckpointContents {
270        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
271        where
272            S: Serializer,
273        {
274            use serde::ser::SerializeSeq;
275            use serde::ser::SerializeTupleVariant;
276
277            #[derive(serde_derive::Serialize)]
278            struct Digests<'a> {
279                transaction: &'a Digest,
280                effects: &'a Digest,
281            }
282
283            struct DigestSeq<'a>(&'a CheckpointContents);
284            impl Serialize for DigestSeq<'_> {
285                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
286                where
287                    S: Serializer,
288                {
289                    let mut seq = serializer.serialize_seq(Some(self.0 .0.len()))?;
290                    for txn in &self.0 .0 {
291                        let digests = Digests {
292                            transaction: &txn.transaction,
293                            effects: &txn.effects,
294                        };
295                        seq.serialize_element(&digests)?;
296                    }
297                    seq.end()
298                }
299            }
300
301            struct SignatureSeq<'a>(&'a CheckpointContents);
302            impl Serialize for SignatureSeq<'_> {
303                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
304                where
305                    S: Serializer,
306                {
307                    let mut seq = serializer.serialize_seq(Some(self.0 .0.len()))?;
308                    for txn in &self.0 .0 {
309                        seq.serialize_element(&txn.signatures)?;
310                    }
311                    seq.end()
312                }
313            }
314
315            let mut s = serializer.serialize_tuple_variant("CheckpointContents", 0, "V1", 2)?;
316            s.serialize_field(&DigestSeq(self))?;
317            s.serialize_field(&SignatureSeq(self))?;
318            s.end()
319        }
320    }
321
322    #[derive(serde_derive::Deserialize)]
323    struct ExecutionDigests {
324        transaction: Digest,
325        effects: Digest,
326    }
327
328    #[derive(serde_derive::Deserialize)]
329    struct BinaryContentsV1 {
330        digests: Vec<ExecutionDigests>,
331        signatures: Vec<Vec<UserSignature>>,
332    }
333
334    #[derive(serde_derive::Deserialize)]
335    enum BinaryContents {
336        V1(BinaryContentsV1),
337    }
338
339    impl<'de> Deserialize<'de> for CheckpointContents {
340        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
341        where
342            D: Deserializer<'de>,
343        {
344            let BinaryContents::V1(BinaryContentsV1 {
345                digests,
346                signatures,
347            }) = Deserialize::deserialize(deserializer)?;
348
349            if digests.len() != signatures.len() {
350                return Err(serde::de::Error::custom(
351                    "must have same number of signatures as transactions",
352                ));
353            }
354
355            Ok(Self(
356                digests
357                    .into_iter()
358                    .zip(signatures)
359                    .map(
360                        |(
361                            ExecutionDigests {
362                                transaction,
363                                effects,
364                            },
365                            signatures,
366                        )| CheckpointTransactionInfo {
367                            transaction,
368                            effects,
369                            signatures,
370                        },
371                    )
372                    .collect(),
373            ))
374        }
375    }
376
377    #[cfg(test)]
378    mod test {
379        use super::*;
380        use base64ct::Base64;
381        use base64ct::Encoding;
382
383        #[cfg(target_arch = "wasm32")]
384        use wasm_bindgen_test::wasm_bindgen_test as test;
385
386        #[test]
387        fn signed_checkpoint_fixture() {
388            const FIXTURES: &[&str] = &[
389                "CgAAAAAAAAAUAAAAAAAAABUAAAAAAAAAIJ6CIMG/6Un4MKNM8h+R9r8bQ6dNTk0WZxBMUQH1XFQBASCWUVucdQkje+4YbXVpvQZcg74nndL1NK7ccj1dDR04agAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAKAAAAAAAAAKOonlp6Vf8dJEjQYa/VyigZruaZwSwu3u/ZZVCsdrS1iaGPIAERZcNnfM75tOh10hI6MAAAAQAAAAAAAAAQAAAAAAA=",
390                "AgAAAAAAAAAFAAAAAAAAAAYAAAAAAAAAIINaPEm+WRQV2vGcPR9fe6fYhxl48GpqB+DqDYQqRHkuASBe+6BDLHSRCMiWqBkvVMqWXPWUsZnpc2gbOVdre3vnowAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAQFgqGJldzxWMt2CZow1QiLmDf0RdLE6udu0bVdc1xaExX37NByF27rDH5C1DF+mkpLdA6YZnXMvuUw+zoWo71qe2DTdIDU4AcNaSUE3OoEHceuT+fBa6dMib3yDkkhmOZLyECcAAAAAAAAkAAAAAAAAAAAAAgAAAAAAAACvljn+1LWFSpu3PGx4BlIlVZq7blFK+fV7SOPEU0z9nz7lgkv8a12EA9R0tGm8hEYSOjAAAAEAAAAAAAAAEAAAAAAA",
391                "AAAAAAAAAAACAAAAAAAAAAgAAAAAAAAAIJBUX7gl7mh+M/NoHcFa3oR3I+5BFublxXc33/GPUZ79ASAyWbpVsiA3AaeLJkcLPhQy4QKHM66TkJNFPJLaVqfoJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjf/gOJgBAAABASDxpIv3q2qAdsaAF16ZXzTSU8tRSJ5ylwIRyOzYsdeZigACAAAAAAAAAAAAALkSpPtV6n0lfTq6upYfSk7ZWw8avL3vaG/tU6s2ELoUKK3ucADvyjsDGNVKkhhGkhI6MAAAAQAAAAAAAAAQAAAAAAA="
392            ];
393
394            for fixture in FIXTURES {
395                let bcs = Base64::decode_vec(fixture).unwrap();
396
397                let checkpoint: SignedCheckpointSummary = bcs::from_bytes(&bcs).unwrap();
398                let bytes = bcs::to_bytes(&checkpoint).unwrap();
399                assert_eq!(bcs, bytes);
400                let json = serde_json::to_string_pretty(&checkpoint).unwrap();
401                println!("{json}");
402            }
403        }
404
405        #[test]
406        fn contents_fixture() {
407            let fixture ="AAEgp6oAB8Qadn8+FqtdqeDIp8ViQNOZpMKs44MN0N5y7zIgqn5dKR1+8poL0pLNwRo/2knMnodwMTEDhqYL03kdewQBAWEAgpORkfH6ewjfFQYZJhmjkYq0/B3Set4mLJX/G0wUPb/V4H41gJipYu4I6ToyixnEuPQWxHKLckhNn+0UmI+pAJ9GegzEh0q2HWABmFMpFoPw0229dCfzWNOhHW5bes4H";
408
409            let bcs = Base64::decode_vec(fixture).unwrap();
410
411            let contents: CheckpointContents = bcs::from_bytes(&bcs).unwrap();
412            let bytes = bcs::to_bytes(&contents).unwrap();
413            assert_eq!(bcs, bytes);
414            let json = serde_json::to_string_pretty(&contents).unwrap();
415            println!("{json}");
416        }
417    }
418}