1mod causal_order;
6pub mod checkpoint_executor;
7mod checkpoint_output;
8mod epoch_info;
9mod full_checkpoint_contents_cache;
10mod metrics;
11
12use std::{
13 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
14 fs::File,
15 future::Future,
16 io::Write,
17 path::Path,
18 pin::Pin,
19 sync::{Arc, Weak},
20 task::{Context, Poll},
21 time::{Duration, SystemTime},
22};
23
24use diffy::create_patch;
25use iota_common::{
26 debug_fatal, fatal,
27 random::get_rng,
28 sync::notify_read::{CHECKPOINT_BUILDER_NOTIFY_READ_TASK_NAME, NotifyRead},
29};
30use iota_metrics::{MonitoredFutureExt, monitored_future, monitored_scope};
31use iota_network::default_iota_network_config;
32use iota_sdk_types::{
33 CheckpointContentsDigest, CheckpointDigest, GasCostSummary, TransactionDigest, TransactionKind,
34 UserSignature,
35 checkpoint::{CheckpointCommitment, CheckpointContents, CheckpointSummary, EndOfEpochData},
36};
37use iota_types::{
38 base_types::{AuthorityName, ConciseableName, EpochId, ExecutionData},
39 committee::StakeUnit,
40 crypto::AuthorityStrongQuorumSignInfo,
41 effects::{TransactionEffects, TransactionEffectsAPI, TransactionEffectsExt},
42 error::{IotaError, IotaResult},
43 event::SystemEpochInfoEvent,
44 iota_system_state::{
45 IotaSystemState, IotaSystemStateTrait,
46 epoch_start_iota_system_state::EpochStartSystemStateTrait,
47 },
48 messages_checkpoint::{
49 CertifiedCheckpointSummary, CheckpointContentsExt, CheckpointRequest, CheckpointResponse,
50 CheckpointSequenceNumber, CheckpointSignatureMessage, CheckpointSummaryExt,
51 CheckpointSummaryResponse, CheckpointTimestamp, FullCheckpointContents,
52 SignedCheckpointSummary, TrustedCheckpoint, VerifiedCheckpoint, VerifiedCheckpointContents,
53 },
54 messages_consensus::ConsensusTransactionKey,
55 storage::EpochInfoV2,
56 transaction::{TransactionDataAPI, TransactionEnvelope, TransactionKey},
57};
58use itertools::Itertools;
59use nonempty::NonEmpty;
60use parking_lot::Mutex;
61use pin_project_lite::pin_project;
62use rand::seq::SliceRandom;
63use serde::{Deserialize, Serialize};
64use tokio::{
65 sync::{Notify, mpsc, watch},
66 task::JoinSet,
67 time::timeout,
68};
69use tracing::{debug, error, info, instrument, trace, warn};
70use typed_store::{
71 DBMapUtils, Map, TypedStoreError,
72 rocks::{DBMap, MetricConf},
73};
74
75pub use crate::checkpoints::{
76 checkpoint_output::{
77 LogCheckpointOutput, SendCheckpointToStateSync, SubmitCheckpointToConsensus,
78 },
79 full_checkpoint_contents_cache::{
80 FullCheckpointContentsCache, FullCheckpointContentsCacheMetrics,
81 },
82 metrics::CheckpointMetrics,
83};
84use crate::{
85 authority::{
86 AuthorityState,
87 authority_per_epoch_store::{AuthorityPerEpochStore, scorer::MAX_SCORE},
88 },
89 authority_client::{
90 make_network_authority_clients_with_network_config, validator_peer::ValidatorPeerAPI,
91 },
92 checkpoints::{
93 causal_order::CausalOrder,
94 checkpoint_output::{CertifiedCheckpointOutput, CheckpointOutput},
95 },
96 consensus_handler::SequencedConsensusTransactionKey,
97 consensus_manager::ReplayWaiter,
98 execution_cache::TransactionCacheRead,
99 global_state_hasher::GlobalStateHasher,
100 stake_aggregator::{InsertResult, MultiStakeAggregator},
101};
102
103pub type CheckpointHeight = u64;
104
105pub struct EpochStats {
106 pub checkpoint_count: u64,
107 pub transaction_count: u64,
108 pub total_gas_reward: u64,
109}
110
111#[derive(Clone, Debug, Serialize, Deserialize)]
112pub struct PendingCheckpointInfo {
113 pub timestamp_ms: CheckpointTimestamp,
114 pub last_of_epoch: bool,
115 pub checkpoint_height: CheckpointHeight,
116}
117
118#[derive(Clone, Debug, Serialize, Deserialize)]
119pub enum PendingCheckpoint {
120 V1(PendingCheckpointContentsV1),
122}
123
124#[derive(Clone, Debug, Serialize, Deserialize)]
125pub struct PendingCheckpointContentsV1 {
126 pub roots: Vec<TransactionKey>,
127 pub details: PendingCheckpointInfo,
128}
129
130impl PendingCheckpoint {
131 pub fn as_v1(&self) -> &PendingCheckpointContentsV1 {
132 match self {
133 PendingCheckpoint::V1(contents) => contents,
134 }
135 }
136
137 pub fn into_v1(self) -> PendingCheckpointContentsV1 {
138 match self {
139 PendingCheckpoint::V1(contents) => contents,
140 }
141 }
142
143 pub fn roots(&self) -> &Vec<TransactionKey> {
144 &self.as_v1().roots
145 }
146
147 pub fn details(&self) -> &PendingCheckpointInfo {
148 &self.as_v1().details
149 }
150
151 pub fn height(&self) -> CheckpointHeight {
152 self.details().checkpoint_height
153 }
154}
155
156#[derive(Clone, Debug, Serialize, Deserialize)]
157pub struct BuilderCheckpointSummary {
158 pub summary: CheckpointSummary,
159 pub checkpoint_height: Option<CheckpointHeight>,
161 pub position_in_commit: usize,
162}
163
164#[derive(DBMapUtils)]
165pub struct CheckpointStoreTables {
166 pub(crate) checkpoint_content: DBMap<CheckpointContentsDigest, CheckpointContents>,
168
169 #[allow(dead_code)]
173 #[deprecated_db_map]
174 checkpoint_sequence_by_contents_digest: Option<DBMap<(), ()>>,
175
176 #[allow(dead_code)]
182 #[deprecated_db_map]
183 full_checkpoint_content: Option<DBMap<(), ()>>,
184
185 pub(crate) certified_checkpoints: DBMap<CheckpointSequenceNumber, TrustedCheckpoint>,
187 pub(crate) checkpoint_by_digest: DBMap<CheckpointDigest, TrustedCheckpoint>,
189
190 pub(crate) locally_computed_checkpoints: DBMap<CheckpointSequenceNumber, CheckpointSummary>,
194
195 epoch_last_checkpoint_map: DBMap<EpochId, CheckpointSequenceNumber>,
202
203 epoch_info: DBMap<EpochId, EpochInfoV2>,
215
216 epoch_info_watermark: DBMap<(), EpochId>,
222
223 pub(crate) watermarks: DBMap<CheckpointWatermark, (CheckpointSequenceNumber, CheckpointDigest)>,
226}
227
228impl CheckpointStoreTables {
229 pub fn new(path: &Path, metric_name: &'static str) -> Self {
230 Self::open_tables_read_write(path.to_path_buf(), MetricConf::new(metric_name), None, None)
231 }
232 pub fn open_readonly(path: &Path) -> CheckpointStoreTablesReadOnly {
233 Self::get_read_only_handle(
234 path.to_path_buf(),
235 None,
236 None,
237 MetricConf::new("checkpoint_readonly"),
238 )
239 }
240}
241
242pub struct CheckpointStore {
243 pub(crate) tables: CheckpointStoreTables,
244 full_checkpoint_contents_cache: FullCheckpointContentsCache,
245 synced_checkpoint_notify_read: NotifyRead<CheckpointSequenceNumber, VerifiedCheckpoint>,
246 executed_checkpoint_notify_read: NotifyRead<CheckpointSequenceNumber, VerifiedCheckpoint>,
247}
248
249impl CheckpointStore {
250 pub fn new(path: &Path) -> Arc<Self> {
251 Self::new_with_contents_cache(path, FullCheckpointContentsCache::default())
252 }
253
254 pub fn new_with_contents_cache(
255 path: &Path,
256 contents_cache: FullCheckpointContentsCache,
257 ) -> Arc<Self> {
258 let tables = CheckpointStoreTables::new(path, "checkpoint");
259 Arc::new(Self {
260 tables,
261 full_checkpoint_contents_cache: contents_cache,
262 synced_checkpoint_notify_read: NotifyRead::new(),
263 executed_checkpoint_notify_read: NotifyRead::new(),
264 })
265 }
266
267 pub fn new_for_tests() -> Arc<Self> {
268 let storage_dir = iota_common::tempdir().keep();
269 CheckpointStore::new(storage_dir.as_path())
270 }
271
272 pub fn open_readonly(path: &Path) -> CheckpointStoreTablesReadOnly {
273 CheckpointStoreTables::open_readonly(path)
274 }
275
276 #[instrument(level = "info", skip_all)]
277 pub fn insert_genesis_checkpoint(
278 &self,
279 checkpoint: VerifiedCheckpoint,
280 contents: CheckpointContents,
281 epoch_store: &AuthorityPerEpochStore,
282 ) {
283 assert_eq!(
284 checkpoint.epoch(),
285 0,
286 "can't call insert_genesis_checkpoint with a checkpoint not in epoch 0"
287 );
288 assert_eq!(
289 checkpoint.sequence_number(),
290 0,
291 "can't call insert_genesis_checkpoint with a checkpoint that doesn't have a sequence number of 0"
292 );
293
294 if self
297 .get_checkpoint_by_digest(checkpoint.digest())
298 .unwrap()
299 .is_none()
300 {
301 if epoch_store.epoch() == checkpoint.epoch {
302 epoch_store
303 .put_genesis_checkpoint_in_builder(checkpoint.data(), &contents)
304 .unwrap();
305 } else {
306 debug!(
307 validator_epoch =% epoch_store.epoch(),
308 genesis_epoch =% checkpoint.epoch(),
309 "Not inserting checkpoint builder data for genesis checkpoint",
310 );
311 }
312 self.insert_checkpoint_contents(contents).unwrap();
313 self.insert_verified_checkpoint(&checkpoint).unwrap();
314 self.update_highest_synced_checkpoint(&checkpoint).unwrap();
315 }
316 }
317
318 pub fn get_checkpoint_by_digest(
319 &self,
320 digest: &CheckpointDigest,
321 ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
322 self.tables
323 .checkpoint_by_digest
324 .get(digest)
325 .map(|maybe_checkpoint| maybe_checkpoint.map(|c| c.into()))
326 }
327
328 pub fn get_checkpoint_by_sequence_number(
329 &self,
330 sequence_number: CheckpointSequenceNumber,
331 ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
332 self.tables
333 .certified_checkpoints
334 .get(&sequence_number)
335 .map(|maybe_checkpoint| maybe_checkpoint.map(|c| c.into()))
336 }
337
338 pub fn get_locally_computed_checkpoint(
339 &self,
340 sequence_number: CheckpointSequenceNumber,
341 ) -> Result<Option<CheckpointSummary>, TypedStoreError> {
342 self.tables
343 .locally_computed_checkpoints
344 .get(&sequence_number)
345 }
346
347 pub fn get_full_checkpoint_contents_by_digest(
354 &self,
355 digest: &CheckpointContentsDigest,
356 ) -> Option<Arc<FullCheckpointContents>> {
357 self.full_checkpoint_contents_cache.get_by_digest(digest)
358 }
359
360 pub fn get_latest_certified_checkpoint(
361 &self,
362 ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
363 Ok(self
364 .tables
365 .certified_checkpoints
366 .safe_range_iter_reversed(..)
367 .next()
368 .transpose()?
369 .map(|(_, v)| v.into()))
370 }
371
372 pub fn get_latest_locally_computed_checkpoint(
373 &self,
374 ) -> Result<Option<CheckpointSummary>, TypedStoreError> {
375 Ok(self
376 .tables
377 .locally_computed_checkpoints
378 .safe_range_iter_reversed(..)
379 .next()
380 .transpose()?
381 .map(|(_, v)| v))
382 }
383
384 pub fn multi_get_checkpoint_by_sequence_number(
385 &self,
386 sequence_numbers: &[CheckpointSequenceNumber],
387 ) -> Result<Vec<Option<VerifiedCheckpoint>>, TypedStoreError> {
388 let checkpoints = self
389 .tables
390 .certified_checkpoints
391 .multi_get(sequence_numbers)?
392 .into_iter()
393 .map(|maybe_checkpoint| maybe_checkpoint.map(|c| c.into()))
394 .collect();
395
396 Ok(checkpoints)
397 }
398
399 pub fn multi_get_checkpoint_content(
400 &self,
401 contents_digest: &[CheckpointContentsDigest],
402 ) -> Result<Vec<Option<CheckpointContents>>, TypedStoreError> {
403 self.tables.checkpoint_content.multi_get(contents_digest)
404 }
405
406 pub fn get_highest_verified_checkpoint(
407 &self,
408 ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
409 let highest_verified = if let Some(highest_verified) = self
410 .tables
411 .watermarks
412 .get(&CheckpointWatermark::HighestVerified)?
413 {
414 highest_verified
415 } else {
416 return Ok(None);
417 };
418 self.get_checkpoint_by_digest(&highest_verified.1)
419 }
420
421 pub fn get_highest_synced_checkpoint(
422 &self,
423 ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
424 let highest_synced = if let Some(highest_synced) = self
425 .tables
426 .watermarks
427 .get(&CheckpointWatermark::HighestSynced)?
428 {
429 highest_synced
430 } else {
431 return Ok(None);
432 };
433 self.get_checkpoint_by_digest(&highest_synced.1)
434 }
435
436 pub fn get_highest_synced_checkpoint_seq_number(
437 &self,
438 ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
439 if let Some(highest_synced) = self
440 .tables
441 .watermarks
442 .get(&CheckpointWatermark::HighestSynced)?
443 {
444 Ok(Some(highest_synced.0))
445 } else {
446 Ok(None)
447 }
448 }
449
450 pub fn get_highest_executed_checkpoint_seq_number(
451 &self,
452 ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
453 if let Some(highest_executed) = self
454 .tables
455 .watermarks
456 .get(&CheckpointWatermark::HighestExecuted)?
457 {
458 Ok(Some(highest_executed.0))
459 } else {
460 Ok(None)
461 }
462 }
463
464 pub fn get_highest_executed_checkpoint(
465 &self,
466 ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
467 let highest_executed = if let Some(highest_executed) = self
468 .tables
469 .watermarks
470 .get(&CheckpointWatermark::HighestExecuted)?
471 {
472 highest_executed
473 } else {
474 return Ok(None);
475 };
476 self.get_checkpoint_by_digest(&highest_executed.1)
477 }
478
479 pub fn get_highest_pruned_checkpoint_seq_number(
480 &self,
481 ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
482 self.tables
483 .watermarks
484 .get(&CheckpointWatermark::HighestPruned)
485 .map(|watermark| watermark.map(|w| w.0))
486 }
487
488 pub fn get_checkpoint_contents(
489 &self,
490 digest: &CheckpointContentsDigest,
491 ) -> Result<Option<CheckpointContents>, TypedStoreError> {
492 self.tables.checkpoint_content.get(digest)
493 }
494
495 pub fn get_full_checkpoint_contents_by_sequence_number(
500 &self,
501 seq: CheckpointSequenceNumber,
502 ) -> Option<Arc<FullCheckpointContents>> {
503 self.full_checkpoint_contents_cache.get_by_seq(seq)
504 }
505
506 fn prune_local_summaries(&self) -> IotaResult {
507 if let Some((last_local_summary, _)) = self
508 .tables
509 .locally_computed_checkpoints
510 .safe_range_iter_reversed(..)
511 .next()
512 .transpose()?
513 {
514 let mut batch = self.tables.locally_computed_checkpoints.batch();
515 batch.schedule_delete_range(
516 &self.tables.locally_computed_checkpoints,
517 &0,
518 &last_local_summary,
519 )?;
520 batch.write()?;
521 info!("Pruned local summaries up to {:?}", last_local_summary);
522 }
523 Ok(())
524 }
525
526 #[instrument(level = "trace", skip_all)]
527 fn check_for_checkpoint_fork(
528 &self,
529 local_checkpoint: &CheckpointSummary,
530 verified_checkpoint: &VerifiedCheckpoint,
531 ) {
532 if local_checkpoint != verified_checkpoint.data() {
533 let verified_contents = self
534 .get_checkpoint_contents(&verified_checkpoint.contents_digest)
535 .map(|opt_contents| {
536 opt_contents
537 .map(|contents| format!("{contents:?}"))
538 .unwrap_or_else(|| {
539 format!(
540 "Verified checkpoint contents not found, digest: {:?}",
541 verified_checkpoint.contents_digest,
542 )
543 })
544 })
545 .map_err(|e| {
546 format!(
547 "Failed to get verified checkpoint contents, digest: {:?} error: {:?}",
548 verified_checkpoint.contents_digest, e
549 )
550 })
551 .unwrap_or_else(|err_msg| err_msg);
552
553 let local_contents = self
554 .get_checkpoint_contents(&local_checkpoint.contents_digest)
555 .map(|opt_contents| {
556 opt_contents
557 .map(|contents| format!("{contents:?}"))
558 .unwrap_or_else(|| {
559 format!(
560 "Local checkpoint contents not found, digest: {:?}",
561 local_checkpoint.contents_digest
562 )
563 })
564 })
565 .map_err(|e| {
566 format!(
567 "Failed to get local checkpoint contents, digest: {:?} error: {:?}",
568 local_checkpoint.contents_digest, e
569 )
570 })
571 .unwrap_or_else(|err_msg| err_msg);
572
573 error!(
575 verified_checkpoint = ?verified_checkpoint.data(),
576 ?verified_contents,
577 ?local_checkpoint,
578 ?local_contents,
579 "Local checkpoint fork detected!",
580 );
581 fatal!(
582 "Local checkpoint fork detected for sequence number: {}",
583 local_checkpoint.sequence_number()
584 );
585 }
586 }
587
588 pub fn insert_certified_checkpoint(
594 &self,
595 checkpoint: &VerifiedCheckpoint,
596 ) -> Result<(), TypedStoreError> {
597 debug!(
598 checkpoint_seq = checkpoint.sequence_number(),
599 "Inserting certified checkpoint",
600 );
601 let mut batch = self.tables.certified_checkpoints.batch();
602 batch
603 .insert_batch(
604 &self.tables.certified_checkpoints,
605 [(checkpoint.sequence_number(), checkpoint.serializable_ref())],
606 )?
607 .insert_batch(
608 &self.tables.checkpoint_by_digest,
609 [(checkpoint.digest(), checkpoint.serializable_ref())],
610 )?;
611 if checkpoint.next_epoch_committee().is_some() {
612 batch.insert_batch(
613 &self.tables.epoch_last_checkpoint_map,
614 [(&checkpoint.epoch(), checkpoint.sequence_number())],
615 )?;
616 }
617 batch.write()?;
618
619 if let Some(local_checkpoint) = self
620 .tables
621 .locally_computed_checkpoints
622 .get(&checkpoint.sequence_number())?
623 {
624 self.check_for_checkpoint_fork(&local_checkpoint, checkpoint);
625 }
626
627 Ok(())
628 }
629
630 #[instrument(level = "trace", skip_all)]
633 pub fn insert_verified_checkpoint(
634 &self,
635 checkpoint: &VerifiedCheckpoint,
636 ) -> Result<(), TypedStoreError> {
637 self.insert_certified_checkpoint(checkpoint)?;
638 self.update_highest_verified_checkpoint(checkpoint)
639 }
640
641 pub fn update_highest_verified_checkpoint(
642 &self,
643 checkpoint: &VerifiedCheckpoint,
644 ) -> Result<(), TypedStoreError> {
645 if Some(checkpoint.sequence_number())
646 > self
647 .get_highest_verified_checkpoint()?
648 .map(|x| x.sequence_number())
649 {
650 debug!(
651 checkpoint_seq = checkpoint.sequence_number(),
652 "Updating highest verified checkpoint",
653 );
654 self.tables.watermarks.insert(
655 &CheckpointWatermark::HighestVerified,
656 &(checkpoint.sequence_number(), *checkpoint.digest()),
657 )?;
658 }
659
660 Ok(())
661 }
662
663 pub fn update_highest_synced_checkpoint(
664 &self,
665 checkpoint: &VerifiedCheckpoint,
666 ) -> Result<(), TypedStoreError> {
667 let seq = checkpoint.sequence_number();
668 debug!(checkpoint_seq = seq, "Updating highest synced checkpoint",);
669 self.tables.watermarks.insert(
670 &CheckpointWatermark::HighestSynced,
671 &(seq, *checkpoint.digest()),
672 )?;
673 self.synced_checkpoint_notify_read.notify(&seq, checkpoint);
674 Ok(())
675 }
676
677 async fn notify_read_checkpoint_watermark<F>(
678 &self,
679 notify_read: &NotifyRead<CheckpointSequenceNumber, VerifiedCheckpoint>,
680 seq: CheckpointSequenceNumber,
681 get_watermark: F,
682 ) -> VerifiedCheckpoint
683 where
684 F: Fn() -> Option<CheckpointSequenceNumber>,
685 {
686 type ReadResult = Result<Vec<Option<VerifiedCheckpoint>>, TypedStoreError>;
687
688 notify_read
689 .read("notify_read_checkpoint_watermark", &[seq], |seqs| {
690 let seq = seqs[0];
691 let Some(highest) = get_watermark() else {
692 return Ok(vec![None]) as ReadResult;
693 };
694 if highest < seq {
695 return Ok(vec![None]) as ReadResult;
696 }
697 let checkpoint = self
698 .get_checkpoint_by_sequence_number(seq)
699 .expect("db error")
700 .expect("checkpoint not found");
701 Ok(vec![Some(checkpoint)]) as ReadResult
702 })
703 .await
704 .unwrap()
705 .into_iter()
706 .next()
707 .unwrap()
708 }
709
710 pub async fn notify_read_synced_checkpoint(
711 &self,
712 seq: CheckpointSequenceNumber,
713 ) -> VerifiedCheckpoint {
714 self.notify_read_checkpoint_watermark(&self.synced_checkpoint_notify_read, seq, || {
715 self.get_highest_synced_checkpoint_seq_number()
716 .expect("db error")
717 })
718 .await
719 }
720
721 pub async fn notify_read_executed_checkpoint(
722 &self,
723 seq: CheckpointSequenceNumber,
724 ) -> VerifiedCheckpoint {
725 self.notify_read_checkpoint_watermark(&self.executed_checkpoint_notify_read, seq, || {
726 self.get_highest_executed_checkpoint_seq_number()
727 .expect("db error")
728 })
729 .await
730 }
731
732 pub fn update_highest_executed_checkpoint(
733 &self,
734 checkpoint: &VerifiedCheckpoint,
735 ) -> Result<(), TypedStoreError> {
736 if let Some(seq_number) = self.get_highest_executed_checkpoint_seq_number()? {
737 if seq_number >= checkpoint.sequence_number() {
738 return Ok(());
739 }
740 assert_eq!(
741 seq_number + 1,
742 checkpoint.sequence_number(),
743 "Cannot update highest executed checkpoint to {} when current highest executed checkpoint is {}",
744 checkpoint.sequence_number(),
745 seq_number
746 );
747 }
748 let seq = checkpoint.sequence_number();
749 debug!(checkpoint_seq = seq, "Updating highest executed checkpoint",);
750 self.tables.watermarks.insert(
751 &CheckpointWatermark::HighestExecuted,
752 &(seq, *checkpoint.digest()),
753 )?;
754 self.executed_checkpoint_notify_read
755 .notify(&seq, checkpoint);
756 Ok(())
757 }
758
759 pub fn update_highest_pruned_checkpoint(
760 &self,
761 checkpoint: &VerifiedCheckpoint,
762 ) -> Result<(), TypedStoreError> {
763 self.tables.watermarks.insert(
764 &CheckpointWatermark::HighestPruned,
765 &(checkpoint.sequence_number(), *checkpoint.digest()),
766 )
767 }
768
769 pub fn set_highest_executed_checkpoint_subtle(
775 &self,
776 checkpoint: &VerifiedCheckpoint,
777 ) -> Result<(), TypedStoreError> {
778 self.tables.watermarks.insert(
779 &CheckpointWatermark::HighestExecuted,
780 &(checkpoint.sequence_number(), *checkpoint.digest()),
781 )
782 }
783
784 pub fn insert_checkpoint_contents(
785 &self,
786 contents: CheckpointContents,
787 ) -> Result<(), TypedStoreError> {
788 debug!(
789 checkpoint_seq = ?contents.digest(),
790 "Inserting checkpoint contents",
791 );
792 self.tables
793 .checkpoint_content
794 .insert(&contents.digest(), &contents)
795 }
796
797 pub fn insert_verified_checkpoint_contents(
803 &self,
804 checkpoint: &VerifiedCheckpoint,
805 full_contents: VerifiedCheckpointContents,
806 ) -> Result<(), TypedStoreError> {
807 let full_contents = full_contents.into_inner();
808 let contents = full_contents.checkpoint_contents();
809 assert_eq!(checkpoint.contents_digest, contents.digest());
810
811 self.tables
812 .checkpoint_content
813 .insert(&contents.digest(), &contents)?;
814
815 self.cache_full_checkpoint_contents(
816 checkpoint.sequence_number(),
817 checkpoint.contents_digest,
818 full_contents,
819 );
820 Ok(())
821 }
822
823 pub fn should_cache_full_checkpoint_contents(&self, seq: CheckpointSequenceNumber) -> bool {
828 self.full_checkpoint_contents_cache.should_cache(seq)
829 }
830
831 pub fn cache_full_checkpoint_contents(
848 &self,
849 sequence_number: CheckpointSequenceNumber,
850 contents_digest: CheckpointContentsDigest,
851 full_contents: FullCheckpointContents,
852 ) {
853 let size = match bcs::serialized_size(&full_contents) {
854 Ok(size) => size,
855 Err(e) => {
856 warn!(
857 sequence_number,
858 "failed to serialize full checkpoint contents for caching: {e}"
859 );
860 return;
861 }
862 };
863 self.full_checkpoint_contents_cache.insert(
864 sequence_number,
865 contents_digest,
866 Arc::new(full_contents),
867 size,
868 );
869 }
870
871 pub fn get_epoch_last_checkpoint(
872 &self,
873 epoch_id: EpochId,
874 ) -> IotaResult<Option<VerifiedCheckpoint>> {
875 let seq = self.get_epoch_last_checkpoint_seq_number(epoch_id)?;
876 let checkpoint = match seq {
877 Some(seq) => self.get_checkpoint_by_sequence_number(seq)?,
878 None => None,
879 };
880 Ok(checkpoint)
881 }
882
883 pub fn get_epoch_last_checkpoint_seq_number(
887 &self,
888 epoch_id: EpochId,
889 ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
890 self.tables.epoch_last_checkpoint_map.get(&epoch_id)
891 }
892
893 pub fn insert_epoch_last_checkpoint(
894 &self,
895 epoch_id: EpochId,
896 checkpoint: &VerifiedCheckpoint,
897 ) -> IotaResult {
898 self.tables
899 .epoch_last_checkpoint_map
900 .insert(&epoch_id, &checkpoint.sequence_number())?;
901 Ok(())
902 }
903
904 pub fn get_epoch_state_commitments(
905 &self,
906 epoch: EpochId,
907 ) -> IotaResult<Option<Vec<CheckpointCommitment>>> {
908 let commitments = self.get_epoch_last_checkpoint(epoch)?.map(|checkpoint| {
909 checkpoint
910 .end_of_epoch_data
911 .as_ref()
912 .expect("Last checkpoint of epoch expected to have EndOfEpochData")
913 .epoch_commitments
914 .clone()
915 });
916 Ok(commitments)
917 }
918
919 pub fn get_epoch_stats(
922 &self,
923 epoch: EpochId,
924 last_checkpoint: &CheckpointSummary,
925 ) -> Option<EpochStats> {
926 let (first_checkpoint, prev_epoch_network_transactions) = if epoch == 0 {
927 (0, 0)
928 } else if let Ok(Some(checkpoint)) = self.get_epoch_last_checkpoint(epoch - 1) {
929 (
930 checkpoint.sequence_number + 1,
931 checkpoint.network_total_transactions,
932 )
933 } else {
934 return None;
935 };
936 Some(EpochStats {
937 checkpoint_count: last_checkpoint.sequence_number - first_checkpoint + 1,
938 transaction_count: last_checkpoint.network_total_transactions
939 - prev_epoch_network_transactions,
940 total_gas_reward: last_checkpoint
941 .epoch_rolling_gas_cost_summary
942 .computation_cost,
943 })
944 }
945}
946
947#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
948pub enum CheckpointWatermark {
949 HighestVerified,
950 HighestSynced,
951 HighestExecuted,
952 HighestPruned,
953}
954
955struct CheckpointStateHasher {
956 epoch_store: Arc<AuthorityPerEpochStore>,
957 hasher: Weak<GlobalStateHasher>,
958 receive_from_builder: mpsc::Receiver<(CheckpointSequenceNumber, Vec<TransactionEffects>)>,
959}
960
961impl CheckpointStateHasher {
962 fn new(
963 epoch_store: Arc<AuthorityPerEpochStore>,
964 hasher: Weak<GlobalStateHasher>,
965 receive_from_builder: mpsc::Receiver<(CheckpointSequenceNumber, Vec<TransactionEffects>)>,
966 ) -> Self {
967 Self {
968 epoch_store,
969 hasher,
970 receive_from_builder,
971 }
972 }
973
974 async fn run(self) {
975 let Self {
976 epoch_store,
977 hasher,
978 mut receive_from_builder,
979 } = self;
980 while let Some((seq, effects)) = receive_from_builder.recv().await {
981 let Some(hasher) = hasher.upgrade() else {
982 info!("GlobalStateHash was dropped, stopping checkpoint accumulation");
983 break;
984 };
985 hasher
986 .accumulate_checkpoint(&effects, seq, &epoch_store)
987 .expect("epoch ended while accumulating checkpoint");
988 }
989 }
990}
991
992struct BuiltCheckpoint {
995 summary: CheckpointSummary,
996 contents: CheckpointContents,
997 full_contents: Option<FullCheckpointContents>,
1000}
1001
1002#[derive(Debug)]
1003pub enum CheckpointBuilderError {
1004 ChangeEpochTxAlreadyExecuted,
1005 SystemPackagesMissing,
1006 Retry(anyhow::Error),
1007}
1008
1009impl<IotaError: std::error::Error + Send + Sync + 'static> From<IotaError>
1010 for CheckpointBuilderError
1011{
1012 fn from(e: IotaError) -> Self {
1013 Self::Retry(e.into())
1014 }
1015}
1016
1017pub type CheckpointBuilderResult<T = ()> = Result<T, CheckpointBuilderError>;
1018
1019pub struct CheckpointBuilder {
1020 state: Arc<AuthorityState>,
1021 store: Arc<CheckpointStore>,
1022 epoch_store: Arc<AuthorityPerEpochStore>,
1023 notify: Arc<Notify>,
1024 notify_aggregator: Arc<Notify>,
1025 last_built: watch::Sender<CheckpointSequenceNumber>,
1026 effects_store: Arc<dyn TransactionCacheRead>,
1027 global_state_hasher: Weak<GlobalStateHasher>,
1028 send_to_hasher: mpsc::Sender<(CheckpointSequenceNumber, Vec<TransactionEffects>)>,
1029 output: Box<dyn CheckpointOutput>,
1030 metrics: Arc<CheckpointMetrics>,
1031 max_transactions_per_checkpoint: usize,
1032 max_checkpoint_size_bytes: usize,
1033}
1034
1035pub struct CheckpointAggregator {
1036 store: Arc<CheckpointStore>,
1037 epoch_store: Arc<AuthorityPerEpochStore>,
1038 notify: Arc<Notify>,
1039 current: Option<CheckpointSignatureAggregator>,
1040 output: Box<dyn CertifiedCheckpointOutput>,
1041 state: Arc<AuthorityState>,
1042 metrics: Arc<CheckpointMetrics>,
1043}
1044
1045pub struct CheckpointSignatureAggregator {
1047 next_index: u64,
1048 summary: CheckpointSummary,
1049 digest: CheckpointDigest,
1050 signatures_by_digest: MultiStakeAggregator<CheckpointDigest, CheckpointSummary, true>,
1052 store: Arc<CheckpointStore>,
1053 state: Arc<AuthorityState>,
1054 metrics: Arc<CheckpointMetrics>,
1055}
1056
1057impl CheckpointBuilder {
1058 fn new(
1059 state: Arc<AuthorityState>,
1060 store: Arc<CheckpointStore>,
1061 epoch_store: Arc<AuthorityPerEpochStore>,
1062 notify: Arc<Notify>,
1063 effects_store: Arc<dyn TransactionCacheRead>,
1064 global_state_hasher: Weak<GlobalStateHasher>,
1066 send_to_hasher: mpsc::Sender<(CheckpointSequenceNumber, Vec<TransactionEffects>)>,
1068 output: Box<dyn CheckpointOutput>,
1069 notify_aggregator: Arc<Notify>,
1070 last_built: watch::Sender<CheckpointSequenceNumber>,
1071 metrics: Arc<CheckpointMetrics>,
1072 max_transactions_per_checkpoint: usize,
1073 max_checkpoint_size_bytes: usize,
1074 ) -> Self {
1075 Self {
1076 state,
1077 store,
1078 epoch_store,
1079 notify,
1080 effects_store,
1081 global_state_hasher,
1082 send_to_hasher,
1083 output,
1084 notify_aggregator,
1085 last_built,
1086 metrics,
1087 max_transactions_per_checkpoint,
1088 max_checkpoint_size_bytes,
1089 }
1090 }
1091
1092 async fn run(mut self, consensus_replay_waiter: Option<ReplayWaiter>) {
1101 if let Some(replay_waiter) = consensus_replay_waiter {
1102 info!("Waiting for consensus commits to replay ...");
1103 replay_waiter.wait_for_replay().await;
1104 info!("Consensus commits finished replaying");
1105 }
1106 info!("Starting CheckpointBuilder");
1107 loop {
1108 match self.maybe_build_checkpoints().await {
1109 Ok(()) => {}
1110 err @ Err(
1111 CheckpointBuilderError::ChangeEpochTxAlreadyExecuted
1112 | CheckpointBuilderError::SystemPackagesMissing,
1113 ) => {
1114 info!("CheckpointBuilder stopping: {:?}", err);
1115 return;
1116 }
1117 Err(CheckpointBuilderError::Retry(inner)) => {
1118 let msg = format!("{inner:?}");
1119 debug_fatal!("Error while making checkpoint, will retry in 1s: {}", msg);
1120 tokio::time::sleep(Duration::from_secs(1)).await;
1121 self.metrics.checkpoint_errors.inc();
1122 continue;
1123 }
1124 }
1125
1126 self.notify.notified().await;
1127 }
1128 }
1129
1130 async fn maybe_build_checkpoints(&mut self) -> CheckpointBuilderResult {
1131 let _scope = monitored_scope("BuildCheckpoints");
1132
1133 let summary = self
1135 .epoch_store
1136 .last_built_checkpoint_builder_summary()
1137 .expect("epoch should not have ended");
1138 let mut last_height = summary.as_ref().and_then(|s| s.checkpoint_height);
1139 let mut last_timestamp = summary.as_ref().map(|s| s.summary.timestamp_ms);
1140 let mut last_seq = summary.map(|s| s.summary.sequence_number);
1141
1142 let min_checkpoint_interval_ms = self
1143 .epoch_store
1144 .protocol_config()
1145 .min_checkpoint_interval_ms_as_option()
1146 .unwrap_or_default();
1147 let checkpoint_rate_window_size = self
1150 .epoch_store
1151 .protocol_config()
1152 .checkpoint_rate_window_size_as_option();
1153 let mut grouped_pending_checkpoints = Vec::new();
1154 let mut checkpoints_iter = self
1155 .epoch_store
1156 .get_pending_checkpoints(last_height)
1157 .expect("unexpected epoch store error")
1158 .into_iter()
1159 .peekable();
1160 while let Some((height, pending)) = checkpoints_iter.next() {
1161 let current_timestamp = pending.details().timestamp_ms;
1162 let adjacent_interval_elapsed = match last_timestamp {
1164 Some(last_timestamp) => {
1165 current_timestamp >= last_timestamp + min_checkpoint_interval_ms
1166 }
1167 None => true,
1168 };
1169 let interval_elapsed = adjacent_interval_elapsed
1180 || checkpoint_rate_window_size.is_some_and(|window| {
1181 last_seq
1182 .and_then(|seq| (seq + 1).checked_sub(window))
1183 .and_then(|window_start_seq| {
1184 self.epoch_store
1185 .get_built_checkpoint_summary(window_start_seq)
1186 .expect("epoch store should not error reading a built checkpoint summary")
1187 })
1188 .is_some_and(|window_start| {
1189 current_timestamp
1190 >= window_start.timestamp_ms + window * min_checkpoint_interval_ms
1191 })
1192 });
1193 let can_build = interval_elapsed
1196 || checkpoints_iter
1199 .peek()
1200 .is_some_and(|(_, next_pending)| next_pending.details().last_of_epoch)
1201 || pending.details().last_of_epoch;
1203 grouped_pending_checkpoints.push(pending);
1204 if !can_build {
1205 debug!(
1206 checkpoint_commit_height = height,
1207 ?last_timestamp,
1208 ?current_timestamp,
1209 "waiting for more PendingCheckpoints: minimum interval not yet elapsed"
1210 );
1211 continue;
1212 }
1213
1214 last_height = Some(height);
1216 last_timestamp = Some(current_timestamp);
1217 let commits_in_checkpoint = grouped_pending_checkpoints.len();
1218 debug!(
1219 checkpoint_commit_height_from = grouped_pending_checkpoints
1220 .first()
1221 .unwrap()
1222 .details()
1223 .checkpoint_height,
1224 checkpoint_commit_height_to = last_height,
1225 "Making checkpoint with commit height range"
1226 );
1227
1228 let seq = self
1229 .make_checkpoint(std::mem::take(&mut grouped_pending_checkpoints))
1230 .await?;
1231
1232 self.metrics
1235 .commits_per_checkpoint
1236 .observe(commits_in_checkpoint as f64);
1237 last_seq = Some(seq);
1240 self.last_built.send_if_modified(|cur| {
1241 if seq > *cur {
1243 *cur = seq;
1244 true
1245 } else {
1246 false
1247 }
1248 });
1249
1250 tokio::task::yield_now().await;
1253 }
1254 debug!(
1255 "Waiting for more checkpoints from consensus after processing {last_height:?}; {} pending checkpoints left unprocessed until next interval",
1256 grouped_pending_checkpoints.len(),
1257 );
1258
1259 Ok(())
1260 }
1261
1262 #[instrument(level = "debug", skip_all, fields(last_height = pendings.last().unwrap().details().checkpoint_height
1263 ))]
1264 async fn make_checkpoint(
1265 &self,
1266 pendings: Vec<PendingCheckpoint>,
1267 ) -> CheckpointBuilderResult<CheckpointSequenceNumber> {
1268 let _scope = monitored_scope("CheckpointBuilder::make_checkpoint");
1269 let last_details = pendings.last().unwrap().details().clone();
1270
1271 let highest_executed_sequence = self
1274 .store
1275 .get_highest_executed_checkpoint_seq_number()
1276 .expect("db error")
1277 .unwrap_or(0);
1278
1279 let (poll_count, result) = poll_count(self.resolve_checkpoint_transactions(pendings)).await;
1280 let (sorted_tx_effects_included_in_checkpoint, all_roots) = result?;
1281
1282 let new_checkpoints = self
1283 .create_checkpoints(
1284 sorted_tx_effects_included_in_checkpoint,
1285 &last_details,
1286 &all_roots,
1287 )
1288 .await?;
1289 let highest_sequence = new_checkpoints.last().summary.sequence_number();
1290 if highest_sequence <= highest_executed_sequence && poll_count > 1 {
1291 debug_fatal!(
1292 "resolve_checkpoint_transactions should be instantaneous when executed checkpoint is ahead of checkpoint builder"
1293 );
1294 }
1295
1296 self.write_checkpoints(last_details.checkpoint_height, new_checkpoints)
1297 .await?;
1298 Ok(highest_sequence)
1299 }
1300
1301 #[instrument(level = "debug", skip_all)]
1306 async fn resolve_checkpoint_transactions(
1307 &self,
1308 pending_checkpoints: Vec<PendingCheckpoint>,
1309 ) -> IotaResult<(Vec<TransactionEffects>, HashSet<TransactionDigest>)> {
1310 let _scope = monitored_scope("CheckpointBuilder::resolve_checkpoint_transactions");
1311
1312 let mut effects_in_current_checkpoint = BTreeSet::new();
1318
1319 let mut tx_effects = Vec::new();
1320 let mut tx_roots = HashSet::new();
1321
1322 for pending_checkpoint in pending_checkpoints.into_iter() {
1323 let pending = pending_checkpoint.into_v1();
1324 debug!(
1325 checkpoint_commit_height = pending.details.checkpoint_height,
1326 roots = ?pending.roots,
1327 "Resolving checkpoint transactions for pending checkpoint.",
1328 );
1329
1330 let roots = &pending.roots;
1331
1332 self.metrics
1333 .checkpoint_roots_count
1334 .inc_by(roots.len() as u64);
1335
1336 let root_digests = self
1337 .epoch_store
1338 .notify_read_executed_digests(roots)
1339 .in_monitored_scope("CheckpointNotifyDigests")
1340 .await?;
1341 let root_effects = self
1342 .effects_store
1343 .try_notify_read_executed_effects(
1344 CHECKPOINT_BUILDER_NOTIFY_READ_TASK_NAME,
1345 &root_digests,
1346 )
1347 .in_monitored_scope("CheckpointNotifyRead")
1348 .await?;
1349
1350 let consensus_commit_prologue = {
1351 let consensus_commit_prologue = self
1355 .extract_consensus_commit_prologue(&root_digests, &root_effects)
1356 .await?;
1357
1358 if let Some((ccp_digest, ccp_effects)) = &consensus_commit_prologue {
1362 let unsorted_ccp = self.complete_checkpoint_effects(
1363 vec![ccp_effects.clone()],
1364 &mut effects_in_current_checkpoint,
1365 )?;
1366
1367 if unsorted_ccp.len() != 1 {
1370 fatal!(
1371 "Expected 1 consensus commit prologue, got {:?}",
1372 unsorted_ccp
1373 .iter()
1374 .map(|e| e.transaction_digest())
1375 .collect::<Vec<_>>()
1376 );
1377 }
1378 assert_eq!(unsorted_ccp.len(), 1);
1379 assert_eq!(unsorted_ccp[0].transaction_digest(), ccp_digest);
1380 }
1381 consensus_commit_prologue
1382 };
1383
1384 let unsorted =
1385 self.complete_checkpoint_effects(root_effects, &mut effects_in_current_checkpoint)?;
1386
1387 let _scope = monitored_scope("CheckpointBuilder::causal_sort");
1388 let mut sorted: Vec<TransactionEffects> = Vec::with_capacity(unsorted.len() + 1);
1389 if let Some((_ccp_digest, ccp_effects)) = consensus_commit_prologue {
1390 #[cfg(debug_assertions)]
1391 {
1392 for tx in unsorted.iter() {
1395 assert!(tx.transaction_digest() != &_ccp_digest);
1396 }
1397 }
1398 sorted.push(ccp_effects);
1399 }
1400 sorted.extend(CausalOrder::causal_sort(unsorted));
1401
1402 #[cfg(msim)]
1403 {
1404 self.expensive_consensus_commit_prologue_invariants_check(&root_digests, &sorted);
1406 }
1407
1408 tx_effects.extend(sorted);
1409 tx_roots.extend(root_digests);
1410 }
1411
1412 Ok((tx_effects, tx_roots))
1413 }
1414
1415 async fn extract_consensus_commit_prologue(
1420 &self,
1421 root_digests: &[TransactionDigest],
1422 root_effects: &[TransactionEffects],
1423 ) -> IotaResult<Option<(TransactionDigest, TransactionEffects)>> {
1424 let _scope = monitored_scope("CheckpointBuilder::extract_consensus_commit_prologue");
1425 if root_digests.is_empty() {
1426 return Ok(None);
1427 }
1428
1429 let first_tx = self
1434 .state
1435 .get_transaction_cache_reader()
1436 .try_get_transaction_block(&root_digests[0])?
1437 .expect("Transaction block must exist");
1438
1439 Ok(match first_tx.transaction().kind() {
1440 TransactionKind::ConsensusCommitPrologueV1(_) => {
1441 assert_eq!(first_tx.digest(), root_effects[0].transaction_digest());
1442 Some((*first_tx.digest(), root_effects[0].clone()))
1443 }
1444 _ => None,
1445 })
1446 }
1447
1448 #[instrument(level = "debug", skip_all)]
1450 async fn write_checkpoints(
1451 &self,
1452 height: CheckpointHeight,
1453 mut new_checkpoints: NonEmpty<BuiltCheckpoint>,
1454 ) -> IotaResult {
1455 let _scope = monitored_scope("CheckpointBuilder::write_checkpoints");
1456 let mut batch = self.store.tables.checkpoint_content.batch();
1457 let mut all_tx_digests =
1458 Vec::with_capacity(new_checkpoints.iter().map(|c| c.contents.len()).sum());
1459
1460 for BuiltCheckpoint {
1462 summary, contents, ..
1463 } in &new_checkpoints
1464 {
1465 debug!(
1466 checkpoint_commit_height = height,
1467 checkpoint_seq = summary.sequence_number,
1468 contents_digest = ?contents.digest(),
1469 "writing checkpoint",
1470 );
1471
1472 if let Some(previously_computed_summary) = self
1473 .store
1474 .tables
1475 .locally_computed_checkpoints
1476 .get(&summary.sequence_number)?
1477 {
1478 if previously_computed_summary != *summary {
1479 fatal!(
1481 "Checkpoint {} was previously built with a different result: {previously_computed_summary:?} vs {summary:?}",
1482 summary.sequence_number,
1483 );
1484 }
1485 }
1486
1487 all_tx_digests.extend(contents.iter().map(|digests| digests.transaction));
1488
1489 self.metrics
1490 .transactions_included_in_checkpoint
1491 .inc_by(contents.len() as u64);
1492 let sequence_number = summary.sequence_number;
1493 self.metrics
1494 .last_constructed_checkpoint
1495 .set(sequence_number as i64);
1496
1497 batch.insert_batch(
1498 &self.store.tables.checkpoint_content,
1499 [(contents.digest(), contents)],
1500 )?;
1501
1502 batch.insert_batch(
1503 &self.store.tables.locally_computed_checkpoints,
1504 [(sequence_number, summary)],
1505 )?;
1506 }
1507
1508 batch.write()?;
1509
1510 for checkpoint in new_checkpoints.iter_mut() {
1513 if let Some(full_contents) = checkpoint.full_contents.take() {
1514 self.store.cache_full_checkpoint_contents(
1515 checkpoint.summary.sequence_number,
1516 checkpoint.summary.contents_digest,
1517 full_contents,
1518 );
1519 }
1520 }
1521
1522 for BuiltCheckpoint {
1525 summary, contents, ..
1526 } in &new_checkpoints
1527 {
1528 self.output
1529 .checkpoint_created(summary, contents, &self.epoch_store, &self.store)
1530 .await?;
1531 }
1532
1533 for BuiltCheckpoint {
1534 summary: local_checkpoint,
1535 ..
1536 } in &new_checkpoints
1537 {
1538 if let Some(certified_checkpoint) = self
1539 .store
1540 .tables
1541 .certified_checkpoints
1542 .get(&local_checkpoint.sequence_number())?
1543 {
1544 self.store
1545 .check_for_checkpoint_fork(local_checkpoint, &certified_checkpoint.into());
1546 }
1547 }
1548
1549 self.notify_aggregator.notify_one();
1550 self.epoch_store.process_constructed_checkpoint(
1551 height,
1552 new_checkpoints.map(|c| (c.summary, c.contents)),
1553 );
1554 Ok(())
1555 }
1556
1557 #[expect(clippy::type_complexity)]
1558 fn split_checkpoint_chunks(
1559 &self,
1560 transactions_effects_and_sizes: Vec<(TransactionEnvelope, TransactionEffects, usize)>,
1561 signatures: Vec<Vec<UserSignature>>,
1562 ) -> CheckpointBuilderResult<
1563 Vec<Vec<(TransactionEnvelope, TransactionEffects, Vec<UserSignature>)>>,
1564 > {
1565 let _guard = monitored_scope("CheckpointBuilder::split_checkpoint_chunks");
1566 let mut chunks = Vec::new();
1567 let mut chunk = Vec::new();
1568 let mut chunk_size: usize = 0;
1569 for ((transaction, effects, transaction_size), signatures) in
1570 transactions_effects_and_sizes.into_iter().zip(signatures)
1571 {
1572 let size = transaction_size
1577 + bcs::serialized_size(&effects)?
1578 + bcs::serialized_size(&signatures)?;
1579 if chunk.len() == self.max_transactions_per_checkpoint
1580 || (chunk_size + size) > self.max_checkpoint_size_bytes
1581 {
1582 if chunk.is_empty() {
1583 warn!(
1585 "Size of single transaction ({size}) exceeds max checkpoint size ({}); allowing excessively large checkpoint to go through.",
1586 self.max_checkpoint_size_bytes
1587 );
1588 } else {
1589 chunks.push(chunk);
1590 chunk = Vec::new();
1591 chunk_size = 0;
1592 }
1593 }
1594
1595 chunk.push((transaction, effects, signatures));
1596 chunk_size += size;
1597 }
1598
1599 if !chunk.is_empty() || chunks.is_empty() {
1600 chunks.push(chunk);
1605 }
1611 Ok(chunks)
1612 }
1613
1614 fn load_last_built_checkpoint_summary(
1617 epoch_store: &AuthorityPerEpochStore,
1618 store: &CheckpointStore,
1619 ) -> IotaResult<Option<(CheckpointSequenceNumber, CheckpointSummary)>> {
1620 let mut last_checkpoint = epoch_store.last_built_checkpoint_summary()?;
1621 if last_checkpoint.is_none() {
1622 let epoch = epoch_store.epoch();
1623 if epoch > 0 {
1624 let previous_epoch = epoch - 1;
1625 let last_verified = store.get_epoch_last_checkpoint(previous_epoch)?;
1626 last_checkpoint = last_verified.map(VerifiedCheckpoint::into_summary_and_sequence);
1627 if let Some((ref seq, _)) = last_checkpoint {
1628 debug!(
1629 "No checkpoints in builder DB, taking checkpoint from previous epoch with sequence {seq}"
1630 );
1631 } else {
1632 panic!("Can not find last checkpoint for previous epoch {previous_epoch}");
1635 }
1636 }
1637 }
1638 Ok(last_checkpoint)
1639 }
1640
1641 #[instrument(level = "debug", skip_all)]
1642 async fn create_checkpoints(
1643 &self,
1644 all_effects: Vec<TransactionEffects>,
1645 details: &PendingCheckpointInfo,
1646 all_roots: &HashSet<TransactionDigest>,
1647 ) -> CheckpointBuilderResult<NonEmpty<BuiltCheckpoint>> {
1648 let _scope = monitored_scope("CheckpointBuilder::create_checkpoints");
1649
1650 let total = all_effects.len();
1651 let mut last_checkpoint =
1652 Self::load_last_built_checkpoint_summary(&self.epoch_store, &self.store)?;
1653 let last_checkpoint_seq = last_checkpoint.as_ref().map(|(seq, _)| *seq);
1654 debug!(
1655 checkpoint_commit_height = details.checkpoint_height,
1656 next_checkpoint_seq = last_checkpoint_seq.unwrap_or_default() + 1,
1657 checkpoint_timestamp = details.timestamp_ms,
1658 "Creating checkpoint(s) for {} transactions",
1659 all_effects.len(),
1660 );
1661
1662 let all_digests: Vec<_> = all_effects
1663 .iter()
1664 .map(|effect| *effect.transaction_digest())
1665 .collect();
1666 let transactions_and_sizes = self
1667 .state
1668 .get_transaction_cache_reader()
1669 .try_get_transactions_and_serialized_sizes(&all_digests)?;
1670 let mut all_effects_and_transaction_sizes = Vec::with_capacity(all_effects.len());
1671 let mut transactions = Vec::with_capacity(all_effects.len());
1672 let mut transaction_keys = Vec::with_capacity(all_effects.len());
1673 let mut randomness_rounds = BTreeMap::new();
1674 {
1675 let _guard = monitored_scope("CheckpointBuilder::wait_for_transactions_sequenced");
1676 debug!(
1677 ?last_checkpoint_seq,
1678 "Waiting for {:?} certificates to appear in consensus",
1679 all_effects.len()
1680 );
1681
1682 for (effects, transaction_and_size) in all_effects
1683 .into_iter()
1684 .zip(transactions_and_sizes.into_iter())
1685 {
1686 let (transaction, size) = transaction_and_size
1687 .unwrap_or_else(|| panic!("Could not find executed transaction {effects:?}"));
1688 match transaction.inner().transaction().kind() {
1689 #[allow(deprecated)]
1690 TransactionKind::ConsensusCommitPrologueV1(_)
1691 | TransactionKind::AuthenticatorStateUpdateV1Deprecated => {
1692 }
1700 TransactionKind::RandomnessStateUpdate(rsu) => {
1701 randomness_rounds
1702 .insert(*effects.transaction_digest(), rsu.randomness_round);
1703 }
1704 _ => {
1705 let digest = *effects.transaction_digest();
1710 if !all_roots.contains(&digest) {
1711 let key = if self.epoch_store.protocol_config().enable_pcool_flow() {
1714 ConsensusTransactionKey::UserTransaction(digest)
1715 } else {
1716 ConsensusTransactionKey::Certificate(digest)
1717 };
1718 transaction_keys.push(SequencedConsensusTransactionKey::External(key));
1719 }
1720 }
1721 }
1722 transactions.push(transaction);
1723 all_effects_and_transaction_sizes.push((effects, size));
1724 }
1725
1726 self.epoch_store
1727 .consensus_messages_processed_notify(transaction_keys)
1728 .await?;
1729 }
1730
1731 let signatures = self
1732 .epoch_store
1733 .user_signatures_for_checkpoint(&transactions, &all_digests)?;
1734 debug!(
1735 ?last_checkpoint_seq,
1736 "Received {} checkpoint user signatures from consensus",
1737 signatures.len()
1738 );
1739
1740 let transactions_effects_and_sizes = transactions
1741 .into_iter()
1742 .zip(all_effects_and_transaction_sizes)
1743 .map(|(transaction, (effects, size))| (transaction.into_inner(), effects, size))
1744 .collect();
1745 let chunks = self.split_checkpoint_chunks(transactions_effects_and_sizes, signatures)?;
1746 let chunks_count = chunks.len();
1747
1748 let mut checkpoints = Vec::with_capacity(chunks_count);
1749 debug!(
1750 ?last_checkpoint_seq,
1751 "Creating {} checkpoints with {} transactions", chunks_count, total,
1752 );
1753
1754 let epoch = self.epoch_store.epoch();
1755 for (index, chunk) in chunks.into_iter().enumerate() {
1756 let first_checkpoint_of_epoch = index == 0
1757 && last_checkpoint
1758 .as_ref()
1759 .map(|(_, c)| c.epoch != epoch)
1760 .unwrap_or(true);
1761 if first_checkpoint_of_epoch {
1762 self.epoch_store
1763 .record_epoch_first_checkpoint_creation_time_metric();
1764 }
1765 let last_checkpoint_of_epoch = details.last_of_epoch && index == chunks_count - 1;
1766
1767 let sequence_number = last_checkpoint
1768 .as_ref()
1769 .map(|(_, c)| c.sequence_number + 1)
1770 .unwrap_or_default();
1771 let mut timestamp_ms = details.timestamp_ms;
1772 if let Some((_, last_checkpoint)) = &last_checkpoint {
1773 if last_checkpoint.timestamp_ms > timestamp_ms {
1774 debug!(
1776 "Decrease of checkpoint timestamp, possibly due to epoch change. Sequence: {}, previous: {}, current: {}",
1777 sequence_number, last_checkpoint.timestamp_ms, timestamp_ms,
1778 );
1779 timestamp_ms = last_checkpoint.timestamp_ms;
1780 }
1781 }
1782
1783 let (chunk_transactions, mut effects, mut signatures): (
1784 Vec<TransactionEnvelope>,
1785 Vec<TransactionEffects>,
1786 Vec<Vec<UserSignature>>,
1787 ) = chunk.into_iter().multiunzip();
1788 let epoch_rolling_gas_cost_summary =
1789 self.get_epoch_total_gas_cost(last_checkpoint.as_ref().map(|(_, c)| c), &effects);
1790
1791 let end_of_epoch_data = if last_checkpoint_of_epoch {
1792 let scores: Vec<u64> = if self
1793 .epoch_store
1794 .protocol_config()
1795 .pass_calculated_validator_scores_to_advance_epoch()
1796 {
1797 self.epoch_store.scoreboard.current_scores()
1798 } else {
1799 vec![MAX_SCORE; self.epoch_store.committee().num_members()]
1801 };
1802
1803 let (system_state_obj, system_epoch_info_event) = self
1804 .augment_epoch_last_checkpoint(
1805 &epoch_rolling_gas_cost_summary,
1806 timestamp_ms,
1807 &mut effects,
1808 &mut signatures,
1809 sequence_number,
1810 scores,
1811 )
1812 .await?;
1813
1814 let epoch_supply_change =
1822 system_epoch_info_event.map_or(0, |event| event.supply_change());
1823
1824 let committee = system_state_obj
1825 .get_current_epoch_committee()
1826 .committee()
1827 .clone();
1828
1829 let root_state_digest = {
1832 let state_acc = self
1833 .global_state_hasher
1834 .upgrade()
1835 .expect("No checkpoints should be getting built after local configuration");
1836 let acc = state_acc.accumulate_checkpoint(
1837 &effects,
1838 sequence_number,
1839 &self.epoch_store,
1840 )?;
1841
1842 state_acc
1843 .wait_for_previous_running_root(&self.epoch_store, sequence_number)
1844 .await?;
1845
1846 state_acc.accumulate_running_root(
1847 &self.epoch_store,
1848 sequence_number,
1849 Some(acc),
1850 )?;
1851 state_acc
1852 .digest_epoch(self.epoch_store.clone(), sequence_number)
1853 .await?
1854 };
1855 self.metrics.highest_accumulated_epoch.set(epoch as i64);
1856 info!("Epoch {epoch} root state hash digest: {root_state_digest:?}");
1857
1858 let epoch_commitments = vec![CheckpointCommitment::EcmhLiveObjectSet {
1859 digest: root_state_digest.digest,
1860 }];
1861
1862 Some(EndOfEpochData {
1863 next_epoch_committee: committee.committee_members(),
1864 next_epoch_protocol_version: system_state_obj.protocol_version(),
1865 epoch_commitments,
1866 epoch_supply_change,
1867 })
1868 } else {
1869 self.send_to_hasher
1870 .send((sequence_number, effects.clone()))
1871 .await?;
1872 None
1873 };
1874 let contents = CheckpointContents::new_with_digests_and_signatures(
1875 effects.iter().map(TransactionEffects::execution_digests),
1876 signatures,
1877 );
1878
1879 let num_txns = contents.len() as u64;
1880
1881 let network_total_transactions = last_checkpoint
1882 .as_ref()
1883 .map(|(_, c)| c.network_total_transactions + num_txns)
1884 .unwrap_or(num_txns);
1885
1886 let previous_digest = last_checkpoint.as_ref().map(|(_, c)| c.digest());
1887
1888 let matching_randomness_rounds: Vec<_> = effects
1889 .iter()
1890 .filter_map(|e| randomness_rounds.get(e.transaction_digest()))
1891 .copied()
1892 .collect();
1893
1894 let summary = CheckpointSummary::new_with_protocol_config(
1895 self.epoch_store.protocol_config(),
1896 epoch,
1897 sequence_number,
1898 network_total_transactions,
1899 &contents,
1900 previous_digest,
1901 epoch_rolling_gas_cost_summary,
1902 end_of_epoch_data,
1903 timestamp_ms,
1904 matching_randomness_rounds,
1905 );
1906 summary.report_checkpoint_age(&self.metrics.last_created_checkpoint_age);
1907 if last_checkpoint_of_epoch {
1908 info!(
1909 checkpoint_seq = sequence_number,
1910 "creating last checkpoint of epoch {}", epoch
1911 );
1912 if let Some(stats) = self.store.get_epoch_stats(epoch, &summary) {
1913 self.epoch_store
1914 .report_epoch_metrics_at_last_checkpoint(stats);
1915 }
1916 }
1917
1918 let full_contents = (!last_checkpoint_of_epoch
1923 && self
1924 .store
1925 .should_cache_full_checkpoint_contents(sequence_number))
1926 .then(|| {
1927 let execution_data = chunk_transactions
1928 .into_iter()
1929 .zip(effects.iter().cloned())
1930 .map(|(transaction, effects)| ExecutionData::new(transaction, effects));
1931 FullCheckpointContents::from_contents_and_execution_data(
1932 contents.clone(),
1933 execution_data,
1934 )
1935 });
1936
1937 last_checkpoint = Some((sequence_number, summary.clone()));
1938 checkpoints.push(BuiltCheckpoint {
1939 summary,
1940 contents,
1941 full_contents,
1942 });
1943 }
1944
1945 Ok(NonEmpty::from_vec(checkpoints).expect("at least one checkpoint"))
1946 }
1947
1948 fn get_epoch_total_gas_cost(
1949 &self,
1950 last_checkpoint: Option<&CheckpointSummary>,
1951 cur_checkpoint_effects: &[TransactionEffects],
1952 ) -> GasCostSummary {
1953 let (previous_epoch, previous_gas_costs) = last_checkpoint
1954 .map(|c| (c.epoch, c.epoch_rolling_gas_cost_summary.clone()))
1955 .unwrap_or_default();
1956 let current_gas_costs =
1957 checked::new_gas_cost_summary_from_txn_effects(cur_checkpoint_effects.iter());
1958 if previous_epoch == self.epoch_store.epoch() {
1959 GasCostSummary::new(
1961 previous_gas_costs.computation_cost + current_gas_costs.computation_cost,
1962 previous_gas_costs.computation_cost_burned
1963 + current_gas_costs.computation_cost_burned,
1964 previous_gas_costs.storage_cost + current_gas_costs.storage_cost,
1965 previous_gas_costs.storage_rebate + current_gas_costs.storage_rebate,
1966 previous_gas_costs.non_refundable_storage_fee
1967 + current_gas_costs.non_refundable_storage_fee,
1968 )
1969 } else {
1970 current_gas_costs
1971 }
1972 }
1973
1974 #[instrument(level = "error", skip_all)]
1977 async fn augment_epoch_last_checkpoint(
1978 &self,
1979 epoch_total_gas_cost: &GasCostSummary,
1980 epoch_start_timestamp_ms: CheckpointTimestamp,
1981 checkpoint_effects: &mut Vec<TransactionEffects>,
1982 signatures: &mut Vec<Vec<UserSignature>>,
1983 checkpoint: CheckpointSequenceNumber,
1984 scores: Vec<u64>,
1985 ) -> CheckpointBuilderResult<(IotaSystemState, Option<SystemEpochInfoEvent>)> {
1986 let (system_state, system_epoch_info_event, effects) = self
1987 .state
1988 .create_and_execute_advance_epoch_tx(
1989 &self.epoch_store,
1990 epoch_total_gas_cost,
1991 checkpoint,
1992 epoch_start_timestamp_ms,
1993 scores,
1994 )
1995 .await?;
1996 checkpoint_effects.push(effects);
1997 signatures.push(vec![]);
1998 Ok((system_state, system_epoch_info_event))
1999 }
2000
2001 #[instrument(level = "debug", skip_all)]
2010 fn complete_checkpoint_effects(
2011 &self,
2012 mut roots: Vec<TransactionEffects>,
2013 existing_tx_digests_in_checkpoint: &mut BTreeSet<TransactionDigest>,
2014 ) -> IotaResult<Vec<TransactionEffects>> {
2015 let _scope = monitored_scope("CheckpointBuilder::complete_checkpoint_effects");
2016 let mut results = vec![];
2017 let mut seen = HashSet::new();
2018 loop {
2019 let mut pending = HashSet::new();
2020
2021 let transactions_included = self
2022 .epoch_store
2023 .builder_included_transactions_in_checkpoint(
2024 roots.iter().map(|e| e.transaction_digest()),
2025 )?;
2026
2027 for (effect, tx_included) in roots.into_iter().zip(transactions_included.into_iter()) {
2028 let digest = effect.transaction_digest();
2029 seen.insert(*digest);
2032
2033 if existing_tx_digests_in_checkpoint.contains(effect.transaction_digest()) {
2035 continue;
2036 }
2037
2038 if tx_included || effect.epoch() < self.epoch_store.epoch() {
2040 continue;
2041 }
2042
2043 let existing_effects = self
2044 .epoch_store
2045 .transactions_executed_in_cur_epoch(effect.dependencies())?;
2046
2047 for (dependency, effects_signature_exists) in
2048 effect.dependencies().iter().zip(existing_effects.iter())
2049 {
2050 if !effects_signature_exists {
2055 continue;
2056 }
2057 if seen.insert(*dependency) {
2058 pending.insert(*dependency);
2059 }
2060 }
2061 results.push(effect);
2062 }
2063 if pending.is_empty() {
2064 break;
2065 }
2066 let pending = pending.into_iter().collect::<Vec<_>>();
2067 let effects = self
2068 .effects_store
2069 .try_multi_get_executed_effects(&pending)?;
2070 let effects = effects
2071 .into_iter()
2072 .zip(pending)
2073 .map(|(opt, digest)| match opt {
2074 Some(x) => x,
2075 None => panic!(
2076 "Can not find effect for transaction {digest}, however transaction that depend on it was already executed"
2077 ),
2078 })
2079 .collect::<Vec<_>>();
2080 roots = effects;
2081 }
2082
2083 existing_tx_digests_in_checkpoint.extend(results.iter().map(|e| e.transaction_digest()));
2084 Ok(results)
2085 }
2086
2087 #[cfg(msim)]
2090 fn expensive_consensus_commit_prologue_invariants_check(
2091 &self,
2092 root_digests: &[TransactionDigest],
2093 sorted: &[TransactionEffects],
2094 ) {
2095 let root_txs = self
2097 .state
2098 .get_transaction_cache_reader()
2099 .multi_get_transaction_blocks(root_digests);
2100 let ccps = root_txs
2101 .iter()
2102 .filter_map(|tx| {
2103 tx.as_ref().filter(|tx| {
2104 matches!(
2105 tx.transaction().kind(),
2106 TransactionKind::ConsensusCommitPrologueV1(_)
2107 )
2108 })
2109 })
2110 .collect::<Vec<_>>();
2111
2112 assert!(ccps.len() <= 1);
2115
2116 let txs = self
2118 .state
2119 .get_transaction_cache_reader()
2120 .multi_get_transaction_blocks(
2121 &sorted
2122 .iter()
2123 .map(|tx| *tx.transaction_digest())
2124 .collect::<Vec<_>>(),
2125 );
2126
2127 if ccps.is_empty() {
2128 for tx in txs.iter().flatten() {
2132 assert!(!matches!(
2133 tx.transaction().kind(),
2134 TransactionKind::ConsensusCommitPrologueV1(_)
2135 ));
2136 }
2137 } else {
2138 assert!(matches!(
2141 txs[0].as_ref().unwrap().transaction().kind(),
2142 TransactionKind::ConsensusCommitPrologueV1(_)
2143 ));
2144
2145 assert_eq!(ccps[0].digest(), txs[0].as_ref().unwrap().digest());
2146
2147 for tx in txs.iter().skip(1).flatten() {
2148 assert!(!matches!(
2149 tx.transaction().kind(),
2150 TransactionKind::ConsensusCommitPrologueV1(_)
2151 ));
2152 }
2153 }
2154 }
2155}
2156
2157impl CheckpointAggregator {
2158 fn new(
2159 tables: Arc<CheckpointStore>,
2160 epoch_store: Arc<AuthorityPerEpochStore>,
2161 notify: Arc<Notify>,
2162 output: Box<dyn CertifiedCheckpointOutput>,
2163 state: Arc<AuthorityState>,
2164 metrics: Arc<CheckpointMetrics>,
2165 ) -> Self {
2166 let current = None;
2167 Self {
2168 store: tables,
2169 epoch_store,
2170 notify,
2171 current,
2172 output,
2173 state,
2174 metrics,
2175 }
2176 }
2177
2178 async fn run(mut self) {
2184 info!("Starting CheckpointAggregator");
2185 loop {
2186 if let Err(e) = self.run_and_notify().await {
2187 error!(
2188 "Error while aggregating checkpoint, will retry in 1s: {:?}",
2189 e
2190 );
2191 self.metrics.checkpoint_errors.inc();
2192 tokio::time::sleep(Duration::from_secs(1)).await;
2193 continue;
2194 }
2195
2196 let _ = timeout(Duration::from_secs(1), self.notify.notified()).await;
2197 }
2198 }
2199
2200 async fn run_and_notify(&mut self) -> IotaResult {
2201 let summaries = self.run_inner()?;
2202 for summary in summaries {
2203 self.output.certified_checkpoint_created(&summary).await?;
2204 }
2205 Ok(())
2206 }
2207
2208 fn run_inner(&mut self) -> IotaResult<Vec<CertifiedCheckpointSummary>> {
2209 let _scope = monitored_scope("CheckpointAggregator");
2210 let mut result = vec![];
2211 'outer: loop {
2212 let next_to_certify = self.next_checkpoint_to_certify()?;
2213 let current = if let Some(current) = &mut self.current {
2214 if current.summary.sequence_number < next_to_certify {
2220 self.current = None;
2221 continue;
2222 }
2223 current
2224 } else {
2225 let Some(summary) = self
2226 .epoch_store
2227 .get_built_checkpoint_summary(next_to_certify)?
2228 else {
2229 return Ok(result);
2230 };
2231 self.current = Some(CheckpointSignatureAggregator {
2232 next_index: 0,
2233 digest: summary.digest(),
2234 summary,
2235 signatures_by_digest: MultiStakeAggregator::new(
2236 self.epoch_store.committee().clone(),
2237 ),
2238 store: self.store.clone(),
2239 state: self.state.clone(),
2240 metrics: self.metrics.clone(),
2241 });
2242 self.current.as_mut().unwrap()
2243 };
2244
2245 let epoch_tables = self
2246 .epoch_store
2247 .tables()
2248 .expect("should not run past end of epoch");
2249 let iter = epoch_tables
2250 .pending_checkpoint_signatures
2251 .safe_iter_with_bounds(
2252 Some((current.summary.sequence_number, current.next_index)),
2253 None,
2254 );
2255 for item in iter {
2256 let ((seq, index), data) = item?;
2257 if seq != current.summary.sequence_number {
2258 trace!(
2259 checkpoint_seq =? current.summary.sequence_number,
2260 "Not enough checkpoint signatures",
2261 );
2262 return Ok(result);
2264 }
2265 trace!(
2266 checkpoint_seq = current.summary.sequence_number,
2267 "Processing signature for checkpoint (digest: {:?}) from {:?}",
2268 current.summary.digest(),
2269 data.summary.auth_sig().authority.concise()
2270 );
2271 self.metrics
2272 .checkpoint_participation
2273 .with_label_values(&[&format!(
2274 "{:?}",
2275 data.summary.auth_sig().authority.concise()
2276 )])
2277 .inc();
2278 if let Ok(auth_signature) = current.try_aggregate(data) {
2279 debug!(
2280 checkpoint_seq = current.summary.sequence_number,
2281 "Successfully aggregated signatures for checkpoint (digest: {:?})",
2282 current.summary.digest(),
2283 );
2284 let summary = VerifiedCheckpoint::new_unchecked(
2285 CertifiedCheckpointSummary::new_from_data_and_sig(
2286 current.summary.clone(),
2287 auth_signature,
2288 ),
2289 );
2290
2291 self.store.insert_certified_checkpoint(&summary)?;
2292 self.metrics
2293 .last_certified_checkpoint
2294 .set(current.summary.sequence_number as i64);
2295 current
2296 .summary
2297 .report_checkpoint_age(&self.metrics.last_certified_checkpoint_age);
2298 result.push(summary.into_inner());
2299 self.current = None;
2300 continue 'outer;
2301 } else {
2302 current.next_index = index + 1;
2303 }
2304 }
2305 break;
2306 }
2307 Ok(result)
2308 }
2309
2310 fn next_checkpoint_to_certify(&self) -> IotaResult<CheckpointSequenceNumber> {
2311 Ok(self
2312 .store
2313 .tables
2314 .certified_checkpoints
2315 .safe_range_iter_reversed(..)
2316 .next()
2317 .transpose()?
2318 .map(|(seq, _)| seq + 1)
2319 .unwrap_or_default())
2320 }
2321}
2322
2323impl CheckpointSignatureAggregator {
2324 #[expect(clippy::result_unit_err)]
2325 pub fn try_aggregate(
2326 &mut self,
2327 data: CheckpointSignatureMessage,
2328 ) -> Result<AuthorityStrongQuorumSignInfo, ()> {
2329 let their_digest = *data.summary.digest();
2330 let (_, signature) = data.summary.into_data_and_sig();
2331 let author = signature.authority;
2332 let envelope =
2333 SignedCheckpointSummary::new_from_data_and_sig(self.summary.clone(), signature);
2334 match self.signatures_by_digest.insert(their_digest, envelope) {
2335 InsertResult::Failed {
2337 error:
2338 IotaError::StakeAggregatorRepeatedSigner {
2339 conflicting_sig: false,
2340 ..
2341 },
2342 } => Err(()),
2343 InsertResult::Failed { error } => {
2344 warn!(
2345 checkpoint_seq = self.summary.sequence_number,
2346 "Failed to aggregate new signature from validator {:?}: {:?}",
2347 author.concise(),
2348 error
2349 );
2350 self.check_for_split_brain();
2351 Err(())
2352 }
2353 InsertResult::QuorumReached(cert) => {
2354 if their_digest != self.digest {
2358 self.metrics.remote_checkpoint_forks.inc();
2359 warn!(
2360 checkpoint_seq = self.summary.sequence_number,
2361 "Validator {:?} has mismatching checkpoint digest {}, we have digest {}",
2362 author.concise(),
2363 their_digest,
2364 self.digest
2365 );
2366 return Err(());
2367 }
2368 Ok(cert)
2369 }
2370 InsertResult::NotEnoughVotes {
2371 bad_votes: _,
2372 bad_authorities: _,
2373 } => {
2374 self.check_for_split_brain();
2375 Err(())
2376 }
2377 }
2378 }
2379
2380 fn check_for_split_brain(&self) {
2385 debug!(
2386 checkpoint_seq = self.summary.sequence_number,
2387 "Checking for split brain condition"
2388 );
2389 if self.signatures_by_digest.quorum_unreachable() {
2390 let digests_by_stake_messages = self
2396 .signatures_by_digest
2397 .get_all_unique_values()
2398 .into_iter()
2399 .sorted_by_key(|(_, (_, stake))| -(*stake as i64))
2400 .map(|(digest, (_authorities, total_stake))| {
2401 format!("{digest} (total stake: {total_stake})")
2402 })
2403 .collect::<Vec<String>>();
2404 debug_fatal!(
2405 "Split brain detected in checkpoint signature aggregation for checkpoint {:?}. Remaining stake: {:?}, Digests by stake: {:?}",
2406 self.summary.sequence_number,
2407 self.signatures_by_digest.uncommitted_stake(),
2408 digests_by_stake_messages,
2409 );
2410 self.metrics.split_brain_checkpoint_forks.inc();
2411
2412 let all_unique_values = self.signatures_by_digest.get_all_unique_values();
2413 let local_summary = self.summary.clone();
2414 let state = self.state.clone();
2415 let tables = self.store.clone();
2416
2417 tokio::spawn(async move {
2418 diagnose_split_brain(all_unique_values, local_summary, state, tables).await;
2419 });
2420 }
2421 }
2422}
2423
2424async fn diagnose_split_brain(
2430 all_unique_values: BTreeMap<CheckpointDigest, (Vec<AuthorityName>, StakeUnit)>,
2431 local_summary: CheckpointSummary,
2432 state: Arc<AuthorityState>,
2433 tables: Arc<CheckpointStore>,
2434) {
2435 debug!(
2436 checkpoint_seq = local_summary.sequence_number,
2437 "Running split brain diagnostics..."
2438 );
2439 let time = SystemTime::now();
2440 let digest_to_validator = all_unique_values
2442 .iter()
2443 .filter_map(|(digest, (validators, _))| {
2444 if *digest != local_summary.digest() {
2445 let random_validator = validators.choose(&mut get_rng()).unwrap();
2446 Some((*digest, *random_validator))
2447 } else {
2448 None
2449 }
2450 })
2451 .collect::<HashMap<_, _>>();
2452 if digest_to_validator.is_empty() {
2453 panic!(
2454 "Given split brain condition, there should be at \
2455 least one validator that disagrees with local signature"
2456 );
2457 }
2458
2459 let epoch_store = state.load_epoch_store_one_call_per_task();
2460 let committee = epoch_store
2461 .epoch_start_state()
2462 .get_iota_committee_with_network_metadata();
2463 let network_config = default_iota_network_config();
2464 let network_clients =
2465 make_network_authority_clients_with_network_config(&committee, &network_config);
2466
2467 let response_futures = digest_to_validator
2469 .values()
2470 .cloned()
2471 .map(|validator| {
2472 let client = network_clients
2473 .get(&validator)
2474 .expect("Failed to get network client");
2475 let request = CheckpointRequest {
2476 sequence_number: Some(local_summary.sequence_number),
2477 request_content: true,
2478 certified: false,
2479 };
2480 client.get_checkpoint_v2(request)
2481 })
2482 .collect::<Vec<_>>();
2483
2484 let digest_name_pair = digest_to_validator.iter();
2485 let response_data = futures::future::join_all(response_futures)
2486 .await
2487 .into_iter()
2488 .zip(digest_name_pair)
2489 .filter_map(|(response, (digest, name))| match response {
2490 Ok(response) => match response {
2491 CheckpointResponse {
2492 checkpoint: Some(CheckpointSummaryResponse::Pending(summary)),
2493 contents: Some(contents),
2494 } => Some((*name, *digest, summary, contents)),
2495 CheckpointResponse {
2496 checkpoint: Some(CheckpointSummaryResponse::Certified(_)),
2497 contents: _,
2498 } => {
2499 panic!("Expected pending checkpoint, but got certified checkpoint");
2500 }
2501 CheckpointResponse {
2502 checkpoint: None,
2503 contents: _,
2504 } => {
2505 error!(
2506 "Summary for checkpoint {:?} not found on validator {:?}",
2507 local_summary.sequence_number, name
2508 );
2509 None
2510 }
2511 CheckpointResponse {
2512 checkpoint: _,
2513 contents: None,
2514 } => {
2515 error!(
2516 "Contents for checkpoint {:?} not found on validator {:?}",
2517 local_summary.sequence_number, name
2518 );
2519 None
2520 }
2521 },
2522 Err(e) => {
2523 error!(
2524 "Failed to get checkpoint contents from validator for fork diagnostics: {:?}",
2525 e
2526 );
2527 None
2528 }
2529 })
2530 .collect::<Vec<_>>();
2531
2532 let local_checkpoint_contents = tables
2533 .get_checkpoint_contents(&local_summary.contents_digest)
2534 .unwrap_or_else(|_| {
2535 panic!(
2536 "Could not find checkpoint contents for digest {:?}",
2537 local_summary.digest()
2538 )
2539 })
2540 .unwrap_or_else(|| {
2541 panic!(
2542 "Could not find local full checkpoint contents for checkpoint {:?}, digest {:?}",
2543 local_summary.sequence_number,
2544 local_summary.digest()
2545 )
2546 });
2547 let local_contents_text = format!("{local_checkpoint_contents:?}");
2548
2549 let local_summary_text = format!("{local_summary:?}");
2550 let local_validator = state.name.concise();
2551 let diff_patches = response_data
2552 .iter()
2553 .map(|(name, other_digest, other_summary, contents)| {
2554 let other_contents_text = format!("{contents:?}");
2555 let other_summary_text = format!("{other_summary:?}");
2556 let (local_transactions, local_effects): (Vec<_>, Vec<_>) = local_checkpoint_contents
2557 .enumerate_transactions(&local_summary)
2558 .map(|(_, exec_digest)| (exec_digest.transaction, exec_digest.effects))
2559 .unzip();
2560 let (other_transactions, other_effects): (Vec<_>, Vec<_>) = contents
2561 .enumerate_transactions(other_summary)
2562 .map(|(_, exec_digest)| (exec_digest.transaction, exec_digest.effects))
2563 .unzip();
2564 let summary_patch = create_patch(&local_summary_text, &other_summary_text);
2565 let contents_patch = create_patch(&local_contents_text, &other_contents_text);
2566 let local_transactions_text = format!("{local_transactions:#?}");
2567 let other_transactions_text = format!("{other_transactions:#?}");
2568 let transactions_patch =
2569 create_patch(&local_transactions_text, &other_transactions_text);
2570 let local_effects_text = format!("{local_effects:#?}");
2571 let other_effects_text = format!("{other_effects:#?}");
2572 let effects_patch = create_patch(&local_effects_text, &other_effects_text);
2573 let seq_number = local_summary.sequence_number;
2574 let local_digest = local_summary.digest();
2575 let other_validator = name.concise();
2576 format!(
2577 "Checkpoint: {seq_number:?}\n\
2578 Local validator (original): {local_validator:?}, digest: {local_digest}\n\
2579 Other validator (modified): {other_validator:?}, digest: {other_digest}\n\n\
2580 Summary Diff: \n{summary_patch}\n\n\
2581 Contents Diff: \n{contents_patch}\n\n\
2582 Transactions Diff: \n{transactions_patch}\n\n\
2583 Effects Diff: \n{effects_patch}",
2584 )
2585 })
2586 .collect::<Vec<_>>()
2587 .join("\n\n\n");
2588
2589 let header = format!(
2590 "Checkpoint Fork Dump - Authority {local_validator:?}: \n\
2591 Datetime: {time:?}"
2592 );
2593 let fork_logs_text = format!("{header}\n\n{diff_patches}\n\n");
2594 let checkpoint_fork_dir = iota_common::tempdir().keep();
2595 let checkpoint_fork_file_path = checkpoint_fork_dir.join(Path::new("checkpoint_fork_dump.txt"));
2596 let mut file = File::create(checkpoint_fork_file_path).unwrap();
2597 write!(file, "{fork_logs_text}").unwrap();
2598 debug!("{}", fork_logs_text);
2599}
2600
2601pub trait CheckpointServiceNotify {
2602 fn notify_checkpoint_signature(
2603 &self,
2604 epoch_store: &AuthorityPerEpochStore,
2605 info: &CheckpointSignatureMessage,
2606 ) -> IotaResult;
2607
2608 fn notify_checkpoint(&self) -> IotaResult;
2609}
2610
2611enum CheckpointServiceState {
2612 Unstarted(
2613 Box<(
2614 CheckpointBuilder,
2615 CheckpointAggregator,
2616 CheckpointStateHasher,
2617 )>,
2618 ),
2619 Started,
2620}
2621
2622impl CheckpointServiceState {
2623 fn take_unstarted(
2624 &mut self,
2625 ) -> (
2626 CheckpointBuilder,
2627 CheckpointAggregator,
2628 CheckpointStateHasher,
2629 ) {
2630 let mut state = CheckpointServiceState::Started;
2631 std::mem::swap(self, &mut state);
2632
2633 match state {
2634 CheckpointServiceState::Unstarted(tup) => (tup.0, tup.1, tup.2),
2635 CheckpointServiceState::Started => panic!("CheckpointServiceState is already started"),
2636 }
2637 }
2638}
2639
2640pub struct CheckpointService {
2641 tables: Arc<CheckpointStore>,
2642 notify_builder: Arc<Notify>,
2643 notify_aggregator: Arc<Notify>,
2644 last_signature_index: Mutex<u64>,
2645 highest_currently_built_seq_tx: watch::Sender<CheckpointSequenceNumber>,
2647 highest_previously_built_seq: CheckpointSequenceNumber,
2650 metrics: Arc<CheckpointMetrics>,
2651 state: Mutex<CheckpointServiceState>,
2652}
2653
2654impl CheckpointService {
2655 pub fn build(
2659 state: Arc<AuthorityState>,
2660 checkpoint_store: Arc<CheckpointStore>,
2661 epoch_store: Arc<AuthorityPerEpochStore>,
2662 effects_store: Arc<dyn TransactionCacheRead>,
2663 global_state_hasher: Weak<GlobalStateHasher>,
2664 checkpoint_output: Box<dyn CheckpointOutput>,
2665 certified_checkpoint_output: Box<dyn CertifiedCheckpointOutput>,
2666 metrics: Arc<CheckpointMetrics>,
2667 max_transactions_per_checkpoint: usize,
2668 max_checkpoint_size_bytes: usize,
2669 ) -> Arc<Self> {
2670 info!(
2671 "Starting checkpoint service with {max_transactions_per_checkpoint} max_transactions_per_checkpoint and {max_checkpoint_size_bytes} max_checkpoint_size_bytes"
2672 );
2673 let notify_builder = Arc::new(Notify::new());
2674 let notify_aggregator = Arc::new(Notify::new());
2675
2676 let highest_previously_built_seq = checkpoint_store
2678 .get_latest_locally_computed_checkpoint()
2679 .expect("failed to get latest locally computed checkpoint")
2680 .map(|s| s.sequence_number)
2681 .unwrap_or(0);
2682
2683 let highest_currently_built_seq =
2684 CheckpointBuilder::load_last_built_checkpoint_summary(&epoch_store, &checkpoint_store)
2685 .expect("epoch should not have ended")
2686 .map(|(seq, _)| seq)
2687 .unwrap_or(0);
2688
2689 let (highest_currently_built_seq_tx, _) = watch::channel(highest_currently_built_seq);
2690
2691 let aggregator = CheckpointAggregator::new(
2692 checkpoint_store.clone(),
2693 epoch_store.clone(),
2694 notify_aggregator.clone(),
2695 certified_checkpoint_output,
2696 state.clone(),
2697 metrics.clone(),
2698 );
2699
2700 let (send_to_hasher, receive_from_builder) = mpsc::channel(16);
2701
2702 let ckpt_state_hasher = CheckpointStateHasher::new(
2703 epoch_store.clone(),
2704 global_state_hasher.clone(),
2705 receive_from_builder,
2706 );
2707
2708 let builder = CheckpointBuilder::new(
2709 state,
2710 checkpoint_store.clone(),
2711 epoch_store.clone(),
2712 notify_builder.clone(),
2713 effects_store,
2714 global_state_hasher,
2715 send_to_hasher,
2716 checkpoint_output,
2717 notify_aggregator.clone(),
2718 highest_currently_built_seq_tx.clone(),
2719 metrics.clone(),
2720 max_transactions_per_checkpoint,
2721 max_checkpoint_size_bytes,
2722 );
2723
2724 let last_signature_index = epoch_store
2725 .get_last_checkpoint_signature_index()
2726 .expect("should not cross end of epoch");
2727 let last_signature_index = Mutex::new(last_signature_index);
2728
2729 Arc::new(Self {
2730 tables: checkpoint_store,
2731 notify_builder,
2732 notify_aggregator,
2733 last_signature_index,
2734 highest_currently_built_seq_tx,
2735 highest_previously_built_seq,
2736 metrics,
2737 state: Mutex::new(CheckpointServiceState::Unstarted(Box::new((
2738 builder,
2739 aggregator,
2740 ckpt_state_hasher,
2741 )))),
2742 })
2743 }
2744
2745 pub async fn spawn(&self, consensus_replay_waiter: Option<ReplayWaiter>) -> JoinSet<()> {
2754 let mut tasks = JoinSet::new();
2755
2756 let (builder, aggregator, state_hasher) = self.state.lock().take_unstarted();
2757 tasks.spawn(monitored_future!(builder.run(consensus_replay_waiter)));
2758 tasks.spawn(monitored_future!(aggregator.run()));
2759 tasks.spawn(monitored_future!(state_hasher.run()));
2760
2761 if tokio::time::timeout(
2767 Duration::from_secs(120),
2768 self.wait_for_rebuilt_checkpoints(),
2769 )
2770 .await
2771 .is_err()
2772 {
2773 debug_fatal!("Timed out waiting for checkpoints to be rebuilt");
2774 }
2775
2776 tasks
2777 }
2778}
2779
2780impl CheckpointService {
2781 pub async fn wait_for_rebuilt_checkpoints(&self) {
2788 let highest_previously_built_seq = self.highest_previously_built_seq;
2789 let mut rx = self.highest_currently_built_seq_tx.subscribe();
2790 let mut highest_currently_built_seq = *rx.borrow_and_update();
2791 info!(
2792 "Waiting for checkpoints to be rebuilt, previously built seq: \
2793 {highest_previously_built_seq}, currently built seq: {highest_currently_built_seq}"
2794 );
2795 loop {
2796 if highest_currently_built_seq >= highest_previously_built_seq {
2797 info!("Checkpoint rebuild complete");
2798 break;
2799 }
2800 rx.changed().await.unwrap();
2801 highest_currently_built_seq = *rx.borrow_and_update();
2802 }
2803 }
2804
2805 #[cfg(test)]
2806 fn write_and_notify_checkpoint_for_testing(
2807 &self,
2808 epoch_store: &AuthorityPerEpochStore,
2809 checkpoint: PendingCheckpoint,
2810 ) -> IotaResult {
2811 use crate::authority::authority_per_epoch_store::consensus_quarantine::ConsensusCommitOutput;
2812
2813 let mut output = ConsensusCommitOutput::new(0);
2814 epoch_store.write_pending_checkpoint(&mut output, &checkpoint)?;
2815 output.set_default_commit_stats_for_testing();
2816 epoch_store.push_consensus_output_for_tests(output);
2817 self.notify_checkpoint()?;
2818 Ok(())
2819 }
2820}
2821
2822impl CheckpointServiceNotify for CheckpointService {
2823 fn notify_checkpoint_signature(
2824 &self,
2825 epoch_store: &AuthorityPerEpochStore,
2826 info: &CheckpointSignatureMessage,
2827 ) -> IotaResult {
2828 let sequence = info.summary.sequence_number;
2829 let signer = info.summary.auth_sig().authority.concise();
2830
2831 if let Some(highest_verified_checkpoint) = self
2832 .tables
2833 .get_highest_verified_checkpoint()?
2834 .map(|x| x.sequence_number())
2835 {
2836 if sequence <= highest_verified_checkpoint {
2837 trace!(
2838 checkpoint_seq = sequence,
2839 "Ignore checkpoint signature from {} - already certified", signer,
2840 );
2841 self.metrics
2842 .last_ignored_checkpoint_signature_received
2843 .set(sequence as i64);
2844 return Ok(());
2845 }
2846 }
2847 trace!(
2848 checkpoint_seq = sequence,
2849 "Received checkpoint signature, digest {} from {}",
2850 info.summary.digest(),
2851 signer,
2852 );
2853 self.metrics
2854 .last_received_checkpoint_signatures
2855 .with_label_values(&[&signer.to_string()])
2856 .set(sequence as i64);
2857 let mut index = self.last_signature_index.lock();
2861 *index += 1;
2862 epoch_store.insert_checkpoint_signature(sequence, *index, info)?;
2863 self.notify_aggregator.notify_one();
2864 Ok(())
2865 }
2866
2867 fn notify_checkpoint(&self) -> IotaResult {
2868 self.notify_builder.notify_one();
2869 Ok(())
2870 }
2871}
2872
2873#[iota_macros::with_checked_arithmetic]
2874mod checked {
2875 use iota_sdk_types::GasCostSummary;
2876 use iota_types::effects::{TransactionEffects, TransactionEffectsAPI};
2877 use itertools::MultiUnzip;
2878
2879 #[expect(clippy::type_complexity)]
2880 pub fn new_gas_cost_summary_from_txn_effects<'a>(
2881 transactions: impl Iterator<Item = &'a TransactionEffects>,
2882 ) -> GasCostSummary {
2883 let (
2884 storage_costs,
2885 computation_costs,
2886 computation_costs_burned,
2887 storage_rebates,
2888 non_refundable_storage_fee,
2889 ): (Vec<u64>, Vec<u64>, Vec<u64>, Vec<u64>, Vec<u64>) = transactions
2890 .map(|e| {
2891 (
2892 e.gas_cost_summary().storage_cost,
2893 e.gas_cost_summary().computation_cost,
2894 e.gas_cost_summary().computation_cost_burned,
2895 e.gas_cost_summary().storage_rebate,
2896 e.gas_cost_summary().non_refundable_storage_fee,
2897 )
2898 })
2899 .multiunzip();
2900
2901 GasCostSummary::new(
2902 computation_costs.iter().sum(),
2903 computation_costs_burned.iter().sum(),
2904 storage_costs.iter().sum(),
2905 storage_rebates.iter().sum(),
2906 non_refundable_storage_fee.iter().sum(),
2907 )
2908 }
2909}
2910pub struct CheckpointServiceNoop {}
2912impl CheckpointServiceNotify for CheckpointServiceNoop {
2913 fn notify_checkpoint_signature(
2914 &self,
2915 _: &AuthorityPerEpochStore,
2916 _: &CheckpointSignatureMessage,
2917 ) -> IotaResult {
2918 Ok(())
2919 }
2920
2921 fn notify_checkpoint(&self) -> IotaResult {
2922 Ok(())
2923 }
2924}
2925
2926pin_project! {
2927 pub struct PollCounter<Fut> {
2928 #[pin]
2929 future: Fut,
2930 count: usize,
2931 }
2932}
2933
2934impl<Fut> PollCounter<Fut> {
2935 pub fn new(future: Fut) -> Self {
2936 Self { future, count: 0 }
2937 }
2938
2939 pub fn count(&self) -> usize {
2940 self.count
2941 }
2942}
2943
2944impl<Fut: Future> Future for PollCounter<Fut> {
2945 type Output = (usize, Fut::Output);
2946
2947 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
2948 let this = self.project();
2949 *this.count += 1;
2950 match this.future.poll(cx) {
2951 Poll::Ready(output) => Poll::Ready((*this.count, output)),
2952 Poll::Pending => Poll::Pending,
2953 }
2954 }
2955}
2956
2957fn poll_count<Fut>(future: Fut) -> PollCounter<Fut> {
2958 PollCounter::new(future)
2959}
2960
2961#[cfg(test)]
2965pub(crate) fn test_checkpoint_with_contents(
2966 sequence_number: CheckpointSequenceNumber,
2967 full_contents: &FullCheckpointContents,
2968) -> VerifiedCheckpoint {
2969 let contents = full_contents.checkpoint_contents();
2970 let summary = CheckpointSummary {
2971 epoch: 0,
2972 sequence_number,
2973 network_total_transactions: full_contents.size() as u64,
2974 contents_digest: contents.digest(),
2975 previous_digest: None,
2976 epoch_rolling_gas_cost_summary: GasCostSummary::default(),
2977 end_of_epoch_data: None,
2978 timestamp_ms: 0,
2979 version_specific_data: Vec::new(),
2980 checkpoint_commitments: Vec::new(),
2981 };
2982 let sig = AuthorityStrongQuorumSignInfo {
2983 epoch: 0,
2984 signature: Default::default(),
2985 signers_map: Default::default(),
2986 };
2987 VerifiedCheckpoint::new_unchecked(
2988 iota_types::message_envelope::Envelope::new_from_data_and_sig(summary, sig),
2989 )
2990}
2991
2992#[cfg(test)]
2993mod tests {
2994 use std::{
2995 collections::{BTreeMap, HashMap},
2996 ops::Deref,
2997 };
2998
2999 use futures::{FutureExt as _, future::BoxFuture};
3000 use iota_macros::sim_test;
3001 use iota_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
3002 use iota_sdk_types::{
3003 GenesisObject, Identifier, ObjectData, ObjectId, Owner, TransactionEffectsDigest, Version,
3004 move_package::MovePackage,
3005 };
3006 use iota_types::{
3007 effects::{
3008 TransactionEffects, TransactionEffectsAPIForTesting, TransactionEffectsExtForTesting,
3009 TransactionEvents,
3010 },
3011 messages_checkpoint::SignedCheckpointSummary,
3012 transaction::VerifiedTransaction,
3013 };
3014 use tokio::sync::mpsc;
3015
3016 use super::*;
3017 use crate::authority::test_authority_builder::TestAuthorityBuilder;
3018
3019 #[tokio::test]
3020 async fn insert_verified_checkpoint_contents_persists_digests_and_caches_full_contents() {
3021 let tempdir = iota_common::tempdir();
3022 let path = tempdir.path();
3023
3024 let full_contents = FullCheckpointContents::random_for_testing();
3025 let checkpoint = test_checkpoint_with_contents(0, &full_contents);
3026 let contents_digest = checkpoint.contents_digest;
3027
3028 {
3029 let store = CheckpointStore::new(path);
3030 store
3031 .insert_verified_checkpoint_contents(
3032 &checkpoint,
3033 VerifiedCheckpointContents::new_unchecked(full_contents.clone()),
3034 )
3035 .unwrap();
3036
3037 assert_eq!(
3039 store
3040 .get_full_checkpoint_contents_by_sequence_number(0)
3041 .unwrap()
3042 .as_ref(),
3043 &full_contents
3044 );
3045 assert_eq!(
3046 store
3047 .get_full_checkpoint_contents_by_digest(&contents_digest)
3048 .unwrap()
3049 .as_ref(),
3050 &full_contents
3051 );
3052 assert_eq!(
3054 store
3055 .get_checkpoint_contents(&contents_digest)
3056 .unwrap()
3057 .map(|c| c.digest()),
3058 Some(contents_digest)
3059 );
3060 }
3061
3062 let store = CheckpointStore::new(path);
3065 assert!(
3066 store
3067 .get_full_checkpoint_contents_by_sequence_number(0)
3068 .is_none()
3069 );
3070 assert!(
3071 store
3072 .get_full_checkpoint_contents_by_digest(&contents_digest)
3073 .is_none()
3074 );
3075 assert!(
3076 store
3077 .get_checkpoint_contents(&contents_digest)
3078 .unwrap()
3079 .is_some()
3080 );
3081 }
3082
3083 #[tokio::test]
3084 async fn cache_full_checkpoint_contents_serves_reads_without_disk_writes() {
3085 let store = CheckpointStore::new_for_tests();
3086 let full_contents = FullCheckpointContents::random_for_testing();
3087 let checkpoint = test_checkpoint_with_contents(0, &full_contents);
3088 let contents_digest = checkpoint.contents_digest;
3089
3090 store.cache_full_checkpoint_contents(
3091 checkpoint.sequence_number(),
3092 contents_digest,
3093 full_contents.clone(),
3094 );
3095
3096 assert_eq!(
3097 store
3098 .get_full_checkpoint_contents_by_sequence_number(0)
3099 .unwrap()
3100 .as_ref(),
3101 &full_contents
3102 );
3103 assert_eq!(
3104 store
3105 .get_full_checkpoint_contents_by_digest(&contents_digest)
3106 .unwrap()
3107 .as_ref(),
3108 &full_contents
3109 );
3110 assert!(
3112 store
3113 .get_checkpoint_contents(&contents_digest)
3114 .unwrap()
3115 .is_none()
3116 );
3117 }
3118
3119 #[tokio::test]
3124 async fn builder_caches_full_contents_only_after_durable_contents_write() {
3125 let state = TestAuthorityBuilder::new().build().await;
3126
3127 let tx = VerifiedTransaction::new_genesis_transaction(vec![], vec![]);
3128 let digest = *tx.digest();
3129 state
3130 .database_for_testing()
3131 .perpetual_tables
3132 .transactions
3133 .insert(&digest, tx.serializable_ref())
3134 .unwrap();
3135
3136 let mut effects_map = HashMap::new();
3137 commit_cert_for_test(
3138 &mut effects_map,
3139 state.clone(),
3140 digest,
3141 vec![],
3142 GasCostSummary::new(1, 1, 1, 1, 1),
3143 );
3144 let effects = effects_map[&digest].clone();
3145
3146 let signature = iota_types::crypto::zero_ed25519_signature().into();
3147 state
3148 .epoch_store_for_testing()
3149 .test_insert_user_signature(digest, vec![signature]);
3150
3151 let (output, _result) = mpsc::channel::<(CheckpointContents, CheckpointSummary)>(10);
3152 let (certified_output, _certified_result) = mpsc::channel::<CertifiedCheckpointSummary>(10);
3153
3154 let tmp_dir = iota_common::tempdir();
3155 let checkpoint_store = CheckpointStore::new(tmp_dir.path());
3156 let epoch_store = state.epoch_store_for_testing();
3157
3158 let global_state_hasher = Arc::new(GlobalStateHasher::new_for_tests(
3159 state.get_global_state_hash_store().clone(),
3160 ));
3161
3162 let checkpoint_service = CheckpointService::build(
3163 state.clone(),
3164 checkpoint_store.clone(),
3165 epoch_store.clone(),
3166 Arc::new(effects_map),
3167 Arc::downgrade(&global_state_hasher),
3168 Box::new(output),
3169 Box::new(certified_output),
3170 CheckpointMetrics::new_for_tests(),
3171 3,
3172 100_000,
3173 );
3174 let (builder, _aggregator, _hasher) = checkpoint_service.state.lock().take_unstarted();
3178
3179 checkpoint_service
3180 .write_and_notify_checkpoint_for_testing(&epoch_store, p(0, vec![digest], 0))
3181 .unwrap();
3182
3183 let details = PendingCheckpointInfo {
3184 timestamp_ms: 0,
3185 last_of_epoch: false,
3186 checkpoint_height: 0,
3187 };
3188 let new_checkpoints = builder
3189 .create_checkpoints(vec![effects], &details, &HashSet::from([digest]))
3190 .await
3191 .unwrap();
3192 let summary = new_checkpoints.first().summary.clone();
3193
3194 assert!(
3195 checkpoint_store
3196 .get_full_checkpoint_contents_by_sequence_number(summary.sequence_number)
3197 .is_none(),
3198 "full contents must not be served before the checkpoint_content row is durable"
3199 );
3200 assert!(
3201 checkpoint_store
3202 .get_checkpoint_contents(&summary.contents_digest)
3203 .unwrap()
3204 .is_none()
3205 );
3206
3207 builder
3208 .write_checkpoints(details.checkpoint_height, new_checkpoints)
3209 .await
3210 .unwrap();
3211
3212 assert!(
3213 checkpoint_store
3214 .get_checkpoint_contents(&summary.contents_digest)
3215 .unwrap()
3216 .is_some()
3217 );
3218 let cached = checkpoint_store
3219 .get_full_checkpoint_contents_by_sequence_number(summary.sequence_number)
3220 .expect("builder should cache full contents once the row is durable");
3221 assert_eq!(
3222 cached.checkpoint_contents().digest(),
3223 summary.contents_digest
3224 );
3225 }
3226
3227 #[sim_test]
3228 pub async fn checkpoint_builder_test() {
3229 telemetry_subscribers::init_for_testing();
3230
3231 let mut protocol_config =
3232 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
3233 protocol_config.set_min_checkpoint_interval_ms_for_testing(100);
3234 protocol_config.disable_checkpoint_rate_window_size_for_testing();
3236 let state = TestAuthorityBuilder::new()
3237 .with_protocol_config(protocol_config)
3238 .build()
3239 .await;
3240
3241 let make_tx = |seed: u8, payload_size: usize| {
3248 let mut id = [0u8; 32];
3249 id[0] = seed;
3250 VerifiedTransaction::new_genesis_transaction(
3251 vec![GenesisObject::new(
3252 ObjectData::Package(
3253 MovePackage::new(
3254 ObjectId::new(id),
3255 Version::default(),
3256 BTreeMap::from([(
3257 Identifier::new_unchecked("m"),
3258 vec![0u8; payload_size],
3259 )]),
3260 100_000,
3261 Vec::new(),
3264 BTreeMap::new(),
3267 )
3268 .unwrap(),
3269 ),
3270 Owner::Immutable,
3271 )],
3272 vec![],
3273 )
3274 };
3275
3276 let mut small: Vec<_> = (0..15).map(|seed| make_tx(seed, 1)).collect();
3277 small.sort_by_key(|tx| *tx.digest());
3278
3279 let mut large: Vec<_> = (0..5).map(|seed| make_tx(100 + seed, 40000)).collect();
3280 large.sort_by_key(|tx| *tx.digest());
3281
3282 let txns: Vec<_> = small.into_iter().chain(large).collect();
3283 let digests: Vec<TransactionDigest> = txns.iter().map(|tx| *tx.digest()).collect();
3284
3285 let d = |i: u8| digests[i as usize];
3288
3289 for (tx, digest) in txns.iter().zip(&digests) {
3290 state
3291 .database_for_testing()
3292 .perpetual_tables
3293 .transactions
3294 .insert(digest, tx.serializable_ref())
3295 .unwrap();
3296 }
3297
3298 let mut store = HashMap::<TransactionDigest, TransactionEffects>::new();
3299 commit_cert_for_test(
3300 &mut store,
3301 state.clone(),
3302 d(1),
3303 vec![d(2), d(3)],
3304 GasCostSummary::new(11, 11, 12, 11, 1),
3305 );
3306 commit_cert_for_test(
3307 &mut store,
3308 state.clone(),
3309 d(2),
3310 vec![d(3), d(4)],
3311 GasCostSummary::new(21, 21, 22, 21, 1),
3312 );
3313 commit_cert_for_test(
3314 &mut store,
3315 state.clone(),
3316 d(3),
3317 vec![],
3318 GasCostSummary::new(31, 31, 32, 31, 1),
3319 );
3320 commit_cert_for_test(
3321 &mut store,
3322 state.clone(),
3323 d(4),
3324 vec![],
3325 GasCostSummary::new(41, 41, 42, 41, 1),
3326 );
3327 for i in [5, 6, 7, 10, 11, 12, 13] {
3328 commit_cert_for_test(
3329 &mut store,
3330 state.clone(),
3331 d(i),
3332 vec![],
3333 GasCostSummary::new(41, 41, 42, 41, 1),
3334 );
3335 }
3336 for i in [15, 16, 17] {
3337 commit_cert_for_test(
3338 &mut store,
3339 state.clone(),
3340 d(i),
3341 vec![],
3342 GasCostSummary::new(51, 51, 52, 51, 1),
3343 );
3344 }
3345 let all_digests: Vec<_> = store.keys().copied().collect();
3346 for digest in all_digests {
3347 let signature = iota_types::crypto::zero_ed25519_signature().into();
3348 state
3349 .epoch_store_for_testing()
3350 .test_insert_user_signature(digest, vec![signature]);
3351 }
3352
3353 let (output, mut result) = mpsc::channel::<(CheckpointContents, CheckpointSummary)>(10);
3354 let (certified_output, mut certified_result) =
3355 mpsc::channel::<CertifiedCheckpointSummary>(10);
3356 let store = Arc::new(store);
3357
3358 let tmp_dir = iota_common::tempdir();
3359 let checkpoint_store = CheckpointStore::new(tmp_dir.path());
3360 let epoch_store = state.epoch_store_for_testing();
3361
3362 let global_state_hasher = Arc::new(GlobalStateHasher::new_for_tests(
3363 state.get_global_state_hash_store().clone(),
3364 ));
3365
3366 let checkpoint_service = CheckpointService::build(
3367 state.clone(),
3368 checkpoint_store.clone(),
3369 epoch_store.clone(),
3370 store,
3371 Arc::downgrade(&global_state_hasher),
3372 Box::new(output),
3373 Box::new(certified_output),
3374 CheckpointMetrics::new_for_tests(),
3375 3,
3376 100_000,
3377 );
3378 let _tasks = checkpoint_service.spawn(None).await;
3379
3380 checkpoint_service
3381 .write_and_notify_checkpoint_for_testing(&epoch_store, p(0, vec![d(4)], 0))
3382 .unwrap();
3383 checkpoint_service
3384 .write_and_notify_checkpoint_for_testing(&epoch_store, p(1, vec![d(1), d(3)], 2000))
3385 .unwrap();
3386 checkpoint_service
3387 .write_and_notify_checkpoint_for_testing(
3388 &epoch_store,
3389 p(2, vec![d(10), d(11), d(12), d(13)], 3000),
3390 )
3391 .unwrap();
3392 checkpoint_service
3393 .write_and_notify_checkpoint_for_testing(
3394 &epoch_store,
3395 p(3, vec![d(15), d(16), d(17)], 4000),
3396 )
3397 .unwrap();
3398 checkpoint_service
3399 .write_and_notify_checkpoint_for_testing(&epoch_store, p(4, vec![d(5)], 4001))
3400 .unwrap();
3401 checkpoint_service
3402 .write_and_notify_checkpoint_for_testing(&epoch_store, p(5, vec![d(6)], 5000))
3403 .unwrap();
3404
3405 let (c1c, c1s) = result.recv().await.unwrap();
3406 let (c2c, c2s) = result.recv().await.unwrap();
3407
3408 let c1t = c1c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3409 let c2t = c2c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3410 assert_eq!(c1t, vec![d(4)]);
3411 assert_eq!(c1s.previous_digest, None);
3412 assert_eq!(c1s.sequence_number, 0);
3413 assert_eq!(
3414 c1s.epoch_rolling_gas_cost_summary,
3415 GasCostSummary::new(41, 41, 42, 41, 1)
3416 );
3417
3418 assert_eq!(c2t, vec![d(3), d(2), d(1)]);
3419 assert_eq!(c2s.previous_digest, Some(c1s.digest()));
3420 assert_eq!(c2s.sequence_number, 1);
3421 assert_eq!(
3422 c2s.epoch_rolling_gas_cost_summary,
3423 GasCostSummary::new(104, 104, 108, 104, 4)
3424 );
3425
3426 for (summary, digest_contents) in [(&c1s, &c1c), (&c2s, &c2c)] {
3430 let cached = checkpoint_store
3431 .get_full_checkpoint_contents_by_sequence_number(summary.sequence_number)
3432 .expect("builder should cache full contents of a locally built checkpoint");
3433 assert_eq!(&cached.checkpoint_contents(), digest_contents);
3434 assert_eq!(
3435 cached.checkpoint_contents().digest(),
3436 summary.contents_digest
3437 );
3438 assert!(
3442 checkpoint_store
3443 .get_checkpoint_contents(&summary.contents_digest)
3444 .unwrap()
3445 .is_some()
3446 );
3447 }
3448
3449 let (c3c, c3s) = result.recv().await.unwrap();
3452 let c3t = c3c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3453 let (c4c, c4s) = result.recv().await.unwrap();
3454 let c4t = c4c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3455 assert_eq!(c3s.sequence_number, 2);
3456 assert_eq!(c3s.previous_digest, Some(c2s.digest()));
3457 assert_eq!(c4s.sequence_number, 3);
3458 assert_eq!(c4s.previous_digest, Some(c3s.digest()));
3459 assert_eq!(c3t, vec![d(10), d(11), d(12)]);
3460 assert_eq!(c4t, vec![d(13)]);
3461
3462 let (c5c, c5s) = result.recv().await.unwrap();
3465 let c5t = c5c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3466 let (c6c, c6s) = result.recv().await.unwrap();
3467 let c6t = c6c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3468 assert_eq!(c5s.sequence_number, 4);
3469 assert_eq!(c5s.previous_digest, Some(c4s.digest()));
3470 assert_eq!(c6s.sequence_number, 5);
3471 assert_eq!(c6s.previous_digest, Some(c5s.digest()));
3472 assert_eq!(c5t, vec![d(15), d(16)]);
3473 assert_eq!(c6t, vec![d(17)]);
3474
3475 let (c7c, c7s) = result.recv().await.unwrap();
3478 let c7t = c7c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3479 assert_eq!(c7t, vec![d(5), d(6)]);
3480 assert_eq!(c7s.previous_digest, Some(c6s.digest()));
3481 assert_eq!(c7s.sequence_number, 6);
3482
3483 let c1ss = SignedCheckpointSummary::new(c1s.epoch, c1s, state.secret.deref(), state.name);
3484 let c2ss = SignedCheckpointSummary::new(c2s.epoch, c2s, state.secret.deref(), state.name);
3485
3486 checkpoint_service
3487 .notify_checkpoint_signature(
3488 &epoch_store,
3489 &CheckpointSignatureMessage { summary: c2ss },
3490 )
3491 .unwrap();
3492 checkpoint_service
3493 .notify_checkpoint_signature(
3494 &epoch_store,
3495 &CheckpointSignatureMessage { summary: c1ss },
3496 )
3497 .unwrap();
3498
3499 let c1sc = certified_result.recv().await.unwrap();
3500 let c2sc = certified_result.recv().await.unwrap();
3501 assert_eq!(c1sc.sequence_number, 0);
3502 assert_eq!(c2sc.sequence_number, 1);
3503 }
3504
3505 #[sim_test]
3506 pub async fn checkpoint_builder_windowed_interval_test() {
3507 telemetry_subscribers::init_for_testing();
3508
3509 let mut protocol_config =
3515 ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
3516 protocol_config.set_min_checkpoint_interval_ms_for_testing(100);
3517 protocol_config.set_checkpoint_rate_window_size_for_testing(3);
3518 let state = TestAuthorityBuilder::new()
3519 .with_protocol_config(protocol_config)
3520 .build()
3521 .await;
3522
3523 let make_tx = |seed: u8| {
3527 let mut id = [0u8; 32];
3528 id[0] = seed;
3529 VerifiedTransaction::new_genesis_transaction(
3530 vec![GenesisObject::new(
3531 ObjectData::Package(
3532 MovePackage::new(
3533 ObjectId::new(id),
3534 Version::default(),
3535 BTreeMap::from([(Identifier::new_unchecked("m"), vec![0u8; 1])]),
3536 100_000,
3537 Vec::new(),
3540 BTreeMap::new(),
3543 )
3544 .unwrap(),
3545 ),
3546 Owner::Immutable,
3547 )],
3548 vec![],
3549 )
3550 };
3551 let txns: Vec<_> = (1..=8).map(make_tx).collect();
3552 let digests: Vec<TransactionDigest> = txns.iter().map(|tx| *tx.digest()).collect();
3553 let d = |i: u8| digests[(i - 1) as usize];
3555
3556 for tx in &txns {
3557 state
3558 .database_for_testing()
3559 .perpetual_tables
3560 .transactions
3561 .insert(tx.digest(), tx.serializable_ref())
3562 .unwrap();
3563 }
3564
3565 let mut store = HashMap::<TransactionDigest, TransactionEffects>::new();
3566 for i in 1..=8 {
3567 commit_cert_for_test(
3568 &mut store,
3569 state.clone(),
3570 d(i),
3571 vec![],
3572 GasCostSummary::new(11, 11, 12, 11, 1),
3573 );
3574 }
3575 let all_digests: Vec<_> = store.keys().copied().collect();
3576 for digest in all_digests {
3577 let signature = iota_types::crypto::zero_ed25519_signature().into();
3578 state
3579 .epoch_store_for_testing()
3580 .test_insert_user_signature(digest, vec![signature]);
3581 }
3582
3583 let (output, mut result) = mpsc::channel::<(CheckpointContents, CheckpointSummary)>(10);
3584 let (certified_output, _certified_result) = mpsc::channel::<CertifiedCheckpointSummary>(10);
3585 let store = Arc::new(store);
3586
3587 let tmp_dir = iota_common::tempdir();
3588 let checkpoint_store = CheckpointStore::new(tmp_dir.path());
3589 let epoch_store = state.epoch_store_for_testing();
3590
3591 let global_state_hasher = Arc::new(GlobalStateHasher::new_for_tests(
3592 state.get_global_state_hash_store().clone(),
3593 ));
3594
3595 let checkpoint_service = CheckpointService::build(
3596 state.clone(),
3597 checkpoint_store,
3598 epoch_store.clone(),
3599 store,
3600 Arc::downgrade(&global_state_hasher),
3601 Box::new(output),
3602 Box::new(certified_output),
3603 CheckpointMetrics::new_for_tests(),
3604 3,
3605 100_000,
3606 );
3607 let _tasks = checkpoint_service.spawn(None).await;
3608
3609 for (height, root, timestamp_ms) in [(0, 1u8, 0), (1, 2, 1000), (2, 3, 2000)] {
3614 checkpoint_service
3615 .write_and_notify_checkpoint_for_testing(
3616 &epoch_store,
3617 p(height, vec![d(root)], timestamp_ms),
3618 )
3619 .unwrap();
3620 }
3621 checkpoint_service
3625 .write_and_notify_checkpoint_for_testing(&epoch_store, p(3, vec![d(4)], 2010))
3626 .unwrap();
3627 checkpoint_service
3630 .write_and_notify_checkpoint_for_testing(&epoch_store, p(4, vec![d(5)], 2020))
3631 .unwrap();
3632 checkpoint_service
3636 .write_and_notify_checkpoint_for_testing(&epoch_store, p(5, vec![d(6)], 2030))
3637 .unwrap();
3638 checkpoint_service
3642 .write_and_notify_checkpoint_for_testing(&epoch_store, p(6, vec![d(7)], 2130))
3643 .unwrap();
3644 checkpoint_service
3647 .write_and_notify_checkpoint_for_testing(&epoch_store, p(7, vec![d(8)], 2300))
3648 .unwrap();
3649
3650 let mut built = Vec::new();
3651 for _ in 0..7 {
3652 let (contents, summary) = result.recv().await.unwrap();
3653 built.push((summary.sequence_number, contents.iter().count()));
3654 }
3655
3656 let sequence_numbers: Vec<_> = built.iter().map(|(seq, _)| *seq).collect();
3658 assert_eq!(sequence_numbers, vec![0, 1, 2, 3, 4, 5, 6]);
3659 let sizes: Vec<_> = built.iter().map(|(_, size)| *size).collect();
3666 assert_eq!(sizes, vec![1, 1, 1, 1, 1, 2, 1]);
3667 }
3668
3669 impl TransactionCacheRead for HashMap<TransactionDigest, TransactionEffects> {
3670 fn try_notify_read_executed_effects(
3671 &self,
3672 _: &str,
3673 digests: &[TransactionDigest],
3674 ) -> BoxFuture<'_, IotaResult<Vec<TransactionEffects>>> {
3675 std::future::ready(Ok(digests
3676 .iter()
3677 .map(|d| self.get(d).expect("effects not found").clone())
3678 .collect()))
3679 .boxed()
3680 }
3681
3682 fn try_notify_read_executed_effects_digests(
3683 &self,
3684 _: &str,
3685 digests: &[TransactionDigest],
3686 ) -> BoxFuture<'_, IotaResult<Vec<TransactionEffectsDigest>>> {
3687 std::future::ready(Ok(digests
3688 .iter()
3689 .map(|d| {
3690 self.get(d)
3691 .map(|fx| fx.digest())
3692 .expect("effects not found")
3693 })
3694 .collect()))
3695 .boxed()
3696 }
3697
3698 fn try_multi_get_executed_effects(
3699 &self,
3700 digests: &[TransactionDigest],
3701 ) -> IotaResult<Vec<Option<TransactionEffects>>> {
3702 Ok(digests.iter().map(|d| self.get(d).cloned()).collect())
3703 }
3704
3705 fn try_multi_get_transaction_blocks(
3712 &self,
3713 _: &[TransactionDigest],
3714 ) -> IotaResult<Vec<Option<Arc<VerifiedTransaction>>>> {
3715 unimplemented!()
3716 }
3717
3718 fn try_multi_get_executed_effects_digests(
3719 &self,
3720 _: &[TransactionDigest],
3721 ) -> IotaResult<Vec<Option<TransactionEffectsDigest>>> {
3722 unimplemented!()
3723 }
3724
3725 fn try_multi_get_effects(
3726 &self,
3727 _: &[TransactionEffectsDigest],
3728 ) -> IotaResult<Vec<Option<TransactionEffects>>> {
3729 unimplemented!()
3730 }
3731
3732 fn try_multi_get_events(
3733 &self,
3734 _: &[TransactionDigest],
3735 ) -> IotaResult<Vec<Option<TransactionEvents>>> {
3736 unimplemented!()
3737 }
3738 }
3739
3740 #[async_trait::async_trait]
3741 impl CheckpointOutput for mpsc::Sender<(CheckpointContents, CheckpointSummary)> {
3742 async fn checkpoint_created(
3743 &self,
3744 summary: &CheckpointSummary,
3745 contents: &CheckpointContents,
3746 _epoch_store: &Arc<AuthorityPerEpochStore>,
3747 _checkpoint_store: &Arc<CheckpointStore>,
3748 ) -> IotaResult {
3749 self.try_send((contents.clone(), summary.clone())).unwrap();
3750 Ok(())
3751 }
3752 }
3753
3754 #[async_trait::async_trait]
3755 impl CertifiedCheckpointOutput for mpsc::Sender<CertifiedCheckpointSummary> {
3756 async fn certified_checkpoint_created(
3757 &self,
3758 summary: &CertifiedCheckpointSummary,
3759 ) -> IotaResult {
3760 self.try_send(summary.clone()).unwrap();
3761 Ok(())
3762 }
3763 }
3764
3765 fn p(i: u64, roots: Vec<TransactionDigest>, timestamp_ms: u64) -> PendingCheckpoint {
3766 PendingCheckpoint::V1(PendingCheckpointContentsV1 {
3767 roots: roots.into_iter().map(TransactionKey::Digest).collect(),
3768 details: PendingCheckpointInfo {
3769 timestamp_ms,
3770 last_of_epoch: false,
3771 checkpoint_height: i,
3772 },
3773 })
3774 }
3775
3776 fn e(
3777 transaction_digest: TransactionDigest,
3778 dependencies: Vec<TransactionDigest>,
3779 gas_cost_summary: GasCostSummary,
3780 ) -> TransactionEffects {
3781 let mut effects = TransactionEffects::new_empty_v1_for_testing(transaction_digest);
3782 *effects.dependencies_mut_for_testing() = dependencies;
3783 *effects.gas_cost_summary_mut_for_testing() = gas_cost_summary;
3784 effects
3785 }
3786
3787 fn commit_cert_for_test(
3788 store: &mut HashMap<TransactionDigest, TransactionEffects>,
3789 state: Arc<AuthorityState>,
3790 digest: TransactionDigest,
3791 dependencies: Vec<TransactionDigest>,
3792 gas_cost_summary: GasCostSummary,
3793 ) {
3794 let epoch_store = state.epoch_store_for_testing();
3795 let effects = e(digest, dependencies, gas_cost_summary);
3796 store.insert(digest, effects);
3797 epoch_store
3798 .insert_tx_key_and_digest(&TransactionKey::Digest(digest), &digest)
3799 .expect("Inserting cert fx and sigs should not fail");
3800 }
3801}