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(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    pub next_epoch_committee: Vec<ValidatorCommitteeMember>,
61
62    /// The protocol version that is in effect during the next epoch.
63    #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
64    pub next_epoch_protocol_version: ProtocolVersion,
65
66    /// Commitments to epoch specific state (e.g. live object set)
67    pub epoch_commitments: Vec<CheckpointCommitment>,
68}
69
70/// A header for a Checkpoint on the Sui blockchain.
71///
72/// On the Sui network, checkpoints define the history of the blockchain. They are quite similar to
73/// the concept of blocks used by other blockchains like Bitcoin or Ethereum. The Sui blockchain,
74/// however, forms checkpoints after transaction execution has already happened to provide a
75/// certified history of the chain, instead of being formed before execution.
76///
77/// Checkpoints commit to a variety of state including but not limited to:
78/// - The hash of the previous checkpoint.
79/// - The set of transaction digests, their corresponding effects digests, as well as the set of
80///   user signatures which authorized its execution.
81/// - The object's produced by a transaction.
82/// - The set of live objects that make up the current state of the chain.
83/// - On epoch transitions, the next validator committee.
84///
85/// `CheckpointSummary`s themselves don't directly include all of the above information but they
86/// are the top-level type by which all the above are committed to transitively via cryptographic
87/// hashes included in the summary. `CheckpointSummary`s are signed and certified by a quorum of
88/// the validator committee in a given epoch in order to allow verification of the chain's state.
89///
90/// # BCS
91///
92/// The BCS serialized form for this type is defined by the following ABNF:
93///
94/// ```text
95/// checkpoint-summary = u64                            ; epoch
96///                      u64                            ; sequence_number
97///                      u64                            ; network_total_transactions
98///                      digest                         ; content_digest
99///                      (option digest)                ; previous_digest
100///                      gas-cost-summary               ; epoch_rolling_gas_cost_summary
101///                      u64                            ; timestamp_ms
102///                      (vector checkpoint-commitment) ; checkpoint_commitments
103///                      (option end-of-epoch-data)     ; end_of_epoch_data
104///                      bytes                          ; version_specific_data
105/// ```
106#[derive(Clone, Debug, PartialEq, Eq)]
107#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
108pub struct CheckpointSummary {
109    /// Epoch that this checkpoint belongs to.
110    pub epoch: EpochId,
111
112    /// The height of this checkpoint.
113    pub sequence_number: CheckpointSequenceNumber,
114
115    /// Total number of transactions committed since genesis, including those in this
116    /// checkpoint.
117    pub network_total_transactions: u64,
118
119    /// The hash of the [`CheckpointContents`] for this checkpoint.
120    pub content_digest: CheckpointContentsDigest,
121
122    /// The hash of the previous `CheckpointSummary`.
123    ///
124    /// This will be only be `None` for the first, or genesis checkpoint.
125    pub previous_digest: Option<CheckpointDigest>,
126
127    /// The running total gas costs of all transactions included in the current epoch so far
128    /// until this checkpoint.
129    pub epoch_rolling_gas_cost_summary: GasCostSummary,
130
131    /// Timestamp of the checkpoint - number of milliseconds from the Unix epoch
132    /// Checkpoint timestamps are monotonic, but not strongly monotonic - subsequent
133    /// checkpoints can have same timestamp if they originate from the same underlining consensus commit
134    pub timestamp_ms: CheckpointTimestamp,
135
136    /// Commitments to checkpoint-specific state.
137    pub checkpoint_commitments: Vec<CheckpointCommitment>,
138
139    /// Extra data only present in the final checkpoint of an epoch.
140    pub end_of_epoch_data: Option<EndOfEpochData>,
141
142    /// `CheckpointSummary` is not an evolvable structure - it must be readable by any version of
143    /// the code. Therefore, in order to allow extensions to be added to `CheckpointSummary`, we
144    /// allow opaque data to be added to checkpoints which can be deserialized based on the current
145    /// protocol version.
146    pub version_specific_data: Vec<u8>,
147}
148
149#[derive(Clone, Debug, PartialEq)]
150#[cfg_attr(
151    feature = "serde",
152    derive(serde_derive::Serialize, serde_derive::Deserialize)
153)]
154#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
155pub struct SignedCheckpointSummary {
156    pub checkpoint: CheckpointSummary,
157    pub signature: ValidatorAggregatedSignature,
158}
159
160/// The committed to contents of a checkpoint.
161///
162/// `CheckpointContents` contains a list of digests of Transactions, their effects, and the user
163/// signatures that authorized their execution included in a checkpoint.
164///
165/// # BCS
166///
167/// The BCS serialized form for this type is defined by the following ABNF:
168///
169/// ```text
170/// checkpoint-contents = %x00 checkpoint-contents-v1 ; variant 0
171///
172/// checkpoint-contents-v1 = (vector (digest digest)) ; vector of transaction and effect digests
173///                          (vector (vector bcs-user-signature)) ; set of user signatures for each
174///                                                               ; transaction. MUST be the same
175///                                                               ; length as the vector of digests
176/// ```
177#[derive(Clone, Debug, PartialEq, Eq)]
178#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
179pub struct CheckpointContents(
180    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
181    Vec<CheckpointTransactionInfo>,
182);
183
184impl CheckpointContents {
185    pub fn new(transactions: Vec<CheckpointTransactionInfo>) -> Self {
186        Self(transactions)
187    }
188
189    pub fn transactions(&self) -> &[CheckpointTransactionInfo] {
190        &self.0
191    }
192
193    pub fn into_v1(self) -> Vec<CheckpointTransactionInfo> {
194        self.0
195    }
196}
197
198/// Transaction information committed to in a checkpoint
199#[derive(Clone, Debug, PartialEq, Eq)]
200#[cfg_attr(
201    feature = "serde",
202    derive(serde_derive::Serialize, serde_derive::Deserialize)
203)]
204#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
205pub struct CheckpointTransactionInfo {
206    pub transaction: TransactionDigest,
207    pub effects: TransactionEffectsDigest,
208    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
209    pub signatures: Vec<UserSignature>,
210}
211
212#[derive(Clone, Debug, PartialEq)]
213#[cfg_attr(
214    feature = "serde",
215    derive(serde_derive::Serialize, serde_derive::Deserialize)
216)]
217#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
218pub struct CheckpointData {
219    pub checkpoint_summary: SignedCheckpointSummary,
220    pub checkpoint_contents: CheckpointContents,
221    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
222    pub transactions: Vec<CheckpointTransaction>,
223}
224
225#[derive(Clone, Debug, PartialEq, Eq)]
226#[cfg_attr(
227    feature = "serde",
228    derive(serde_derive::Serialize, serde_derive::Deserialize)
229)]
230#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
231pub struct CheckpointTransaction {
232    /// The input Transaction
233    #[cfg_attr(
234        feature = "serde",
235        serde(with = "::serde_with::As::<crate::_serde::SignedTransactionWithIntentMessage>")
236    )]
237    pub transaction: SignedTransaction,
238    /// The effects produced by executing this transaction
239    pub effects: TransactionEffects,
240    /// The events, if any, emitted by this transaciton during execution
241    pub events: Option<TransactionEvents>,
242    /// The state of all inputs to this transaction as they were prior to execution.
243    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
244    pub input_objects: Vec<Object>,
245    /// The state of all output objects created or mutated by this transaction.
246    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
247    pub output_objects: Vec<Object>,
248}
249
250#[cfg(feature = "serde")]
251#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
252mod serialization {
253    use super::*;
254
255    use serde::Deserialize;
256    use serde::Deserializer;
257    use serde::Serialize;
258    use serde::Serializer;
259
260    #[derive(serde_derive::Serialize)]
261    struct ReadableCheckpointSummaryRef<'a> {
262        #[serde(with = "crate::_serde::ReadableDisplay")]
263        epoch: &'a EpochId,
264        #[serde(with = "crate::_serde::ReadableDisplay")]
265        sequence_number: &'a CheckpointSequenceNumber,
266        #[serde(with = "crate::_serde::ReadableDisplay")]
267        network_total_transactions: &'a u64,
268        content_digest: &'a CheckpointContentsDigest,
269        #[serde(skip_serializing_if = "Option::is_none")]
270        previous_digest: &'a Option<CheckpointDigest>,
271        epoch_rolling_gas_cost_summary: &'a GasCostSummary,
272        #[serde(with = "crate::_serde::ReadableDisplay")]
273        timestamp_ms: &'a CheckpointTimestamp,
274        #[serde(skip_serializing_if = "Vec::is_empty")]
275        checkpoint_commitments: &'a Vec<CheckpointCommitment>,
276        #[serde(skip_serializing_if = "Option::is_none")]
277        end_of_epoch_data: &'a Option<EndOfEpochData>,
278        #[serde(skip_serializing_if = "Vec::is_empty")]
279        #[serde(with = "::serde_with::As::<crate::_serde::Base64Encoded>")]
280        version_specific_data: &'a Vec<u8>,
281    }
282
283    #[derive(serde_derive::Deserialize)]
284    struct ReadableCheckpointSummary {
285        #[serde(with = "crate::_serde::ReadableDisplay")]
286        epoch: EpochId,
287        #[serde(with = "crate::_serde::ReadableDisplay")]
288        sequence_number: CheckpointSequenceNumber,
289        #[serde(with = "crate::_serde::ReadableDisplay")]
290        network_total_transactions: u64,
291        content_digest: CheckpointContentsDigest,
292        #[serde(default)]
293        previous_digest: Option<CheckpointDigest>,
294        epoch_rolling_gas_cost_summary: GasCostSummary,
295        #[serde(with = "crate::_serde::ReadableDisplay")]
296        timestamp_ms: CheckpointTimestamp,
297        #[serde(default)]
298        checkpoint_commitments: Vec<CheckpointCommitment>,
299        #[serde(default)]
300        end_of_epoch_data: Option<EndOfEpochData>,
301        #[serde(default)]
302        #[serde(with = "::serde_with::As::<crate::_serde::Base64Encoded>")]
303        version_specific_data: Vec<u8>,
304    }
305
306    #[derive(serde_derive::Serialize)]
307    struct BinaryCheckpointSummaryRef<'a> {
308        epoch: &'a EpochId,
309        sequence_number: &'a CheckpointSequenceNumber,
310        network_total_transactions: &'a u64,
311        content_digest: &'a CheckpointContentsDigest,
312        previous_digest: &'a Option<CheckpointDigest>,
313        epoch_rolling_gas_cost_summary: &'a GasCostSummary,
314        timestamp_ms: &'a CheckpointTimestamp,
315        checkpoint_commitments: &'a Vec<CheckpointCommitment>,
316        end_of_epoch_data: &'a Option<EndOfEpochData>,
317        version_specific_data: &'a Vec<u8>,
318    }
319
320    #[derive(serde_derive::Deserialize)]
321    struct BinaryCheckpointSummary {
322        epoch: EpochId,
323        sequence_number: CheckpointSequenceNumber,
324        network_total_transactions: u64,
325        content_digest: CheckpointContentsDigest,
326        previous_digest: Option<CheckpointDigest>,
327        epoch_rolling_gas_cost_summary: GasCostSummary,
328        timestamp_ms: CheckpointTimestamp,
329        checkpoint_commitments: Vec<CheckpointCommitment>,
330        end_of_epoch_data: Option<EndOfEpochData>,
331        version_specific_data: Vec<u8>,
332    }
333
334    impl Serialize for CheckpointSummary {
335        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
336        where
337            S: Serializer,
338        {
339            let Self {
340                epoch,
341                sequence_number,
342                network_total_transactions,
343                content_digest,
344                previous_digest,
345                epoch_rolling_gas_cost_summary,
346                timestamp_ms,
347                checkpoint_commitments,
348                end_of_epoch_data,
349                version_specific_data,
350            } = self;
351
352            if serializer.is_human_readable() {
353                let readable = ReadableCheckpointSummaryRef {
354                    epoch,
355                    sequence_number,
356                    network_total_transactions,
357                    content_digest,
358                    previous_digest,
359                    epoch_rolling_gas_cost_summary,
360                    timestamp_ms,
361                    checkpoint_commitments,
362                    end_of_epoch_data,
363                    version_specific_data,
364                };
365                readable.serialize(serializer)
366            } else {
367                let binary = BinaryCheckpointSummaryRef {
368                    epoch,
369                    sequence_number,
370                    network_total_transactions,
371                    content_digest,
372                    previous_digest,
373                    epoch_rolling_gas_cost_summary,
374                    timestamp_ms,
375                    checkpoint_commitments,
376                    end_of_epoch_data,
377                    version_specific_data,
378                };
379                binary.serialize(serializer)
380            }
381        }
382    }
383
384    impl<'de> Deserialize<'de> for CheckpointSummary {
385        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
386        where
387            D: Deserializer<'de>,
388        {
389            if deserializer.is_human_readable() {
390                let ReadableCheckpointSummary {
391                    epoch,
392                    sequence_number,
393                    network_total_transactions,
394                    content_digest,
395                    previous_digest,
396                    epoch_rolling_gas_cost_summary,
397                    timestamp_ms,
398                    checkpoint_commitments,
399                    end_of_epoch_data,
400                    version_specific_data,
401                } = Deserialize::deserialize(deserializer)?;
402                Ok(Self {
403                    epoch,
404                    sequence_number,
405                    network_total_transactions,
406                    content_digest,
407                    previous_digest,
408                    epoch_rolling_gas_cost_summary,
409                    timestamp_ms,
410                    checkpoint_commitments,
411                    end_of_epoch_data,
412                    version_specific_data,
413                })
414            } else {
415                let BinaryCheckpointSummary {
416                    epoch,
417                    sequence_number,
418                    network_total_transactions,
419                    content_digest,
420                    previous_digest,
421                    epoch_rolling_gas_cost_summary,
422                    timestamp_ms,
423                    checkpoint_commitments,
424                    end_of_epoch_data,
425                    version_specific_data,
426                } = Deserialize::deserialize(deserializer)?;
427                Ok(Self {
428                    epoch,
429                    sequence_number,
430                    network_total_transactions,
431                    content_digest,
432                    previous_digest,
433                    epoch_rolling_gas_cost_summary,
434                    timestamp_ms,
435                    checkpoint_commitments,
436                    end_of_epoch_data,
437                    version_specific_data,
438                })
439            }
440        }
441    }
442
443    impl Serialize for CheckpointContents {
444        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
445        where
446            S: Serializer,
447        {
448            use serde::ser::SerializeSeq;
449            use serde::ser::SerializeTupleVariant;
450
451            if serializer.is_human_readable() {
452                serializer.serialize_newtype_struct("CheckpointContents", &self.0)
453            } else {
454                #[derive(serde_derive::Serialize)]
455                struct Digests<'a> {
456                    transaction: &'a TransactionDigest,
457                    effects: &'a TransactionEffectsDigest,
458                }
459
460                struct DigestSeq<'a>(&'a CheckpointContents);
461                impl Serialize for DigestSeq<'_> {
462                    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
463                    where
464                        S: Serializer,
465                    {
466                        let mut seq = serializer.serialize_seq(Some(self.0 .0.len()))?;
467                        for txn in &self.0 .0 {
468                            let digests = Digests {
469                                transaction: &txn.transaction,
470                                effects: &txn.effects,
471                            };
472                            seq.serialize_element(&digests)?;
473                        }
474                        seq.end()
475                    }
476                }
477
478                struct SignatureSeq<'a>(&'a CheckpointContents);
479                impl Serialize for SignatureSeq<'_> {
480                    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
481                    where
482                        S: Serializer,
483                    {
484                        let mut seq = serializer.serialize_seq(Some(self.0 .0.len()))?;
485                        for txn in &self.0 .0 {
486                            seq.serialize_element(&txn.signatures)?;
487                        }
488                        seq.end()
489                    }
490                }
491
492                let mut s = serializer.serialize_tuple_variant("CheckpointContents", 0, "V1", 2)?;
493                s.serialize_field(&DigestSeq(self))?;
494                s.serialize_field(&SignatureSeq(self))?;
495                s.end()
496            }
497        }
498    }
499
500    #[derive(serde_derive::Deserialize)]
501    struct ExecutionDigests {
502        transaction: TransactionDigest,
503        effects: TransactionEffectsDigest,
504    }
505
506    #[derive(serde_derive::Deserialize)]
507    struct BinaryContentsV1 {
508        digests: Vec<ExecutionDigests>,
509        signatures: Vec<Vec<UserSignature>>,
510    }
511
512    #[derive(serde_derive::Deserialize)]
513    enum BinaryContents {
514        V1(BinaryContentsV1),
515    }
516
517    impl<'de> Deserialize<'de> for CheckpointContents {
518        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
519        where
520            D: Deserializer<'de>,
521        {
522            if deserializer.is_human_readable() {
523                let contents: Vec<CheckpointTransactionInfo> =
524                    Deserialize::deserialize(deserializer)?;
525                Ok(Self(contents))
526            } else {
527                let BinaryContents::V1(BinaryContentsV1 {
528                    digests,
529                    signatures,
530                }) = Deserialize::deserialize(deserializer)?;
531
532                if digests.len() != signatures.len() {
533                    return Err(serde::de::Error::custom(
534                        "must have same number of signatures as transactions",
535                    ));
536                }
537
538                Ok(Self(
539                    digests
540                        .into_iter()
541                        .zip(signatures)
542                        .map(
543                            |(
544                                ExecutionDigests {
545                                    transaction,
546                                    effects,
547                                },
548                                signatures,
549                            )| CheckpointTransactionInfo {
550                                transaction,
551                                effects,
552                                signatures,
553                            },
554                        )
555                        .collect(),
556                ))
557            }
558        }
559    }
560
561    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
562    #[serde(tag = "type", rename_all = "snake_case")]
563    enum ReadableCommitment {
564        EcmhLiveObjectSet { digest: Digest },
565    }
566
567    #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
568    enum BinaryCommitment {
569        EcmhLiveObjectSet { digest: Digest },
570    }
571
572    impl Serialize for CheckpointCommitment {
573        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
574        where
575            S: Serializer,
576        {
577            if serializer.is_human_readable() {
578                let readable = match *self {
579                    CheckpointCommitment::EcmhLiveObjectSet { digest } => {
580                        ReadableCommitment::EcmhLiveObjectSet { digest }
581                    }
582                };
583                readable.serialize(serializer)
584            } else {
585                let binary = match *self {
586                    CheckpointCommitment::EcmhLiveObjectSet { digest } => {
587                        BinaryCommitment::EcmhLiveObjectSet { digest }
588                    }
589                };
590                binary.serialize(serializer)
591            }
592        }
593    }
594
595    impl<'de> Deserialize<'de> for CheckpointCommitment {
596        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
597        where
598            D: Deserializer<'de>,
599        {
600            if deserializer.is_human_readable() {
601                Ok(match ReadableCommitment::deserialize(deserializer)? {
602                    ReadableCommitment::EcmhLiveObjectSet { digest } => {
603                        Self::EcmhLiveObjectSet { digest }
604                    }
605                })
606            } else {
607                Ok(match BinaryCommitment::deserialize(deserializer)? {
608                    BinaryCommitment::EcmhLiveObjectSet { digest } => {
609                        Self::EcmhLiveObjectSet { digest }
610                    }
611                })
612            }
613        }
614    }
615
616    #[cfg(test)]
617    mod test {
618        use super::*;
619        use base64ct::Base64;
620        use base64ct::Encoding;
621
622        #[cfg(target_arch = "wasm32")]
623        use wasm_bindgen_test::wasm_bindgen_test as test;
624
625        #[test]
626        fn signed_checkpoint_fixture() {
627            const FIXTURES: &[&str] = &[
628                "CgAAAAAAAAAUAAAAAAAAABUAAAAAAAAAIJ6CIMG/6Un4MKNM8h+R9r8bQ6dNTk0WZxBMUQH1XFQBASCWUVucdQkje+4YbXVpvQZcg74nndL1NK7ccj1dDR04agAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAKAAAAAAAAAKOonlp6Vf8dJEjQYa/VyigZruaZwSwu3u/ZZVCsdrS1iaGPIAERZcNnfM75tOh10hI6MAAAAQAAAAAAAAAQAAAAAAA=",
629                "AgAAAAAAAAAFAAAAAAAAAAYAAAAAAAAAIINaPEm+WRQV2vGcPR9fe6fYhxl48GpqB+DqDYQqRHkuASBe+6BDLHSRCMiWqBkvVMqWXPWUsZnpc2gbOVdre3vnowAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAQFgqGJldzxWMt2CZow1QiLmDf0RdLE6udu0bVdc1xaExX37NByF27rDH5C1DF+mkpLdA6YZnXMvuUw+zoWo71qe2DTdIDU4AcNaSUE3OoEHceuT+fBa6dMib3yDkkhmOZLyECcAAAAAAAAkAAAAAAAAAAAAAgAAAAAAAACvljn+1LWFSpu3PGx4BlIlVZq7blFK+fV7SOPEU0z9nz7lgkv8a12EA9R0tGm8hEYSOjAAAAEAAAAAAAAAEAAAAAAA",
630            ];
631
632            for fixture in FIXTURES {
633                let bcs = Base64::decode_vec(fixture).unwrap();
634
635                let checkpoint: SignedCheckpointSummary = bcs::from_bytes(&bcs).unwrap();
636                let bytes = bcs::to_bytes(&checkpoint).unwrap();
637                assert_eq!(bcs, bytes);
638                let json = serde_json::to_string_pretty(&checkpoint).unwrap();
639                println!("{json}");
640            }
641        }
642
643        #[test]
644        fn contents_fixture() {
645            let fixture ="AAEgp6oAB8Qadn8+FqtdqeDIp8ViQNOZpMKs44MN0N5y7zIgqn5dKR1+8poL0pLNwRo/2knMnodwMTEDhqYL03kdewQBAWEAgpORkfH6ewjfFQYZJhmjkYq0/B3Set4mLJX/G0wUPb/V4H41gJipYu4I6ToyixnEuPQWxHKLckhNn+0UmI+pAJ9GegzEh0q2HWABmFMpFoPw0229dCfzWNOhHW5bes4H";
646
647            let bcs = Base64::decode_vec(fixture).unwrap();
648
649            let contents: CheckpointContents = bcs::from_bytes(&bcs).unwrap();
650            let bytes = bcs::to_bytes(&contents).unwrap();
651            assert_eq!(bcs, bytes);
652            let json = serde_json::to_string_pretty(&contents).unwrap();
653            println!("{json}");
654        }
655    }
656}