1use super::{
6 CheckpointContentsDigest, CheckpointDigest, Digest, GasCostSummary, Object, SignedTransaction,
7 TransactionDigest, TransactionEffects, TransactionEffectsDigest, TransactionEvents,
8 UserSignature, ValidatorAggregatedSignature, ValidatorCommitteeMember,
9};
10
11pub type CheckpointSequenceNumber = u64;
12pub type CheckpointTimestamp = u64;
13pub type EpochId = u64;
14pub type StakeUnit = u64;
15pub type ProtocolVersion = u64;
16
17#[derive(Clone, Debug, Eq, PartialEq)]
29#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
30#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
31#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
32#[non_exhaustive]
33pub enum CheckpointCommitment {
34 EcmhLiveObjectSet { digest: Digest },
37 }
39
40impl CheckpointCommitment {
41 crate::def_is!(EcmhLiveObjectSet);
42
43 pub fn as_ecmh_live_object_set_digest(&self) -> Digest {
44 let Self::EcmhLiveObjectSet { digest } = self;
45 *digest
46 }
47}
48
49impl std::fmt::Display for CheckpointCommitment {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 match self {
52 CheckpointCommitment::EcmhLiveObjectSet { digest } => {
53 write!(f, "EcmhLiveObjectSet({digest})")
54 }
55 }
56 }
57}
58
59#[derive(Clone, Debug, Eq, PartialEq)]
73#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
74#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
75#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
76pub struct EndOfEpochData {
77 pub next_epoch_committee: Vec<ValidatorCommitteeMember>,
80 #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
82 #[cfg_attr(feature = "bcs-schema", bcs_schema(as_type = "u64"))]
83 pub next_epoch_protocol_version: ProtocolVersion,
84 pub epoch_commitments: Vec<CheckpointCommitment>,
86 pub epoch_supply_change: i64,
89}
90
91impl crate::TreeDisplay for EndOfEpochData {
92 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
93 w.header("End of Epoch Data")?;
94 w.children("Next Epoch Committee", &self.next_epoch_committee, false)?;
95 w.leaf(
96 "Next Epoch Protocol Version",
97 &self.next_epoch_protocol_version,
98 false,
99 )?;
100 w.leaves("Epoch Commitments", &self.epoch_commitments, false)?;
101 w.leaf("Epoch Supply Change", &self.epoch_supply_change, true)
102 }
103}
104
105#[derive(Clone, derive_more::Debug, Eq, PartialEq)]
145#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
146#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
147#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
148pub struct CheckpointSummary {
149 #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
151 #[cfg_attr(feature = "bcs-schema", bcs_schema(as_type = "u64"))]
152 pub epoch: EpochId,
153 #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
155 #[cfg_attr(feature = "bcs-schema", bcs_schema(as_type = "u64"))]
156 pub sequence_number: CheckpointSequenceNumber,
157 #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
160 pub network_total_transactions: u64,
161 pub contents_digest: CheckpointContentsDigest,
163 #[cfg_attr(feature = "serde", serde(default))]
167 pub previous_digest: Option<CheckpointDigest>,
168 pub epoch_rolling_gas_cost_summary: GasCostSummary,
171 #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
176 #[cfg_attr(feature = "bcs-schema", bcs_schema(as_type = "u64"))]
177 pub timestamp_ms: CheckpointTimestamp,
178 #[cfg_attr(feature = "serde", serde(default))]
180 pub checkpoint_commitments: Vec<CheckpointCommitment>,
181 #[cfg_attr(feature = "serde", serde(default))]
183 pub end_of_epoch_data: Option<EndOfEpochData>,
184 #[cfg_attr(
190 feature = "serde",
191 serde(default, with = "crate::_serde::ReadableBase64Encoded")
192 )]
193 #[debug(
194 "{:?}",
195 <base64ct::Base64 as base64ct::Encoding>::encode_string(version_specific_data)
196 )]
197 pub version_specific_data: Vec<u8>,
198}
199
200impl crate::TreeDisplay for CheckpointSummary {
201 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
202 w.header("Checkpoint Summary")?;
203 w.leaf("Epoch", &self.epoch, false)?;
204 w.leaf("Sequence Number", &self.sequence_number, false)?;
205 w.leaf(
206 "Network Total Transactions",
207 &self.network_total_transactions,
208 false,
209 )?;
210 w.leaf("Contents Digest", &self.contents_digest, false)?;
211 w.option_leaf("Previous Digest", &self.previous_digest, false)?;
212 w.child(
213 "Epoch Rolling Gas Cost",
214 &self.epoch_rolling_gas_cost_summary,
215 false,
216 )?;
217 w.leaf("Timestamp (ms)", &self.timestamp_ms, false)?;
218 w.leaves(
219 "Checkpoint Commitments",
220 &self.checkpoint_commitments,
221 false,
222 )?;
223 w.option_child("End of Epoch Data", &self.end_of_epoch_data, true)
224 }
225}
226
227impl CheckpointSummary {
228 #[expect(clippy::too_many_arguments)]
230 pub fn new(
231 epoch: EpochId,
232 sequence_number: CheckpointSequenceNumber,
233 network_total_transactions: u64,
234 contents_digest: CheckpointContentsDigest,
235 previous_digest: Option<CheckpointDigest>,
236 epoch_rolling_gas_cost_summary: GasCostSummary,
237 timestamp_ms: CheckpointTimestamp,
238 checkpoint_commitments: Vec<CheckpointCommitment>,
239 end_of_epoch_data: Option<EndOfEpochData>,
240 version_specific_data: Vec<u8>,
241 ) -> Self {
242 Self {
243 epoch,
244 sequence_number,
245 network_total_transactions,
246 contents_digest,
247 previous_digest,
248 epoch_rolling_gas_cost_summary,
249 timestamp_ms,
250 checkpoint_commitments,
251 end_of_epoch_data,
252 version_specific_data,
253 }
254 }
255
256 pub fn epoch(&self) -> EpochId {
258 self.epoch
259 }
260
261 pub fn sequence_number(&self) -> CheckpointSequenceNumber {
263 self.sequence_number
264 }
265
266 pub fn network_total_transactions(&self) -> u64 {
269 self.network_total_transactions
270 }
271
272 pub fn contents_digest(&self) -> CheckpointContentsDigest {
274 self.contents_digest
275 }
276
277 pub fn previous_digest(&self) -> Option<CheckpointDigest> {
280 self.previous_digest
281 }
282
283 pub fn epoch_rolling_gas_cost_summary(&self) -> &GasCostSummary {
286 &self.epoch_rolling_gas_cost_summary
287 }
288
289 pub fn timestamp_ms(&self) -> CheckpointTimestamp {
291 self.timestamp_ms
292 }
293
294 pub fn checkpoint_commitments(&self) -> &[CheckpointCommitment] {
296 &self.checkpoint_commitments
297 }
298
299 pub fn end_of_epoch_data(&self) -> Option<&EndOfEpochData> {
301 self.end_of_epoch_data.as_ref()
302 }
303
304 pub fn version_specific_data(&self) -> &[u8] {
306 &self.version_specific_data
307 }
308
309 pub fn next_epoch_committee(&self) -> Option<&[ValidatorCommitteeMember]> {
312 self.end_of_epoch_data
313 .as_ref()
314 .map(|data| data.next_epoch_committee.as_slice())
315 }
316
317 pub fn is_last_checkpoint_of_epoch(&self) -> bool {
319 self.end_of_epoch_data.is_some()
320 }
321}
322
323#[derive(Clone, Debug, PartialEq)]
324#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
325#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
326#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
327pub struct SignedCheckpointSummary {
328 pub checkpoint: CheckpointSummary,
329 pub signature: ValidatorAggregatedSignature,
330}
331
332impl crate::TreeDisplay for SignedCheckpointSummary {
333 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
334 w.header("Signed Checkpoint Summary")?;
335 w.child("Checkpoint", &self.checkpoint, false)?;
336 w.child("Signature", &self.signature, true)
337 }
338}
339
340#[derive(Clone, Debug, Eq, PartialEq)]
354#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
355#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
356#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
357#[non_exhaustive]
358pub enum CheckpointContents {
359 V1(CheckpointContentsV1),
360}
361
362impl CheckpointContents {
363 pub fn new_v1(contents: CheckpointContentsV1) -> Self {
364 Self::V1(contents)
365 }
366
367 crate::def_is_as_into_opt!(V1(CheckpointContentsV1));
368
369 pub fn transactions(&self) -> &[CheckpointTransactionInfo] {
371 match self {
372 CheckpointContents::V1(v1) => v1.transactions(),
373 }
374 }
375
376 pub fn into_transactions(self) -> Vec<CheckpointTransactionInfo> {
379 match self {
380 CheckpointContents::V1(v1) => v1.into_transactions(),
381 }
382 }
383
384 pub fn len(&self) -> usize {
386 match self {
387 CheckpointContents::V1(v1) => v1.len(),
388 }
389 }
390
391 pub fn is_empty(&self) -> bool {
393 match self {
394 CheckpointContents::V1(v1) => v1.is_empty(),
395 }
396 }
397}
398
399#[derive(Clone, Debug, Eq, PartialEq)]
417#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
418pub struct CheckpointContentsV1 {
419 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
420 transactions: Vec<CheckpointTransactionInfo>,
421}
422
423impl CheckpointContentsV1 {
424 pub fn new(transactions: Vec<CheckpointTransactionInfo>) -> Self {
425 Self { transactions }
426 }
427
428 pub fn transactions(&self) -> &[CheckpointTransactionInfo] {
430 &self.transactions
431 }
432
433 pub fn into_transactions(self) -> Vec<CheckpointTransactionInfo> {
436 self.transactions
437 }
438
439 pub fn len(&self) -> usize {
441 self.transactions.len()
442 }
443
444 pub fn is_empty(&self) -> bool {
446 self.transactions.is_empty()
447 }
448}
449
450impl crate::TreeDisplay for CheckpointContents {
451 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
452 w.enum_name("Checkpoint Contents");
453 match self {
454 Self::V1(v1) => v1.fmt_tree(w),
455 }
456 }
457}
458
459impl crate::TreeDisplay for CheckpointContentsV1 {
460 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
461 w.header("Checkpoint Contents V1")?;
462 w.children("Transactions", self.transactions(), true)
463 }
464}
465
466#[derive(Clone, Debug, Eq, PartialEq)]
468#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
469#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
470pub struct CheckpointTransactionInfo {
471 pub transaction: TransactionDigest,
472 pub effects: TransactionEffectsDigest,
473 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
474 pub signatures: Vec<UserSignature>,
475}
476
477impl crate::TreeDisplay for CheckpointTransactionInfo {
478 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
479 w.header("Checkpoint Transaction Info")?;
480 w.leaf("Transaction", &self.transaction, false)?;
481 w.leaf("Effects", &self.effects, false)?;
482 w.children("Signatures", &self.signatures, true)
483 }
484}
485
486#[derive(Clone, Debug, PartialEq)]
487#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
488#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
489#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
490pub struct CheckpointData {
491 pub checkpoint_summary: SignedCheckpointSummary,
492 pub checkpoint_contents: CheckpointContents,
493 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
494 pub transactions: Vec<CheckpointTransaction>,
495}
496
497impl crate::TreeDisplay for CheckpointData {
498 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
499 w.header("Checkpoint Data")?;
500 w.child("Checkpoint Summary", &self.checkpoint_summary, false)?;
501 w.child("Contents", &self.checkpoint_contents, false)?;
502 w.children("Transactions", &self.transactions, true)
503 }
504}
505
506#[derive(Clone, Debug, Eq, PartialEq)]
507#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
508#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
509#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
510pub struct CheckpointTransaction {
511 #[cfg_attr(
513 feature = "serde",
514 serde(with = "::serde_with::As::<crate::_serde::SignedTransactionWithIntentMessage>")
515 )]
516 #[cfg_attr(
517 feature = "bcs-schema",
518 bcs_schema(as_type = "%d01 intent-signed-transaction")
519 )]
520 pub transaction: SignedTransaction,
521 pub effects: TransactionEffects,
523 pub events: Option<TransactionEvents>,
525 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
528 pub input_objects: Vec<Object>,
529 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
531 pub output_objects: Vec<Object>,
532}
533
534impl crate::TreeDisplay for CheckpointTransaction {
535 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
536 w.header("Checkpoint Transaction")?;
537 w.child("Transaction", &self.transaction, false)?;
538 w.child("Effects", &self.effects, false)?;
539 w.option_child("Events", &self.events, false)?;
540 w.children("Input Objects", &self.input_objects, false)?;
541 w.children("Output Objects", &self.output_objects, true)
542 }
543}
544
545crate::impl_tree_display!(
546 EndOfEpochData,
547 CheckpointSummary,
548 SignedCheckpointSummary,
549 CheckpointContents,
550 CheckpointContentsV1,
551 CheckpointTransactionInfo,
552 CheckpointData,
553 CheckpointTransaction
554);
555
556#[cfg(feature = "serde")]
557#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
558mod serialization {
559 use serde::{Deserialize, Deserializer, Serialize, Serializer};
560
561 use super::*;
562
563 impl Serialize for CheckpointContentsV1 {
564 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
565 where
566 S: Serializer,
567 {
568 use serde::ser::{SerializeSeq, SerializeTuple};
569
570 if serializer.is_human_readable() {
571 serializer.serialize_newtype_struct("CheckpointContentsV1", &self.transactions)
572 } else {
573 #[derive(serde::Serialize)]
574 struct Digests<'a> {
575 transaction: &'a TransactionDigest,
576 effects: &'a TransactionEffectsDigest,
577 }
578
579 struct DigestSeq<'a>(&'a CheckpointContentsV1);
580 impl Serialize for DigestSeq<'_> {
581 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
582 where
583 S: Serializer,
584 {
585 let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
586 for txn in &self.0.transactions {
587 let digests = Digests {
588 transaction: &txn.transaction,
589 effects: &txn.effects,
590 };
591 seq.serialize_element(&digests)?;
592 }
593 seq.end()
594 }
595 }
596
597 struct SignatureSeq<'a>(&'a CheckpointContentsV1);
598 impl Serialize for SignatureSeq<'_> {
599 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
600 where
601 S: Serializer,
602 {
603 let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
604 for txn in &self.0.transactions {
605 seq.serialize_element(&txn.signatures)?;
606 }
607 seq.end()
608 }
609 }
610
611 let mut s = serializer.serialize_tuple(2)?;
612 s.serialize_element(&DigestSeq(self))?;
613 s.serialize_element(&SignatureSeq(self))?;
614 s.end()
615 }
616 }
617 }
618
619 #[derive(serde::Deserialize)]
620 #[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
621 struct ExecutionDigests {
622 transaction: TransactionDigest,
623 effects: TransactionEffectsDigest,
624 }
625
626 #[derive(serde::Deserialize)]
627 #[cfg_attr(
628 feature = "bcs-schema",
629 derive(iota_bcs_schema::BcsSchema),
630 bcs_schema(name = "checkpoint-contents-v1")
631 )]
632 struct BinaryContentsV1 {
633 digests: Vec<ExecutionDigests>,
634 signatures: Vec<Vec<UserSignature>>,
635 }
636
637 impl<'de> Deserialize<'de> for CheckpointContentsV1 {
638 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
639 where
640 D: Deserializer<'de>,
641 {
642 if deserializer.is_human_readable() {
643 let transactions: Vec<CheckpointTransactionInfo> =
644 Deserialize::deserialize(deserializer)?;
645 Ok(Self { transactions })
646 } else {
647 let BinaryContentsV1 {
648 digests,
649 signatures,
650 } = Deserialize::deserialize(deserializer)?;
651
652 if digests.len() != signatures.len() {
653 return Err(serde::de::Error::custom(
654 "must have same number of signatures as transactions",
655 ));
656 }
657
658 Ok(Self {
659 transactions: digests
660 .into_iter()
661 .zip(signatures)
662 .map(
663 |(
664 ExecutionDigests {
665 transaction,
666 effects,
667 },
668 signatures,
669 )| CheckpointTransactionInfo {
670 transaction,
671 effects,
672 signatures,
673 },
674 )
675 .collect(),
676 })
677 }
678 }
679 }
680
681 #[cfg(test)]
682 mod tests {
683 use base64ct::{Base64, Encoding};
684 #[cfg(target_arch = "wasm32")]
685 use wasm_bindgen_test::wasm_bindgen_test as test;
686
687 use super::*;
688
689 #[test]
690 fn signed_checkpoint_fixture() {
691 const FIXTURES: &[&str] = &[
696 "AAAAAAAAAAABAAAAAAAAAAIAAAAAAAAAIBqk0HxZmh1Bym2oL/3TlEnvb0FZbMJ594JGx2ZX9w2oASBCLJ9nhRE2EUG3C/XMPTdJTbK/1GjM585faUsOUQhFYgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9f941lwEAAAAAAgAAAAAAAAAAAACx8KVNWdScdFfM3RDAC41byY37f2pdIhrjGI8SQVY7Vel7TCBQ/kvuRdINIrazvwgUOjAAAAEAAAAAAAEAEAAAAAAAAQA=",
697 "DQAAAAAAAAB4DgAAAAAAAEo/AAAAAAAAIGJzt6qiBfbQHQufWpLivtr60pLRjm9dy7ulx34XrVVTASCV+2EoRe+2oCMWuVWVtl3ZIEdyaJgPhs+mCXiNtq6YygAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADyWus1lwEAAAABAmCNz/bRVQQKZW9IGbExEbUsV0aoa6cvOV+6/i7DhH0egUDmJKdR/fa18gULxyBc+dMABMkLDHQK/9Mmzmc8wrI6LSTVPir+sobfxmj9QGAInW0rF7eZ3Tb5DTMuVKejONSIEwAAAAAAAGCZ8l72H4AyuRRjZGCYLFzG8TTvHdrnZlfyy/7B6/yNCXN0CA32/PDcuLxLDY4K9dgOu/8rTFmfVPQtYxLfwxQnYHjBzDR+u77FGYviWFE/OGuTDQLCdJqAPiMwlV69GhCIEwAAAAAAAAkAAAAAAAAAAQAgIeRTzjDpjnTS3fkN3QCskISnmr5Z49j8JKFBGGuQjcAA8IoalbkCAAoAAWsAAAAAAAAADQAAAAAAAAC4F4HnXo6T6kpusCM8Gm7uXzE44DhcL0Faldy/mECSwlxBrcy4taqwhCdfgWVMmAsUOjAAAAEAAAAAAAEAEAAAAAAAAQA=",
698 ];
699
700 for fixture in FIXTURES {
701 let bcs = Base64::decode_vec(fixture).unwrap();
702
703 let checkpoint: SignedCheckpointSummary = bcs::from_bytes(&bcs).unwrap();
704 let bytes = bcs::to_bytes(&checkpoint).unwrap();
705 assert_eq!(bcs, bytes);
706 let json = serde_json::to_string_pretty(&checkpoint).unwrap();
707 println!("{json}");
708 }
709 }
710
711 #[test]
712 fn contents_fixture() {
713 let fixture = "AAEgp6oAB8Qadn8+FqtdqeDIp8ViQNOZpMKs44MN0N5y7zIgqn5dKR1+8poL0pLNwRo/2knMnodwMTEDhqYL03kdewQBAWEAgpORkfH6ewjfFQYZJhmjkYq0/B3Set4mLJX/G0wUPb/V4H41gJipYu4I6ToyixnEuPQWxHKLckhNn+0UmI+pAJ9GegzEh0q2HWABmFMpFoPw0229dCfzWNOhHW5bes4H";
714
715 let bcs = Base64::decode_vec(fixture).unwrap();
716
717 let contents: CheckpointContents = bcs::from_bytes(&bcs).unwrap();
718 let bytes = bcs::to_bytes(&contents).unwrap();
719 assert_eq!(bcs, bytes);
720 let json = serde_json::to_string_pretty(&contents).unwrap();
721 println!("{json}");
722 }
723 }
724}