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)]
899pub enum VersionedFullCheckpointContents {
900 V1(FullCheckpointContents),
901 V2(FullCheckpointContentsV2),
902}
903
904impl VersionedFullCheckpointContents {
905 pub fn from_contents_and_execution_data(
906 contents: CheckpointContents,
907 execution_data: impl Iterator<Item = ExecutionData>,
908 ) -> Self {
909 let transactions: Vec<_> = execution_data.collect();
910 match contents {
911 CheckpointContents::V1(v1) => Self::V1(FullCheckpointContents {
912 transactions,
913 user_signatures: v1.user_signatures,
914 }),
915 CheckpointContents::V2(v2) => Self::V2(FullCheckpointContentsV2 {
916 transactions,
917 user_signatures: v2
918 .transactions
919 .into_iter()
920 .map(|tx| tx.user_signatures)
921 .collect(),
922 }),
923 }
924 }
925
926 pub fn verify_digests(&self, digest: CheckpointContentsDigest) -> Result<()> {
929 let self_digest = *self.checkpoint_contents().digest();
930 fp_ensure!(
931 digest == self_digest,
932 anyhow::anyhow!(
933 "checkpoint contents digest {self_digest} does not match expected digest {digest}"
934 )
935 );
936 for tx in self.iter() {
937 let transaction_digest = tx.transaction.digest();
938 fp_ensure!(
939 tx.effects.transaction_digest() == transaction_digest,
940 anyhow::anyhow!(
941 "transaction digest {transaction_digest} does not match expected digest {}",
942 tx.effects.transaction_digest()
943 )
944 );
945 }
946 Ok(())
947 }
948
949 pub fn into_v1(self) -> FullCheckpointContents {
950 match self {
951 Self::V1(c) => c,
952 Self::V2(c) => FullCheckpointContents {
953 transactions: c.transactions,
954 user_signatures: c
955 .user_signatures
956 .into_iter()
957 .map(|sigs| sigs.into_iter().map(|(sig, _)| sig).collect())
958 .collect(),
959 },
960 }
961 }
962
963 pub fn into_checkpoint_contents(self) -> CheckpointContents {
964 match self {
965 Self::V1(c) => c.into_checkpoint_contents(),
966 Self::V2(c) => c.into_checkpoint_contents(),
967 }
968 }
969
970 pub fn checkpoint_contents(&self) -> CheckpointContents {
971 match self {
972 Self::V1(c) => c.checkpoint_contents(),
973 Self::V2(c) => c.checkpoint_contents(),
974 }
975 }
976
977 pub fn iter(&self) -> Iter<'_, ExecutionData> {
978 match self {
979 Self::V1(c) => c.iter(),
980 Self::V2(c) => c.transactions.iter(),
981 }
982 }
983
984 pub fn size(&self) -> usize {
985 match self {
986 Self::V1(c) => c.transactions.len(),
987 Self::V2(c) => c.transactions.len(),
988 }
989 }
990}
991
992#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
993pub struct FullCheckpointContentsV2 {
994 transactions: Vec<ExecutionData>,
995 user_signatures: Vec<Vec<(GenericSignature, Option<SequenceNumber>)>>,
996}
997
998impl FullCheckpointContentsV2 {
999 pub fn checkpoint_contents(&self) -> CheckpointContents {
1000 CheckpointContents::V2(CheckpointContentsV2 {
1001 digest: Default::default(),
1002 transactions: self
1003 .transactions
1004 .iter()
1005 .zip_debug_eq(&self.user_signatures)
1006 .map(|(tx, sigs)| CheckpointTransactionContents {
1007 digest: tx.digests(),
1008 user_signatures: sigs.clone(),
1009 })
1010 .collect(),
1011 })
1012 }
1013
1014 pub fn into_checkpoint_contents(self) -> CheckpointContents {
1015 CheckpointContents::V2(CheckpointContentsV2 {
1016 digest: Default::default(),
1017 transactions: self
1018 .transactions
1019 .into_iter()
1020 .zip_debug_eq(self.user_signatures)
1021 .map(|(tx, sigs)| CheckpointTransactionContents {
1022 digest: tx.digests(),
1023 user_signatures: sigs,
1024 })
1025 .collect(),
1026 })
1027 }
1028}
1029
1030#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1032pub struct FullCheckpointContents {
1033 transactions: Vec<ExecutionData>,
1034 user_signatures: Vec<Vec<GenericSignature>>,
1038}
1039
1040impl FullCheckpointContents {
1041 pub fn new_with_causally_ordered_transactions<T>(contents: T) -> Self
1042 where
1043 T: IntoIterator<Item = ExecutionData>,
1044 {
1045 let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
1046 .into_iter()
1047 .map(|data| {
1048 let sig = data.transaction.data().tx_signatures().to_owned();
1049 (data, sig)
1050 })
1051 .unzip();
1052 assert_eq!(transactions.len(), user_signatures.len());
1053 Self {
1054 transactions,
1055 user_signatures,
1056 }
1057 }
1058
1059 pub fn iter(&self) -> Iter<'_, ExecutionData> {
1060 self.transactions.iter()
1061 }
1062
1063 pub fn checkpoint_contents(&self) -> CheckpointContents {
1064 CheckpointContents::V1(CheckpointContentsV1 {
1065 digest: Default::default(),
1066 transactions: self.transactions.iter().map(|tx| tx.digests()).collect(),
1067 user_signatures: self.user_signatures.clone(),
1068 })
1069 }
1070
1071 pub fn into_checkpoint_contents(self) -> CheckpointContents {
1072 CheckpointContents::V1(CheckpointContentsV1 {
1073 digest: Default::default(),
1074 transactions: self
1075 .transactions
1076 .into_iter()
1077 .map(|tx| tx.digests())
1078 .collect(),
1079 user_signatures: self.user_signatures,
1080 })
1081 }
1082
1083 pub fn random_for_testing() -> Self {
1084 let (a, key): (_, AccountKeyPair) = get_key_pair();
1085 let transaction = Transaction::from_data_and_signer(
1086 TransactionData::new_transfer(
1087 a,
1088 FullObjectRef::from_fastpath_ref(random_object_ref()),
1089 a,
1090 random_object_ref(),
1091 100000000000,
1092 100,
1093 ),
1094 vec![&key],
1095 );
1096 let effects = TestEffectsBuilder::new(transaction.data()).build();
1097 let exe_data = ExecutionData {
1098 transaction,
1099 effects,
1100 };
1101 FullCheckpointContents::new_with_causally_ordered_transactions(vec![exe_data])
1102 }
1103}
1104
1105impl IntoIterator for VersionedFullCheckpointContents {
1106 type Item = ExecutionData;
1107 type IntoIter = std::vec::IntoIter<Self::Item>;
1108
1109 fn into_iter(self) -> Self::IntoIter {
1110 match self {
1111 Self::V1(c) => c.transactions.into_iter(),
1112 Self::V2(c) => c.transactions.into_iter(),
1113 }
1114 }
1115}
1116
1117#[derive(Clone, Debug, PartialEq, Eq)]
1118pub enum VerifiedUserSignatures {
1119 V1(Vec<Vec<GenericSignature>>),
1120 V2(Vec<Vec<(GenericSignature, Option<SequenceNumber>)>>),
1121}
1122
1123#[derive(Clone, Debug, PartialEq, Eq)]
1124pub struct VerifiedCheckpointContents {
1125 transactions: Vec<VerifiedExecutionData>,
1126 user_signatures: VerifiedUserSignatures,
1130}
1131
1132impl VerifiedCheckpointContents {
1133 pub fn new_unchecked(contents: VersionedFullCheckpointContents) -> Self {
1134 match contents {
1135 VersionedFullCheckpointContents::V1(c) => Self {
1136 transactions: c
1137 .transactions
1138 .into_iter()
1139 .map(VerifiedExecutionData::new_unchecked)
1140 .collect(),
1141 user_signatures: VerifiedUserSignatures::V1(c.user_signatures),
1142 },
1143 VersionedFullCheckpointContents::V2(c) => Self {
1144 transactions: c
1145 .transactions
1146 .into_iter()
1147 .map(VerifiedExecutionData::new_unchecked)
1148 .collect(),
1149 user_signatures: VerifiedUserSignatures::V2(c.user_signatures),
1150 },
1151 }
1152 }
1153
1154 pub fn iter(&self) -> Iter<'_, VerifiedExecutionData> {
1155 self.transactions.iter()
1156 }
1157
1158 pub fn transactions(&self) -> &[VerifiedExecutionData] {
1159 &self.transactions
1160 }
1161
1162 pub fn into_inner(self) -> VersionedFullCheckpointContents {
1163 let transactions: Vec<_> = self
1164 .transactions
1165 .into_iter()
1166 .map(|tx| tx.into_inner())
1167 .collect();
1168
1169 match self.user_signatures {
1170 VerifiedUserSignatures::V1(user_signatures) => {
1171 VersionedFullCheckpointContents::V1(FullCheckpointContents {
1172 transactions,
1173 user_signatures,
1174 })
1175 }
1176 VerifiedUserSignatures::V2(user_signatures) => {
1177 VersionedFullCheckpointContents::V2(FullCheckpointContentsV2 {
1178 transactions,
1179 user_signatures,
1180 })
1181 }
1182 }
1183 }
1184
1185 pub fn into_checkpoint_contents(self) -> CheckpointContents {
1186 self.into_inner().into_checkpoint_contents()
1187 }
1188
1189 pub fn into_checkpoint_contents_digest(self) -> CheckpointContentsDigest {
1190 *self.into_inner().into_checkpoint_contents().digest()
1191 }
1192
1193 pub fn num_of_transactions(&self) -> usize {
1194 self.transactions.len()
1195 }
1196}
1197
1198#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1200pub enum CheckpointVersionSpecificData {
1201 V1(CheckpointVersionSpecificDataV1),
1202}
1203
1204impl CheckpointVersionSpecificData {
1205 pub fn as_v1(&self) -> &CheckpointVersionSpecificDataV1 {
1206 match self {
1207 Self::V1(v) => v,
1208 }
1209 }
1210
1211 pub fn into_v1(self) -> CheckpointVersionSpecificDataV1 {
1212 match self {
1213 Self::V1(v) => v,
1214 }
1215 }
1216
1217 pub fn empty_for_tests() -> CheckpointVersionSpecificData {
1218 CheckpointVersionSpecificData::V1(CheckpointVersionSpecificDataV1 {
1219 randomness_rounds: Vec::new(),
1220 })
1221 }
1222}
1223
1224#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1225pub struct CheckpointVersionSpecificDataV1 {
1226 pub randomness_rounds: Vec<RandomnessRound>,
1228}
1229
1230#[cfg(test)]
1231mod tests {
1232 use crate::digests::{ConsensusCommitDigest, TransactionDigest, TransactionEffectsDigest};
1233 use crate::messages_consensus::ConsensusDeterminedVersionAssignments;
1234 use crate::transaction::VerifiedTransaction;
1235 use fastcrypto::traits::KeyPair;
1236 use rand::SeedableRng;
1237 use rand::prelude::StdRng;
1238
1239 use super::*;
1240 use crate::utils::make_committee_key;
1241
1242 const RNG_SEED: [u8; 32] = [
1244 21, 23, 199, 200, 234, 250, 252, 178, 94, 15, 202, 178, 62, 186, 88, 137, 233, 192, 130,
1245 157, 179, 179, 65, 9, 31, 249, 221, 123, 225, 112, 199, 247,
1246 ];
1247
1248 #[test]
1249 fn test_signed_checkpoint() {
1250 let mut rng = StdRng::from_seed(RNG_SEED);
1251 let (keys, committee) = make_committee_key(&mut rng);
1252 let (_, committee2) = make_committee_key(&mut rng);
1253
1254 let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
1255
1256 let signed_checkpoints: Vec<_> = keys
1259 .iter()
1260 .map(|k| {
1261 let name = k.public().into();
1262
1263 SignedCheckpointSummary::new(
1264 committee.epoch,
1265 CheckpointSummary::new(
1266 &ProtocolConfig::get_for_max_version_UNSAFE(),
1267 committee.epoch,
1268 1,
1269 0,
1270 &set,
1271 None,
1272 GasCostSummary::default(),
1273 None,
1274 0,
1275 Vec::new(),
1276 Vec::new(),
1277 ),
1278 k,
1279 name,
1280 )
1281 })
1282 .collect();
1283
1284 signed_checkpoints.iter().for_each(|c| {
1285 c.verify_authority_signatures(&committee)
1286 .expect("signature ok")
1287 });
1288
1289 signed_checkpoints
1291 .iter()
1292 .for_each(|c| assert!(c.verify_authority_signatures(&committee2).is_err()));
1293 }
1294
1295 #[test]
1296 fn test_certified_checkpoint() {
1297 let mut rng = StdRng::from_seed(RNG_SEED);
1298 let (keys, committee) = make_committee_key(&mut rng);
1299
1300 let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
1301
1302 let summary = CheckpointSummary::new(
1303 &ProtocolConfig::get_for_max_version_UNSAFE(),
1304 committee.epoch,
1305 1,
1306 0,
1307 &set,
1308 None,
1309 GasCostSummary::default(),
1310 None,
1311 0,
1312 Vec::new(),
1313 Vec::new(),
1314 );
1315
1316 let sign_infos: Vec<_> = keys
1317 .iter()
1318 .map(|k| {
1319 let name = k.public().into();
1320
1321 SignedCheckpointSummary::sign(committee.epoch, &summary, k, name)
1322 })
1323 .collect();
1324
1325 let checkpoint_cert =
1326 CertifiedCheckpointSummary::new(summary, sign_infos, &committee).expect("Cert is OK");
1327
1328 assert!(
1330 checkpoint_cert
1331 .verify_with_contents(&committee, Some(&set))
1332 .is_ok()
1333 );
1334
1335 let signed_checkpoints: Vec<_> = keys
1337 .iter()
1338 .map(|k| {
1339 let name = k.public().into();
1340 let set = CheckpointContents::new_with_digests_only_for_tests([
1341 ExecutionDigests::random(),
1342 ]);
1343
1344 SignedCheckpointSummary::new(
1345 committee.epoch,
1346 CheckpointSummary::new(
1347 &ProtocolConfig::get_for_max_version_UNSAFE(),
1348 committee.epoch,
1349 1,
1350 0,
1351 &set,
1352 None,
1353 GasCostSummary::default(),
1354 None,
1355 0,
1356 Vec::new(),
1357 Vec::new(),
1358 ),
1359 k,
1360 name,
1361 )
1362 })
1363 .collect();
1364
1365 let summary = signed_checkpoints[0].data().clone();
1366 let sign_infos = signed_checkpoints
1367 .into_iter()
1368 .map(|v| v.into_sig())
1369 .collect();
1370 assert!(
1371 CertifiedCheckpointSummary::new(summary, sign_infos, &committee)
1372 .unwrap()
1373 .verify_authority_signatures(&committee)
1374 .is_err()
1375 )
1376 }
1377
1378 fn generate_test_checkpoint_summary_from_digest(
1382 digest: TransactionDigest,
1383 ) -> CheckpointSummary {
1384 CheckpointSummary::new(
1385 &ProtocolConfig::get_for_max_version_UNSAFE(),
1386 1,
1387 2,
1388 10,
1389 &CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::new(
1390 digest,
1391 TransactionEffectsDigest::ZERO,
1392 )]),
1393 None,
1394 GasCostSummary::default(),
1395 None,
1396 100,
1397 Vec::new(),
1398 Vec::new(),
1399 )
1400 }
1401
1402 #[test]
1404 fn test_checkpoint_summary_with_different_consensus_digest() {
1405 {
1407 let t1 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1408 1,
1409 2,
1410 100,
1411 ConsensusCommitDigest::default(),
1412 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1413 );
1414 let t2 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1415 1,
1416 2,
1417 100,
1418 ConsensusCommitDigest::default(),
1419 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1420 );
1421 let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
1422 let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
1423 assert_eq!(c1.digest(), c2.digest());
1424 }
1425
1426 {
1428 let t1 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1429 1,
1430 2,
1431 100,
1432 ConsensusCommitDigest::default(),
1433 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1434 );
1435 let t2 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1436 1,
1437 2,
1438 100,
1439 ConsensusCommitDigest::random(),
1440 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1441 );
1442 let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
1443 let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
1444 assert_ne!(c1.digest(), c2.digest());
1445 }
1446 }
1447
1448 #[test]
1449 fn test_artifacts() {
1450 let mut artifacts = CheckpointArtifacts::new();
1451 let o = CheckpointArtifact::ObjectStates(BTreeMap::new());
1452 assert!(artifacts.add_artifact(o.clone()).is_ok());
1453 assert!(artifacts.add_artifact(o.clone()).is_err());
1454 }
1455}