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#[derive(Clone, Debug, PartialEq, Eq)]
33#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
34pub enum CheckpointCommitment {
35 EcmhLiveObjectSet { digest: Digest },
38 }
40
41#[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 pub next_epoch_committee: Vec<ValidatorCommitteeMember>,
61
62 #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
64 pub next_epoch_protocol_version: ProtocolVersion,
65
66 pub epoch_commitments: Vec<CheckpointCommitment>,
68}
69
70#[derive(Clone, Debug, PartialEq, Eq)]
107#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
108pub struct CheckpointSummary {
109 pub epoch: EpochId,
111
112 pub sequence_number: CheckpointSequenceNumber,
114
115 pub network_total_transactions: u64,
118
119 pub content_digest: CheckpointContentsDigest,
121
122 pub previous_digest: Option<CheckpointDigest>,
126
127 pub epoch_rolling_gas_cost_summary: GasCostSummary,
130
131 pub timestamp_ms: CheckpointTimestamp,
135
136 pub checkpoint_commitments: Vec<CheckpointCommitment>,
138
139 pub end_of_epoch_data: Option<EndOfEpochData>,
141
142 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#[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#[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 #[cfg_attr(
234 feature = "serde",
235 serde(with = "::serde_with::As::<crate::_serde::SignedTransactionWithIntentMessage>")
236 )]
237 pub transaction: SignedTransaction,
238 pub effects: TransactionEffects,
240 pub events: Option<TransactionEvents>,
242 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
244 pub input_objects: Vec<Object>,
245 #[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}