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/// checkpoint-commitment = ecmh-live-object-set
26/// ecmh-live-object-set = %x00 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))]
34pub enum CheckpointCommitment {
35    /// An Elliptic Curve Multiset Hash attesting to the set of Objects that comprise the live
36    /// state of the Sui blockchain.
37    EcmhLiveObjectSet { digest: Digest },
38    // Other commitment types (e.g. merkle roots) go here.
39}
40
41/// Data, which when included in a [`CheckpointSummary`], signals the end of an `Epoch`.
42///
43/// # BCS
44///
45/// The BCS serialized form for this type is defined by the following ABNF:
46///
47/// ```text
48/// end-of-epoch-data = (vector validator-committee-member) ; next_epoch_committee
49///                     u64                                 ; next_epoch_protocol_version
50///                     (vector checkpoint-commitment)      ; epoch_commitments
51/// ```
52#[derive(Clone, Debug, PartialEq, Eq)]
53#[cfg_attr(
54    feature = "serde",
55    derive(serde_derive::Serialize, serde_derive::Deserialize)
56)]
57#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
58pub struct EndOfEpochData {
59    /// The set of Validators that will be in the ValidatorCommittee for the next epoch.
60    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
61    pub next_epoch_committee: Vec<ValidatorCommitteeMember>,
62
63    /// The protocol version that is in effect during the next epoch.
64    pub next_epoch_protocol_version: ProtocolVersion,
65
66    /// Commitments to epoch specific state (e.g. live object set)
67    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
68    pub epoch_commitments: Vec<CheckpointCommitment>,
69}
70
71/// A header for a Checkpoint on the Sui blockchain.
72///
73/// On the Sui network, checkpoints define the history of the blockchain. They are quite similar to
74/// the concept of blocks used by other blockchains like Bitcoin or Ethereum. The Sui blockchain,
75/// however, forms checkpoints after transaction execution has already happened to provide a
76/// certified history of the chain, instead of being formed before execution.
77///
78/// Checkpoints commit to a variety of state including but not limited to:
79/// - The hash of the previous checkpoint.
80/// - The set of transaction digests, their corresponding effects digests, as well as the set of
81///   user signatures which authorized its execution.
82/// - The object's produced by a transaction.
83/// - The set of live objects that make up the current state of the chain.
84/// - On epoch transitions, the next validator committee.
85///
86/// `CheckpointSummary`s themselves don't directly include all of the above information but they
87/// are the top-level type by which all the above are committed to transitively via cryptographic
88/// hashes included in the summary. `CheckpointSummary`s are signed and certified by a quorum of
89/// the validator committee in a given epoch in order to allow verification of the chain's state.
90///
91/// # BCS
92///
93/// The BCS serialized form for this type is defined by the following ABNF:
94///
95/// ```text
96/// checkpoint-summary = u64                            ; epoch
97///                      u64                            ; sequence_number
98///                      u64                            ; network_total_transactions
99///                      digest                         ; content_digest
100///                      (option digest)                ; previous_digest
101///                      gas-cost-summary               ; epoch_rolling_gas_cost_summary
102///                      u64                            ; timestamp_ms
103///                      (vector checkpoint-commitment) ; checkpoint_commitments
104///                      (option end-of-epoch-data)     ; end_of_epoch_data
105///                      bytes                          ; version_specific_data
106/// ```
107#[derive(Clone, Debug, PartialEq, Eq)]
108#[cfg_attr(
109    feature = "serde",
110    derive(serde_derive::Serialize, serde_derive::Deserialize)
111)]
112#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
113pub struct CheckpointSummary {
114    /// Epoch that this checkpoint belongs to.
115    pub epoch: EpochId,
116
117    /// The height of this checkpoint.
118    pub sequence_number: CheckpointSequenceNumber,
119
120    /// Total number of transactions committed since genesis, including those in this
121    /// checkpoint.
122    pub network_total_transactions: u64,
123
124    /// The hash of the [`CheckpointContents`] for this checkpoint.
125    pub content_digest: Digest,
126
127    /// The hash of the previous `CheckpointSummary`.
128    ///
129    /// This will be only be `None` for the first, or genesis checkpoint.
130    pub previous_digest: Option<Digest>,
131
132    /// The running total gas costs of all transactions included in the current epoch so far
133    /// until this checkpoint.
134    pub epoch_rolling_gas_cost_summary: GasCostSummary,
135
136    /// Timestamp of the checkpoint - number of milliseconds from the Unix epoch
137    /// Checkpoint timestamps are monotonic, but not strongly monotonic - subsequent
138    /// checkpoints can have same timestamp if they originate from the same underlining consensus commit
139    pub timestamp_ms: CheckpointTimestamp,
140
141    /// Commitments to checkpoint-specific state.
142    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
143    pub checkpoint_commitments: Vec<CheckpointCommitment>,
144
145    /// Extra data only present in the final checkpoint of an epoch.
146    pub end_of_epoch_data: Option<EndOfEpochData>,
147
148    /// `CheckpointSummary` is not an evolvable structure - it must be readable by any version of
149    /// the code. Therefore, in order to allow extensions to be added to `CheckpointSummary`, we
150    /// allow opaque data to be added to checkpoints which can be deserialized based on the current
151    /// protocol version.
152    pub version_specific_data: Vec<u8>,
153}
154
155#[derive(Clone, Debug, PartialEq)]
156#[cfg_attr(
157    feature = "serde",
158    derive(serde_derive::Serialize, serde_derive::Deserialize)
159)]
160#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
161pub struct SignedCheckpointSummary {
162    pub checkpoint: CheckpointSummary,
163    pub signature: ValidatorAggregatedSignature,
164}
165
166/// The committed to contents of a checkpoint.
167///
168/// `CheckpointContents` contains a list of digests of Transactions, their effects, and the user
169/// signatures that authorized their execution included in a checkpoint.
170///
171/// # BCS
172///
173/// The BCS serialized form for this type is defined by the following ABNF:
174///
175/// ```text
176/// checkpoint-contents = %x00 checkpoint-contents-v1 ; variant 0
177///
178/// checkpoint-contents-v1 = (vector (digest digest)) ; vector of transaction and effect digests
179///                          (vector (vector bcs-user-signature)) ; set of user signatures for each
180///                                                               ; transaction. MUST be the same
181///                                                               ; length as the vector of digests
182/// ```
183#[derive(Clone, Debug, PartialEq, Eq)]
184#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
185pub struct CheckpointContents(
186    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
187    Vec<CheckpointTransactionInfo>,
188);
189
190impl CheckpointContents {
191    pub fn new(transactions: Vec<CheckpointTransactionInfo>) -> Self {
192        Self(transactions)
193    }
194
195    pub fn transactions(&self) -> &[CheckpointTransactionInfo] {
196        &self.0
197    }
198
199    pub fn into_v1(self) -> Vec<CheckpointTransactionInfo> {
200        self.0
201    }
202}
203
204/// Transaction information committed to in a checkpoint
205#[derive(Clone, Debug, PartialEq, Eq)]
206#[cfg_attr(
207    feature = "serde",
208    derive(serde_derive::Serialize, serde_derive::Deserialize)
209)]
210#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
211pub struct CheckpointTransactionInfo {
212    pub transaction: Digest,
213    pub effects: Digest,
214    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
215    pub signatures: Vec<UserSignature>,
216}
217
218#[derive(Clone, Debug, PartialEq)]
219#[cfg_attr(
220    feature = "serde",
221    derive(serde_derive::Serialize, serde_derive::Deserialize)
222)]
223#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
224pub struct CheckpointData {
225    pub checkpoint_summary: SignedCheckpointSummary,
226    pub checkpoint_contents: CheckpointContents,
227    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
228    pub transactions: Vec<CheckpointTransaction>,
229}
230
231#[derive(Clone, Debug, PartialEq, Eq)]
232#[cfg_attr(
233    feature = "serde",
234    derive(serde_derive::Serialize, serde_derive::Deserialize)
235)]
236#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
237pub struct CheckpointTransaction {
238    /// The input Transaction
239    #[cfg_attr(
240        feature = "serde",
241        serde(with = "::serde_with::As::<crate::_serde::SignedTransactionWithIntentMessage>")
242    )]
243    pub transaction: SignedTransaction,
244    /// The effects produced by executing this transaction
245    pub effects: TransactionEffects,
246    /// The events, if any, emitted by this transaciton during execution
247    pub events: Option<TransactionEvents>,
248    /// The state of all inputs to this transaction as they were prior to execution.
249    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
250    pub input_objects: Vec<Object>,
251    /// The state of all output objects created or mutated by this transaction.
252    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
253    pub output_objects: Vec<Object>,
254}
255
256#[cfg(feature = "serde")]
257#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
258mod serialization {
259    use super::*;
260
261    use serde::Deserialize;
262    use serde::Deserializer;
263    use serde::Serialize;
264    use serde::Serializer;
265
266    impl Serialize for CheckpointContents {
267        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
268        where
269            S: Serializer,
270        {
271            use serde::ser::SerializeSeq;
272            use serde::ser::SerializeTupleVariant;
273
274            #[derive(serde_derive::Serialize)]
275            struct Digests<'a> {
276                transaction: &'a Digest,
277                effects: &'a Digest,
278            }
279
280            struct DigestSeq<'a>(&'a CheckpointContents);
281            impl Serialize for DigestSeq<'_> {
282                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
283                where
284                    S: Serializer,
285                {
286                    let mut seq = serializer.serialize_seq(Some(self.0 .0.len()))?;
287                    for txn in &self.0 .0 {
288                        let digests = Digests {
289                            transaction: &txn.transaction,
290                            effects: &txn.effects,
291                        };
292                        seq.serialize_element(&digests)?;
293                    }
294                    seq.end()
295                }
296            }
297
298            struct SignatureSeq<'a>(&'a CheckpointContents);
299            impl Serialize for SignatureSeq<'_> {
300                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
301                where
302                    S: Serializer,
303                {
304                    let mut seq = serializer.serialize_seq(Some(self.0 .0.len()))?;
305                    for txn in &self.0 .0 {
306                        seq.serialize_element(&txn.signatures)?;
307                    }
308                    seq.end()
309                }
310            }
311
312            let mut s = serializer.serialize_tuple_variant("CheckpointContents", 0, "V1", 2)?;
313            s.serialize_field(&DigestSeq(self))?;
314            s.serialize_field(&SignatureSeq(self))?;
315            s.end()
316        }
317    }
318
319    #[derive(serde_derive::Deserialize)]
320    struct ExecutionDigests {
321        transaction: Digest,
322        effects: Digest,
323    }
324
325    #[derive(serde_derive::Deserialize)]
326    struct BinaryContentsV1 {
327        digests: Vec<ExecutionDigests>,
328        signatures: Vec<Vec<UserSignature>>,
329    }
330
331    #[derive(serde_derive::Deserialize)]
332    enum BinaryContents {
333        V1(BinaryContentsV1),
334    }
335
336    impl<'de> Deserialize<'de> for CheckpointContents {
337        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
338        where
339            D: Deserializer<'de>,
340        {
341            let BinaryContents::V1(BinaryContentsV1 {
342                digests,
343                signatures,
344            }) = Deserialize::deserialize(deserializer)?;
345
346            if digests.len() != signatures.len() {
347                return Err(serde::de::Error::custom(
348                    "must have same number of signatures as transactions",
349                ));
350            }
351
352            Ok(Self(
353                digests
354                    .into_iter()
355                    .zip(signatures)
356                    .map(
357                        |(
358                            ExecutionDigests {
359                                transaction,
360                                effects,
361                            },
362                            signatures,
363                        )| CheckpointTransactionInfo {
364                            transaction,
365                            effects,
366                            signatures,
367                        },
368                    )
369                    .collect(),
370            ))
371        }
372    }
373
374    #[cfg(test)]
375    mod test {
376        use super::*;
377        use base64ct::Base64;
378        use base64ct::Encoding;
379
380        #[cfg(target_arch = "wasm32")]
381        use wasm_bindgen_test::wasm_bindgen_test as test;
382
383        #[test]
384        fn signed_checkpoint_fixture() {
385            const FIXTURES: &[&str] = &[
386                "CgAAAAAAAAAUAAAAAAAAABUAAAAAAAAAIJ6CIMG/6Un4MKNM8h+R9r8bQ6dNTk0WZxBMUQH1XFQBASCWUVucdQkje+4YbXVpvQZcg74nndL1NK7ccj1dDR04agAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAKAAAAAAAAAKOonlp6Vf8dJEjQYa/VyigZruaZwSwu3u/ZZVCsdrS1iaGPIAERZcNnfM75tOh10hI6MAAAAQAAAAAAAAAQAAAAAAA=",
387                "AgAAAAAAAAAFAAAAAAAAAAYAAAAAAAAAIINaPEm+WRQV2vGcPR9fe6fYhxl48GpqB+DqDYQqRHkuASBe+6BDLHSRCMiWqBkvVMqWXPWUsZnpc2gbOVdre3vnowAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAQFgqGJldzxWMt2CZow1QiLmDf0RdLE6udu0bVdc1xaExX37NByF27rDH5C1DF+mkpLdA6YZnXMvuUw+zoWo71qe2DTdIDU4AcNaSUE3OoEHceuT+fBa6dMib3yDkkhmOZLyECcAAAAAAAAkAAAAAAAAAAAAAgAAAAAAAACvljn+1LWFSpu3PGx4BlIlVZq7blFK+fV7SOPEU0z9nz7lgkv8a12EA9R0tGm8hEYSOjAAAAEAAAAAAAAAEAAAAAAA",
388            ];
389
390            for fixture in FIXTURES {
391                let bcs = Base64::decode_vec(fixture).unwrap();
392
393                let checkpoint: SignedCheckpointSummary = bcs::from_bytes(&bcs).unwrap();
394                let bytes = bcs::to_bytes(&checkpoint).unwrap();
395                assert_eq!(bcs, bytes);
396                let json = serde_json::to_string_pretty(&checkpoint).unwrap();
397                println!("{json}");
398            }
399        }
400
401        #[test]
402        fn contents_fixture() {
403            let fixture ="AAEgp6oAB8Qadn8+FqtdqeDIp8ViQNOZpMKs44MN0N5y7zIgqn5dKR1+8poL0pLNwRo/2knMnodwMTEDhqYL03kdewQBAWEAgpORkfH6ewjfFQYZJhmjkYq0/B3Set4mLJX/G0wUPb/V4H41gJipYu4I6ToyixnEuPQWxHKLckhNn+0UmI+pAJ9GegzEh0q2HWABmFMpFoPw0229dCfzWNOhHW5bes4H";
404
405            let bcs = Base64::decode_vec(fixture).unwrap();
406
407            let contents: CheckpointContents = bcs::from_bytes(&bcs).unwrap();
408            let bytes = bcs::to_bytes(&contents).unwrap();
409            assert_eq!(bcs, bytes);
410            let json = serde_json::to_string_pretty(&contents).unwrap();
411            println!("{json}");
412        }
413    }
414}