1use super::Digest;
2use super::GasCostSummary;
3use super::Object;
4use super::SignedTransaction;
5use super::TransactionEffects;
6use super::TransactionEvents;
7use super::UserSignature;
8use super::ValidatorAggregatedSignature;
9use super::ValidatorCommitteeMember;
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, PartialEq, Eq)]
29#[cfg_attr(
30 feature = "serde",
31 derive(serde_derive::Serialize, serde_derive::Deserialize)
32)]
33#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
34#[non_exhaustive]
35pub enum CheckpointCommitment {
36 EcmhLiveObjectSet { digest: Digest },
39
40 CheckpointArtifacts { digest: Digest },
42}
43
44#[derive(Clone, Debug, PartialEq, Eq)]
56#[cfg_attr(
57 feature = "serde",
58 derive(serde_derive::Serialize, serde_derive::Deserialize)
59)]
60#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
61pub struct EndOfEpochData {
62 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
64 pub next_epoch_committee: Vec<ValidatorCommitteeMember>,
65
66 pub next_epoch_protocol_version: ProtocolVersion,
68
69 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
71 pub epoch_commitments: Vec<CheckpointCommitment>,
72}
73
74#[derive(Clone, Debug, PartialEq, Eq)]
111#[cfg_attr(
112 feature = "serde",
113 derive(serde_derive::Serialize, serde_derive::Deserialize)
114)]
115#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
116pub struct CheckpointSummary {
117 pub epoch: EpochId,
119
120 pub sequence_number: CheckpointSequenceNumber,
122
123 pub network_total_transactions: u64,
126
127 pub content_digest: Digest,
129
130 pub previous_digest: Option<Digest>,
134
135 pub epoch_rolling_gas_cost_summary: GasCostSummary,
138
139 pub timestamp_ms: CheckpointTimestamp,
143
144 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
146 pub checkpoint_commitments: Vec<CheckpointCommitment>,
147
148 pub end_of_epoch_data: Option<EndOfEpochData>,
150
151 pub version_specific_data: Vec<u8>,
156}
157
158#[derive(Clone, Debug, PartialEq)]
159#[cfg_attr(
160 feature = "serde",
161 derive(serde_derive::Serialize, serde_derive::Deserialize)
162)]
163#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
164pub struct SignedCheckpointSummary {
165 pub checkpoint: CheckpointSummary,
166 pub signature: ValidatorAggregatedSignature,
167}
168
169#[derive(Clone, Debug, PartialEq, Eq)]
187pub struct CheckpointContents {
188 version: usize,
189 transactions: Vec<CheckpointTransactionInfo>,
190}
191
192impl CheckpointContents {
193 pub fn new_v1(mut transactions: Vec<CheckpointTransactionInfo>) -> Self {
194 transactions
195 .iter_mut()
196 .flat_map(|t| t.signatures.iter_mut())
197 .for_each(|(_, v)| *v = None);
198 Self {
199 version: 1,
200 transactions,
201 }
202 }
203
204 pub fn new_v2(transactions: Vec<CheckpointTransactionInfo>) -> Self {
205 Self {
206 version: 2,
207 transactions,
208 }
209 }
210
211 pub fn transactions(&self) -> &[CheckpointTransactionInfo] {
212 &self.transactions
213 }
214
215 pub fn version(&self) -> usize {
216 self.version
217 }
218}
219
220#[cfg(feature = "proptest")]
221impl proptest::arbitrary::Arbitrary for CheckpointContents {
222 type Parameters = ();
223 type Strategy = proptest::strategy::BoxedStrategy<Self>;
224
225 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
226 use proptest::collection::vec;
227 use proptest::prelude::*;
228 use proptest::strategy::Strategy;
229
230 (
231 any::<bool>(),
232 vec(any::<CheckpointTransactionInfo>(), 0..=2),
233 )
234 .prop_map(|(version, txns)| match version {
235 false => Self::new_v1(txns),
236 true => Self::new_v2(txns),
237 })
238 .boxed()
239 }
240}
241
242#[derive(Clone, Debug, PartialEq, Eq)]
244#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
245pub struct CheckpointTransactionInfo {
246 transaction: Digest,
247 effects: Digest,
248 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
249 signatures: Vec<(UserSignature, Option<u64>)>,
250}
251
252impl CheckpointTransactionInfo {
253 pub fn new(transaction: Digest, effects: Digest, signatures: Vec<UserSignature>) -> Self {
254 Self {
255 transaction,
256 effects,
257 signatures: signatures.into_iter().map(|s| (s, None)).collect(),
258 }
259 }
260
261 pub fn new_with_address_aliases_versions(
262 transaction: Digest,
263 effects: Digest,
264 signatures: Vec<(UserSignature, Option<u64>)>,
265 ) -> Self {
266 Self {
267 transaction,
268 effects,
269 signatures,
270 }
271 }
272
273 pub fn transaction(&self) -> &Digest {
274 &self.transaction
275 }
276
277 pub fn effects(&self) -> &Digest {
278 &self.effects
279 }
280
281 pub fn signatures(&self) -> impl Iterator<Item = &UserSignature> {
282 self.signatures.iter().map(|(s, _)| s)
283 }
284
285 pub fn signatures_with_address_aliases_versions(
286 &self,
287 ) -> impl Iterator<Item = (&UserSignature, Option<u64>)> {
288 self.signatures.iter().map(|(s, v)| (s, *v))
289 }
290}
291
292#[derive(Clone, Debug, PartialEq)]
293#[cfg_attr(
294 feature = "serde",
295 derive(serde_derive::Serialize, serde_derive::Deserialize)
296)]
297#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
298pub struct CheckpointData {
299 pub checkpoint_summary: SignedCheckpointSummary,
300 pub checkpoint_contents: CheckpointContents,
301 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
302 pub transactions: Vec<CheckpointTransaction>,
303}
304
305#[derive(Clone, Debug, PartialEq, Eq)]
306#[cfg_attr(
307 feature = "serde",
308 derive(serde_derive::Serialize, serde_derive::Deserialize)
309)]
310#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
311pub struct CheckpointTransaction {
312 #[cfg_attr(
314 feature = "serde",
315 serde(with = "::serde_with::As::<crate::_serde::SignedTransactionWithIntentMessage>")
316 )]
317 pub transaction: SignedTransaction,
318 pub effects: TransactionEffects,
320 pub events: Option<TransactionEvents>,
322 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
324 pub input_objects: Vec<Object>,
325 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
327 pub output_objects: Vec<Object>,
328}
329
330#[cfg(feature = "serde")]
331#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
332mod serialization {
333 use super::*;
334
335 use serde::Deserialize;
336 use serde::Deserializer;
337 use serde::Serialize;
338 use serde::Serializer;
339
340 impl Serialize for CheckpointContents {
341 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
342 where
343 S: Serializer,
344 {
345 use serde::ser::SerializeSeq;
346 use serde::ser::SerializeTupleVariant;
347
348 #[derive(serde_derive::Serialize)]
349 struct Digests<'a> {
350 transaction: &'a Digest,
351 effects: &'a Digest,
352 }
353
354 match self.version() {
355 1 => {
356 struct DigestSeq<'a>(&'a CheckpointContents);
357 impl Serialize for DigestSeq<'_> {
358 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
359 where
360 S: Serializer,
361 {
362 let mut seq =
363 serializer.serialize_seq(Some(self.0.transactions.len()))?;
364 for txn in &self.0.transactions {
365 let digests = Digests {
366 transaction: &txn.transaction,
367 effects: &txn.effects,
368 };
369 seq.serialize_element(&digests)?;
370 }
371 seq.end()
372 }
373 }
374
375 struct SignatureSeq<'a>(&'a CheckpointContents);
376 impl Serialize for SignatureSeq<'_> {
377 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
378 where
379 S: Serializer,
380 {
381 let mut seq =
382 serializer.serialize_seq(Some(self.0.transactions.len()))?;
383 for txn in &self.0.transactions {
384 let sigs: Vec<&UserSignature> = txn.signatures().collect();
385 seq.serialize_element(&sigs)?;
386 }
387 seq.end()
388 }
389 }
390
391 let mut s =
392 serializer.serialize_tuple_variant("CheckpointContents", 0, "V1", 2)?;
393 s.serialize_field(&DigestSeq(self))?;
394 s.serialize_field(&SignatureSeq(self))?;
395 s.end()
396 }
397 2 => {
398 #[derive(serde_derive::Serialize)]
399 struct CheckpointTransactionInfoV2<'a> {
400 digests: Digests<'a>,
401 signatures: &'a [(UserSignature, Option<u64>)],
402 }
403
404 struct V2<'a>(&'a CheckpointContents);
405 impl Serialize for V2<'_> {
406 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
407 where
408 S: Serializer,
409 {
410 let mut seq =
411 serializer.serialize_seq(Some(self.0.transactions.len()))?;
412 for txn in &self.0.transactions {
413 let txn = CheckpointTransactionInfoV2 {
414 digests: Digests {
415 transaction: &txn.transaction,
416 effects: &txn.effects,
417 },
418 signatures: &txn.signatures,
419 };
420 seq.serialize_element(&txn)?;
421 }
422 seq.end()
423 }
424 }
425
426 let mut s =
427 serializer.serialize_tuple_variant("CheckpointContents", 1, "V2", 1)?;
428 s.serialize_field(&V2(self))?;
429 s.end()
430 }
431 _ => {
432 unreachable!("invalid checkpoint contents version");
433 }
434 }
435 }
436 }
437
438 #[derive(serde_derive::Deserialize)]
439 struct ExecutionDigests {
440 transaction: Digest,
441 effects: Digest,
442 }
443
444 #[derive(serde_derive::Deserialize)]
445 struct BinaryContentsV1 {
446 digests: Vec<ExecutionDigests>,
447 signatures: Vec<Vec<UserSignature>>,
448 }
449
450 #[derive(serde_derive::Deserialize)]
451 struct BinaryContentsV2 {
452 transactions: Vec<CheckpointTransactionContents>,
453 }
454
455 #[derive(serde_derive::Deserialize)]
456 struct CheckpointTransactionContents {
457 digests: ExecutionDigests,
458 signatures: Vec<(UserSignature, Option<u64>)>,
459 }
460
461 #[derive(serde_derive::Deserialize)]
462 enum BinaryContents {
463 V1(BinaryContentsV1),
464 V2(BinaryContentsV2),
465 }
466
467 impl<'de> Deserialize<'de> for CheckpointContents {
468 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
469 where
470 D: Deserializer<'de>,
471 {
472 match Deserialize::deserialize(deserializer)? {
473 BinaryContents::V1(BinaryContentsV1 {
474 digests,
475 signatures,
476 }) => {
477 if digests.len() != signatures.len() {
478 return Err(serde::de::Error::custom(
479 "must have same number of signatures as transactions",
480 ));
481 }
482
483 Ok(Self::new_v1(
484 digests
485 .into_iter()
486 .zip(signatures)
487 .map(
488 |(
489 ExecutionDigests {
490 transaction,
491 effects,
492 },
493 signatures,
494 )| {
495 CheckpointTransactionInfo::new(transaction, effects, signatures)
496 },
497 )
498 .collect(),
499 ))
500 }
501 BinaryContents::V2(v2) => Ok(Self::new_v2(
502 v2.transactions
503 .into_iter()
504 .map(|info| {
505 CheckpointTransactionInfo::new_with_address_aliases_versions(
506 info.digests.transaction,
507 info.digests.effects,
508 info.signatures,
509 )
510 })
511 .collect(),
512 )),
513 }
514 }
515 }
516
517 #[cfg(test)]
518 mod test {
519 use super::*;
520 use base64ct::Base64;
521 use base64ct::Encoding;
522
523 #[cfg(target_arch = "wasm32")]
524 use wasm_bindgen_test::wasm_bindgen_test as test;
525
526 #[test]
527 fn signed_checkpoint_fixture() {
528 const FIXTURES: &[&str] = &[
529 "CgAAAAAAAAAUAAAAAAAAABUAAAAAAAAAIJ6CIMG/6Un4MKNM8h+R9r8bQ6dNTk0WZxBMUQH1XFQBASCWUVucdQkje+4YbXVpvQZcg74nndL1NK7ccj1dDR04agAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAKAAAAAAAAAKOonlp6Vf8dJEjQYa/VyigZruaZwSwu3u/ZZVCsdrS1iaGPIAERZcNnfM75tOh10hI6MAAAAQAAAAAAAAAQAAAAAAA=",
530 "AgAAAAAAAAAFAAAAAAAAAAYAAAAAAAAAIINaPEm+WRQV2vGcPR9fe6fYhxl48GpqB+DqDYQqRHkuASBe+6BDLHSRCMiWqBkvVMqWXPWUsZnpc2gbOVdre3vnowAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAQFgqGJldzxWMt2CZow1QiLmDf0RdLE6udu0bVdc1xaExX37NByF27rDH5C1DF+mkpLdA6YZnXMvuUw+zoWo71qe2DTdIDU4AcNaSUE3OoEHceuT+fBa6dMib3yDkkhmOZLyECcAAAAAAAAkAAAAAAAAAAAAAgAAAAAAAACvljn+1LWFSpu3PGx4BlIlVZq7blFK+fV7SOPEU0z9nz7lgkv8a12EA9R0tGm8hEYSOjAAAAEAAAAAAAAAEAAAAAAA",
531 "AAAAAAAAAAACAAAAAAAAAAgAAAAAAAAAIJBUX7gl7mh+M/NoHcFa3oR3I+5BFublxXc33/GPUZ79ASAyWbpVsiA3AaeLJkcLPhQy4QKHM66TkJNFPJLaVqfoJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjf/gOJgBAAABASDxpIv3q2qAdsaAF16ZXzTSU8tRSJ5ylwIRyOzYsdeZigACAAAAAAAAAAAAALkSpPtV6n0lfTq6upYfSk7ZWw8avL3vaG/tU6s2ELoUKK3ucADvyjsDGNVKkhhGkhI6MAAAAQAAAAAAAAAQAAAAAAA=",
532 ];
533
534 for fixture in FIXTURES {
535 let bcs = Base64::decode_vec(fixture).unwrap();
536
537 let checkpoint: SignedCheckpointSummary = bcs::from_bytes(&bcs).unwrap();
538 let bytes = bcs::to_bytes(&checkpoint).unwrap();
539 assert_eq!(bcs, bytes);
540 let json = serde_json::to_string_pretty(&checkpoint).unwrap();
541 println!("{json}");
542 }
543 }
544
545 #[test]
546 fn contents_fixture() {
547 let fixture = "AAEgp6oAB8Qadn8+FqtdqeDIp8ViQNOZpMKs44MN0N5y7zIgqn5dKR1+8poL0pLNwRo/2knMnodwMTEDhqYL03kdewQBAWEAgpORkfH6ewjfFQYZJhmjkYq0/B3Set4mLJX/G0wUPb/V4H41gJipYu4I6ToyixnEuPQWxHKLckhNn+0UmI+pAJ9GegzEh0q2HWABmFMpFoPw0229dCfzWNOhHW5bes4H";
548
549 let bcs = Base64::decode_vec(fixture).unwrap();
550
551 let contents: CheckpointContents = bcs::from_bytes(&bcs).unwrap();
552 let bytes = bcs::to_bytes(&contents).unwrap();
553 assert_eq!(bcs, bytes);
554 let json = serde_json::to_string_pretty(&contents).unwrap();
555 println!("{json}");
556 }
557 }
558}