1use crate::base_types::{
5 ExecutionData, ExecutionDigests, FullObjectRef, VerifiedExecutionData, random_object_ref,
6};
7use crate::base_types::{ObjectID, SequenceNumber};
8use crate::committee::{EpochId, ProtocolVersion, StakeUnit};
9use crate::crypto::{
10 AccountKeyPair, AggregateAuthoritySignature, AuthoritySignInfo, AuthoritySignInfoTrait,
11 AuthorityStrongQuorumSignInfo, RandomnessRound, default_hash, get_key_pair,
12};
13use crate::digests::{CheckpointArtifactsDigest, Digest, ObjectDigest};
14use crate::effects::{TestEffectsBuilder, TransactionEffects, TransactionEffectsAPI};
15use crate::error::SuiResult;
16use crate::full_checkpoint_content::CheckpointData;
17use crate::gas::GasCostSummary;
18use crate::global_state_hash::GlobalStateHash;
19use crate::message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope};
20use crate::signature::GenericSignature;
21use crate::sui_serde::AsProtocolVersion;
22use crate::sui_serde::BigInt;
23use crate::sui_serde::Readable;
24use crate::transaction::{Transaction, TransactionData};
25use crate::{base_types::AuthorityName, committee::Committee, error::SuiErrorKind};
26use anyhow::Result;
27use fastcrypto::hash::Blake2b256;
28use fastcrypto::hash::MultisetHash;
29use fastcrypto::merkle::MerkleTree;
30use mysten_metrics::histogram::Histogram as MystenHistogram;
31use once_cell::sync::OnceCell;
32use prometheus::Histogram;
33use schemars::JsonSchema;
34use serde::{Deserialize, Serialize};
35use serde_with::serde_as;
36use shared_crypto::intent::{Intent, IntentScope};
37use std::collections::{BTreeMap, BTreeSet};
38use std::fmt::{Debug, Display, Formatter};
39use std::slice::Iter;
40use std::time::{Duration, SystemTime, UNIX_EPOCH};
41use sui_protocol_config::ProtocolConfig;
42use tap::TapFallible;
43use tracing::warn;
44
45pub use crate::digests::CheckpointContentsDigest;
46pub use crate::digests::CheckpointDigest;
47
48pub type CheckpointSequenceNumber = u64;
49pub type CheckpointTimestamp = u64;
50
51#[derive(Clone, Debug, Serialize, Deserialize)]
52pub struct CheckpointRequest {
53 pub sequence_number: Option<CheckpointSequenceNumber>,
56 pub request_content: bool,
59}
60
61#[derive(Clone, Debug, Serialize, Deserialize)]
62pub struct CheckpointRequestV2 {
63 pub sequence_number: Option<CheckpointSequenceNumber>,
67 pub request_content: bool,
70 pub certified: bool,
72}
73
74#[allow(clippy::large_enum_variant)]
75#[derive(Clone, Debug, Serialize, Deserialize)]
76pub enum CheckpointSummaryResponse {
77 Certified(CertifiedCheckpointSummary),
78 Pending(CheckpointSummary),
79}
80
81impl CheckpointSummaryResponse {
82 pub fn content_digest(&self) -> CheckpointContentsDigest {
83 match self {
84 Self::Certified(s) => s.content_digest,
85 Self::Pending(s) => s.content_digest,
86 }
87 }
88}
89
90#[allow(clippy::large_enum_variant)]
91#[derive(Clone, Debug, Serialize, Deserialize)]
92pub struct CheckpointResponse {
93 pub checkpoint: Option<CertifiedCheckpointSummary>,
94 pub contents: Option<CheckpointContents>,
95}
96
97#[allow(clippy::large_enum_variant)]
98#[derive(Clone, Debug, Serialize, Deserialize)]
99pub struct CheckpointResponseV2 {
100 pub checkpoint: Option<CheckpointSummaryResponse>,
101 pub contents: Option<CheckpointContents>,
102}
103
104#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
108pub struct ECMHLiveObjectSetDigest {
109 #[schemars(with = "[u8; 32]")]
110 pub digest: Digest,
111}
112
113impl From<fastcrypto::hash::Digest<32>> for ECMHLiveObjectSetDigest {
114 fn from(digest: fastcrypto::hash::Digest<32>) -> Self {
115 Self {
116 digest: Digest::new(digest.digest),
117 }
118 }
119}
120
121impl Default for ECMHLiveObjectSetDigest {
122 fn default() -> Self {
123 GlobalStateHash::default().digest().into()
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
131pub enum CheckpointArtifact {
132 ObjectStates(BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>),
135 }
137
138impl CheckpointArtifact {
139 pub fn digest(&self) -> SuiResult<Digest> {
140 match self {
141 Self::ObjectStates(object_states) => {
142 let tree = MerkleTree::<Blake2b256>::build_from_unserialized(
143 object_states
144 .iter()
145 .map(|(id, (seq, digest))| (id, seq, digest)),
146 )
147 .map_err(|e| SuiErrorKind::GenericAuthorityError {
148 error: format!("Failed to build Merkle tree: {}", e),
149 })?;
150 let root = tree.root().bytes();
151 Ok(Digest::new(root))
152 }
153 }
154 }
155
156 pub fn artifact_type(&self) -> &'static str {
157 match self {
158 Self::ObjectStates(_) => "ObjectStates",
159 }
161 }
162}
163
164#[derive(Debug)]
165pub struct CheckpointArtifacts {
166 artifacts: BTreeSet<CheckpointArtifact>,
168}
169
170impl CheckpointArtifacts {
171 pub fn new() -> Self {
172 Self {
173 artifacts: BTreeSet::new(),
174 }
175 }
176
177 pub fn add_artifact(&mut self, artifact: CheckpointArtifact) -> SuiResult<()> {
178 if self
179 .artifacts
180 .iter()
181 .any(|existing| existing.artifact_type() == artifact.artifact_type())
182 {
183 return Err(SuiErrorKind::GenericAuthorityError {
184 error: format!("Artifact {} already exists", artifact.artifact_type()),
185 }
186 .into());
187 }
188 self.artifacts.insert(artifact);
189 Ok(())
190 }
191
192 pub fn from_object_states(
193 object_states: BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>,
194 ) -> Self {
195 CheckpointArtifacts {
196 artifacts: BTreeSet::from([CheckpointArtifact::ObjectStates(object_states)]),
197 }
198 }
199
200 pub fn object_states(&self) -> SuiResult<&BTreeMap<ObjectID, (SequenceNumber, ObjectDigest)>> {
202 self.artifacts
203 .iter()
204 .find(|artifact| matches!(artifact, CheckpointArtifact::ObjectStates(_)))
205 .map(|artifact| match artifact {
206 CheckpointArtifact::ObjectStates(states) => states,
207 })
208 .ok_or(
209 SuiErrorKind::GenericAuthorityError {
210 error: "Object states not found in checkpoint artifacts".to_string(),
211 }
212 .into(),
213 )
214 }
215
216 pub fn digest(&self) -> SuiResult<CheckpointArtifactsDigest> {
217 let digests = self
219 .artifacts
220 .iter()
221 .map(|a| a.digest())
222 .collect::<Result<Vec<_>, _>>()?;
223
224 CheckpointArtifactsDigest::from_artifact_digests(digests)
225 }
226}
227
228impl Default for CheckpointArtifacts {
229 fn default() -> Self {
230 Self::new()
231 }
232}
233
234impl From<&[&TransactionEffects]> for CheckpointArtifacts {
235 fn from(effects: &[&TransactionEffects]) -> Self {
236 let mut latest_object_states = BTreeMap::new();
237 for e in effects {
238 for (id, seq, digest) in e.written() {
239 if let Some((old_seq, _)) = latest_object_states.insert(id, (seq, digest)) {
240 assert!(
241 old_seq < seq,
242 "Object states should be monotonically increasing"
243 );
244 }
245 }
246 }
247
248 CheckpointArtifacts::from_object_states(latest_object_states)
249 }
250}
251
252impl From<&[TransactionEffects]> for CheckpointArtifacts {
253 fn from(effects: &[TransactionEffects]) -> Self {
254 let effect_refs: Vec<&TransactionEffects> = effects.iter().collect();
255 Self::from(effect_refs.as_slice())
256 }
257}
258
259impl From<&CheckpointData> for CheckpointArtifacts {
260 fn from(checkpoint_data: &CheckpointData) -> Self {
261 let effects = checkpoint_data
262 .transactions
263 .iter()
264 .map(|tx| &tx.effects)
265 .collect::<Vec<_>>();
266
267 Self::from(effects.as_slice())
268 }
269}
270
271#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
272pub enum CheckpointCommitment {
273 ECMHLiveObjectSetDigest(ECMHLiveObjectSetDigest),
274 CheckpointArtifactsDigest(CheckpointArtifactsDigest),
275}
276
277impl From<ECMHLiveObjectSetDigest> for CheckpointCommitment {
278 fn from(d: ECMHLiveObjectSetDigest) -> Self {
279 Self::ECMHLiveObjectSetDigest(d)
280 }
281}
282
283impl From<CheckpointArtifactsDigest> for CheckpointCommitment {
284 fn from(d: CheckpointArtifactsDigest) -> Self {
285 Self::CheckpointArtifactsDigest(d)
286 }
287}
288
289#[serde_as]
290#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
291#[serde(rename_all = "camelCase")]
292pub struct EndOfEpochData {
293 #[schemars(with = "Vec<(AuthorityName, BigInt<u64>)>")]
301 #[serde_as(as = "Vec<(_, Readable<BigInt<u64>, _>)>")]
302 pub next_epoch_committee: Vec<(AuthorityName, StakeUnit)>,
303
304 #[schemars(with = "AsProtocolVersion")]
307 #[serde_as(as = "Readable<AsProtocolVersion, _>")]
308 pub next_epoch_protocol_version: ProtocolVersion,
309
310 pub epoch_commitments: Vec<CheckpointCommitment>,
312}
313
314#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
315pub struct CheckpointSummary {
316 pub epoch: EpochId,
317 pub sequence_number: CheckpointSequenceNumber,
318 pub network_total_transactions: u64,
321 pub content_digest: CheckpointContentsDigest,
322 pub previous_digest: Option<CheckpointDigest>,
323 pub epoch_rolling_gas_cost_summary: GasCostSummary,
326
327 pub timestamp_ms: CheckpointTimestamp,
331
332 pub checkpoint_commitments: Vec<CheckpointCommitment>,
335
336 pub end_of_epoch_data: Option<EndOfEpochData>,
338
339 pub version_specific_data: Vec<u8>,
346}
347
348impl Message for CheckpointSummary {
349 type DigestType = CheckpointDigest;
350 const SCOPE: IntentScope = IntentScope::CheckpointSummary;
351
352 fn digest(&self) -> Self::DigestType {
353 CheckpointDigest::new(default_hash(self))
354 }
355}
356
357impl CheckpointSummary {
358 pub fn new(
359 protocol_config: &ProtocolConfig,
360 epoch: EpochId,
361 sequence_number: CheckpointSequenceNumber,
362 network_total_transactions: u64,
363 transactions: &CheckpointContents,
364 previous_digest: Option<CheckpointDigest>,
365 epoch_rolling_gas_cost_summary: GasCostSummary,
366 end_of_epoch_data: Option<EndOfEpochData>,
367 timestamp_ms: CheckpointTimestamp,
368 randomness_rounds: Vec<RandomnessRound>,
369 checkpoint_commitments: Vec<CheckpointCommitment>,
370 ) -> CheckpointSummary {
371 let content_digest = *transactions.digest();
372
373 let version_specific_data = match protocol_config
374 .checkpoint_summary_version_specific_data_as_option()
375 {
376 None | Some(0) => Vec::new(),
377 Some(1) => bcs::to_bytes(&CheckpointVersionSpecificData::V1(
378 CheckpointVersionSpecificDataV1 { randomness_rounds },
379 ))
380 .expect("version specific data should serialize"),
381 _ => unimplemented!("unrecognized version_specific_data version for CheckpointSummary"),
382 };
383
384 Self {
385 epoch,
386 sequence_number,
387 network_total_transactions,
388 content_digest,
389 previous_digest,
390 epoch_rolling_gas_cost_summary,
391 end_of_epoch_data,
392 timestamp_ms,
393 version_specific_data,
394 checkpoint_commitments,
395 }
396 }
397
398 pub fn verify_epoch(&self, epoch: EpochId) -> SuiResult {
399 fp_ensure!(
400 self.epoch == epoch,
401 SuiErrorKind::WrongEpoch {
402 expected_epoch: epoch,
403 actual_epoch: self.epoch,
404 }
405 .into()
406 );
407 Ok(())
408 }
409
410 pub fn sequence_number(&self) -> &CheckpointSequenceNumber {
411 &self.sequence_number
412 }
413
414 pub fn timestamp(&self) -> SystemTime {
415 UNIX_EPOCH + Duration::from_millis(self.timestamp_ms)
416 }
417
418 pub fn next_epoch_committee(&self) -> Option<&[(AuthorityName, StakeUnit)]> {
419 self.end_of_epoch_data
420 .as_ref()
421 .map(|e| e.next_epoch_committee.as_slice())
422 }
423
424 pub fn report_checkpoint_age(&self, metrics: &Histogram, metrics_deprecated: &MystenHistogram) {
425 SystemTime::now()
426 .duration_since(self.timestamp())
427 .map(|latency| {
428 metrics.observe(latency.as_secs_f64());
429 metrics_deprecated.report(latency.as_millis() as u64);
430 })
431 .tap_err(|err| {
432 warn!(
433 checkpoint_seq = self.sequence_number,
434 "unable to compute checkpoint age: {}", err
435 )
436 })
437 .ok();
438 }
439
440 pub fn is_last_checkpoint_of_epoch(&self) -> bool {
441 self.end_of_epoch_data.is_some()
442 }
443
444 pub fn version_specific_data(
445 &self,
446 config: &ProtocolConfig,
447 ) -> Result<Option<CheckpointVersionSpecificData>> {
448 match config.checkpoint_summary_version_specific_data_as_option() {
449 None | Some(0) => Ok(None),
450 Some(1) => Ok(Some(bcs::from_bytes(&self.version_specific_data)?)),
451 _ => unimplemented!("unrecognized version_specific_data version in CheckpointSummary"),
452 }
453 }
454
455 pub fn checkpoint_artifacts_digest(&self) -> SuiResult<&CheckpointArtifactsDigest> {
456 self.checkpoint_commitments
457 .iter()
458 .find_map(|c| match c {
459 CheckpointCommitment::CheckpointArtifactsDigest(digest) => Some(digest),
460 _ => None,
461 })
462 .ok_or(
463 SuiErrorKind::GenericAuthorityError {
464 error: "Checkpoint artifacts digest not found in checkpoint commitments"
465 .to_string(),
466 }
467 .into(),
468 )
469 }
470}
471
472impl Display for CheckpointSummary {
473 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
474 write!(
475 f,
476 "CheckpointSummary {{ epoch: {:?}, seq: {:?}, content_digest: {},
477 epoch_rolling_gas_cost_summary: {:?}}}",
478 self.epoch,
479 self.sequence_number,
480 self.content_digest,
481 self.epoch_rolling_gas_cost_summary,
482 )
483 }
484}
485
486pub type CheckpointSummaryEnvelope<S> = Envelope<CheckpointSummary, S>;
495pub type CertifiedCheckpointSummary = CheckpointSummaryEnvelope<AuthorityStrongQuorumSignInfo>;
496pub type SignedCheckpointSummary = CheckpointSummaryEnvelope<AuthoritySignInfo>;
497
498pub type VerifiedCheckpoint = VerifiedEnvelope<CheckpointSummary, AuthorityStrongQuorumSignInfo>;
499pub type TrustedCheckpoint = TrustedEnvelope<CheckpointSummary, AuthorityStrongQuorumSignInfo>;
500
501impl CertifiedCheckpointSummary {
502 pub fn verify_authority_signatures(&self, committee: &Committee) -> SuiResult {
503 self.data().verify_epoch(self.auth_sig().epoch)?;
504 self.auth_sig().verify_secure(
505 self.data(),
506 Intent::sui_app(IntentScope::CheckpointSummary),
507 committee,
508 )
509 }
510
511 pub fn try_into_verified(self, committee: &Committee) -> SuiResult<VerifiedCheckpoint> {
512 self.verify_authority_signatures(committee)?;
513 Ok(VerifiedCheckpoint::new_from_verified(self))
514 }
515
516 pub fn verify_with_contents(
517 &self,
518 committee: &Committee,
519 contents: Option<&CheckpointContents>,
520 ) -> SuiResult {
521 self.verify_authority_signatures(committee)?;
522
523 if let Some(contents) = contents {
524 let content_digest = *contents.digest();
525 fp_ensure!(
526 content_digest == self.data().content_digest,
527 SuiErrorKind::GenericAuthorityError{error:format!("Checkpoint contents digest mismatch: summary={:?}, received content digest {:?}, received {} transactions", self.data(), content_digest, contents.size())}.into()
528 );
529 }
530
531 Ok(())
532 }
533
534 pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
535 let summary = self.into_data();
536 (summary.sequence_number, summary)
537 }
538
539 pub fn get_validator_signature(self) -> AggregateAuthoritySignature {
540 self.auth_sig().signature.clone()
541 }
542}
543
544impl SignedCheckpointSummary {
545 pub fn verify_authority_signatures(&self, committee: &Committee) -> SuiResult {
546 self.data().verify_epoch(self.auth_sig().epoch)?;
547 self.auth_sig().verify_secure(
548 self.data(),
549 Intent::sui_app(IntentScope::CheckpointSummary),
550 committee,
551 )
552 }
553
554 pub fn try_into_verified(
555 self,
556 committee: &Committee,
557 ) -> SuiResult<VerifiedEnvelope<CheckpointSummary, AuthoritySignInfo>> {
558 self.verify_authority_signatures(committee)?;
559 Ok(VerifiedEnvelope::<CheckpointSummary, AuthoritySignInfo>::new_from_verified(self))
560 }
561}
562
563impl VerifiedCheckpoint {
564 pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
565 self.into_inner().into_summary_and_sequence()
566 }
567}
568
569#[derive(Clone, Debug, Serialize, Deserialize)]
571pub struct CheckpointSignatureMessage {
572 pub summary: SignedCheckpointSummary,
573}
574
575impl CheckpointSignatureMessage {
576 pub fn verify(&self, committee: &Committee) -> SuiResult {
577 self.summary.verify_authority_signatures(committee)
578 }
579}
580
581#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
582pub enum CheckpointContents {
583 V1(CheckpointContentsV1),
584 V2(CheckpointContentsV2),
585}
586
587#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
592pub struct CheckpointContentsV1 {
593 #[serde(skip)]
594 digest: OnceCell<CheckpointContentsDigest>,
595
596 transactions: Vec<ExecutionDigests>,
597 user_signatures: Vec<Vec<GenericSignature>>,
601}
602
603#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
604pub struct CheckpointContentsV2 {
605 #[serde(skip)]
606 digest: OnceCell<CheckpointContentsDigest>,
607
608 transactions: Vec<CheckpointTransactionContents>,
609}
610
611#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
612pub struct CheckpointTransactionContents {
613 pub digest: ExecutionDigests,
614
615 pub user_signatures: Vec<(GenericSignature, Option<SequenceNumber>)>,
619}
620
621impl CheckpointContents {
622 pub fn new_with_digests_and_signatures<T>(
623 contents: T,
624 user_signatures: Vec<Vec<GenericSignature>>,
625 ) -> Self
626 where
627 T: IntoIterator<Item = ExecutionDigests>,
628 {
629 let transactions: Vec<_> = contents.into_iter().collect();
630 assert_eq!(transactions.len(), user_signatures.len());
631 Self::V1(CheckpointContentsV1 {
632 digest: Default::default(),
633 transactions,
634 user_signatures,
635 })
636 }
637
638 pub fn new_v2(
639 effects: &[TransactionEffects],
640 signatures: Vec<Vec<(GenericSignature, Option<SequenceNumber>)>>,
641 ) -> Self {
642 assert_eq!(effects.len(), signatures.len());
643 Self::V2(CheckpointContentsV2 {
644 digest: Default::default(),
645 transactions: effects
646 .iter()
647 .zip(signatures)
648 .map(|(e, s)| CheckpointTransactionContents {
649 digest: e.execution_digests(),
650 user_signatures: s,
651 })
652 .collect(),
653 })
654 }
655
656 pub fn new_with_causally_ordered_execution_data<'a, T>(contents: T) -> Self
657 where
658 T: IntoIterator<Item = &'a VerifiedExecutionData>,
659 {
660 let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
661 .into_iter()
662 .map(|data| {
663 (
664 data.digests(),
665 data.transaction.inner().data().tx_signatures().to_owned(),
666 )
667 })
668 .unzip();
669 assert_eq!(transactions.len(), user_signatures.len());
670 Self::V1(CheckpointContentsV1 {
671 digest: Default::default(),
672 transactions,
673 user_signatures,
674 })
675 }
676
677 pub fn new_with_digests_only_for_tests<T>(contents: T) -> Self
678 where
679 T: IntoIterator<Item = ExecutionDigests>,
680 {
681 let transactions: Vec<_> = contents.into_iter().collect();
682 let user_signatures = transactions.iter().map(|_| vec![]).collect();
683 Self::V1(CheckpointContentsV1 {
684 digest: Default::default(),
685 transactions,
686 user_signatures,
687 })
688 }
689
690 fn into_v1(self) -> CheckpointContentsV1 {
691 let digest = *self.digest();
692 match self {
693 Self::V1(c) => c,
694 Self::V2(c) => CheckpointContentsV1 {
695 digest: OnceCell::with_value(digest),
697 transactions: c.transactions.iter().map(|t| t.digest).collect(),
698 user_signatures: c
699 .transactions
700 .iter()
701 .map(|t| {
702 t.user_signatures
703 .iter()
704 .map(|(s, _)| s.to_owned())
705 .collect()
706 })
707 .collect(),
708 },
709 }
710 }
711
712 pub fn iter(&self) -> impl DoubleEndedIterator<Item = &ExecutionDigests> + '_ {
713 match self {
714 Self::V1(v) => itertools::Either::Left(v.transactions.iter()),
715 Self::V2(v) => itertools::Either::Right(v.transactions.iter().map(|t| &t.digest)),
716 }
717 }
718
719 pub fn into_iter_with_signatures(
720 self,
721 ) -> impl Iterator<Item = (ExecutionDigests, Vec<GenericSignature>)> {
722 let CheckpointContentsV1 {
723 transactions,
724 user_signatures,
725 ..
726 } = self.into_v1();
727
728 transactions.into_iter().zip(user_signatures)
729 }
730
731 pub fn enumerate_transactions(
736 &self,
737 ckpt: &CheckpointSummary,
738 ) -> impl Iterator<Item = (u64, &ExecutionDigests)> {
739 let start = ckpt.network_total_transactions - self.size() as u64;
740
741 (0u64..)
742 .zip(self.iter())
743 .map(move |(i, digests)| (i + start, digests))
744 }
745
746 pub fn into_inner(self) -> Vec<ExecutionDigests> {
747 self.into_v1().transactions
748 }
749
750 pub fn inner(&self) -> CheckpointContentsView<'_> {
751 match self {
752 Self::V1(c) => CheckpointContentsView::V1 {
753 transactions: &c.transactions,
754 user_signatures: &c.user_signatures,
755 },
756 Self::V2(c) => CheckpointContentsView::V2(&c.transactions),
757 }
758 }
759
760 pub fn size(&self) -> usize {
761 match self {
762 Self::V1(c) => c.transactions.len(),
763 Self::V2(c) => c.transactions.len(),
764 }
765 }
766
767 pub fn digest(&self) -> &CheckpointContentsDigest {
768 match self {
769 Self::V1(c) => c
770 .digest
771 .get_or_init(|| CheckpointContentsDigest::new(default_hash(self))),
772 Self::V2(c) => c
773 .digest
774 .get_or_init(|| CheckpointContentsDigest::new(default_hash(self))),
775 }
776 }
777}
778
779pub enum CheckpointContentsView<'a> {
781 V1 {
782 transactions: &'a [ExecutionDigests],
783 user_signatures: &'a [Vec<GenericSignature>],
784 },
785 V2(&'a [CheckpointTransactionContents]),
786}
787
788impl CheckpointContentsView<'_> {
789 pub fn len(&self) -> usize {
790 match self {
791 Self::V1 { transactions, .. } => transactions.len(),
792 Self::V2(v) => v.len(),
793 }
794 }
795
796 pub fn is_empty(&self) -> bool {
797 self.len() == 0
798 }
799
800 pub fn get_digests(&self, index: usize) -> Option<&ExecutionDigests> {
801 match self {
802 Self::V1 { transactions, .. } => transactions.get(index),
803 Self::V2(v) => v.get(index).map(|t| &t.digest),
804 }
805 }
806
807 pub fn first_digests(&self) -> Option<&ExecutionDigests> {
808 self.get_digests(0)
809 }
810
811 pub fn digests_iter(
812 &self,
813 ) -> impl DoubleEndedIterator<Item = &ExecutionDigests> + ExactSizeIterator {
814 match self {
815 Self::V1 { transactions, .. } => itertools::Either::Left(transactions.iter()),
816 Self::V2(v) => itertools::Either::Right(v.iter().map(|t| &t.digest)),
817 }
818 }
819
820 pub fn user_signatures(
823 &self,
824 index: usize,
825 ) -> Option<Vec<(GenericSignature, Option<SequenceNumber>)>> {
826 match self {
827 Self::V1 {
828 user_signatures, ..
829 } => user_signatures
830 .get(index)
831 .map(|sigs| sigs.iter().map(|sig| (sig.clone(), None)).collect()),
832 Self::V2(v) => v.get(index).map(|t| t.user_signatures.clone()),
833 }
834 }
835
836 pub fn iter(
837 &self,
838 ) -> impl Iterator<
839 Item = (
840 &ExecutionDigests,
841 impl Iterator<Item = (&GenericSignature, Option<SequenceNumber>)>,
842 ),
843 > {
844 match self {
845 Self::V1 {
846 transactions,
847 user_signatures,
848 } => itertools::Either::Left(transactions.iter().zip(user_signatures.iter()).map(
849 |(digests, signatures)| {
850 let signatures_iter =
851 itertools::Either::Left(signatures.iter().map(|s| (s, None)));
852 (digests, signatures_iter)
853 },
854 )),
855 Self::V2(v) => itertools::Either::Right(v.iter().map(|t| {
856 (
857 &t.digest,
858 itertools::Either::Right(t.user_signatures.iter().map(|(s, v)| (s, *v))),
859 )
860 })),
861 }
862 }
863}
864
865impl std::ops::Index<usize> for CheckpointContentsView<'_> {
866 type Output = ExecutionDigests;
867
868 fn index(&self, index: usize) -> &Self::Output {
869 match self {
870 Self::V1 { transactions, .. } => &transactions[index],
871 Self::V2(v) => &v[index].digest,
872 }
873 }
874}
875
876#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
883pub enum VersionedFullCheckpointContents {
884 V1(FullCheckpointContents),
885 V2(FullCheckpointContentsV2),
886}
887
888impl VersionedFullCheckpointContents {
889 pub fn from_contents_and_execution_data(
890 contents: CheckpointContents,
891 execution_data: impl Iterator<Item = ExecutionData>,
892 ) -> Self {
893 let transactions: Vec<_> = execution_data.collect();
894 match contents {
895 CheckpointContents::V1(v1) => Self::V1(FullCheckpointContents {
896 transactions,
897 user_signatures: v1.user_signatures,
898 }),
899 CheckpointContents::V2(v2) => Self::V2(FullCheckpointContentsV2 {
900 transactions,
901 user_signatures: v2
902 .transactions
903 .into_iter()
904 .map(|tx| tx.user_signatures)
905 .collect(),
906 }),
907 }
908 }
909
910 pub fn verify_digests(&self, digest: CheckpointContentsDigest) -> Result<()> {
913 let self_digest = *self.checkpoint_contents().digest();
914 fp_ensure!(
915 digest == self_digest,
916 anyhow::anyhow!(
917 "checkpoint contents digest {self_digest} does not match expected digest {digest}"
918 )
919 );
920 for tx in self.iter() {
921 let transaction_digest = tx.transaction.digest();
922 fp_ensure!(
923 tx.effects.transaction_digest() == transaction_digest,
924 anyhow::anyhow!(
925 "transaction digest {transaction_digest} does not match expected digest {}",
926 tx.effects.transaction_digest()
927 )
928 );
929 }
930 Ok(())
931 }
932
933 pub fn into_v1(self) -> FullCheckpointContents {
934 match self {
935 Self::V1(c) => c,
936 Self::V2(c) => FullCheckpointContents {
937 transactions: c.transactions,
938 user_signatures: c
939 .user_signatures
940 .into_iter()
941 .map(|sigs| sigs.into_iter().map(|(sig, _)| sig).collect())
942 .collect(),
943 },
944 }
945 }
946
947 pub fn into_checkpoint_contents(self) -> CheckpointContents {
948 match self {
949 Self::V1(c) => c.into_checkpoint_contents(),
950 Self::V2(c) => c.into_checkpoint_contents(),
951 }
952 }
953
954 pub fn checkpoint_contents(&self) -> CheckpointContents {
955 match self {
956 Self::V1(c) => c.checkpoint_contents(),
957 Self::V2(c) => c.checkpoint_contents(),
958 }
959 }
960
961 pub fn iter(&self) -> Iter<'_, ExecutionData> {
962 match self {
963 Self::V1(c) => c.iter(),
964 Self::V2(c) => c.transactions.iter(),
965 }
966 }
967
968 pub fn size(&self) -> usize {
969 match self {
970 Self::V1(c) => c.transactions.len(),
971 Self::V2(c) => c.transactions.len(),
972 }
973 }
974}
975
976#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
977pub struct FullCheckpointContentsV2 {
978 transactions: Vec<ExecutionData>,
979 user_signatures: Vec<Vec<(GenericSignature, Option<SequenceNumber>)>>,
980}
981
982impl FullCheckpointContentsV2 {
983 pub fn checkpoint_contents(&self) -> CheckpointContents {
984 CheckpointContents::V2(CheckpointContentsV2 {
985 digest: Default::default(),
986 transactions: self
987 .transactions
988 .iter()
989 .zip(&self.user_signatures)
990 .map(|(tx, sigs)| CheckpointTransactionContents {
991 digest: tx.digests(),
992 user_signatures: sigs.clone(),
993 })
994 .collect(),
995 })
996 }
997
998 pub fn into_checkpoint_contents(self) -> CheckpointContents {
999 CheckpointContents::V2(CheckpointContentsV2 {
1000 digest: Default::default(),
1001 transactions: self
1002 .transactions
1003 .into_iter()
1004 .zip(self.user_signatures)
1005 .map(|(tx, sigs)| CheckpointTransactionContents {
1006 digest: tx.digests(),
1007 user_signatures: sigs,
1008 })
1009 .collect(),
1010 })
1011 }
1012}
1013
1014#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1016pub struct FullCheckpointContents {
1017 transactions: Vec<ExecutionData>,
1018 user_signatures: Vec<Vec<GenericSignature>>,
1022}
1023
1024impl FullCheckpointContents {
1025 pub fn new_with_causally_ordered_transactions<T>(contents: T) -> Self
1026 where
1027 T: IntoIterator<Item = ExecutionData>,
1028 {
1029 let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
1030 .into_iter()
1031 .map(|data| {
1032 let sig = data.transaction.data().tx_signatures().to_owned();
1033 (data, sig)
1034 })
1035 .unzip();
1036 assert_eq!(transactions.len(), user_signatures.len());
1037 Self {
1038 transactions,
1039 user_signatures,
1040 }
1041 }
1042
1043 pub fn iter(&self) -> Iter<'_, ExecutionData> {
1044 self.transactions.iter()
1045 }
1046
1047 pub fn checkpoint_contents(&self) -> CheckpointContents {
1048 CheckpointContents::V1(CheckpointContentsV1 {
1049 digest: Default::default(),
1050 transactions: self.transactions.iter().map(|tx| tx.digests()).collect(),
1051 user_signatures: self.user_signatures.clone(),
1052 })
1053 }
1054
1055 pub fn into_checkpoint_contents(self) -> CheckpointContents {
1056 CheckpointContents::V1(CheckpointContentsV1 {
1057 digest: Default::default(),
1058 transactions: self
1059 .transactions
1060 .into_iter()
1061 .map(|tx| tx.digests())
1062 .collect(),
1063 user_signatures: self.user_signatures,
1064 })
1065 }
1066
1067 pub fn random_for_testing() -> Self {
1068 let (a, key): (_, AccountKeyPair) = get_key_pair();
1069 let transaction = Transaction::from_data_and_signer(
1070 TransactionData::new_transfer(
1071 a,
1072 FullObjectRef::from_fastpath_ref(random_object_ref()),
1073 a,
1074 random_object_ref(),
1075 100000000000,
1076 100,
1077 ),
1078 vec![&key],
1079 );
1080 let effects = TestEffectsBuilder::new(transaction.data()).build();
1081 let exe_data = ExecutionData {
1082 transaction,
1083 effects,
1084 };
1085 FullCheckpointContents::new_with_causally_ordered_transactions(vec![exe_data])
1086 }
1087}
1088
1089impl IntoIterator for VersionedFullCheckpointContents {
1090 type Item = ExecutionData;
1091 type IntoIter = std::vec::IntoIter<Self::Item>;
1092
1093 fn into_iter(self) -> Self::IntoIter {
1094 match self {
1095 Self::V1(c) => c.transactions.into_iter(),
1096 Self::V2(c) => c.transactions.into_iter(),
1097 }
1098 }
1099}
1100
1101#[derive(Clone, Debug, PartialEq, Eq)]
1102pub enum VerifiedUserSignatures {
1103 V1(Vec<Vec<GenericSignature>>),
1104 V2(Vec<Vec<(GenericSignature, Option<SequenceNumber>)>>),
1105}
1106
1107#[derive(Clone, Debug, PartialEq, Eq)]
1108pub struct VerifiedCheckpointContents {
1109 transactions: Vec<VerifiedExecutionData>,
1110 user_signatures: VerifiedUserSignatures,
1114}
1115
1116impl VerifiedCheckpointContents {
1117 pub fn new_unchecked(contents: VersionedFullCheckpointContents) -> Self {
1118 match contents {
1119 VersionedFullCheckpointContents::V1(c) => Self {
1120 transactions: c
1121 .transactions
1122 .into_iter()
1123 .map(VerifiedExecutionData::new_unchecked)
1124 .collect(),
1125 user_signatures: VerifiedUserSignatures::V1(c.user_signatures),
1126 },
1127 VersionedFullCheckpointContents::V2(c) => Self {
1128 transactions: c
1129 .transactions
1130 .into_iter()
1131 .map(VerifiedExecutionData::new_unchecked)
1132 .collect(),
1133 user_signatures: VerifiedUserSignatures::V2(c.user_signatures),
1134 },
1135 }
1136 }
1137
1138 pub fn iter(&self) -> Iter<'_, VerifiedExecutionData> {
1139 self.transactions.iter()
1140 }
1141
1142 pub fn transactions(&self) -> &[VerifiedExecutionData] {
1143 &self.transactions
1144 }
1145
1146 pub fn into_inner(self) -> VersionedFullCheckpointContents {
1147 let transactions: Vec<_> = self
1148 .transactions
1149 .into_iter()
1150 .map(|tx| tx.into_inner())
1151 .collect();
1152
1153 match self.user_signatures {
1154 VerifiedUserSignatures::V1(user_signatures) => {
1155 VersionedFullCheckpointContents::V1(FullCheckpointContents {
1156 transactions,
1157 user_signatures,
1158 })
1159 }
1160 VerifiedUserSignatures::V2(user_signatures) => {
1161 VersionedFullCheckpointContents::V2(FullCheckpointContentsV2 {
1162 transactions,
1163 user_signatures,
1164 })
1165 }
1166 }
1167 }
1168
1169 pub fn into_checkpoint_contents(self) -> CheckpointContents {
1170 self.into_inner().into_checkpoint_contents()
1171 }
1172
1173 pub fn into_checkpoint_contents_digest(self) -> CheckpointContentsDigest {
1174 *self.into_inner().into_checkpoint_contents().digest()
1175 }
1176
1177 pub fn num_of_transactions(&self) -> usize {
1178 self.transactions.len()
1179 }
1180}
1181
1182#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1184pub enum CheckpointVersionSpecificData {
1185 V1(CheckpointVersionSpecificDataV1),
1186}
1187
1188impl CheckpointVersionSpecificData {
1189 pub fn as_v1(&self) -> &CheckpointVersionSpecificDataV1 {
1190 match self {
1191 Self::V1(v) => v,
1192 }
1193 }
1194
1195 pub fn into_v1(self) -> CheckpointVersionSpecificDataV1 {
1196 match self {
1197 Self::V1(v) => v,
1198 }
1199 }
1200
1201 pub fn empty_for_tests() -> CheckpointVersionSpecificData {
1202 CheckpointVersionSpecificData::V1(CheckpointVersionSpecificDataV1 {
1203 randomness_rounds: Vec::new(),
1204 })
1205 }
1206}
1207
1208#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1209pub struct CheckpointVersionSpecificDataV1 {
1210 pub randomness_rounds: Vec<RandomnessRound>,
1212}
1213
1214#[cfg(test)]
1215mod tests {
1216 use crate::digests::{ConsensusCommitDigest, TransactionDigest, TransactionEffectsDigest};
1217 use crate::messages_consensus::ConsensusDeterminedVersionAssignments;
1218 use crate::transaction::VerifiedTransaction;
1219 use fastcrypto::traits::KeyPair;
1220 use rand::SeedableRng;
1221 use rand::prelude::StdRng;
1222
1223 use super::*;
1224 use crate::utils::make_committee_key;
1225
1226 const RNG_SEED: [u8; 32] = [
1228 21, 23, 199, 200, 234, 250, 252, 178, 94, 15, 202, 178, 62, 186, 88, 137, 233, 192, 130,
1229 157, 179, 179, 65, 9, 31, 249, 221, 123, 225, 112, 199, 247,
1230 ];
1231
1232 #[test]
1233 fn test_signed_checkpoint() {
1234 let mut rng = StdRng::from_seed(RNG_SEED);
1235 let (keys, committee) = make_committee_key(&mut rng);
1236 let (_, committee2) = make_committee_key(&mut rng);
1237
1238 let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
1239
1240 let signed_checkpoints: Vec<_> = keys
1243 .iter()
1244 .map(|k| {
1245 let name = k.public().into();
1246
1247 SignedCheckpointSummary::new(
1248 committee.epoch,
1249 CheckpointSummary::new(
1250 &ProtocolConfig::get_for_max_version_UNSAFE(),
1251 committee.epoch,
1252 1,
1253 0,
1254 &set,
1255 None,
1256 GasCostSummary::default(),
1257 None,
1258 0,
1259 Vec::new(),
1260 Vec::new(),
1261 ),
1262 k,
1263 name,
1264 )
1265 })
1266 .collect();
1267
1268 signed_checkpoints.iter().for_each(|c| {
1269 c.verify_authority_signatures(&committee)
1270 .expect("signature ok")
1271 });
1272
1273 signed_checkpoints
1275 .iter()
1276 .for_each(|c| assert!(c.verify_authority_signatures(&committee2).is_err()));
1277 }
1278
1279 #[test]
1280 fn test_certified_checkpoint() {
1281 let mut rng = StdRng::from_seed(RNG_SEED);
1282 let (keys, committee) = make_committee_key(&mut rng);
1283
1284 let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
1285
1286 let summary = CheckpointSummary::new(
1287 &ProtocolConfig::get_for_max_version_UNSAFE(),
1288 committee.epoch,
1289 1,
1290 0,
1291 &set,
1292 None,
1293 GasCostSummary::default(),
1294 None,
1295 0,
1296 Vec::new(),
1297 Vec::new(),
1298 );
1299
1300 let sign_infos: Vec<_> = keys
1301 .iter()
1302 .map(|k| {
1303 let name = k.public().into();
1304
1305 SignedCheckpointSummary::sign(committee.epoch, &summary, k, name)
1306 })
1307 .collect();
1308
1309 let checkpoint_cert =
1310 CertifiedCheckpointSummary::new(summary, sign_infos, &committee).expect("Cert is OK");
1311
1312 assert!(
1314 checkpoint_cert
1315 .verify_with_contents(&committee, Some(&set))
1316 .is_ok()
1317 );
1318
1319 let signed_checkpoints: Vec<_> = keys
1321 .iter()
1322 .map(|k| {
1323 let name = k.public().into();
1324 let set = CheckpointContents::new_with_digests_only_for_tests([
1325 ExecutionDigests::random(),
1326 ]);
1327
1328 SignedCheckpointSummary::new(
1329 committee.epoch,
1330 CheckpointSummary::new(
1331 &ProtocolConfig::get_for_max_version_UNSAFE(),
1332 committee.epoch,
1333 1,
1334 0,
1335 &set,
1336 None,
1337 GasCostSummary::default(),
1338 None,
1339 0,
1340 Vec::new(),
1341 Vec::new(),
1342 ),
1343 k,
1344 name,
1345 )
1346 })
1347 .collect();
1348
1349 let summary = signed_checkpoints[0].data().clone();
1350 let sign_infos = signed_checkpoints
1351 .into_iter()
1352 .map(|v| v.into_sig())
1353 .collect();
1354 assert!(
1355 CertifiedCheckpointSummary::new(summary, sign_infos, &committee)
1356 .unwrap()
1357 .verify_authority_signatures(&committee)
1358 .is_err()
1359 )
1360 }
1361
1362 fn generate_test_checkpoint_summary_from_digest(
1366 digest: TransactionDigest,
1367 ) -> CheckpointSummary {
1368 CheckpointSummary::new(
1369 &ProtocolConfig::get_for_max_version_UNSAFE(),
1370 1,
1371 2,
1372 10,
1373 &CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::new(
1374 digest,
1375 TransactionEffectsDigest::ZERO,
1376 )]),
1377 None,
1378 GasCostSummary::default(),
1379 None,
1380 100,
1381 Vec::new(),
1382 Vec::new(),
1383 )
1384 }
1385
1386 #[test]
1388 fn test_checkpoint_summary_with_different_consensus_digest() {
1389 {
1391 let t1 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1392 1,
1393 2,
1394 100,
1395 ConsensusCommitDigest::default(),
1396 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1397 );
1398 let t2 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1399 1,
1400 2,
1401 100,
1402 ConsensusCommitDigest::default(),
1403 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1404 );
1405 let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
1406 let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
1407 assert_eq!(c1.digest(), c2.digest());
1408 }
1409
1410 {
1412 let t1 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1413 1,
1414 2,
1415 100,
1416 ConsensusCommitDigest::default(),
1417 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1418 );
1419 let t2 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1420 1,
1421 2,
1422 100,
1423 ConsensusCommitDigest::random(),
1424 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1425 );
1426 let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
1427 let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
1428 assert_ne!(c1.digest(), c2.digest());
1429 }
1430 }
1431
1432 #[test]
1433 fn test_artifacts() {
1434 let mut artifacts = CheckpointArtifacts::new();
1435 let o = CheckpointArtifact::ObjectStates(BTreeMap::new());
1436 assert!(artifacts.add_artifact(o.clone()).is_ok());
1437 assert!(artifacts.add_artifact(o.clone()).is_err());
1438 }
1439}