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