1use super::{
6 Digest, GasCostSummary, Object, SignedTransaction, TransactionEffects, TransactionEvents,
7 UserSignature, ValidatorAggregatedSignature, ValidatorCommitteeMember,
8};
9
10pub type CheckpointSequenceNumber = u64;
11pub type CheckpointTimestamp = u64;
12pub type EpochId = u64;
13pub type StakeUnit = u64;
14pub type ProtocolVersion = u64;
15
16#[derive(Clone, Debug, PartialEq, Eq)]
28#[cfg_attr(
29 feature = "schemars",
30 derive(schemars::JsonSchema),
31 schemars(tag = "type", rename_all = "snake_case")
32)]
33#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
34pub enum CheckpointCommitment {
35 EcmhLiveObjectSet { digest: Digest },
38 }
40
41impl CheckpointCommitment {
42 crate::def_is!(EcmhLiveObjectSet);
43
44 pub fn as_ecmh_live_object_set_digest(&self) -> Digest {
45 let Self::EcmhLiveObjectSet { digest } = self;
46 *digest
47 }
48}
49
50#[derive(Clone, Debug, PartialEq, Eq)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
65#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
66pub struct EndOfEpochData {
67 pub next_epoch_committee: Vec<ValidatorCommitteeMember>,
70 #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
72 #[cfg_attr(feature = "schemars", schemars(with = "crate::_schemars::U64"))]
73 pub next_epoch_protocol_version: ProtocolVersion,
74 pub epoch_commitments: Vec<CheckpointCommitment>,
76 pub epoch_supply_change: i64,
79}
80
81#[derive(Clone, Debug, PartialEq, Eq)]
121#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
122#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
123pub struct CheckpointSummary {
124 #[cfg_attr(feature = "schemars", schemars(with = "crate::_schemars::U64"))]
126 pub epoch: EpochId,
127 #[cfg_attr(feature = "schemars", schemars(with = "crate::_schemars::U64"))]
129 pub sequence_number: CheckpointSequenceNumber,
130 #[cfg_attr(feature = "schemars", schemars(with = "crate::_schemars::U64"))]
133 pub network_total_transactions: u64,
134 pub content_digest: Digest,
136 pub previous_digest: Option<Digest>,
140 pub epoch_rolling_gas_cost_summary: GasCostSummary,
143 #[cfg_attr(feature = "schemars", schemars(with = "crate::_schemars::U64"))]
148 pub timestamp_ms: CheckpointTimestamp,
149 #[cfg_attr(
151 feature = "schemars",
152 schemars(with = "Option<Vec<CheckpointCommitment>>")
153 )]
154 pub checkpoint_commitments: Vec<CheckpointCommitment>,
155 pub end_of_epoch_data: Option<EndOfEpochData>,
157 #[cfg_attr(
163 feature = "schemars",
164 schemars(with = "Option<crate::_schemars::Base64>")
165 )]
166 pub version_specific_data: Vec<u8>,
167}
168
169#[derive(Clone, Debug, PartialEq)]
170#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
171#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
172#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
173pub struct SignedCheckpointSummary {
174 pub checkpoint: CheckpointSummary,
175 pub signature: ValidatorAggregatedSignature,
176}
177
178#[derive(Clone, Debug, PartialEq, Eq)]
197#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
198#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
199pub struct CheckpointContents(
200 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
201 pub Vec<CheckpointTransactionInfo>,
202);
203
204impl CheckpointContents {
205 pub fn new(transactions: Vec<CheckpointTransactionInfo>) -> Self {
206 Self(transactions)
207 }
208
209 pub fn transactions(&self) -> &[CheckpointTransactionInfo] {
210 &self.0
211 }
212
213 pub fn into_v1(self) -> Vec<CheckpointTransactionInfo> {
214 self.0
215 }
216}
217
218#[derive(Clone, Debug, PartialEq, Eq)]
220#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
221#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
222#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
223pub struct CheckpointTransactionInfo {
224 pub transaction: Digest,
225 pub effects: Digest,
226 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
227 pub signatures: Vec<UserSignature>,
228}
229
230#[derive(Clone, Debug, PartialEq)]
231#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
232#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
233#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
234pub struct CheckpointData {
235 pub checkpoint_summary: SignedCheckpointSummary,
236 pub checkpoint_contents: CheckpointContents,
237 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
238 pub transactions: Vec<CheckpointTransaction>,
239}
240
241#[derive(Clone, Debug, PartialEq, Eq)]
242#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
243#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
244#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
245pub struct CheckpointTransaction {
246 #[cfg_attr(
248 feature = "serde",
249 serde(with = "::serde_with::As::<crate::_serde::SignedTransactionWithIntentMessage>")
250 )]
251 #[cfg_attr(feature = "schemars", schemars(with = "SignedTransaction"))]
252 pub transaction: SignedTransaction,
253 pub effects: TransactionEffects,
255 pub events: Option<TransactionEvents>,
257 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
260 pub input_objects: Vec<Object>,
261 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
263 pub output_objects: Vec<Object>,
264}
265
266#[cfg(feature = "serde")]
267#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
268mod serialization {
269 use serde::{Deserialize, Deserializer, Serialize, Serializer};
270
271 use super::*;
272
273 #[derive(serde::Serialize)]
274 struct ReadableCheckpointSummaryRef<'a> {
275 #[serde(with = "crate::_serde::ReadableDisplay")]
276 epoch: &'a EpochId,
277 #[serde(with = "crate::_serde::ReadableDisplay")]
278 sequence_number: &'a CheckpointSequenceNumber,
279 #[serde(with = "crate::_serde::ReadableDisplay")]
280 network_total_transactions: &'a u64,
281 content_digest: &'a Digest,
282 #[serde(skip_serializing_if = "Option::is_none")]
283 previous_digest: &'a Option<Digest>,
284 epoch_rolling_gas_cost_summary: &'a GasCostSummary,
285 #[serde(with = "crate::_serde::ReadableDisplay")]
286 timestamp_ms: &'a CheckpointTimestamp,
287 #[serde(skip_serializing_if = "Vec::is_empty")]
288 checkpoint_commitments: &'a Vec<CheckpointCommitment>,
289 #[serde(skip_serializing_if = "Option::is_none")]
290 end_of_epoch_data: &'a Option<EndOfEpochData>,
291 #[serde(skip_serializing_if = "Vec::is_empty")]
292 #[serde(with = "::serde_with::As::<crate::_serde::Base64Encoded>")]
293 version_specific_data: &'a Vec<u8>,
294 }
295
296 #[derive(serde::Deserialize)]
297 struct ReadableCheckpointSummary {
298 #[serde(with = "crate::_serde::ReadableDisplay")]
299 epoch: EpochId,
300 #[serde(with = "crate::_serde::ReadableDisplay")]
301 sequence_number: CheckpointSequenceNumber,
302 #[serde(with = "crate::_serde::ReadableDisplay")]
303 network_total_transactions: u64,
304 content_digest: Digest,
305 #[serde(default)]
306 previous_digest: Option<Digest>,
307 epoch_rolling_gas_cost_summary: GasCostSummary,
308 #[serde(with = "crate::_serde::ReadableDisplay")]
309 timestamp_ms: CheckpointTimestamp,
310 #[serde(default)]
311 checkpoint_commitments: Vec<CheckpointCommitment>,
312 #[serde(default)]
313 end_of_epoch_data: Option<EndOfEpochData>,
314 #[serde(default)]
315 #[serde(with = "::serde_with::As::<crate::_serde::Base64Encoded>")]
316 version_specific_data: Vec<u8>,
317 }
318
319 #[derive(serde::Serialize)]
320 struct BinaryCheckpointSummaryRef<'a> {
321 epoch: &'a EpochId,
322 sequence_number: &'a CheckpointSequenceNumber,
323 network_total_transactions: &'a u64,
324 content_digest: &'a Digest,
325 previous_digest: &'a Option<Digest>,
326 epoch_rolling_gas_cost_summary: &'a GasCostSummary,
327 timestamp_ms: &'a CheckpointTimestamp,
328 checkpoint_commitments: &'a Vec<CheckpointCommitment>,
329 end_of_epoch_data: &'a Option<EndOfEpochData>,
330 version_specific_data: &'a Vec<u8>,
331 }
332
333 #[derive(serde::Deserialize)]
334 struct BinaryCheckpointSummary {
335 epoch: EpochId,
336 sequence_number: CheckpointSequenceNumber,
337 network_total_transactions: u64,
338 content_digest: Digest,
339 previous_digest: Option<Digest>,
340 epoch_rolling_gas_cost_summary: GasCostSummary,
341 timestamp_ms: CheckpointTimestamp,
342 checkpoint_commitments: Vec<CheckpointCommitment>,
343 end_of_epoch_data: Option<EndOfEpochData>,
344 version_specific_data: Vec<u8>,
345 }
346
347 impl Serialize for CheckpointSummary {
348 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
349 where
350 S: Serializer,
351 {
352 let Self {
353 epoch,
354 sequence_number,
355 network_total_transactions,
356 content_digest,
357 previous_digest,
358 epoch_rolling_gas_cost_summary,
359 timestamp_ms,
360 checkpoint_commitments,
361 end_of_epoch_data,
362 version_specific_data,
363 } = self;
364
365 if serializer.is_human_readable() {
366 let readable = ReadableCheckpointSummaryRef {
367 epoch,
368 sequence_number,
369 network_total_transactions,
370 content_digest,
371 previous_digest,
372 epoch_rolling_gas_cost_summary,
373 timestamp_ms,
374 checkpoint_commitments,
375 end_of_epoch_data,
376 version_specific_data,
377 };
378 readable.serialize(serializer)
379 } else {
380 let binary = BinaryCheckpointSummaryRef {
381 epoch,
382 sequence_number,
383 network_total_transactions,
384 content_digest,
385 previous_digest,
386 epoch_rolling_gas_cost_summary,
387 timestamp_ms,
388 checkpoint_commitments,
389 end_of_epoch_data,
390 version_specific_data,
391 };
392 binary.serialize(serializer)
393 }
394 }
395 }
396
397 impl<'de> Deserialize<'de> for CheckpointSummary {
398 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
399 where
400 D: Deserializer<'de>,
401 {
402 if deserializer.is_human_readable() {
403 let ReadableCheckpointSummary {
404 epoch,
405 sequence_number,
406 network_total_transactions,
407 content_digest,
408 previous_digest,
409 epoch_rolling_gas_cost_summary,
410 timestamp_ms,
411 checkpoint_commitments,
412 end_of_epoch_data,
413 version_specific_data,
414 } = Deserialize::deserialize(deserializer)?;
415 Ok(Self {
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 })
427 } else {
428 let BinaryCheckpointSummary {
429 epoch,
430 sequence_number,
431 network_total_transactions,
432 content_digest,
433 previous_digest,
434 epoch_rolling_gas_cost_summary,
435 timestamp_ms,
436 checkpoint_commitments,
437 end_of_epoch_data,
438 version_specific_data,
439 } = Deserialize::deserialize(deserializer)?;
440 Ok(Self {
441 epoch,
442 sequence_number,
443 network_total_transactions,
444 content_digest,
445 previous_digest,
446 epoch_rolling_gas_cost_summary,
447 timestamp_ms,
448 checkpoint_commitments,
449 end_of_epoch_data,
450 version_specific_data,
451 })
452 }
453 }
454 }
455
456 impl Serialize for CheckpointContents {
457 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
458 where
459 S: Serializer,
460 {
461 use serde::ser::{SerializeSeq, SerializeTupleVariant};
462
463 if serializer.is_human_readable() {
464 serializer.serialize_newtype_struct("CheckpointContents", &self.0)
465 } else {
466 #[derive(serde::Serialize)]
467 struct Digests<'a> {
468 transaction: &'a Digest,
469 effects: &'a Digest,
470 }
471
472 struct DigestSeq<'a>(&'a CheckpointContents);
473 impl Serialize for DigestSeq<'_> {
474 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
475 where
476 S: Serializer,
477 {
478 let mut seq = serializer.serialize_seq(Some(self.0.0.len()))?;
479 for txn in &self.0.0 {
480 let digests = Digests {
481 transaction: &txn.transaction,
482 effects: &txn.effects,
483 };
484 seq.serialize_element(&digests)?;
485 }
486 seq.end()
487 }
488 }
489
490 struct SignatureSeq<'a>(&'a CheckpointContents);
491 impl Serialize for SignatureSeq<'_> {
492 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
493 where
494 S: Serializer,
495 {
496 let mut seq = serializer.serialize_seq(Some(self.0.0.len()))?;
497 for txn in &self.0.0 {
498 seq.serialize_element(&txn.signatures)?;
499 }
500 seq.end()
501 }
502 }
503
504 let mut s = serializer.serialize_tuple_variant("CheckpointContents", 0, "V1", 2)?;
505 s.serialize_field(&DigestSeq(self))?;
506 s.serialize_field(&SignatureSeq(self))?;
507 s.end()
508 }
509 }
510 }
511
512 #[derive(serde::Deserialize)]
513 struct ExecutionDigests {
514 transaction: Digest,
515 effects: Digest,
516 }
517
518 #[derive(serde::Deserialize)]
519 struct BinaryContentsV1 {
520 digests: Vec<ExecutionDigests>,
521 signatures: Vec<Vec<UserSignature>>,
522 }
523
524 #[derive(serde::Deserialize)]
525 enum BinaryContents {
526 V1(BinaryContentsV1),
527 }
528
529 impl<'de> Deserialize<'de> for CheckpointContents {
530 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
531 where
532 D: Deserializer<'de>,
533 {
534 if deserializer.is_human_readable() {
535 let contents: Vec<CheckpointTransactionInfo> =
536 Deserialize::deserialize(deserializer)?;
537 Ok(Self(contents))
538 } else {
539 let BinaryContents::V1(BinaryContentsV1 {
540 digests,
541 signatures,
542 }) = Deserialize::deserialize(deserializer)?;
543
544 if digests.len() != signatures.len() {
545 return Err(serde::de::Error::custom(
546 "must have same number of signatures as transactions",
547 ));
548 }
549
550 Ok(Self(
551 digests
552 .into_iter()
553 .zip(signatures)
554 .map(
555 |(
556 ExecutionDigests {
557 transaction,
558 effects,
559 },
560 signatures,
561 )| CheckpointTransactionInfo {
562 transaction,
563 effects,
564 signatures,
565 },
566 )
567 .collect(),
568 ))
569 }
570 }
571 }
572
573 #[derive(serde::Serialize, serde::Deserialize)]
574 #[serde(tag = "type", rename_all = "snake_case")]
575 enum ReadableCommitment {
576 EcmhLiveObjectSet { digest: Digest },
577 }
578
579 #[derive(serde::Serialize, serde::Deserialize)]
580 enum BinaryCommitment {
581 EcmhLiveObjectSet { digest: Digest },
582 }
583
584 impl Serialize for CheckpointCommitment {
585 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
586 where
587 S: Serializer,
588 {
589 if serializer.is_human_readable() {
590 let readable = match *self {
591 CheckpointCommitment::EcmhLiveObjectSet { digest } => {
592 ReadableCommitment::EcmhLiveObjectSet { digest }
593 }
594 };
595 readable.serialize(serializer)
596 } else {
597 let binary = match *self {
598 CheckpointCommitment::EcmhLiveObjectSet { digest } => {
599 BinaryCommitment::EcmhLiveObjectSet { digest }
600 }
601 };
602 binary.serialize(serializer)
603 }
604 }
605 }
606
607 impl<'de> Deserialize<'de> for CheckpointCommitment {
608 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
609 where
610 D: Deserializer<'de>,
611 {
612 if deserializer.is_human_readable() {
613 Ok(match ReadableCommitment::deserialize(deserializer)? {
614 ReadableCommitment::EcmhLiveObjectSet { digest } => {
615 Self::EcmhLiveObjectSet { digest }
616 }
617 })
618 } else {
619 Ok(match BinaryCommitment::deserialize(deserializer)? {
620 BinaryCommitment::EcmhLiveObjectSet { digest } => {
621 Self::EcmhLiveObjectSet { digest }
622 }
623 })
624 }
625 }
626 }
627
628 #[cfg(test)]
629 mod tests {
630 use base64ct::{Base64, Encoding};
631 #[cfg(target_arch = "wasm32")]
632 use wasm_bindgen_test::wasm_bindgen_test as test;
633
634 use super::*;
635
636 #[test]
637 fn signed_checkpoint_fixture() {
638 const FIXTURES: &[&str] = &[
643 "AAAAAAAAAAABAAAAAAAAAAIAAAAAAAAAIBqk0HxZmh1Bym2oL/3TlEnvb0FZbMJ594JGx2ZX9w2oASBCLJ9nhRE2EUG3C/XMPTdJTbK/1GjM585faUsOUQhFYgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9f941lwEAAAAAAgAAAAAAAAAAAACx8KVNWdScdFfM3RDAC41byY37f2pdIhrjGI8SQVY7Vel7TCBQ/kvuRdINIrazvwgUOjAAAAEAAAAAAAEAEAAAAAAAAQA=",
644 "DQAAAAAAAAB4DgAAAAAAAEo/AAAAAAAAIGJzt6qiBfbQHQufWpLivtr60pLRjm9dy7ulx34XrVVTASCV+2EoRe+2oCMWuVWVtl3ZIEdyaJgPhs+mCXiNtq6YygAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADyWus1lwEAAAABAmCNz/bRVQQKZW9IGbExEbUsV0aoa6cvOV+6/i7DhH0egUDmJKdR/fa18gULxyBc+dMABMkLDHQK/9Mmzmc8wrI6LSTVPir+sobfxmj9QGAInW0rF7eZ3Tb5DTMuVKejONSIEwAAAAAAAGCZ8l72H4AyuRRjZGCYLFzG8TTvHdrnZlfyy/7B6/yNCXN0CA32/PDcuLxLDY4K9dgOu/8rTFmfVPQtYxLfwxQnYHjBzDR+u77FGYviWFE/OGuTDQLCdJqAPiMwlV69GhCIEwAAAAAAAAkAAAAAAAAAAQAgIeRTzjDpjnTS3fkN3QCskISnmr5Z49j8JKFBGGuQjcAA8IoalbkCAAoAAWsAAAAAAAAADQAAAAAAAAC4F4HnXo6T6kpusCM8Gm7uXzE44DhcL0Faldy/mECSwlxBrcy4taqwhCdfgWVMmAsUOjAAAAEAAAAAAAEAEAAAAAAAAQA=",
645 ];
646
647 for fixture in FIXTURES {
648 let bcs = Base64::decode_vec(fixture).unwrap();
649
650 let checkpoint: SignedCheckpointSummary = bcs::from_bytes(&bcs).unwrap();
651 let bytes = bcs::to_bytes(&checkpoint).unwrap();
652 assert_eq!(bcs, bytes);
653 let json = serde_json::to_string_pretty(&checkpoint).unwrap();
654 println!("{json}");
655 }
656 }
657
658 #[test]
659 fn contents_fixture() {
660 let fixture = "AAEgp6oAB8Qadn8+FqtdqeDIp8ViQNOZpMKs44MN0N5y7zIgqn5dKR1+8poL0pLNwRo/2knMnodwMTEDhqYL03kdewQBAWEAgpORkfH6ewjfFQYZJhmjkYq0/B3Set4mLJX/G0wUPb/V4H41gJipYu4I6ToyixnEuPQWxHKLckhNn+0UmI+pAJ9GegzEh0q2HWABmFMpFoPw0229dCfzWNOhHW5bes4H";
661
662 let bcs = Base64::decode_vec(fixture).unwrap();
663
664 let contents: CheckpointContents = bcs::from_bytes(&bcs).unwrap();
665 let bytes = bcs::to_bytes(&contents).unwrap();
666 assert_eq!(bcs, bytes);
667 let json = serde_json::to_string_pretty(&contents).unwrap();
668 println!("{json}");
669 }
670 }
671}