sui_sdk_types/
checkpoint.rs

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