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}
585
586#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
591pub struct CheckpointContentsV1 {
592 #[serde(skip)]
593 digest: OnceCell<CheckpointContentsDigest>,
594
595 transactions: Vec<ExecutionDigests>,
596 user_signatures: Vec<Vec<GenericSignature>>,
600}
601
602impl CheckpointContents {
603 pub fn new_with_digests_and_signatures<T>(
604 contents: T,
605 user_signatures: Vec<Vec<GenericSignature>>,
606 ) -> Self
607 where
608 T: IntoIterator<Item = ExecutionDigests>,
609 {
610 let transactions: Vec<_> = contents.into_iter().collect();
611 assert_eq!(transactions.len(), user_signatures.len());
612 Self::V1(CheckpointContentsV1 {
613 digest: Default::default(),
614 transactions,
615 user_signatures,
616 })
617 }
618
619 pub fn new_with_causally_ordered_execution_data<'a, T>(contents: T) -> Self
620 where
621 T: IntoIterator<Item = &'a VerifiedExecutionData>,
622 {
623 let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
624 .into_iter()
625 .map(|data| {
626 (
627 data.digests(),
628 data.transaction.inner().data().tx_signatures().to_owned(),
629 )
630 })
631 .unzip();
632 assert_eq!(transactions.len(), user_signatures.len());
633 Self::V1(CheckpointContentsV1 {
634 digest: Default::default(),
635 transactions,
636 user_signatures,
637 })
638 }
639
640 pub fn new_with_digests_only_for_tests<T>(contents: T) -> Self
641 where
642 T: IntoIterator<Item = ExecutionDigests>,
643 {
644 let transactions: Vec<_> = contents.into_iter().collect();
645 let user_signatures = transactions.iter().map(|_| vec![]).collect();
646 Self::V1(CheckpointContentsV1 {
647 digest: Default::default(),
648 transactions,
649 user_signatures,
650 })
651 }
652
653 fn as_v1(&self) -> &CheckpointContentsV1 {
654 match self {
655 Self::V1(v) => v,
656 }
657 }
658
659 fn into_v1(self) -> CheckpointContentsV1 {
660 match self {
661 Self::V1(v) => v,
662 }
663 }
664
665 pub fn iter(&self) -> Iter<'_, ExecutionDigests> {
666 self.as_v1().transactions.iter()
667 }
668
669 pub fn into_iter_with_signatures(
670 self,
671 ) -> impl Iterator<Item = (ExecutionDigests, Vec<GenericSignature>)> {
672 let CheckpointContentsV1 {
673 transactions,
674 user_signatures,
675 ..
676 } = self.into_v1();
677
678 transactions.into_iter().zip(user_signatures)
679 }
680
681 pub fn enumerate_transactions(
686 &self,
687 ckpt: &CheckpointSummary,
688 ) -> impl Iterator<Item = (u64, &ExecutionDigests)> {
689 let start = ckpt.network_total_transactions - self.size() as u64;
690
691 (0u64..)
692 .zip(self.iter())
693 .map(move |(i, digests)| (i + start, digests))
694 }
695
696 pub fn into_inner(self) -> Vec<ExecutionDigests> {
697 self.into_v1().transactions
698 }
699
700 pub fn inner(&self) -> &[ExecutionDigests] {
701 &self.as_v1().transactions
702 }
703
704 pub fn size(&self) -> usize {
705 self.as_v1().transactions.len()
706 }
707
708 pub fn digest(&self) -> &CheckpointContentsDigest {
709 self.as_v1()
710 .digest
711 .get_or_init(|| CheckpointContentsDigest::new(default_hash(self)))
712 }
713}
714
715#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
722pub struct FullCheckpointContents {
723 transactions: Vec<ExecutionData>,
724 user_signatures: Vec<Vec<GenericSignature>>,
728}
729
730impl FullCheckpointContents {
731 pub fn new_with_causally_ordered_transactions<T>(contents: T) -> Self
732 where
733 T: IntoIterator<Item = ExecutionData>,
734 {
735 let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
736 .into_iter()
737 .map(|data| {
738 let sig = data.transaction.data().tx_signatures().to_owned();
739 (data, sig)
740 })
741 .unzip();
742 assert_eq!(transactions.len(), user_signatures.len());
743 Self {
744 transactions,
745 user_signatures,
746 }
747 }
748 pub fn from_contents_and_execution_data(
749 contents: CheckpointContents,
750 execution_data: impl Iterator<Item = ExecutionData>,
751 ) -> Self {
752 let transactions: Vec<_> = execution_data.collect();
753 Self {
754 transactions,
755 user_signatures: contents.into_v1().user_signatures,
756 }
757 }
758
759 pub fn iter(&self) -> Iter<'_, ExecutionData> {
760 self.transactions.iter()
761 }
762
763 pub fn verify_digests(&self, digest: CheckpointContentsDigest) -> Result<()> {
766 let self_digest = *self.checkpoint_contents().digest();
767 fp_ensure!(
768 digest == self_digest,
769 anyhow::anyhow!(
770 "checkpoint contents digest {self_digest} does not match expected digest {digest}"
771 )
772 );
773 for tx in self.iter() {
774 let transaction_digest = tx.transaction.digest();
775 fp_ensure!(
776 tx.effects.transaction_digest() == transaction_digest,
777 anyhow::anyhow!(
778 "transaction digest {transaction_digest} does not match expected digest {}",
779 tx.effects.transaction_digest()
780 )
781 );
782 }
783 Ok(())
784 }
785
786 pub fn checkpoint_contents(&self) -> CheckpointContents {
787 CheckpointContents::V1(CheckpointContentsV1 {
788 digest: Default::default(),
789 transactions: self.transactions.iter().map(|tx| tx.digests()).collect(),
790 user_signatures: self.user_signatures.clone(),
791 })
792 }
793
794 pub fn into_checkpoint_contents(self) -> CheckpointContents {
795 CheckpointContents::V1(CheckpointContentsV1 {
796 digest: Default::default(),
797 transactions: self
798 .transactions
799 .into_iter()
800 .map(|tx| tx.digests())
801 .collect(),
802 user_signatures: self.user_signatures,
803 })
804 }
805
806 pub fn size(&self) -> usize {
807 self.transactions.len()
808 }
809
810 pub fn random_for_testing() -> Self {
811 let (a, key): (_, AccountKeyPair) = get_key_pair();
812 let transaction = Transaction::from_data_and_signer(
813 TransactionData::new_transfer(
814 a,
815 FullObjectRef::from_fastpath_ref(random_object_ref()),
816 a,
817 random_object_ref(),
818 100000000000,
819 100,
820 ),
821 vec![&key],
822 );
823 let effects = TestEffectsBuilder::new(transaction.data()).build();
824 let exe_data = ExecutionData {
825 transaction,
826 effects,
827 };
828 FullCheckpointContents::new_with_causally_ordered_transactions(vec![exe_data])
829 }
830}
831
832impl IntoIterator for FullCheckpointContents {
833 type Item = ExecutionData;
834 type IntoIter = std::vec::IntoIter<Self::Item>;
835
836 fn into_iter(self) -> Self::IntoIter {
837 self.transactions.into_iter()
838 }
839}
840
841#[derive(Clone, Debug, PartialEq, Eq)]
842pub struct VerifiedCheckpointContents {
843 transactions: Vec<VerifiedExecutionData>,
844 user_signatures: Vec<Vec<GenericSignature>>,
848}
849
850impl VerifiedCheckpointContents {
851 pub fn new_unchecked(contents: FullCheckpointContents) -> Self {
852 Self {
853 transactions: contents
854 .transactions
855 .into_iter()
856 .map(VerifiedExecutionData::new_unchecked)
857 .collect(),
858 user_signatures: contents.user_signatures,
859 }
860 }
861
862 pub fn iter(&self) -> Iter<'_, VerifiedExecutionData> {
863 self.transactions.iter()
864 }
865
866 pub fn transactions(&self) -> &[VerifiedExecutionData] {
867 &self.transactions
868 }
869
870 pub fn into_inner(self) -> FullCheckpointContents {
871 FullCheckpointContents {
872 transactions: self
873 .transactions
874 .into_iter()
875 .map(|tx| tx.into_inner())
876 .collect(),
877 user_signatures: self.user_signatures,
878 }
879 }
880
881 pub fn into_checkpoint_contents(self) -> CheckpointContents {
882 self.into_inner().into_checkpoint_contents()
883 }
884
885 pub fn into_checkpoint_contents_digest(self) -> CheckpointContentsDigest {
886 *self.into_inner().into_checkpoint_contents().digest()
887 }
888
889 pub fn num_of_transactions(&self) -> usize {
890 self.transactions.len()
891 }
892}
893
894#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
896pub enum CheckpointVersionSpecificData {
897 V1(CheckpointVersionSpecificDataV1),
898}
899
900impl CheckpointVersionSpecificData {
901 pub fn as_v1(&self) -> &CheckpointVersionSpecificDataV1 {
902 match self {
903 Self::V1(v) => v,
904 }
905 }
906
907 pub fn into_v1(self) -> CheckpointVersionSpecificDataV1 {
908 match self {
909 Self::V1(v) => v,
910 }
911 }
912
913 pub fn empty_for_tests() -> CheckpointVersionSpecificData {
914 CheckpointVersionSpecificData::V1(CheckpointVersionSpecificDataV1 {
915 randomness_rounds: Vec::new(),
916 })
917 }
918}
919
920#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
921pub struct CheckpointVersionSpecificDataV1 {
922 pub randomness_rounds: Vec<RandomnessRound>,
924}
925
926#[cfg(test)]
927mod tests {
928 use crate::digests::{ConsensusCommitDigest, TransactionDigest, TransactionEffectsDigest};
929 use crate::messages_consensus::ConsensusDeterminedVersionAssignments;
930 use crate::transaction::VerifiedTransaction;
931 use fastcrypto::traits::KeyPair;
932 use rand::SeedableRng;
933 use rand::prelude::StdRng;
934
935 use super::*;
936 use crate::utils::make_committee_key;
937
938 const RNG_SEED: [u8; 32] = [
940 21, 23, 199, 200, 234, 250, 252, 178, 94, 15, 202, 178, 62, 186, 88, 137, 233, 192, 130,
941 157, 179, 179, 65, 9, 31, 249, 221, 123, 225, 112, 199, 247,
942 ];
943
944 #[test]
945 fn test_signed_checkpoint() {
946 let mut rng = StdRng::from_seed(RNG_SEED);
947 let (keys, committee) = make_committee_key(&mut rng);
948 let (_, committee2) = make_committee_key(&mut rng);
949
950 let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
951
952 let signed_checkpoints: Vec<_> = keys
955 .iter()
956 .map(|k| {
957 let name = k.public().into();
958
959 SignedCheckpointSummary::new(
960 committee.epoch,
961 CheckpointSummary::new(
962 &ProtocolConfig::get_for_max_version_UNSAFE(),
963 committee.epoch,
964 1,
965 0,
966 &set,
967 None,
968 GasCostSummary::default(),
969 None,
970 0,
971 Vec::new(),
972 Vec::new(),
973 ),
974 k,
975 name,
976 )
977 })
978 .collect();
979
980 signed_checkpoints.iter().for_each(|c| {
981 c.verify_authority_signatures(&committee)
982 .expect("signature ok")
983 });
984
985 signed_checkpoints
987 .iter()
988 .for_each(|c| assert!(c.verify_authority_signatures(&committee2).is_err()));
989 }
990
991 #[test]
992 fn test_certified_checkpoint() {
993 let mut rng = StdRng::from_seed(RNG_SEED);
994 let (keys, committee) = make_committee_key(&mut rng);
995
996 let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
997
998 let summary = CheckpointSummary::new(
999 &ProtocolConfig::get_for_max_version_UNSAFE(),
1000 committee.epoch,
1001 1,
1002 0,
1003 &set,
1004 None,
1005 GasCostSummary::default(),
1006 None,
1007 0,
1008 Vec::new(),
1009 Vec::new(),
1010 );
1011
1012 let sign_infos: Vec<_> = keys
1013 .iter()
1014 .map(|k| {
1015 let name = k.public().into();
1016
1017 SignedCheckpointSummary::sign(committee.epoch, &summary, k, name)
1018 })
1019 .collect();
1020
1021 let checkpoint_cert =
1022 CertifiedCheckpointSummary::new(summary, sign_infos, &committee).expect("Cert is OK");
1023
1024 assert!(
1026 checkpoint_cert
1027 .verify_with_contents(&committee, Some(&set))
1028 .is_ok()
1029 );
1030
1031 let signed_checkpoints: Vec<_> = keys
1033 .iter()
1034 .map(|k| {
1035 let name = k.public().into();
1036 let set = CheckpointContents::new_with_digests_only_for_tests([
1037 ExecutionDigests::random(),
1038 ]);
1039
1040 SignedCheckpointSummary::new(
1041 committee.epoch,
1042 CheckpointSummary::new(
1043 &ProtocolConfig::get_for_max_version_UNSAFE(),
1044 committee.epoch,
1045 1,
1046 0,
1047 &set,
1048 None,
1049 GasCostSummary::default(),
1050 None,
1051 0,
1052 Vec::new(),
1053 Vec::new(),
1054 ),
1055 k,
1056 name,
1057 )
1058 })
1059 .collect();
1060
1061 let summary = signed_checkpoints[0].data().clone();
1062 let sign_infos = signed_checkpoints
1063 .into_iter()
1064 .map(|v| v.into_sig())
1065 .collect();
1066 assert!(
1067 CertifiedCheckpointSummary::new(summary, sign_infos, &committee)
1068 .unwrap()
1069 .verify_authority_signatures(&committee)
1070 .is_err()
1071 )
1072 }
1073
1074 fn generate_test_checkpoint_summary_from_digest(
1078 digest: TransactionDigest,
1079 ) -> CheckpointSummary {
1080 CheckpointSummary::new(
1081 &ProtocolConfig::get_for_max_version_UNSAFE(),
1082 1,
1083 2,
1084 10,
1085 &CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::new(
1086 digest,
1087 TransactionEffectsDigest::ZERO,
1088 )]),
1089 None,
1090 GasCostSummary::default(),
1091 None,
1092 100,
1093 Vec::new(),
1094 Vec::new(),
1095 )
1096 }
1097
1098 #[test]
1100 fn test_checkpoint_summary_with_different_consensus_digest() {
1101 {
1103 let t1 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1104 1,
1105 2,
1106 100,
1107 ConsensusCommitDigest::default(),
1108 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1109 );
1110 let t2 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1111 1,
1112 2,
1113 100,
1114 ConsensusCommitDigest::default(),
1115 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1116 );
1117 let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
1118 let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
1119 assert_eq!(c1.digest(), c2.digest());
1120 }
1121
1122 {
1124 let t1 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1125 1,
1126 2,
1127 100,
1128 ConsensusCommitDigest::default(),
1129 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1130 );
1131 let t2 = VerifiedTransaction::new_consensus_commit_prologue_v3(
1132 1,
1133 2,
1134 100,
1135 ConsensusCommitDigest::random(),
1136 ConsensusDeterminedVersionAssignments::empty_for_testing(),
1137 );
1138 let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
1139 let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
1140 assert_ne!(c1.digest(), c2.digest());
1141 }
1142 }
1143
1144 #[test]
1145 fn test_artifacts() {
1146 let mut artifacts = CheckpointArtifacts::new();
1147 let o = CheckpointArtifact::ObjectStates(BTreeMap::new());
1148 assert!(artifacts.add_artifact(o.clone()).is_ok());
1149 assert!(artifacts.add_artifact(o.clone()).is_err());
1150 }
1151}