sui_core/checkpoints/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4pub(crate) mod causal_order;
5pub mod checkpoint_executor;
6mod checkpoint_output;
7mod metrics;
8
9use crate::accumulators::{self, AccumulatorSettlementTxBuilder};
10use crate::authority::AuthorityState;
11use crate::authority_client::{AuthorityAPI, make_network_authority_clients_with_network_config};
12use crate::checkpoints::causal_order::CausalOrder;
13use crate::checkpoints::checkpoint_output::CertifiedCheckpointOutput;
14pub use crate::checkpoints::checkpoint_output::{
15    CheckpointOutput, LogCheckpointOutput, SendCheckpointToStateSync, SubmitCheckpointToConsensus,
16};
17pub use crate::checkpoints::metrics::CheckpointMetrics;
18use crate::consensus_manager::ReplayWaiter;
19use crate::execution_cache::TransactionCacheRead;
20
21use crate::global_state_hasher::GlobalStateHasher;
22use crate::stake_aggregator::{InsertResult, MultiStakeAggregator};
23use consensus_core::CommitRef;
24use diffy::create_patch;
25use itertools::Itertools;
26use mysten_common::ZipDebugEqIteratorExt;
27use mysten_common::random::get_rng;
28use mysten_common::sync::notify_read::{CHECKPOINT_BUILDER_NOTIFY_READ_TASK_NAME, NotifyRead};
29use mysten_common::{assert_reachable, debug_fatal, fatal, in_antithesis};
30use mysten_metrics::{MonitoredFutureExt, monitored_scope, spawn_monitored_task};
31use parking_lot::Mutex;
32use pin_project_lite::pin_project;
33use serde::{Deserialize, Serialize};
34use sui_macros::fail_point_arg;
35use sui_network::default_mysten_network_config;
36use sui_types::accumulator_metadata;
37use sui_types::base_types::{ConciseableName, SequenceNumber};
38use sui_types::execution::ExecutionTimeObservationKey;
39use sui_types::messages_checkpoint::{
40    CheckpointArtifacts, CheckpointCommitment, VersionedFullCheckpointContents,
41};
42use sui_types::sui_system_state::epoch_start_sui_system_state::EpochStartSystemStateTrait;
43use tokio::sync::{mpsc, watch};
44#[cfg(not(tidehunter))]
45use typed_store::rocks::{DBOptions, ReadWriteOptions, default_db_options};
46
47use crate::authority::authority_per_epoch_store::AuthorityPerEpochStore;
48use crate::authority::authority_store_pruner::PrunerWatermarks;
49use crate::consensus_handler::SequencedConsensusTransactionKey;
50use rand::seq::SliceRandom;
51use std::collections::{BTreeMap, HashMap, HashSet};
52use std::fs::File;
53use std::future::Future;
54use std::io::Write;
55use std::path::Path;
56use std::pin::Pin;
57use std::sync::Arc;
58use std::sync::Weak;
59use std::task::{Context, Poll};
60use std::time::{Duration, SystemTime};
61use sui_protocol_config::ProtocolVersion;
62use sui_types::base_types::{AuthorityName, EpochId, TransactionDigest};
63use sui_types::committee::StakeUnit;
64use sui_types::crypto::AuthorityStrongQuorumSignInfo;
65use sui_types::digests::{
66    CheckpointContentsDigest, CheckpointDigest, Digest, TransactionEffectsDigest,
67};
68use sui_types::effects::{TransactionEffects, TransactionEffectsAPI};
69use sui_types::error::{SuiErrorKind, SuiResult};
70use sui_types::gas::GasCostSummary;
71use sui_types::message_envelope::Message;
72use sui_types::messages_checkpoint::{
73    CertifiedCheckpointSummary, CheckpointContents, CheckpointResponseV2, CheckpointSequenceNumber,
74    CheckpointSignatureMessage, CheckpointSummary, CheckpointSummaryResponse, CheckpointTimestamp,
75    EndOfEpochData, FullCheckpointContents, TrustedCheckpoint, VerifiedCheckpoint,
76    VerifiedCheckpointContents,
77};
78use sui_types::messages_checkpoint::{CheckpointRequestV2, SignedCheckpointSummary};
79use sui_types::messages_consensus::ConsensusTransactionKey;
80use sui_types::signature::GenericSignature;
81use sui_types::sui_system_state::{SuiSystemState, SuiSystemStateTrait};
82use sui_types::transaction::{
83    TransactionDataAPI, TransactionKey, TransactionKind, VerifiedTransaction,
84};
85use tokio::sync::Notify;
86use tracing::{debug, error, info, instrument, trace, warn};
87use typed_store::DBMapUtils;
88use typed_store::Map;
89use typed_store::{
90    TypedStoreError,
91    rocks::{DBMap, MetricConf},
92};
93
94const TRANSACTION_FORK_DETECTED_KEY: u8 = 0;
95
96pub type CheckpointHeight = u64;
97
98pub struct EpochStats {
99    pub checkpoint_count: u64,
100    pub transaction_count: u64,
101    pub total_gas_reward: u64,
102}
103
104#[derive(Clone, Debug)]
105pub struct PendingCheckpointInfo {
106    pub timestamp_ms: CheckpointTimestamp,
107    pub last_of_epoch: bool,
108    pub checkpoint_height: CheckpointHeight,
109    // Consensus commit ref and rejected transactions digest which corresponds to this checkpoint.
110    pub consensus_commit_ref: CommitRef,
111    pub rejected_transactions_digest: Digest,
112    // Pre-assigned checkpoint sequence number from consensus handler.
113    pub checkpoint_seq: CheckpointSequenceNumber,
114}
115
116#[derive(Clone, Debug, Default)]
117pub struct CheckpointRoots {
118    pub tx_roots: Vec<TransactionKey>,
119    pub settlement_root: Option<TransactionKey>,
120    pub height: CheckpointHeight,
121}
122
123/// Consensus commits are merged and split into PendingCheckpoints in ConsensusHandler.
124/// Each CheckpointRoots represents a group of transactions settled together.
125#[derive(Clone, Debug)]
126pub struct PendingCheckpoint {
127    pub roots: Vec<CheckpointRoots>,
128    pub details: PendingCheckpointInfo,
129}
130
131#[derive(Clone, Debug, Serialize, Deserialize)]
132pub struct BuilderCheckpointSummary {
133    pub summary: CheckpointSummary,
134    // Height at which this checkpoint summary was built. None for genesis checkpoint
135    pub checkpoint_height: Option<CheckpointHeight>,
136    // Always 0: each height now maps to exactly one checkpoint. Kept for DB format
137    // compatibility; old rows may contain nonzero values from builder-side splitting.
138    pub position_in_commit: usize,
139}
140
141#[derive(DBMapUtils)]
142#[cfg_attr(tidehunter, tidehunter)]
143pub struct CheckpointStoreTables {
144    /// Maps checkpoint contents digest to checkpoint contents
145    pub(crate) checkpoint_content: DBMap<CheckpointContentsDigest, CheckpointContents>,
146
147    /// Maps checkpoint contents digest to checkpoint sequence number
148    pub(crate) checkpoint_sequence_by_contents_digest:
149        DBMap<CheckpointContentsDigest, CheckpointSequenceNumber>,
150
151    /// Stores entire checkpoint contents from state sync, indexed by sequence number, for
152    /// efficient reads of full checkpoints. Entries from this table are deleted after state
153    /// accumulation has completed.
154    #[default_options_override_fn = "full_checkpoint_content_table_default_config"]
155    // TODO: Once the switch to `full_checkpoint_content_v2` is fully active on mainnet,
156    // deprecate this table (and remove when possible).
157    full_checkpoint_content: DBMap<CheckpointSequenceNumber, FullCheckpointContents>,
158
159    /// Stores certified checkpoints
160    pub(crate) certified_checkpoints: DBMap<CheckpointSequenceNumber, TrustedCheckpoint>,
161    /// Map from checkpoint digest to certified checkpoint
162    pub(crate) checkpoint_by_digest: DBMap<CheckpointDigest, TrustedCheckpoint>,
163
164    /// Store locally computed checkpoint summaries so that we can detect forks and log useful
165    /// information. Can be pruned as soon as we verify that we are in agreement with the latest
166    /// certified checkpoint.
167    pub(crate) locally_computed_checkpoints: DBMap<CheckpointSequenceNumber, CheckpointSummary>,
168
169    /// A map from epoch ID to the sequence number of the last checkpoint in that epoch.
170    epoch_last_checkpoint_map: DBMap<EpochId, CheckpointSequenceNumber>,
171
172    /// Watermarks used to determine the highest verified, fully synced, and
173    /// fully executed checkpoints
174    pub(crate) watermarks: DBMap<CheckpointWatermark, (CheckpointSequenceNumber, CheckpointDigest)>,
175
176    /// Stores transaction fork detection information
177    pub(crate) transaction_fork_detected: DBMap<
178        u8,
179        (
180            TransactionDigest,
181            TransactionEffectsDigest,
182            TransactionEffectsDigest,
183        ),
184    >,
185    #[default_options_override_fn = "full_checkpoint_content_table_default_config"]
186    full_checkpoint_content_v2: DBMap<CheckpointSequenceNumber, VersionedFullCheckpointContents>,
187}
188
189#[cfg(not(tidehunter))]
190fn full_checkpoint_content_table_default_config() -> DBOptions {
191    DBOptions {
192        options: default_db_options().options,
193        // We have seen potential data corruption issues in this table after forced shutdowns
194        // so we enable value hash logging to help with debugging.
195        // TODO: remove this once we have a better understanding of the root cause.
196        rw_options: ReadWriteOptions::default().set_log_value_hash(true),
197    }
198}
199
200impl CheckpointStoreTables {
201    #[cfg(not(tidehunter))]
202    pub fn new(path: &Path, metric_name: &'static str, _: Arc<PrunerWatermarks>) -> Self {
203        Self::open_tables_read_write(path.to_path_buf(), MetricConf::new(metric_name), None, None)
204    }
205
206    #[cfg(tidehunter)]
207    pub fn new(
208        path: &Path,
209        metric_name: &'static str,
210        pruner_watermarks: Arc<PrunerWatermarks>,
211    ) -> Self {
212        tracing::warn!("Checkpoint DB using tidehunter");
213        use crate::authority::authority_store_pruner::apply_relocation_filter;
214        use typed_store::tidehunter_util::{
215            Decision, KeySpaceConfig, KeyType, ThConfig, default_cells_per_mutex,
216            default_max_dirty_keys, default_mutex_count, default_value_cache_size,
217        };
218        let mutexes = default_mutex_count();
219        let u64_sequence_key = KeyType::from_prefix_bits(6 * 8);
220        let override_dirty_keys_config = KeySpaceConfig::new()
221            .with_max_dirty_keys(16 * default_max_dirty_keys())
222            .with_value_cache_size(default_value_cache_size());
223        let config_u64 = ThConfig::new_with_config(
224            8,
225            mutexes,
226            u64_sequence_key,
227            override_dirty_keys_config.clone(),
228        );
229        let digest_config = ThConfig::new_with_rm_prefix(
230            32,
231            mutexes,
232            KeyType::uniform(default_cells_per_mutex()),
233            KeySpaceConfig::default(),
234            vec![0, 0, 0, 0, 0, 0, 0, 32],
235        );
236        let watermarks_config = KeySpaceConfig::new()
237            .with_value_cache_size(10)
238            .disable_unload();
239        let lru_config = KeySpaceConfig::new().with_value_cache_size(100);
240        let configs = vec![
241            (
242                "checkpoint_content",
243                digest_config.clone().with_config(
244                    KeySpaceConfig::new().with_relocation_filter(|_, _| Decision::Remove),
245                ),
246            ),
247            (
248                "checkpoint_sequence_by_contents_digest",
249                digest_config.clone().with_config(apply_relocation_filter(
250                    KeySpaceConfig::default(),
251                    pruner_watermarks.checkpoint_id.clone(),
252                    |sequence_number: CheckpointSequenceNumber| sequence_number,
253                    false,
254                )),
255            ),
256            (
257                "full_checkpoint_content",
258                config_u64.clone().with_config(apply_relocation_filter(
259                    override_dirty_keys_config.clone(),
260                    pruner_watermarks.checkpoint_id.clone(),
261                    |sequence_number: CheckpointSequenceNumber| sequence_number,
262                    true,
263                )),
264            ),
265            ("certified_checkpoints", config_u64.clone()),
266            (
267                "checkpoint_by_digest",
268                digest_config.clone().with_config(apply_relocation_filter(
269                    lru_config,
270                    pruner_watermarks.epoch_id.clone(),
271                    |checkpoint: TrustedCheckpoint| checkpoint.inner().epoch,
272                    false,
273                )),
274            ),
275            (
276                "locally_computed_checkpoints",
277                config_u64.clone().with_config(apply_relocation_filter(
278                    override_dirty_keys_config.clone(),
279                    pruner_watermarks.checkpoint_id.clone(),
280                    |checkpoint_id: CheckpointSequenceNumber| checkpoint_id,
281                    true,
282                )),
283            ),
284            ("epoch_last_checkpoint_map", config_u64.clone()),
285            (
286                "watermarks",
287                ThConfig::new_with_config(4, 1, KeyType::uniform(1), watermarks_config.clone()),
288            ),
289            (
290                "transaction_fork_detected",
291                ThConfig::new_with_config(
292                    1,
293                    1,
294                    KeyType::uniform(1),
295                    watermarks_config.with_relocation_filter(|_, _| Decision::Remove),
296                ),
297            ),
298            (
299                "full_checkpoint_content_v2",
300                config_u64.clone().with_config(apply_relocation_filter(
301                    override_dirty_keys_config.clone(),
302                    pruner_watermarks.checkpoint_id.clone(),
303                    |sequence_number: CheckpointSequenceNumber| sequence_number,
304                    true,
305                )),
306            ),
307        ];
308        Self::open_tables_read_write(
309            path.to_path_buf(),
310            MetricConf::new(metric_name),
311            configs
312                .into_iter()
313                .map(|(cf, config)| (cf.to_string(), config))
314                .collect(),
315        )
316    }
317
318    #[cfg(not(tidehunter))]
319    pub fn open_readonly(path: &Path) -> CheckpointStoreTablesReadOnly {
320        Self::get_read_only_handle(
321            path.to_path_buf(),
322            None,
323            None,
324            MetricConf::new("checkpoint_readonly"),
325        )
326    }
327
328    #[cfg(tidehunter)]
329    pub fn open_readonly(path: &Path) -> Self {
330        Self::new(path, "checkpoint", Arc::new(PrunerWatermarks::default()))
331    }
332}
333
334pub struct CheckpointStore {
335    pub(crate) tables: CheckpointStoreTables,
336    synced_checkpoint_notify_read: NotifyRead<CheckpointSequenceNumber, VerifiedCheckpoint>,
337    executed_checkpoint_notify_read: NotifyRead<CheckpointSequenceNumber, VerifiedCheckpoint>,
338}
339
340impl CheckpointStore {
341    pub fn new(path: &Path, pruner_watermarks: Arc<PrunerWatermarks>) -> Arc<Self> {
342        let tables = CheckpointStoreTables::new(path, "checkpoint", pruner_watermarks);
343        Arc::new(Self {
344            tables,
345            synced_checkpoint_notify_read: NotifyRead::new(),
346            executed_checkpoint_notify_read: NotifyRead::new(),
347        })
348    }
349
350    pub fn new_for_tests() -> Arc<Self> {
351        let ckpt_dir = mysten_common::tempdir().unwrap();
352        CheckpointStore::new(ckpt_dir.path(), Arc::new(PrunerWatermarks::default()))
353    }
354
355    pub fn new_for_db_checkpoint_handler(path: &Path) -> Arc<Self> {
356        let tables = CheckpointStoreTables::new(
357            path,
358            "db_checkpoint",
359            Arc::new(PrunerWatermarks::default()),
360        );
361        Arc::new(Self {
362            tables,
363            synced_checkpoint_notify_read: NotifyRead::new(),
364            executed_checkpoint_notify_read: NotifyRead::new(),
365        })
366    }
367
368    #[cfg(not(tidehunter))]
369    pub fn open_readonly(path: &Path) -> CheckpointStoreTablesReadOnly {
370        CheckpointStoreTables::open_readonly(path)
371    }
372
373    #[cfg(tidehunter)]
374    pub fn open_readonly(path: &Path) -> CheckpointStoreTables {
375        CheckpointStoreTables::open_readonly(path)
376    }
377
378    #[instrument(level = "info", skip_all)]
379    pub fn insert_genesis_checkpoint(
380        &self,
381        checkpoint: VerifiedCheckpoint,
382        contents: CheckpointContents,
383        epoch_store: &AuthorityPerEpochStore,
384    ) {
385        assert_eq!(
386            checkpoint.epoch(),
387            0,
388            "can't call insert_genesis_checkpoint with a checkpoint not in epoch 0"
389        );
390        assert_eq!(
391            *checkpoint.sequence_number(),
392            0,
393            "can't call insert_genesis_checkpoint with a checkpoint that doesn't have a sequence number of 0"
394        );
395
396        // Only insert the genesis checkpoint if the DB is empty and doesn't have it already
397        match self.get_checkpoint_by_sequence_number(0).unwrap() {
398            Some(existing_checkpoint) => {
399                assert_eq!(existing_checkpoint.digest(), checkpoint.digest())
400            }
401            None => {
402                if epoch_store.epoch() == checkpoint.epoch {
403                    epoch_store
404                        .put_genesis_checkpoint_in_builder(checkpoint.data())
405                        .unwrap();
406                } else {
407                    debug!(
408                        validator_epoch =% epoch_store.epoch(),
409                        genesis_epoch =% checkpoint.epoch(),
410                        "Not inserting checkpoint builder data for genesis checkpoint",
411                    );
412                }
413                self.insert_checkpoint_contents(contents).unwrap();
414                self.insert_verified_checkpoint(&checkpoint).unwrap();
415                self.update_highest_synced_checkpoint(&checkpoint).unwrap();
416            }
417        }
418    }
419
420    pub fn get_checkpoint_by_digest(
421        &self,
422        digest: &CheckpointDigest,
423    ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
424        self.tables
425            .checkpoint_by_digest
426            .get(digest)
427            .map(|maybe_checkpoint| maybe_checkpoint.map(|c| c.into()))
428    }
429
430    pub fn get_checkpoint_by_sequence_number(
431        &self,
432        sequence_number: CheckpointSequenceNumber,
433    ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
434        self.tables
435            .certified_checkpoints
436            .get(&sequence_number)
437            .map(|maybe_checkpoint| maybe_checkpoint.map(|c| c.into()))
438    }
439
440    pub fn get_locally_computed_checkpoint(
441        &self,
442        sequence_number: CheckpointSequenceNumber,
443    ) -> Result<Option<CheckpointSummary>, TypedStoreError> {
444        self.tables
445            .locally_computed_checkpoints
446            .get(&sequence_number)
447    }
448
449    pub fn multi_get_locally_computed_checkpoints(
450        &self,
451        sequence_numbers: &[CheckpointSequenceNumber],
452    ) -> Result<Vec<Option<CheckpointSummary>>, TypedStoreError> {
453        let checkpoints = self
454            .tables
455            .locally_computed_checkpoints
456            .multi_get(sequence_numbers)?;
457
458        Ok(checkpoints)
459    }
460
461    pub fn get_sequence_number_by_contents_digest(
462        &self,
463        digest: &CheckpointContentsDigest,
464    ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
465        self.tables
466            .checkpoint_sequence_by_contents_digest
467            .get(digest)
468    }
469
470    pub fn delete_contents_digest_sequence_number_mapping(
471        &self,
472        digest: &CheckpointContentsDigest,
473    ) -> Result<(), TypedStoreError> {
474        self.tables
475            .checkpoint_sequence_by_contents_digest
476            .remove(digest)
477    }
478
479    pub fn get_latest_certified_checkpoint(
480        &self,
481    ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
482        Ok(self
483            .tables
484            .certified_checkpoints
485            .reversed_safe_iter_with_bounds(None, None)?
486            .next()
487            .transpose()?
488            .map(|(_, v)| v.into()))
489    }
490
491    pub fn get_latest_locally_computed_checkpoint(
492        &self,
493    ) -> Result<Option<CheckpointSummary>, TypedStoreError> {
494        Ok(self
495            .tables
496            .locally_computed_checkpoints
497            .reversed_safe_iter_with_bounds(None, None)?
498            .next()
499            .transpose()?
500            .map(|(_, v)| v))
501    }
502
503    pub fn multi_get_checkpoint_by_sequence_number(
504        &self,
505        sequence_numbers: &[CheckpointSequenceNumber],
506    ) -> Result<Vec<Option<VerifiedCheckpoint>>, TypedStoreError> {
507        let checkpoints = self
508            .tables
509            .certified_checkpoints
510            .multi_get(sequence_numbers)?
511            .into_iter()
512            .map(|maybe_checkpoint| maybe_checkpoint.map(|c| c.into()))
513            .collect();
514
515        Ok(checkpoints)
516    }
517
518    pub fn multi_get_checkpoint_content(
519        &self,
520        contents_digest: &[CheckpointContentsDigest],
521    ) -> Result<Vec<Option<CheckpointContents>>, TypedStoreError> {
522        self.tables.checkpoint_content.multi_get(contents_digest)
523    }
524
525    pub fn get_highest_verified_checkpoint(
526        &self,
527    ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
528        let highest_verified = if let Some(highest_verified) = self
529            .tables
530            .watermarks
531            .get(&CheckpointWatermark::HighestVerified)?
532        {
533            highest_verified
534        } else {
535            return Ok(None);
536        };
537        self.get_checkpoint_by_digest(&highest_verified.1)
538    }
539
540    pub fn get_highest_synced_checkpoint(
541        &self,
542    ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
543        let highest_synced = if let Some(highest_synced) = self
544            .tables
545            .watermarks
546            .get(&CheckpointWatermark::HighestSynced)?
547        {
548            highest_synced
549        } else {
550            return Ok(None);
551        };
552        self.get_checkpoint_by_digest(&highest_synced.1)
553    }
554
555    pub fn get_highest_synced_checkpoint_seq_number(
556        &self,
557    ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
558        if let Some(highest_synced) = self
559            .tables
560            .watermarks
561            .get(&CheckpointWatermark::HighestSynced)?
562        {
563            Ok(Some(highest_synced.0))
564        } else {
565            Ok(None)
566        }
567    }
568
569    pub fn get_highest_executed_checkpoint_seq_number(
570        &self,
571    ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
572        if let Some(highest_executed) = self
573            .tables
574            .watermarks
575            .get(&CheckpointWatermark::HighestExecuted)?
576        {
577            Ok(Some(highest_executed.0))
578        } else {
579            Ok(None)
580        }
581    }
582
583    pub fn get_highest_executed_checkpoint(
584        &self,
585    ) -> Result<Option<VerifiedCheckpoint>, TypedStoreError> {
586        let highest_executed = if let Some(highest_executed) = self
587            .tables
588            .watermarks
589            .get(&CheckpointWatermark::HighestExecuted)?
590        {
591            highest_executed
592        } else {
593            return Ok(None);
594        };
595        self.get_checkpoint_by_digest(&highest_executed.1)
596    }
597
598    pub fn get_highest_pruned_checkpoint_seq_number(
599        &self,
600    ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
601        self.tables
602            .watermarks
603            .get(&CheckpointWatermark::HighestPruned)
604            .map(|watermark| watermark.map(|w| w.0))
605    }
606
607    pub fn get_checkpoint_contents(
608        &self,
609        digest: &CheckpointContentsDigest,
610    ) -> Result<Option<CheckpointContents>, TypedStoreError> {
611        self.tables.checkpoint_content.get(digest)
612    }
613
614    pub fn get_full_checkpoint_contents_by_sequence_number(
615        &self,
616        seq: CheckpointSequenceNumber,
617    ) -> Result<Option<VersionedFullCheckpointContents>, TypedStoreError> {
618        self.tables.full_checkpoint_content_v2.get(&seq)
619    }
620
621    fn prune_local_summaries(&self) -> SuiResult {
622        if let Some((last_local_summary, _)) = self
623            .tables
624            .locally_computed_checkpoints
625            .reversed_safe_iter_with_bounds(None, None)?
626            .next()
627            .transpose()?
628        {
629            let mut batch = self.tables.locally_computed_checkpoints.batch();
630            batch.schedule_delete_range(
631                &self.tables.locally_computed_checkpoints,
632                &0,
633                &last_local_summary,
634            )?;
635            batch.write()?;
636            info!("Pruned local summaries up to {:?}", last_local_summary);
637        }
638        Ok(())
639    }
640
641    pub fn clear_locally_computed_checkpoints_from(
642        &self,
643        from_seq: CheckpointSequenceNumber,
644    ) -> SuiResult {
645        let keys: Vec<_> = self
646            .tables
647            .locally_computed_checkpoints
648            .safe_iter_with_bounds(Some(from_seq), None)
649            .map(|r| r.map(|(k, _)| k))
650            .collect::<Result<_, _>>()?;
651        if let Some(&last_local_summary) = keys.last() {
652            let mut batch = self.tables.locally_computed_checkpoints.batch();
653            batch
654                .delete_batch(&self.tables.locally_computed_checkpoints, keys.iter())
655                .expect("Failed to delete locally computed checkpoints");
656            batch
657                .write()
658                .expect("Failed to delete locally computed checkpoints");
659            warn!(
660                from_seq,
661                last_local_summary,
662                "Cleared locally_computed_checkpoints from {} (inclusive) through {} (inclusive)",
663                from_seq,
664                last_local_summary
665            );
666        }
667        Ok(())
668    }
669
670    fn check_for_checkpoint_fork(
671        &self,
672        local_checkpoint: &CheckpointSummary,
673        verified_checkpoint: &VerifiedCheckpoint,
674    ) {
675        if local_checkpoint != verified_checkpoint.data() {
676            let verified_contents = self
677                .get_checkpoint_contents(&verified_checkpoint.content_digest)
678                .map(|opt_contents| {
679                    opt_contents
680                        .map(|contents| format!("{:?}", contents))
681                        .unwrap_or_else(|| {
682                            format!(
683                                "Verified checkpoint contents not found, digest: {:?}",
684                                verified_checkpoint.content_digest,
685                            )
686                        })
687                })
688                .map_err(|e| {
689                    format!(
690                        "Failed to get verified checkpoint contents, digest: {:?} error: {:?}",
691                        verified_checkpoint.content_digest, e
692                    )
693                })
694                .unwrap_or_else(|err_msg| err_msg);
695
696            let local_contents = self
697                .get_checkpoint_contents(&local_checkpoint.content_digest)
698                .map(|opt_contents| {
699                    opt_contents
700                        .map(|contents| format!("{:?}", contents))
701                        .unwrap_or_else(|| {
702                            format!(
703                                "Local checkpoint contents not found, digest: {:?}",
704                                local_checkpoint.content_digest
705                            )
706                        })
707                })
708                .map_err(|e| {
709                    format!(
710                        "Failed to get local checkpoint contents, digest: {:?} error: {:?}",
711                        local_checkpoint.content_digest, e
712                    )
713                })
714                .unwrap_or_else(|err_msg| err_msg);
715
716            // checkpoint contents may be too large for panic message.
717            error!(
718                verified_checkpoint = ?verified_checkpoint.data(),
719                ?verified_contents,
720                ?local_checkpoint,
721                ?local_contents,
722                "Local checkpoint fork detected!",
723            );
724
725            // Record the fork in the database before crashing
726            if let Err(e) = self.record_checkpoint_fork_detected(
727                *local_checkpoint.sequence_number(),
728                local_checkpoint.digest(),
729            ) {
730                error!("Failed to record checkpoint fork in database: {:?}", e);
731            }
732
733            fail_point_arg!(
734                "kill_checkpoint_fork_node",
735                |checkpoint_overrides: std::sync::Arc<
736                    std::sync::Mutex<std::collections::BTreeMap<u64, String>>,
737                >| {
738                    #[cfg(msim)]
739                    {
740                        if let Ok(mut overrides) = checkpoint_overrides.lock() {
741                            overrides.insert(
742                                local_checkpoint.sequence_number,
743                                verified_checkpoint.digest().to_string(),
744                            );
745                        }
746                        tracing::error!(
747                            fatal = true,
748                            "Fork recovery test: killing node due to checkpoint fork for sequence number: {}, using verified digest: {}",
749                            local_checkpoint.sequence_number(),
750                            verified_checkpoint.digest()
751                        );
752                        sui_simulator::task::shutdown_current_node();
753                    }
754                }
755            );
756
757            fatal!(
758                "Local checkpoint fork detected for sequence number: {}",
759                local_checkpoint.sequence_number()
760            );
761        }
762    }
763
764    // Called by consensus (ConsensusAggregator).
765    // Different from `insert_verified_checkpoint`, it does not touch
766    // the highest_verified_checkpoint watermark such that state sync
767    // will have a chance to process this checkpoint and perform some
768    // state-sync only things.
769    pub fn insert_certified_checkpoint(
770        &self,
771        checkpoint: &VerifiedCheckpoint,
772    ) -> Result<(), TypedStoreError> {
773        debug!(
774            checkpoint_seq = checkpoint.sequence_number(),
775            "Inserting certified checkpoint",
776        );
777        let mut batch = self.tables.certified_checkpoints.batch();
778        batch
779            .insert_batch(
780                &self.tables.certified_checkpoints,
781                [(checkpoint.sequence_number(), checkpoint.serializable_ref())],
782            )?
783            .insert_batch(
784                &self.tables.checkpoint_by_digest,
785                [(checkpoint.digest(), checkpoint.serializable_ref())],
786            )?;
787        if checkpoint.next_epoch_committee().is_some() {
788            batch.insert_batch(
789                &self.tables.epoch_last_checkpoint_map,
790                [(&checkpoint.epoch(), checkpoint.sequence_number())],
791            )?;
792        }
793        batch.write()?;
794
795        if let Some(local_checkpoint) = self
796            .tables
797            .locally_computed_checkpoints
798            .get(checkpoint.sequence_number())?
799        {
800            self.check_for_checkpoint_fork(&local_checkpoint, checkpoint);
801        }
802
803        Ok(())
804    }
805
806    // Called by state sync, apart from inserting the checkpoint and updating
807    // related tables, it also bumps the highest_verified_checkpoint watermark.
808    #[instrument(level = "debug", skip_all)]
809    pub fn insert_verified_checkpoint(
810        &self,
811        checkpoint: &VerifiedCheckpoint,
812    ) -> Result<(), TypedStoreError> {
813        self.insert_certified_checkpoint(checkpoint)?;
814        self.update_highest_verified_checkpoint(checkpoint)
815    }
816
817    pub fn update_highest_verified_checkpoint(
818        &self,
819        checkpoint: &VerifiedCheckpoint,
820    ) -> Result<(), TypedStoreError> {
821        if Some(*checkpoint.sequence_number())
822            > self
823                .get_highest_verified_checkpoint()?
824                .map(|x| *x.sequence_number())
825        {
826            debug!(
827                checkpoint_seq = checkpoint.sequence_number(),
828                "Updating highest verified checkpoint",
829            );
830            self.tables.watermarks.insert(
831                &CheckpointWatermark::HighestVerified,
832                &(*checkpoint.sequence_number(), *checkpoint.digest()),
833            )?;
834        }
835
836        Ok(())
837    }
838
839    pub fn update_highest_synced_checkpoint(
840        &self,
841        checkpoint: &VerifiedCheckpoint,
842    ) -> Result<(), TypedStoreError> {
843        let seq = *checkpoint.sequence_number();
844        debug!(checkpoint_seq = seq, "Updating highest synced checkpoint",);
845        self.tables.watermarks.insert(
846            &CheckpointWatermark::HighestSynced,
847            &(seq, *checkpoint.digest()),
848        )?;
849        self.synced_checkpoint_notify_read.notify(&seq, checkpoint);
850        Ok(())
851    }
852
853    async fn notify_read_checkpoint_watermark<F>(
854        &self,
855        notify_read: &NotifyRead<CheckpointSequenceNumber, VerifiedCheckpoint>,
856        seq: CheckpointSequenceNumber,
857        get_watermark: F,
858    ) -> VerifiedCheckpoint
859    where
860        F: Fn() -> Option<CheckpointSequenceNumber>,
861    {
862        notify_read
863            .read("notify_read_checkpoint_watermark", &[seq], |seqs| {
864                let seq = seqs[0];
865                let Some(highest) = get_watermark() else {
866                    return vec![None];
867                };
868                if highest < seq {
869                    return vec![None];
870                }
871                let checkpoint = self
872                    .get_checkpoint_by_sequence_number(seq)
873                    .expect("db error")
874                    .expect("checkpoint not found");
875                vec![Some(checkpoint)]
876            })
877            .await
878            .into_iter()
879            .next()
880            .unwrap()
881    }
882
883    pub async fn notify_read_synced_checkpoint(
884        &self,
885        seq: CheckpointSequenceNumber,
886    ) -> VerifiedCheckpoint {
887        self.notify_read_checkpoint_watermark(&self.synced_checkpoint_notify_read, seq, || {
888            self.get_highest_synced_checkpoint_seq_number()
889                .expect("db error")
890        })
891        .await
892    }
893
894    pub async fn notify_read_executed_checkpoint(
895        &self,
896        seq: CheckpointSequenceNumber,
897    ) -> VerifiedCheckpoint {
898        self.notify_read_checkpoint_watermark(&self.executed_checkpoint_notify_read, seq, || {
899            self.get_highest_executed_checkpoint_seq_number()
900                .expect("db error")
901        })
902        .await
903    }
904
905    pub fn update_highest_executed_checkpoint(
906        &self,
907        checkpoint: &VerifiedCheckpoint,
908    ) -> Result<(), TypedStoreError> {
909        if let Some(seq_number) = self.get_highest_executed_checkpoint_seq_number()? {
910            if seq_number >= *checkpoint.sequence_number() {
911                return Ok(());
912            }
913            assert_eq!(
914                seq_number + 1,
915                *checkpoint.sequence_number(),
916                "Cannot update highest executed checkpoint to {} when current highest executed checkpoint is {}",
917                checkpoint.sequence_number(),
918                seq_number
919            );
920        }
921        let seq = *checkpoint.sequence_number();
922        debug!(checkpoint_seq = seq, "Updating highest executed checkpoint",);
923        self.tables.watermarks.insert(
924            &CheckpointWatermark::HighestExecuted,
925            &(seq, *checkpoint.digest()),
926        )?;
927        self.executed_checkpoint_notify_read
928            .notify(&seq, checkpoint);
929        Ok(())
930    }
931
932    pub fn update_highest_pruned_checkpoint(
933        &self,
934        checkpoint: &VerifiedCheckpoint,
935    ) -> Result<(), TypedStoreError> {
936        self.tables.watermarks.insert(
937            &CheckpointWatermark::HighestPruned,
938            &(*checkpoint.sequence_number(), *checkpoint.digest()),
939        )
940    }
941
942    /// Sets highest executed checkpoint to any value.
943    ///
944    /// WARNING: This method is very subtle and can corrupt the database if used incorrectly.
945    /// It should only be used in one-off cases or tests after fully understanding the risk.
946    pub fn set_highest_executed_checkpoint_subtle(
947        &self,
948        checkpoint: &VerifiedCheckpoint,
949    ) -> Result<(), TypedStoreError> {
950        self.tables.watermarks.insert(
951            &CheckpointWatermark::HighestExecuted,
952            &(*checkpoint.sequence_number(), *checkpoint.digest()),
953        )
954    }
955
956    pub fn insert_checkpoint_contents(
957        &self,
958        contents: CheckpointContents,
959    ) -> Result<(), TypedStoreError> {
960        debug!(
961            checkpoint_seq = ?contents.digest(),
962            "Inserting checkpoint contents",
963        );
964        self.tables
965            .checkpoint_content
966            .insert(contents.digest(), &contents)
967    }
968
969    pub fn insert_verified_checkpoint_contents(
970        &self,
971        checkpoint: &VerifiedCheckpoint,
972        full_contents: VerifiedCheckpointContents,
973    ) -> Result<(), TypedStoreError> {
974        let mut batch = self.tables.full_checkpoint_content_v2.batch();
975        batch.insert_batch(
976            &self.tables.checkpoint_sequence_by_contents_digest,
977            [(&checkpoint.content_digest, checkpoint.sequence_number())],
978        )?;
979        let full_contents = full_contents.into_inner();
980        batch.insert_batch(
981            &self.tables.full_checkpoint_content_v2,
982            [(checkpoint.sequence_number(), &full_contents)],
983        )?;
984
985        let contents = full_contents.into_checkpoint_contents();
986        assert_eq!(&checkpoint.content_digest, contents.digest());
987
988        batch.insert_batch(
989            &self.tables.checkpoint_content,
990            [(contents.digest(), &contents)],
991        )?;
992
993        batch.write()
994    }
995
996    pub fn delete_full_checkpoint_contents(
997        &self,
998        seq: CheckpointSequenceNumber,
999    ) -> Result<(), TypedStoreError> {
1000        self.tables.full_checkpoint_content.remove(&seq)?;
1001        self.tables.full_checkpoint_content_v2.remove(&seq)
1002    }
1003
1004    pub fn get_epoch_last_checkpoint(
1005        &self,
1006        epoch_id: EpochId,
1007    ) -> SuiResult<Option<VerifiedCheckpoint>> {
1008        let seq = self.get_epoch_last_checkpoint_seq_number(epoch_id)?;
1009        let checkpoint = match seq {
1010            Some(seq) => self.get_checkpoint_by_sequence_number(seq)?,
1011            None => None,
1012        };
1013        Ok(checkpoint)
1014    }
1015
1016    pub fn get_epoch_last_checkpoint_seq_number(
1017        &self,
1018        epoch_id: EpochId,
1019    ) -> SuiResult<Option<CheckpointSequenceNumber>> {
1020        let seq = self.tables.epoch_last_checkpoint_map.get(&epoch_id)?;
1021        Ok(seq)
1022    }
1023
1024    /// Returns the sequence number of the first checkpoint in the given epoch.
1025    /// For epoch 0 this is always 0; for epoch N > 0 it is last_checkpoint(N-1) + 1.
1026    pub fn get_epoch_first_checkpoint_seq(
1027        &self,
1028        epoch: EpochId,
1029    ) -> SuiResult<Option<CheckpointSequenceNumber>> {
1030        if epoch == 0 {
1031            return Ok(Some(0));
1032        }
1033        Ok(self
1034            .tables
1035            .epoch_last_checkpoint_map
1036            .get(&(epoch - 1))?
1037            .map(|s| s + 1))
1038    }
1039
1040    /// Iterate certified checkpoints starting at `start` (inclusive), up to `limit`.
1041    pub fn list_checkpoints_from_seq(
1042        &self,
1043        start: Option<CheckpointSequenceNumber>,
1044        limit: usize,
1045    ) -> Result<Vec<(CheckpointSequenceNumber, VerifiedCheckpoint)>, TypedStoreError> {
1046        self.tables
1047            .certified_checkpoints
1048            .safe_iter_with_bounds(start, None)
1049            .take(limit)
1050            .map(|r| r.map(|(seq, cp)| (seq, cp.into())))
1051            .collect()
1052    }
1053
1054    /// Iterate epoch→last-checkpoint-seq entries starting at `start` epoch, up to `limit`.
1055    pub fn list_epoch_last_checkpoints(
1056        &self,
1057        start: Option<EpochId>,
1058        limit: usize,
1059    ) -> Result<Vec<(EpochId, CheckpointSequenceNumber)>, TypedStoreError> {
1060        self.tables
1061            .epoch_last_checkpoint_map
1062            .safe_iter_with_bounds(start, None)
1063            .take(limit)
1064            .collect()
1065    }
1066
1067    /// Iterate checkpoint digests from `checkpoint_by_digest`, starting at `start`, up to `limit`.
1068    pub fn list_checkpoint_digests(
1069        &self,
1070        start: Option<CheckpointDigest>,
1071        limit: usize,
1072    ) -> Result<Vec<CheckpointDigest>, TypedStoreError> {
1073        self.tables
1074            .checkpoint_by_digest
1075            .safe_iter_with_bounds(start, None)
1076            .take(limit)
1077            .map(|r| r.map(|(d, _)| d))
1078            .collect()
1079    }
1080
1081    /// Iterate checkpoint contents digests, starting at `start`, up to `limit`.
1082    pub fn list_checkpoint_contents_digests(
1083        &self,
1084        start: Option<CheckpointContentsDigest>,
1085        limit: usize,
1086    ) -> Result<Vec<CheckpointContentsDigest>, TypedStoreError> {
1087        self.tables
1088            .checkpoint_content
1089            .safe_iter_with_bounds(start, None)
1090            .take(limit)
1091            .map(|r| r.map(|(d, _)| d))
1092            .collect()
1093    }
1094
1095    /// Iterate certified checkpoints belonging to `epoch`, starting at `start_seq`, up to `limit`.
1096    pub fn list_epoch_checkpoints(
1097        &self,
1098        epoch: EpochId,
1099        start_seq: Option<CheckpointSequenceNumber>,
1100        limit: usize,
1101    ) -> Result<Vec<(CheckpointSequenceNumber, VerifiedCheckpoint)>, TypedStoreError> {
1102        let Some(last_seq) = self.tables.epoch_last_checkpoint_map.get(&epoch)? else {
1103            return Ok(vec![]);
1104        };
1105        // Compute first seq directly to stay within TypedStoreError.
1106        let first_seq = if epoch == 0 {
1107            0
1108        } else {
1109            self.tables
1110                .epoch_last_checkpoint_map
1111                .get(&(epoch - 1))?
1112                .map(|s| s + 1)
1113                .unwrap_or(0)
1114        };
1115        let start = start_seq.map(|s| s.max(first_seq)).unwrap_or(first_seq);
1116        self.tables
1117            .certified_checkpoints
1118            .safe_iter_with_bounds(Some(start), Some(last_seq + 1))
1119            .take(limit)
1120            .map(|r| r.map(|(seq, cp)| (seq, cp.into())))
1121            .collect()
1122    }
1123
1124    pub fn insert_epoch_last_checkpoint(
1125        &self,
1126        epoch_id: EpochId,
1127        checkpoint: &VerifiedCheckpoint,
1128    ) -> SuiResult {
1129        self.tables
1130            .epoch_last_checkpoint_map
1131            .insert(&epoch_id, checkpoint.sequence_number())?;
1132        Ok(())
1133    }
1134
1135    pub fn get_epoch_state_commitments(
1136        &self,
1137        epoch: EpochId,
1138    ) -> SuiResult<Option<Vec<CheckpointCommitment>>> {
1139        let commitments = self.get_epoch_last_checkpoint(epoch)?.map(|checkpoint| {
1140            checkpoint
1141                .end_of_epoch_data
1142                .as_ref()
1143                .expect("Last checkpoint of epoch expected to have EndOfEpochData")
1144                .epoch_commitments
1145                .clone()
1146        });
1147        Ok(commitments)
1148    }
1149
1150    /// Given the epoch ID, and the last checkpoint of the epoch, derive a few statistics of the epoch.
1151    pub fn get_epoch_stats(
1152        &self,
1153        epoch: EpochId,
1154        last_checkpoint: &CheckpointSummary,
1155    ) -> Option<EpochStats> {
1156        let (first_checkpoint, prev_epoch_network_transactions) = if epoch == 0 {
1157            (0, 0)
1158        } else if let Ok(Some(checkpoint)) = self.get_epoch_last_checkpoint(epoch - 1) {
1159            (
1160                checkpoint.sequence_number + 1,
1161                checkpoint.network_total_transactions,
1162            )
1163        } else {
1164            return None;
1165        };
1166        Some(EpochStats {
1167            checkpoint_count: last_checkpoint.sequence_number - first_checkpoint + 1,
1168            transaction_count: last_checkpoint.network_total_transactions
1169                - prev_epoch_network_transactions,
1170            total_gas_reward: last_checkpoint
1171                .epoch_rolling_gas_cost_summary
1172                .computation_cost,
1173        })
1174    }
1175
1176    pub fn checkpoint_db(&self, path: &Path) -> SuiResult {
1177        // This checkpoints the entire db and not one column family
1178        self.tables
1179            .checkpoint_content
1180            .checkpoint_db(path)
1181            .map_err(Into::into)
1182    }
1183
1184    pub fn delete_highest_executed_checkpoint_test_only(&self) -> Result<(), TypedStoreError> {
1185        let mut wb = self.tables.watermarks.batch();
1186        wb.delete_batch(
1187            &self.tables.watermarks,
1188            std::iter::once(CheckpointWatermark::HighestExecuted),
1189        )?;
1190        wb.write()?;
1191        Ok(())
1192    }
1193
1194    pub fn reset_db_for_execution_since_genesis(&self) -> SuiResult {
1195        self.delete_highest_executed_checkpoint_test_only()?;
1196        Ok(())
1197    }
1198
1199    pub fn record_checkpoint_fork_detected(
1200        &self,
1201        checkpoint_seq: CheckpointSequenceNumber,
1202        checkpoint_digest: CheckpointDigest,
1203    ) -> Result<(), TypedStoreError> {
1204        info!(
1205            checkpoint_seq = checkpoint_seq,
1206            checkpoint_digest = ?checkpoint_digest,
1207            "Recording checkpoint fork detection in database"
1208        );
1209        self.tables.watermarks.insert(
1210            &CheckpointWatermark::CheckpointForkDetected,
1211            &(checkpoint_seq, checkpoint_digest),
1212        )
1213    }
1214
1215    pub fn get_checkpoint_fork_detected(
1216        &self,
1217    ) -> Result<Option<(CheckpointSequenceNumber, CheckpointDigest)>, TypedStoreError> {
1218        self.tables
1219            .watermarks
1220            .get(&CheckpointWatermark::CheckpointForkDetected)
1221    }
1222
1223    pub fn clear_checkpoint_fork_detected(&self) -> Result<(), TypedStoreError> {
1224        self.tables
1225            .watermarks
1226            .remove(&CheckpointWatermark::CheckpointForkDetected)
1227    }
1228
1229    pub fn record_transaction_fork_detected(
1230        &self,
1231        tx_digest: TransactionDigest,
1232        expected_effects_digest: TransactionEffectsDigest,
1233        actual_effects_digest: TransactionEffectsDigest,
1234    ) -> Result<(), TypedStoreError> {
1235        info!(
1236            tx_digest = ?tx_digest,
1237            expected_effects_digest = ?expected_effects_digest,
1238            actual_effects_digest = ?actual_effects_digest,
1239            "Recording transaction fork detection in database"
1240        );
1241        self.tables.transaction_fork_detected.insert(
1242            &TRANSACTION_FORK_DETECTED_KEY,
1243            &(tx_digest, expected_effects_digest, actual_effects_digest),
1244        )
1245    }
1246
1247    pub fn get_transaction_fork_detected(
1248        &self,
1249    ) -> Result<
1250        Option<(
1251            TransactionDigest,
1252            TransactionEffectsDigest,
1253            TransactionEffectsDigest,
1254        )>,
1255        TypedStoreError,
1256    > {
1257        self.tables
1258            .transaction_fork_detected
1259            .get(&TRANSACTION_FORK_DETECTED_KEY)
1260    }
1261
1262    pub fn clear_transaction_fork_detected(&self) -> Result<(), TypedStoreError> {
1263        self.tables
1264            .transaction_fork_detected
1265            .remove(&TRANSACTION_FORK_DETECTED_KEY)
1266    }
1267}
1268
1269#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
1270pub enum CheckpointWatermark {
1271    HighestVerified,
1272    HighestSynced,
1273    HighestExecuted,
1274    HighestPruned,
1275    CheckpointForkDetected,
1276}
1277
1278struct CheckpointStateHasher {
1279    epoch_store: Arc<AuthorityPerEpochStore>,
1280    hasher: Weak<GlobalStateHasher>,
1281    receive_from_builder: mpsc::Receiver<(CheckpointSequenceNumber, Vec<TransactionEffects>)>,
1282}
1283
1284impl CheckpointStateHasher {
1285    fn new(
1286        epoch_store: Arc<AuthorityPerEpochStore>,
1287        hasher: Weak<GlobalStateHasher>,
1288        receive_from_builder: mpsc::Receiver<(CheckpointSequenceNumber, Vec<TransactionEffects>)>,
1289    ) -> Self {
1290        Self {
1291            epoch_store,
1292            hasher,
1293            receive_from_builder,
1294        }
1295    }
1296
1297    async fn run(self) {
1298        let Self {
1299            epoch_store,
1300            hasher,
1301            mut receive_from_builder,
1302        } = self;
1303        while let Some((seq, effects)) = receive_from_builder.recv().await {
1304            let Some(hasher) = hasher.upgrade() else {
1305                info!("Object state hasher was dropped, stopping checkpoint accumulation");
1306                break;
1307            };
1308            hasher
1309                .accumulate_checkpoint(&effects, seq, &epoch_store)
1310                .expect("epoch ended while accumulating checkpoint");
1311        }
1312    }
1313}
1314
1315#[derive(Debug)]
1316pub enum CheckpointBuilderError {
1317    ChangeEpochTxAlreadyExecuted,
1318    SystemPackagesMissing,
1319    Retry(anyhow::Error),
1320}
1321
1322impl<SuiError: std::error::Error + Send + Sync + 'static> From<SuiError>
1323    for CheckpointBuilderError
1324{
1325    fn from(e: SuiError) -> Self {
1326        Self::Retry(e.into())
1327    }
1328}
1329
1330pub type CheckpointBuilderResult<T = ()> = Result<T, CheckpointBuilderError>;
1331
1332pub struct CheckpointBuilder {
1333    state: Arc<AuthorityState>,
1334    store: Arc<CheckpointStore>,
1335    epoch_store: Arc<AuthorityPerEpochStore>,
1336    notify: Arc<Notify>,
1337    notify_aggregator: Arc<Notify>,
1338    last_built: watch::Sender<CheckpointSequenceNumber>,
1339    effects_store: Arc<dyn TransactionCacheRead>,
1340    global_state_hasher: Weak<GlobalStateHasher>,
1341    send_to_hasher: mpsc::Sender<(CheckpointSequenceNumber, Vec<TransactionEffects>)>,
1342    output: Box<dyn CheckpointOutput>,
1343    metrics: Arc<CheckpointMetrics>,
1344}
1345
1346pub struct CheckpointAggregator {
1347    store: Arc<CheckpointStore>,
1348    epoch_store: Arc<AuthorityPerEpochStore>,
1349    notify: Arc<Notify>,
1350    receiver: mpsc::UnboundedReceiver<CheckpointSignatureMessage>,
1351    pending: BTreeMap<CheckpointSequenceNumber, Vec<CheckpointSignatureMessage>>,
1352    current: Option<CheckpointSignatureAggregator>,
1353    output: Box<dyn CertifiedCheckpointOutput>,
1354    state: Arc<AuthorityState>,
1355    metrics: Arc<CheckpointMetrics>,
1356}
1357
1358// This holds information to aggregate signatures for one checkpoint
1359pub struct CheckpointSignatureAggregator {
1360    summary: CheckpointSummary,
1361    digest: CheckpointDigest,
1362    /// Aggregates voting stake for each signed checkpoint proposal by authority
1363    signatures_by_digest: MultiStakeAggregator<CheckpointDigest, CheckpointSummary, true>,
1364    store: Arc<CheckpointStore>,
1365    state: Arc<AuthorityState>,
1366    metrics: Arc<CheckpointMetrics>,
1367}
1368
1369impl CheckpointBuilder {
1370    fn new(
1371        state: Arc<AuthorityState>,
1372        store: Arc<CheckpointStore>,
1373        epoch_store: Arc<AuthorityPerEpochStore>,
1374        notify: Arc<Notify>,
1375        effects_store: Arc<dyn TransactionCacheRead>,
1376        // for synchronous accumulation of end-of-epoch checkpoint
1377        global_state_hasher: Weak<GlobalStateHasher>,
1378        // for asynchronous/concurrent accumulation of regular checkpoints
1379        send_to_hasher: mpsc::Sender<(CheckpointSequenceNumber, Vec<TransactionEffects>)>,
1380        output: Box<dyn CheckpointOutput>,
1381        notify_aggregator: Arc<Notify>,
1382        last_built: watch::Sender<CheckpointSequenceNumber>,
1383        metrics: Arc<CheckpointMetrics>,
1384    ) -> Self {
1385        Self {
1386            state,
1387            store,
1388            epoch_store,
1389            notify,
1390            effects_store,
1391            global_state_hasher,
1392            send_to_hasher,
1393            output,
1394            notify_aggregator,
1395            last_built,
1396            metrics,
1397        }
1398    }
1399
1400    /// This function first waits for ConsensusCommitHandler to finish reprocessing
1401    /// commits that have been processed before the last restart, if consensus_replay_waiter
1402    /// is supplied. Then it starts building checkpoints in a loop.
1403    ///
1404    /// It is optional to pass in consensus_replay_waiter, to make it easier to attribute
1405    /// if slow recovery of previously built checkpoints is due to consensus replay or
1406    /// checkpoint building.
1407    async fn run(mut self, consensus_replay_waiter: Option<ReplayWaiter>) {
1408        if let Some(replay_waiter) = consensus_replay_waiter {
1409            info!("Waiting for consensus commits to replay ...");
1410            replay_waiter.wait_for_replay().await;
1411            info!("Consensus commits finished replaying");
1412        }
1413        info!("Starting CheckpointBuilder");
1414        loop {
1415            match self.maybe_build_checkpoints().await {
1416                Ok(()) => {}
1417                err @ Err(
1418                    CheckpointBuilderError::ChangeEpochTxAlreadyExecuted
1419                    | CheckpointBuilderError::SystemPackagesMissing,
1420                ) => {
1421                    info!("CheckpointBuilder stopping: {:?}", err);
1422                    return;
1423                }
1424                Err(CheckpointBuilderError::Retry(inner)) => {
1425                    let msg = format!("{:?}", inner);
1426                    debug_fatal!("Error while making checkpoint, will retry in 1s: {}", msg);
1427                    tokio::time::sleep(Duration::from_secs(1)).await;
1428                    self.metrics.checkpoint_errors.inc();
1429                    continue;
1430                }
1431            }
1432
1433            self.notify.notified().await;
1434        }
1435    }
1436
1437    async fn maybe_build_checkpoints(&mut self) -> CheckpointBuilderResult {
1438        let _scope = monitored_scope("BuildCheckpoints");
1439
1440        // Collect info about the most recently built checkpoint.
1441        let last_height = self
1442            .epoch_store
1443            .last_built_checkpoint_builder_summary()
1444            .expect("epoch should not have ended")
1445            .and_then(|s| s.checkpoint_height);
1446
1447        for (height, pending) in self
1448            .epoch_store
1449            .get_pending_checkpoints(last_height)
1450            .expect("unexpected epoch store error")
1451        {
1452            debug!(checkpoint_commit_height = height, "Making checkpoint");
1453
1454            let seq = self.make_checkpoint(pending).await?;
1455
1456            self.last_built.send_if_modified(|cur| {
1457                // when rebuilding checkpoints at startup, seq can be for an old checkpoint
1458                if seq > *cur {
1459                    *cur = seq;
1460                    true
1461                } else {
1462                    false
1463                }
1464            });
1465
1466            // ensure that the task can be cancelled at end of epoch, even if no other await yields
1467            // execution.
1468            tokio::task::yield_now().await;
1469        }
1470
1471        Ok(())
1472    }
1473
1474    #[instrument(level = "debug", skip_all, fields(height = pending.details.checkpoint_height))]
1475    async fn make_checkpoint(
1476        &mut self,
1477        pending: PendingCheckpoint,
1478    ) -> CheckpointBuilderResult<CheckpointSequenceNumber> {
1479        let _scope = monitored_scope("CheckpointBuilder::make_checkpoint");
1480
1481        let details = pending.details.clone();
1482
1483        let highest_executed_sequence = self
1484            .store
1485            .get_highest_executed_checkpoint_seq_number()
1486            .expect("db error")
1487            .unwrap_or(0);
1488
1489        let (poll_count, result) = poll_count(self.resolve_checkpoint_transactions(pending)).await;
1490        let (sorted_tx_effects_included_in_checkpoint, all_roots) = result?;
1491
1492        let new_checkpoint = self
1493            .create_checkpoint(
1494                sorted_tx_effects_included_in_checkpoint,
1495                &details,
1496                &all_roots,
1497            )
1498            .await?;
1499        let sequence = *new_checkpoint.0.sequence_number();
1500        let digest = new_checkpoint.0.digest();
1501        if sequence <= highest_executed_sequence && poll_count > 1 {
1502            debug_fatal!(
1503                "resolve_checkpoint_transactions should be instantaneous when executed checkpoint is ahead of checkpoint builder"
1504            );
1505        }
1506
1507        self.write_checkpoint(details.checkpoint_height, new_checkpoint)
1508            .await?;
1509        info!(
1510            seq = sequence,
1511            %digest,
1512            height = details.checkpoint_height,
1513            commit = %details.consensus_commit_ref,
1514            "Made new checkpoint"
1515        );
1516
1517        Ok(sequence)
1518    }
1519
1520    // Given the root transactions of a pending checkpoint, resolve the transactions should be included in
1521    // the checkpoint, and return them in the order they should be included in the checkpoint.
1522    #[instrument(level = "debug", skip_all)]
1523    async fn resolve_checkpoint_transactions(
1524        &self,
1525        pending: PendingCheckpoint,
1526    ) -> SuiResult<(Vec<TransactionEffects>, HashSet<TransactionDigest>)> {
1527        let _scope = monitored_scope("CheckpointBuilder::resolve_checkpoint_transactions");
1528
1529        debug!(
1530            checkpoint_commit_height = pending.details.checkpoint_height,
1531            "Resolving checkpoint transactions for pending checkpoint.",
1532        );
1533
1534        trace!(
1535            "roots for pending checkpoint {:?}: {:?}",
1536            pending.details.checkpoint_height, pending.roots,
1537        );
1538
1539        assert!(
1540            self.epoch_store
1541                .protocol_config()
1542                .prepend_prologue_tx_in_consensus_commit_in_checkpoints()
1543        );
1544
1545        let mut all_effects: Vec<TransactionEffects> = Vec::new();
1546        let mut all_root_digests: Vec<TransactionDigest> = Vec::new();
1547
1548        for checkpoint_roots in &pending.roots {
1549            let tx_roots = &checkpoint_roots.tx_roots;
1550
1551            self.metrics
1552                .checkpoint_roots_count
1553                .inc_by(tx_roots.len() as u64);
1554
1555            let root_digests = self
1556                .epoch_store
1557                .notify_read_tx_key_to_digest(tx_roots)
1558                .in_monitored_scope("CheckpointNotifyDigests")
1559                .await?;
1560
1561            all_root_digests.extend(root_digests.iter().cloned());
1562
1563            let root_effects = self
1564                .effects_store
1565                .notify_read_executed_effects(
1566                    CHECKPOINT_BUILDER_NOTIFY_READ_TASK_NAME,
1567                    &root_digests,
1568                )
1569                .in_monitored_scope("CheckpointNotifyRead")
1570                .await;
1571            let consensus_commit_prologue =
1572                self.extract_consensus_commit_prologue(&root_digests, &root_effects)?;
1573
1574            let _scope = monitored_scope("CheckpointBuilder::causal_sort");
1575            let ccp_digest = consensus_commit_prologue.map(|(d, _)| d);
1576            let mut sorted = CausalOrder::causal_sort_with_ccp(root_effects, ccp_digest);
1577
1578            if let Some(settlement_key) = &checkpoint_roots.settlement_root {
1579                let checkpoint_seq = pending.details.checkpoint_seq;
1580                let tx_index_offset = all_effects.len() as u64;
1581                let effects = self
1582                    .resolve_settlement_effects(
1583                        *settlement_key,
1584                        &sorted,
1585                        checkpoint_roots.height,
1586                        checkpoint_seq,
1587                        tx_index_offset,
1588                    )
1589                    .await;
1590                sorted.extend(effects);
1591            }
1592
1593            #[cfg(msim)]
1594            {
1595                self.expensive_consensus_commit_prologue_invariants_check(&root_digests, &sorted);
1596            }
1597
1598            all_effects.extend(sorted);
1599        }
1600        Ok((all_effects, all_root_digests.into_iter().collect()))
1601    }
1602
1603    /// Constructs settlement transactions to compute their digests, then reads effects
1604    /// directly from the cache. If execution is ahead of the checkpoint builder, the
1605    /// effects are already cached and this returns instantly. Otherwise it waits for
1606    /// the execution scheduler's queue worker to execute them.
1607    async fn resolve_settlement_effects(
1608        &self,
1609        settlement_key: TransactionKey,
1610        sorted_root_effects: &[TransactionEffects],
1611        checkpoint_height: CheckpointHeight,
1612        checkpoint_seq: CheckpointSequenceNumber,
1613        tx_index_offset: u64,
1614    ) -> Vec<TransactionEffects> {
1615        let epoch = self.epoch_store.epoch();
1616        let accumulator_root_obj_initial_shared_version = self
1617            .epoch_store
1618            .epoch_start_config()
1619            .accumulator_root_obj_initial_shared_version()
1620            .expect("accumulator root object must exist");
1621
1622        let builder = AccumulatorSettlementTxBuilder::new(
1623            None,
1624            sorted_root_effects,
1625            checkpoint_seq,
1626            tx_index_offset,
1627        );
1628
1629        let settlement_digests: Vec<_> = builder
1630            .build_tx(
1631                self.epoch_store.protocol_config(),
1632                epoch,
1633                accumulator_root_obj_initial_shared_version,
1634                checkpoint_height,
1635                checkpoint_seq,
1636            )
1637            .into_iter()
1638            .map(|tx| *VerifiedTransaction::new_system_transaction(tx).digest())
1639            .collect();
1640
1641        debug!(
1642            ?settlement_digests,
1643            ?settlement_key,
1644            "reading settlement effects from cache"
1645        );
1646
1647        let settlement_effects = wait_for_effects_with_retry(
1648            self.effects_store.as_ref(),
1649            "CheckpointBuilder::settlement_effects",
1650            &settlement_digests,
1651            settlement_key,
1652        )
1653        .await;
1654        let (accounts_created, accounts_deleted) =
1655            accumulators::count_accumulator_object_changes(&settlement_effects);
1656        self.metrics
1657            .report_accumulator_account_changes(accounts_created, accounts_deleted);
1658
1659        let barrier_digest = *VerifiedTransaction::new_system_transaction(
1660            accumulators::build_accumulator_barrier_tx(
1661                epoch,
1662                accumulator_root_obj_initial_shared_version,
1663                checkpoint_height,
1664                &settlement_effects,
1665            ),
1666        )
1667        .digest();
1668
1669        let barrier_effects = wait_for_effects_with_retry(
1670            self.effects_store.as_ref(),
1671            "CheckpointBuilder::barrier_effects",
1672            &[barrier_digest],
1673            settlement_key,
1674        )
1675        .await;
1676
1677        // Assert success here, in the builder task, before these effects are included in
1678        // the checkpoint. The settlement scheduler also asserts this, but it runs in a
1679        // separate task, so its assertion does not order against checkpoint persistence -
1680        // checking it here is what prevents a checkpoint from being built over the effects
1681        // of a failed settlement transaction.
1682        for fx in settlement_effects.iter().chain(barrier_effects.iter()) {
1683            assert!(
1684                fx.status().is_ok(),
1685                "settlement transaction cannot fail (digest: {:?}) {:#?}",
1686                fx.transaction_digest(),
1687                fx
1688            );
1689        }
1690
1691        settlement_effects
1692            .into_iter()
1693            .chain(barrier_effects)
1694            .collect()
1695    }
1696
1697    // Extracts the consensus commit prologue digest and effects from the root transactions.
1698    // The consensus commit prologue is expected to be the first transaction in the roots.
1699    fn extract_consensus_commit_prologue(
1700        &self,
1701        root_digests: &[TransactionDigest],
1702        root_effects: &[TransactionEffects],
1703    ) -> SuiResult<Option<(TransactionDigest, TransactionEffects)>> {
1704        let _scope = monitored_scope("CheckpointBuilder::extract_consensus_commit_prologue");
1705        if root_digests.is_empty() {
1706            return Ok(None);
1707        }
1708
1709        // Reads the first transaction in the roots, and checks whether it is a consensus commit
1710        // prologue transaction. The consensus commit prologue transaction should be the first
1711        // transaction in the roots written by the consensus handler.
1712        let first_tx = self
1713            .state
1714            .get_transaction_cache_reader()
1715            .get_transaction_block(&root_digests[0])
1716            .expect("Transaction block must exist");
1717
1718        Ok(first_tx
1719            .transaction_data()
1720            .is_consensus_commit_prologue()
1721            .then(|| {
1722                assert_eq!(first_tx.digest(), root_effects[0].transaction_digest());
1723                (*first_tx.digest(), root_effects[0].clone())
1724            }))
1725    }
1726
1727    #[instrument(level = "debug", skip_all)]
1728    async fn write_checkpoint(
1729        &mut self,
1730        height: CheckpointHeight,
1731        new_checkpoint: (CheckpointSummary, CheckpointContents),
1732    ) -> SuiResult {
1733        let _scope = monitored_scope("CheckpointBuilder::write_checkpoint");
1734        let mut batch = self.store.tables.checkpoint_content.batch();
1735
1736        let (summary, contents) = &new_checkpoint;
1737        debug!(
1738            checkpoint_commit_height = height,
1739            checkpoint_seq = summary.sequence_number,
1740            contents_digest = ?contents.digest(),
1741            "writing checkpoint",
1742        );
1743
1744        if let Some(previously_computed_summary) = self
1745            .store
1746            .tables
1747            .locally_computed_checkpoints
1748            .get(&summary.sequence_number)?
1749            && previously_computed_summary.digest() != summary.digest()
1750        {
1751            fatal!(
1752                "Checkpoint {} was previously built with a different result: previously_computed_summary {:?} vs current_summary {:?}",
1753                summary.sequence_number,
1754                previously_computed_summary.digest(),
1755                summary.digest()
1756            );
1757        }
1758
1759        self.metrics
1760            .transactions_included_in_checkpoint
1761            .inc_by(contents.size() as u64);
1762        let sequence_number = summary.sequence_number;
1763        self.metrics
1764            .last_constructed_checkpoint
1765            .set(sequence_number as i64);
1766
1767        batch.insert_batch(
1768            &self.store.tables.checkpoint_content,
1769            [(contents.digest(), contents)],
1770        )?;
1771
1772        batch.insert_batch(
1773            &self.store.tables.locally_computed_checkpoints,
1774            [(sequence_number, summary)],
1775        )?;
1776
1777        batch.write()?;
1778
1779        // Send checkpoint sigs to consensus.
1780        self.output
1781            .checkpoint_created(summary, contents, &self.epoch_store, &self.store)
1782            .await?;
1783
1784        if let Some(certified_checkpoint) = self
1785            .store
1786            .tables
1787            .certified_checkpoints
1788            .get(summary.sequence_number())?
1789        {
1790            self.store
1791                .check_for_checkpoint_fork(summary, &certified_checkpoint.into());
1792        }
1793
1794        self.notify_aggregator.notify_one();
1795        self.epoch_store
1796            .process_constructed_checkpoint(height, new_checkpoint.0);
1797        Ok(())
1798    }
1799
1800    fn load_last_built_checkpoint_summary(
1801        epoch_store: &AuthorityPerEpochStore,
1802        store: &CheckpointStore,
1803    ) -> SuiResult<Option<(CheckpointSequenceNumber, CheckpointSummary)>> {
1804        let mut last_checkpoint = epoch_store.last_built_checkpoint_summary()?;
1805        if last_checkpoint.is_none() {
1806            let epoch = epoch_store.epoch();
1807            if epoch > 0 {
1808                let previous_epoch = epoch - 1;
1809                let last_verified = store.get_epoch_last_checkpoint(previous_epoch)?;
1810                last_checkpoint = last_verified.map(VerifiedCheckpoint::into_summary_and_sequence);
1811                if let Some((ref seq, _)) = last_checkpoint {
1812                    debug!(
1813                        "No checkpoints in builder DB, taking checkpoint from previous epoch with sequence {seq}"
1814                    );
1815                } else {
1816                    // This is some serious bug with when CheckpointBuilder started so surfacing it via panic
1817                    panic!("Can not find last checkpoint for previous epoch {previous_epoch}");
1818                }
1819            }
1820        }
1821        Ok(last_checkpoint)
1822    }
1823
1824    #[instrument(level = "debug", skip_all)]
1825    async fn create_checkpoint(
1826        &self,
1827        all_effects: Vec<TransactionEffects>,
1828        details: &PendingCheckpointInfo,
1829        all_roots: &HashSet<TransactionDigest>,
1830    ) -> CheckpointBuilderResult<(CheckpointSummary, CheckpointContents)> {
1831        let _scope = monitored_scope("CheckpointBuilder::create_checkpoint");
1832
1833        let last_checkpoint =
1834            Self::load_last_built_checkpoint_summary(&self.epoch_store, &self.store)?;
1835        let last_checkpoint_seq = last_checkpoint.as_ref().map(|(seq, _)| *seq);
1836        debug!(
1837            checkpoint_commit_height = details.checkpoint_height,
1838            next_checkpoint_seq = last_checkpoint_seq.unwrap_or_default() + 1,
1839            checkpoint_timestamp = details.timestamp_ms,
1840            "Creating checkpoint for {} transactions",
1841            all_effects.len(),
1842        );
1843
1844        let all_digests: Vec<_> = all_effects
1845            .iter()
1846            .map(|effect| *effect.transaction_digest())
1847            .collect();
1848        let transaction_blocks = self
1849            .state
1850            .get_transaction_cache_reader()
1851            .multi_get_transaction_blocks(&all_digests);
1852        let mut transactions = Vec::with_capacity(all_effects.len());
1853        let mut transaction_keys = Vec::with_capacity(all_effects.len());
1854        let mut randomness_rounds = BTreeMap::new();
1855        {
1856            let _guard = monitored_scope("CheckpointBuilder::wait_for_transactions_sequenced");
1857            debug!(
1858                ?last_checkpoint_seq,
1859                "Waiting for {:?} certificates to appear in consensus",
1860                all_effects.len()
1861            );
1862
1863            for (effects, transaction) in all_effects
1864                .iter()
1865                .zip_debug_eq(transaction_blocks.into_iter())
1866            {
1867                let transaction = transaction
1868                    .unwrap_or_else(|| panic!("Could not find executed transaction {:?}", effects));
1869                match transaction.inner().transaction_data().kind() {
1870                    TransactionKind::ConsensusCommitPrologue(_)
1871                    | TransactionKind::ConsensusCommitPrologueV2(_)
1872                    | TransactionKind::ConsensusCommitPrologueV3(_)
1873                    | TransactionKind::ConsensusCommitPrologueV4(_)
1874                    | TransactionKind::AuthenticatorStateUpdate(_) => {
1875                        // ConsensusCommitPrologue and AuthenticatorStateUpdate are guaranteed to be
1876                        // processed before we reach here.
1877                    }
1878                    TransactionKind::ProgrammableSystemTransaction(_) => {
1879                        // settlement transactions are added by checkpoint builder
1880                    }
1881                    TransactionKind::ChangeEpoch(_)
1882                    | TransactionKind::Genesis(_)
1883                    | TransactionKind::EndOfEpochTransaction(_) => {
1884                        fatal!(
1885                            "unexpected transaction in checkpoint effects: {:?}",
1886                            transaction
1887                        );
1888                    }
1889                    TransactionKind::RandomnessStateUpdate(rsu) => {
1890                        randomness_rounds
1891                            .insert(*effects.transaction_digest(), rsu.randomness_round);
1892                    }
1893                    TransactionKind::ProgrammableTransaction(_) => {
1894                        // Only transactions that are not roots should be included in the call to
1895                        // `consensus_messages_processed_notify`. roots come directly from the consensus
1896                        // commit and so are known to be processed already.
1897                        let digest = *effects.transaction_digest();
1898                        if !all_roots.contains(&digest) {
1899                            transaction_keys.push(SequencedConsensusTransactionKey::External(
1900                                ConsensusTransactionKey::Certificate(digest),
1901                            ));
1902                        }
1903                    }
1904                }
1905                transactions.push((*transaction).clone());
1906            }
1907
1908            self.epoch_store
1909                .consensus_messages_processed_notify(transaction_keys)
1910                .await?;
1911        }
1912
1913        let signatures = self
1914            .epoch_store
1915            .user_signatures_for_checkpoint(&transactions, &all_digests);
1916        debug!(
1917            ?last_checkpoint_seq,
1918            "Received {} checkpoint user signatures from consensus",
1919            signatures.len()
1920        );
1921
1922        let end_of_epoch_observation_keys: Option<Vec<_>> = if details.last_of_epoch {
1923            Some(
1924                transactions
1925                    .iter()
1926                    .flat_map(|tx| {
1927                        if let TransactionKind::ProgrammableTransaction(ptb) =
1928                            tx.transaction_data().kind()
1929                        {
1930                            itertools::Either::Left(
1931                                ptb.commands
1932                                    .iter()
1933                                    .map(ExecutionTimeObservationKey::from_command),
1934                            )
1935                        } else {
1936                            itertools::Either::Right(std::iter::empty())
1937                        }
1938                    })
1939                    .collect(),
1940            )
1941        } else {
1942            None
1943        };
1944
1945        let epoch = self.epoch_store.epoch();
1946        let first_checkpoint_of_epoch = last_checkpoint
1947            .as_ref()
1948            .map(|(_, c)| c.epoch != epoch)
1949            .unwrap_or(true);
1950        if first_checkpoint_of_epoch {
1951            self.epoch_store
1952                .record_epoch_first_checkpoint_creation_time_metric();
1953        }
1954        let last_checkpoint_of_epoch = details.last_of_epoch;
1955
1956        let sequence_number = details.checkpoint_seq;
1957        let mut timestamp_ms = details.timestamp_ms;
1958        if let Some((_, last_checkpoint)) = &last_checkpoint
1959            && last_checkpoint.timestamp_ms > timestamp_ms
1960        {
1961            // First consensus commit of an epoch can have zero timestamp.
1962            debug!(
1963                "Decrease of checkpoint timestamp, possibly due to epoch change. Sequence: {}, previous: {}, current: {}",
1964                sequence_number, last_checkpoint.timestamp_ms, timestamp_ms,
1965            );
1966            if self
1967                .epoch_store
1968                .protocol_config()
1969                .enforce_checkpoint_timestamp_monotonicity()
1970            {
1971                timestamp_ms = last_checkpoint.timestamp_ms;
1972            }
1973        }
1974
1975        let mut effects = all_effects;
1976        let mut signatures = signatures;
1977        let epoch_rolling_gas_cost_summary =
1978            self.get_epoch_total_gas_cost(last_checkpoint.as_ref().map(|(_, c)| c), &effects);
1979
1980        let end_of_epoch_data = if last_checkpoint_of_epoch {
1981            let system_state_obj = self
1982                .augment_epoch_last_checkpoint(
1983                    &epoch_rolling_gas_cost_summary,
1984                    timestamp_ms,
1985                    &mut effects,
1986                    &mut signatures,
1987                    sequence_number,
1988                    end_of_epoch_observation_keys.expect(
1989                        "end_of_epoch_observation_keys must be populated for the last checkpoint",
1990                    ),
1991                    last_checkpoint_seq.unwrap_or_default(),
1992                )
1993                .await?;
1994
1995            let committee = system_state_obj
1996                .get_current_epoch_committee()
1997                .committee()
1998                .clone();
1999
2000            // This must happen after the call to augment_epoch_last_checkpoint,
2001            // otherwise we will not capture the change_epoch tx.
2002            let root_state_digest = {
2003                let state_acc = self
2004                    .global_state_hasher
2005                    .upgrade()
2006                    .expect("No checkpoints should be getting built after local configuration");
2007                let acc = state_acc.accumulate_checkpoint(
2008                    &effects,
2009                    sequence_number,
2010                    &self.epoch_store,
2011                )?;
2012
2013                state_acc
2014                    .wait_for_previous_running_root(&self.epoch_store, sequence_number)
2015                    .await?;
2016
2017                state_acc.accumulate_running_root(&self.epoch_store, sequence_number, Some(acc))?;
2018                state_acc
2019                    .digest_epoch(self.epoch_store.clone(), sequence_number)
2020                    .await?
2021            };
2022            self.metrics.highest_accumulated_epoch.set(epoch as i64);
2023            info!("Epoch {epoch} root state hash digest: {root_state_digest:?}");
2024
2025            let epoch_commitments = if self
2026                .epoch_store
2027                .protocol_config()
2028                .commit_root_state_digest()
2029            {
2030                vec![root_state_digest.into()]
2031            } else {
2032                vec![]
2033            };
2034
2035            Some(EndOfEpochData {
2036                next_epoch_committee: committee.voting_rights,
2037                next_epoch_protocol_version: ProtocolVersion::new(
2038                    system_state_obj.protocol_version(),
2039                ),
2040                epoch_commitments,
2041            })
2042        } else {
2043            self.send_to_hasher
2044                .send((sequence_number, effects.clone()))
2045                .await?;
2046
2047            None
2048        };
2049        let contents = if self.epoch_store.protocol_config().address_aliases() {
2050            CheckpointContents::new_v2(&effects, signatures)
2051        } else {
2052            CheckpointContents::new_with_digests_and_signatures(
2053                effects.iter().map(TransactionEffects::execution_digests),
2054                signatures
2055                    .into_iter()
2056                    .map(|sigs| sigs.into_iter().map(|(s, _)| s).collect())
2057                    .collect(),
2058            )
2059        };
2060
2061        let num_txns = contents.size() as u64;
2062
2063        let network_total_transactions = last_checkpoint
2064            .as_ref()
2065            .map(|(_, c)| c.network_total_transactions + num_txns)
2066            .unwrap_or(num_txns);
2067
2068        let previous_digest = last_checkpoint.as_ref().map(|(_, c)| c.digest());
2069
2070        let matching_randomness_rounds: Vec<_> = effects
2071            .iter()
2072            .filter_map(|e| randomness_rounds.get(e.transaction_digest()))
2073            .copied()
2074            .collect();
2075
2076        let checkpoint_commitments = if self
2077            .epoch_store
2078            .protocol_config()
2079            .include_checkpoint_artifacts_digest_in_summary()
2080        {
2081            let artifacts = CheckpointArtifacts::from(&effects[..]);
2082            let artifacts_digest = artifacts.digest()?;
2083            vec![artifacts_digest.into()]
2084        } else {
2085            Default::default()
2086        };
2087
2088        let summary = CheckpointSummary::new(
2089            self.epoch_store.protocol_config(),
2090            epoch,
2091            sequence_number,
2092            network_total_transactions,
2093            &contents,
2094            previous_digest,
2095            epoch_rolling_gas_cost_summary,
2096            end_of_epoch_data,
2097            timestamp_ms,
2098            matching_randomness_rounds,
2099            checkpoint_commitments,
2100        );
2101        summary.report_checkpoint_age(
2102            &self.metrics.last_created_checkpoint_age,
2103            &self.metrics.last_created_checkpoint_age_ms,
2104        );
2105        if last_checkpoint_of_epoch {
2106            info!(
2107                checkpoint_seq = sequence_number,
2108                "creating last checkpoint of epoch {}", epoch
2109            );
2110            if let Some(stats) = self.store.get_epoch_stats(epoch, &summary) {
2111                self.epoch_store
2112                    .report_epoch_metrics_at_last_checkpoint(stats);
2113            }
2114        }
2115
2116        Ok((summary, contents))
2117    }
2118
2119    fn get_epoch_total_gas_cost(
2120        &self,
2121        last_checkpoint: Option<&CheckpointSummary>,
2122        cur_checkpoint_effects: &[TransactionEffects],
2123    ) -> GasCostSummary {
2124        let (previous_epoch, previous_gas_costs) = last_checkpoint
2125            .map(|c| (c.epoch, c.epoch_rolling_gas_cost_summary.clone()))
2126            .unwrap_or_default();
2127        let current_gas_costs = GasCostSummary::new_from_txn_effects(cur_checkpoint_effects.iter());
2128        if previous_epoch == self.epoch_store.epoch() {
2129            // sum only when we are within the same epoch
2130            GasCostSummary::new(
2131                previous_gas_costs.computation_cost + current_gas_costs.computation_cost,
2132                previous_gas_costs.storage_cost + current_gas_costs.storage_cost,
2133                previous_gas_costs.storage_rebate + current_gas_costs.storage_rebate,
2134                previous_gas_costs.non_refundable_storage_fee
2135                    + current_gas_costs.non_refundable_storage_fee,
2136            )
2137        } else {
2138            current_gas_costs
2139        }
2140    }
2141
2142    #[instrument(level = "error", skip_all)]
2143    async fn augment_epoch_last_checkpoint(
2144        &self,
2145        epoch_total_gas_cost: &GasCostSummary,
2146        epoch_start_timestamp_ms: CheckpointTimestamp,
2147        checkpoint_effects: &mut Vec<TransactionEffects>,
2148        signatures: &mut Vec<Vec<(GenericSignature, Option<SequenceNumber>)>>,
2149        checkpoint: CheckpointSequenceNumber,
2150        end_of_epoch_observation_keys: Vec<ExecutionTimeObservationKey>,
2151        // This may be less than `checkpoint - 1` if the end-of-epoch PendingCheckpoint produced
2152        // >1 checkpoint.
2153        last_checkpoint: CheckpointSequenceNumber,
2154    ) -> CheckpointBuilderResult<SuiSystemState> {
2155        let (system_state, effects) = self
2156            .state
2157            .create_and_execute_advance_epoch_tx(
2158                &self.epoch_store,
2159                epoch_total_gas_cost,
2160                checkpoint,
2161                epoch_start_timestamp_ms,
2162                end_of_epoch_observation_keys,
2163                last_checkpoint,
2164            )
2165            .await?;
2166        checkpoint_effects.push(effects);
2167        signatures.push(vec![]);
2168        Ok(system_state)
2169    }
2170
2171    // Checks the invariants of the consensus commit prologue transactions in the checkpoint
2172    // in simtest.
2173    #[cfg(msim)]
2174    fn expensive_consensus_commit_prologue_invariants_check(
2175        &self,
2176        root_digests: &[TransactionDigest],
2177        sorted: &[TransactionEffects],
2178    ) {
2179        // Gets all the consensus commit prologue transactions from the roots.
2180        let root_txs = self
2181            .state
2182            .get_transaction_cache_reader()
2183            .multi_get_transaction_blocks(root_digests);
2184        let ccps = root_txs
2185            .iter()
2186            .filter_map(|tx| {
2187                if let Some(tx) = tx {
2188                    if tx.transaction_data().is_consensus_commit_prologue() {
2189                        Some(tx)
2190                    } else {
2191                        None
2192                    }
2193                } else {
2194                    None
2195                }
2196            })
2197            .collect::<Vec<_>>();
2198
2199        // There should be at most one consensus commit prologue transaction in the roots.
2200        assert!(ccps.len() <= 1);
2201
2202        // Get all the transactions in the checkpoint.
2203        let txs = self
2204            .state
2205            .get_transaction_cache_reader()
2206            .multi_get_transaction_blocks(
2207                &sorted
2208                    .iter()
2209                    .map(|tx| tx.transaction_digest().clone())
2210                    .collect::<Vec<_>>(),
2211            );
2212
2213        if ccps.len() == 0 {
2214            // If there is no consensus commit prologue transaction in the roots, then there should be no
2215            // consensus commit prologue transaction in the checkpoint.
2216            for tx in txs.iter() {
2217                if let Some(tx) = tx {
2218                    assert!(!tx.transaction_data().is_consensus_commit_prologue());
2219                }
2220            }
2221        } else {
2222            // If there is one consensus commit prologue, it must be the first one in the checkpoint.
2223            assert!(
2224                txs[0]
2225                    .as_ref()
2226                    .unwrap()
2227                    .transaction_data()
2228                    .is_consensus_commit_prologue()
2229            );
2230
2231            assert_eq!(ccps[0].digest(), txs[0].as_ref().unwrap().digest());
2232
2233            for tx in txs.iter().skip(1) {
2234                if let Some(tx) = tx {
2235                    assert!(!tx.transaction_data().is_consensus_commit_prologue());
2236                }
2237            }
2238        }
2239    }
2240}
2241
2242async fn wait_for_effects_with_retry(
2243    effects_store: &dyn TransactionCacheRead,
2244    task_name: &'static str,
2245    digests: &[TransactionDigest],
2246    tx_key: TransactionKey,
2247) -> Vec<TransactionEffects> {
2248    let delay = if in_antithesis() {
2249        // antithesis has aggressive thread pausing, 5 seconds causes false positives
2250        15
2251    } else {
2252        5
2253    };
2254    loop {
2255        match tokio::time::timeout(Duration::from_secs(delay), async {
2256            effects_store
2257                .notify_read_executed_effects(task_name, digests)
2258                .await
2259        })
2260        .await
2261        {
2262            Ok(effects) => break effects,
2263            Err(_) => {
2264                debug_fatal!(
2265                    "Timeout waiting for transactions to be executed {:?}, retrying...",
2266                    tx_key
2267                );
2268            }
2269        }
2270    }
2271}
2272
2273impl CheckpointAggregator {
2274    fn new(
2275        tables: Arc<CheckpointStore>,
2276        epoch_store: Arc<AuthorityPerEpochStore>,
2277        notify: Arc<Notify>,
2278        receiver: mpsc::UnboundedReceiver<CheckpointSignatureMessage>,
2279        output: Box<dyn CertifiedCheckpointOutput>,
2280        state: Arc<AuthorityState>,
2281        metrics: Arc<CheckpointMetrics>,
2282    ) -> Self {
2283        Self {
2284            store: tables,
2285            epoch_store,
2286            notify,
2287            receiver,
2288            pending: BTreeMap::new(),
2289            current: None,
2290            output,
2291            state,
2292            metrics,
2293        }
2294    }
2295
2296    async fn run(mut self) {
2297        info!("Starting CheckpointAggregator");
2298        loop {
2299            // Drain all signatures that arrived since the last iteration into the pending buffer
2300            while let Ok(sig) = self.receiver.try_recv() {
2301                self.pending
2302                    .entry(sig.summary.sequence_number)
2303                    .or_default()
2304                    .push(sig);
2305            }
2306
2307            if let Err(e) = self.run_and_notify().await {
2308                error!(
2309                    "Error while aggregating checkpoint, will retry in 1s: {:?}",
2310                    e
2311                );
2312                self.metrics.checkpoint_errors.inc();
2313                tokio::time::sleep(Duration::from_secs(1)).await;
2314                continue;
2315            }
2316
2317            tokio::select! {
2318                Some(sig) = self.receiver.recv() => {
2319                    self.pending
2320                        .entry(sig.summary.sequence_number)
2321                        .or_default()
2322                        .push(sig);
2323                }
2324                _ = self.notify.notified() => {}
2325                _ = tokio::time::sleep(Duration::from_secs(1)) => {}
2326            }
2327        }
2328    }
2329
2330    async fn run_and_notify(&mut self) -> SuiResult {
2331        let summaries = self.run_inner()?;
2332        for summary in summaries {
2333            self.output.certified_checkpoint_created(&summary).await?;
2334        }
2335        Ok(())
2336    }
2337
2338    fn run_inner(&mut self) -> SuiResult<Vec<CertifiedCheckpointSummary>> {
2339        let _scope = monitored_scope("CheckpointAggregator");
2340        let mut result = vec![];
2341        'outer: loop {
2342            let next_to_certify = self.next_checkpoint_to_certify()?;
2343            // Discard buffered signatures for checkpoints already certified
2344            // (e.g. certified via StateSync before local aggregation completed).
2345            self.pending.retain(|&seq, _| seq >= next_to_certify);
2346            let current = if let Some(current) = &mut self.current {
2347                // It's possible that the checkpoint was already certified by
2348                // the rest of the network and we've already received the
2349                // certified checkpoint via StateSync. In this case, we reset
2350                // the current signature aggregator to the next checkpoint to
2351                // be certified
2352                if current.summary.sequence_number < next_to_certify {
2353                    assert_reachable!("skip checkpoint certification");
2354                    self.current = None;
2355                    continue;
2356                }
2357                current
2358            } else {
2359                let Some(summary) = self
2360                    .epoch_store
2361                    .get_built_checkpoint_summary(next_to_certify)?
2362                else {
2363                    return Ok(result);
2364                };
2365                self.current = Some(CheckpointSignatureAggregator {
2366                    digest: summary.digest(),
2367                    summary,
2368                    signatures_by_digest: MultiStakeAggregator::new(
2369                        self.epoch_store.committee().clone(),
2370                    ),
2371                    store: self.store.clone(),
2372                    state: self.state.clone(),
2373                    metrics: self.metrics.clone(),
2374                });
2375                self.current.as_mut().unwrap()
2376            };
2377
2378            let seq = current.summary.sequence_number;
2379            let sigs = self.pending.remove(&seq).unwrap_or_default();
2380            if sigs.is_empty() {
2381                trace!(
2382                    checkpoint_seq =? seq,
2383                    "Not enough checkpoint signatures",
2384                );
2385                return Ok(result);
2386            }
2387            for data in sigs {
2388                trace!(
2389                    checkpoint_seq = seq,
2390                    "Processing signature for checkpoint (digest: {:?}) from {:?}",
2391                    current.summary.digest(),
2392                    data.summary.auth_sig().authority.concise()
2393                );
2394                self.metrics
2395                    .checkpoint_participation
2396                    .with_label_values(&[&format!(
2397                        "{:?}",
2398                        data.summary.auth_sig().authority.concise()
2399                    )])
2400                    .inc();
2401                if let Ok(auth_signature) = current.try_aggregate(data) {
2402                    debug!(
2403                        checkpoint_seq = seq,
2404                        "Successfully aggregated signatures for checkpoint (digest: {:?})",
2405                        current.summary.digest(),
2406                    );
2407                    let summary = VerifiedCheckpoint::new_unchecked(
2408                        CertifiedCheckpointSummary::new_from_data_and_sig(
2409                            current.summary.clone(),
2410                            auth_signature,
2411                        ),
2412                    );
2413
2414                    self.store.insert_certified_checkpoint(&summary)?;
2415                    self.metrics.last_certified_checkpoint.set(seq as i64);
2416                    current.summary.report_checkpoint_age(
2417                        &self.metrics.last_certified_checkpoint_age,
2418                        &self.metrics.last_certified_checkpoint_age_ms,
2419                    );
2420                    result.push(summary.into_inner());
2421                    self.current = None;
2422                    continue 'outer;
2423                }
2424            }
2425            break;
2426        }
2427        Ok(result)
2428    }
2429
2430    fn next_checkpoint_to_certify(&self) -> SuiResult<CheckpointSequenceNumber> {
2431        Ok(self
2432            .store
2433            .tables
2434            .certified_checkpoints
2435            .reversed_safe_iter_with_bounds(None, None)?
2436            .next()
2437            .transpose()?
2438            .map(|(seq, _)| seq + 1)
2439            .unwrap_or_default())
2440    }
2441}
2442
2443impl CheckpointSignatureAggregator {
2444    #[allow(clippy::result_unit_err)]
2445    pub fn try_aggregate(
2446        &mut self,
2447        data: CheckpointSignatureMessage,
2448    ) -> Result<AuthorityStrongQuorumSignInfo, ()> {
2449        let their_digest = *data.summary.digest();
2450        let (_, signature) = data.summary.into_data_and_sig();
2451        let author = signature.authority;
2452        let envelope =
2453            SignedCheckpointSummary::new_from_data_and_sig(self.summary.clone(), signature);
2454        match self.signatures_by_digest.insert(their_digest, envelope) {
2455            // ignore repeated signatures
2456            InsertResult::Failed { error }
2457                if matches!(
2458                    error.as_inner(),
2459                    SuiErrorKind::StakeAggregatorRepeatedSigner {
2460                        conflicting_sig: false,
2461                        ..
2462                    },
2463                ) =>
2464            {
2465                Err(())
2466            }
2467            InsertResult::Failed { error } => {
2468                warn!(
2469                    checkpoint_seq = self.summary.sequence_number,
2470                    "Failed to aggregate new signature from validator {:?}: {:?}",
2471                    author.concise(),
2472                    error
2473                );
2474                self.check_for_split_brain();
2475                Err(())
2476            }
2477            InsertResult::QuorumReached(cert) => {
2478                // It is not guaranteed that signature.authority == narwhal_cert.author, but we do verify
2479                // the signature so we know that the author signed the message at some point.
2480                if their_digest != self.digest {
2481                    self.metrics.remote_checkpoint_forks.inc();
2482                    warn!(
2483                        checkpoint_seq = self.summary.sequence_number,
2484                        "Validator {:?} has mismatching checkpoint digest {}, we have digest {}",
2485                        author.concise(),
2486                        their_digest,
2487                        self.digest
2488                    );
2489                    return Err(());
2490                }
2491                Ok(cert)
2492            }
2493            InsertResult::NotEnoughVotes {
2494                bad_votes: _,
2495                bad_authorities: _,
2496            } => {
2497                self.check_for_split_brain();
2498                Err(())
2499            }
2500        }
2501    }
2502
2503    /// Check if there is a split brain condition in checkpoint signature aggregation, defined
2504    /// as any state wherein it is no longer possible to achieve quorum on a checkpoint proposal,
2505    /// irrespective of the outcome of any outstanding votes.
2506    fn check_for_split_brain(&self) {
2507        debug!(
2508            checkpoint_seq = self.summary.sequence_number,
2509            "Checking for split brain condition"
2510        );
2511        if self.signatures_by_digest.quorum_unreachable() {
2512            // TODO: at this point we should immediately halt processing
2513            // of new transaction certificates to avoid building on top of
2514            // forked output
2515            // self.halt_all_execution();
2516
2517            let all_unique_values = self.signatures_by_digest.get_all_unique_values();
2518            let digests_by_stake_messages = all_unique_values
2519                .iter()
2520                .sorted_by_key(|(_, (_, stake))| -(*stake as i64))
2521                .map(|(digest, (_authorities, total_stake))| {
2522                    format!("{:?} (total stake: {})", digest, total_stake)
2523                })
2524                .collect::<Vec<String>>();
2525            fail_point_arg!("kill_split_brain_node", |(
2526                checkpoint_overrides,
2527                forked_authorities,
2528            ): (
2529                std::sync::Arc<std::sync::Mutex<std::collections::BTreeMap<u64, String>>>,
2530                std::sync::Arc<std::sync::Mutex<std::collections::HashSet<AuthorityName>>>,
2531            )| {
2532                #[cfg(msim)]
2533                {
2534                    if let (Ok(mut overrides), Ok(forked_authorities_set)) =
2535                        (checkpoint_overrides.lock(), forked_authorities.lock())
2536                    {
2537                        // Find the digest produced by non-forked authorities
2538                        let correct_digest = all_unique_values
2539                            .iter()
2540                            .find(|(_, (authorities, _))| {
2541                                // Check if any authority that produced this digest is NOT in the forked set
2542                                authorities
2543                                    .iter()
2544                                    .any(|auth| !forked_authorities_set.contains(auth))
2545                            })
2546                            .map(|(digest, _)| digest.to_string())
2547                            .unwrap_or_else(|| {
2548                                // Fallback: use the digest with the highest stake
2549                                all_unique_values
2550                                    .iter()
2551                                    .max_by_key(|(_, (_, stake))| *stake)
2552                                    .map(|(digest, _)| digest.to_string())
2553                                    .unwrap_or_else(|| self.digest.to_string())
2554                            });
2555
2556                        overrides.insert(self.summary.sequence_number, correct_digest.clone());
2557
2558                        tracing::error!(
2559                            fatal = true,
2560                            "Fork recovery test: detected split-brain for sequence number: {}, using digest: {}",
2561                            self.summary.sequence_number,
2562                            correct_digest
2563                        );
2564                    }
2565                }
2566            });
2567
2568            debug_fatal!(
2569                "Split brain detected in checkpoint signature aggregation for checkpoint {:?}. Remaining stake: {:?}, Digests by stake: {:?}",
2570                self.summary.sequence_number,
2571                self.signatures_by_digest.uncommitted_stake(),
2572                digests_by_stake_messages
2573            );
2574            self.metrics.split_brain_checkpoint_forks.inc();
2575
2576            let all_unique_values = self.signatures_by_digest.get_all_unique_values();
2577            let local_summary = self.summary.clone();
2578            let state = self.state.clone();
2579            let tables = self.store.clone();
2580
2581            tokio::spawn(async move {
2582                diagnose_split_brain(all_unique_values, local_summary, state, tables).await;
2583            });
2584        }
2585    }
2586}
2587
2588/// Create data dump containing relevant data for diagnosing cause of the
2589/// split brain by querying one disagreeing validator for full checkpoint contents.
2590/// To minimize peer chatter, we only query one validator at random from each
2591/// disagreeing faction, as all honest validators that participated in this round may
2592/// inevitably run the same process.
2593async fn diagnose_split_brain(
2594    all_unique_values: BTreeMap<CheckpointDigest, (Vec<AuthorityName>, StakeUnit)>,
2595    local_summary: CheckpointSummary,
2596    state: Arc<AuthorityState>,
2597    tables: Arc<CheckpointStore>,
2598) {
2599    debug!(
2600        checkpoint_seq = local_summary.sequence_number,
2601        "Running split brain diagnostics..."
2602    );
2603    let time = SystemTime::now();
2604    // collect one random disagreeing validator per differing digest
2605    let digest_to_validator = all_unique_values
2606        .iter()
2607        .filter_map(|(digest, (validators, _))| {
2608            if *digest != local_summary.digest() {
2609                let random_validator = validators.choose(&mut get_rng()).unwrap();
2610                Some((*digest, *random_validator))
2611            } else {
2612                None
2613            }
2614        })
2615        .collect::<HashMap<_, _>>();
2616    if digest_to_validator.is_empty() {
2617        panic!(
2618            "Given split brain condition, there should be at \
2619                least one validator that disagrees with local signature"
2620        );
2621    }
2622
2623    let epoch_store = state.load_epoch_store_one_call_per_task();
2624    let committee = epoch_store
2625        .epoch_start_state()
2626        .get_sui_committee_with_network_metadata();
2627    let network_config = default_mysten_network_config();
2628    let network_clients =
2629        make_network_authority_clients_with_network_config(&committee, &network_config);
2630
2631    // Query all disagreeing validators
2632    let response_futures = digest_to_validator
2633        .values()
2634        .cloned()
2635        .map(|validator| {
2636            let client = network_clients
2637                .get(&validator)
2638                .expect("Failed to get network client");
2639            let request = CheckpointRequestV2 {
2640                sequence_number: Some(local_summary.sequence_number),
2641                request_content: true,
2642                certified: false,
2643            };
2644            client.handle_checkpoint_v2(request)
2645        })
2646        .collect::<Vec<_>>();
2647
2648    let digest_name_pair = digest_to_validator.iter();
2649    let response_data = futures::future::join_all(response_futures)
2650        .await
2651        .into_iter()
2652        .zip_debug_eq(digest_name_pair)
2653        .filter_map(|(response, (digest, name))| match response {
2654            Ok(response) => match response {
2655                CheckpointResponseV2 {
2656                    checkpoint: Some(CheckpointSummaryResponse::Pending(summary)),
2657                    contents: Some(contents),
2658                } => Some((*name, *digest, summary, contents)),
2659                CheckpointResponseV2 {
2660                    checkpoint: Some(CheckpointSummaryResponse::Certified(_)),
2661                    contents: _,
2662                } => {
2663                    panic!("Expected pending checkpoint, but got certified checkpoint");
2664                }
2665                CheckpointResponseV2 {
2666                    checkpoint: None,
2667                    contents: _,
2668                } => {
2669                    error!(
2670                        "Summary for checkpoint {:?} not found on validator {:?}",
2671                        local_summary.sequence_number, name
2672                    );
2673                    None
2674                }
2675                CheckpointResponseV2 {
2676                    checkpoint: _,
2677                    contents: None,
2678                } => {
2679                    error!(
2680                        "Contents for checkpoint {:?} not found on validator {:?}",
2681                        local_summary.sequence_number, name
2682                    );
2683                    None
2684                }
2685            },
2686            Err(e) => {
2687                error!(
2688                    "Failed to get checkpoint contents from validator for fork diagnostics: {:?}",
2689                    e
2690                );
2691                None
2692            }
2693        })
2694        .collect::<Vec<_>>();
2695
2696    let local_checkpoint_contents = tables
2697        .get_checkpoint_contents(&local_summary.content_digest)
2698        .unwrap_or_else(|_| {
2699            panic!(
2700                "Could not find checkpoint contents for digest {:?}",
2701                local_summary.digest()
2702            )
2703        })
2704        .unwrap_or_else(|| {
2705            panic!(
2706                "Could not find local full checkpoint contents for checkpoint {:?}, digest {:?}",
2707                local_summary.sequence_number,
2708                local_summary.digest()
2709            )
2710        });
2711    let local_contents_text = format!("{local_checkpoint_contents:?}");
2712
2713    let local_summary_text = format!("{local_summary:?}");
2714    let local_validator = state.name.concise();
2715    let diff_patches = response_data
2716        .iter()
2717        .map(|(name, other_digest, other_summary, contents)| {
2718            let other_contents_text = format!("{contents:?}");
2719            let other_summary_text = format!("{other_summary:?}");
2720            let (local_transactions, local_effects): (Vec<_>, Vec<_>) = local_checkpoint_contents
2721                .enumerate_transactions(&local_summary)
2722                .map(|(_, exec_digest)| (exec_digest.transaction, exec_digest.effects))
2723                .unzip();
2724            let (other_transactions, other_effects): (Vec<_>, Vec<_>) = contents
2725                .enumerate_transactions(other_summary)
2726                .map(|(_, exec_digest)| (exec_digest.transaction, exec_digest.effects))
2727                .unzip();
2728            let summary_patch = create_patch(&local_summary_text, &other_summary_text);
2729            let contents_patch = create_patch(&local_contents_text, &other_contents_text);
2730            let local_transactions_text = format!("{local_transactions:#?}");
2731            let other_transactions_text = format!("{other_transactions:#?}");
2732            let transactions_patch =
2733                create_patch(&local_transactions_text, &other_transactions_text);
2734            let local_effects_text = format!("{local_effects:#?}");
2735            let other_effects_text = format!("{other_effects:#?}");
2736            let effects_patch = create_patch(&local_effects_text, &other_effects_text);
2737            let seq_number = local_summary.sequence_number;
2738            let local_digest = local_summary.digest();
2739            let other_validator = name.concise();
2740            format!(
2741                "Checkpoint: {seq_number:?}\n\
2742                Local validator (original): {local_validator:?}, digest: {local_digest:?}\n\
2743                Other validator (modified): {other_validator:?}, digest: {other_digest:?}\n\n\
2744                Summary Diff: \n{summary_patch}\n\n\
2745                Contents Diff: \n{contents_patch}\n\n\
2746                Transactions Diff: \n{transactions_patch}\n\n\
2747                Effects Diff: \n{effects_patch}",
2748            )
2749        })
2750        .collect::<Vec<_>>()
2751        .join("\n\n\n");
2752
2753    let header = format!(
2754        "Checkpoint Fork Dump - Authority {local_validator:?}: \n\
2755        Datetime: {:?}",
2756        time
2757    );
2758    let fork_logs_text = format!("{header}\n\n{diff_patches}\n\n");
2759    let path = tempfile::tempdir()
2760        .expect("Failed to create tempdir")
2761        .keep()
2762        .join(Path::new("checkpoint_fork_dump.txt"));
2763    let mut file = File::create(path).unwrap();
2764    write!(file, "{}", fork_logs_text).unwrap();
2765    debug!("{}", fork_logs_text);
2766}
2767
2768pub trait CheckpointServiceNotify {
2769    fn notify_checkpoint_signature(&self, info: &CheckpointSignatureMessage) -> SuiResult;
2770
2771    fn notify_checkpoint(&self) -> SuiResult;
2772}
2773
2774#[allow(clippy::large_enum_variant)]
2775enum CheckpointServiceState {
2776    Unstarted(
2777        (
2778            CheckpointBuilder,
2779            CheckpointAggregator,
2780            CheckpointStateHasher,
2781        ),
2782    ),
2783    Started,
2784}
2785
2786impl CheckpointServiceState {
2787    fn take_unstarted(
2788        &mut self,
2789    ) -> (
2790        CheckpointBuilder,
2791        CheckpointAggregator,
2792        CheckpointStateHasher,
2793    ) {
2794        let mut state = CheckpointServiceState::Started;
2795        std::mem::swap(self, &mut state);
2796
2797        match state {
2798            CheckpointServiceState::Unstarted((builder, aggregator, hasher)) => {
2799                (builder, aggregator, hasher)
2800            }
2801            CheckpointServiceState::Started => panic!("CheckpointServiceState is already started"),
2802        }
2803    }
2804}
2805
2806pub struct CheckpointService {
2807    tables: Arc<CheckpointStore>,
2808    notify_builder: Arc<Notify>,
2809    signature_sender: mpsc::UnboundedSender<CheckpointSignatureMessage>,
2810    // A notification for the current highest built sequence number.
2811    highest_currently_built_seq_tx: watch::Sender<CheckpointSequenceNumber>,
2812    // The highest sequence number that had already been built at the time CheckpointService
2813    // was constructed
2814    highest_previously_built_seq: CheckpointSequenceNumber,
2815    metrics: Arc<CheckpointMetrics>,
2816    state: Mutex<CheckpointServiceState>,
2817}
2818
2819impl CheckpointService {
2820    /// Constructs a new CheckpointService in an un-started state.
2821    // The signature channel is unbounded because notify_checkpoint_signature is called from a
2822    // sync context (consensus_validator.rs implements a sync external trait) and cannot block.
2823    // The channel is consumed by a single async aggregator task that drains it continuously, so
2824    // unbounded growth is not a concern in practice.
2825    #[allow(clippy::disallowed_methods)]
2826    pub fn build(
2827        state: Arc<AuthorityState>,
2828        checkpoint_store: Arc<CheckpointStore>,
2829        epoch_store: Arc<AuthorityPerEpochStore>,
2830        effects_store: Arc<dyn TransactionCacheRead>,
2831        global_state_hasher: Weak<GlobalStateHasher>,
2832        checkpoint_output: Box<dyn CheckpointOutput>,
2833        certified_checkpoint_output: Box<dyn CertifiedCheckpointOutput>,
2834        metrics: Arc<CheckpointMetrics>,
2835    ) -> Arc<Self> {
2836        info!("Starting checkpoint service");
2837        Self::initialize_accumulator_account_metrics(&state, &epoch_store, &metrics);
2838        let notify_builder = Arc::new(Notify::new());
2839        let notify_aggregator = Arc::new(Notify::new());
2840
2841        // We may have built higher checkpoint numbers before restarting.
2842        let highest_previously_built_seq = checkpoint_store
2843            .get_latest_locally_computed_checkpoint()
2844            .expect("failed to get latest locally computed checkpoint")
2845            .map(|s| s.sequence_number)
2846            .unwrap_or(0);
2847
2848        let highest_currently_built_seq =
2849            CheckpointBuilder::load_last_built_checkpoint_summary(&epoch_store, &checkpoint_store)
2850                .expect("epoch should not have ended")
2851                .map(|(seq, _)| seq)
2852                .unwrap_or(0);
2853
2854        let (highest_currently_built_seq_tx, _) = watch::channel(highest_currently_built_seq);
2855
2856        let (signature_sender, signature_receiver) = mpsc::unbounded_channel();
2857
2858        let aggregator = CheckpointAggregator::new(
2859            checkpoint_store.clone(),
2860            epoch_store.clone(),
2861            notify_aggregator.clone(),
2862            signature_receiver,
2863            certified_checkpoint_output,
2864            state.clone(),
2865            metrics.clone(),
2866        );
2867
2868        let (send_to_hasher, receive_from_builder) = mpsc::channel(16);
2869
2870        let ckpt_state_hasher = CheckpointStateHasher::new(
2871            epoch_store.clone(),
2872            global_state_hasher.clone(),
2873            receive_from_builder,
2874        );
2875
2876        let builder = CheckpointBuilder::new(
2877            state.clone(),
2878            checkpoint_store.clone(),
2879            epoch_store.clone(),
2880            notify_builder.clone(),
2881            effects_store,
2882            global_state_hasher,
2883            send_to_hasher,
2884            checkpoint_output,
2885            notify_aggregator.clone(),
2886            highest_currently_built_seq_tx.clone(),
2887            metrics.clone(),
2888        );
2889
2890        Arc::new(Self {
2891            tables: checkpoint_store,
2892            notify_builder,
2893            signature_sender,
2894            highest_currently_built_seq_tx,
2895            highest_previously_built_seq,
2896            metrics,
2897            state: Mutex::new(CheckpointServiceState::Unstarted((
2898                builder,
2899                aggregator,
2900                ckpt_state_hasher,
2901            ))),
2902        })
2903    }
2904
2905    fn initialize_accumulator_account_metrics(
2906        state: &AuthorityState,
2907        epoch_store: &AuthorityPerEpochStore,
2908        metrics: &CheckpointMetrics,
2909    ) {
2910        if !epoch_store.protocol_config().enable_accumulators() {
2911            return;
2912        }
2913
2914        let object_store = state.get_object_store();
2915        match accumulator_metadata::get_accumulator_object_count(object_store.as_ref()) {
2916            Ok(Some(count)) => metrics.initialize_accumulator_accounts_live(count),
2917            Ok(None) => {}
2918            Err(e) => fatal!("failed to initialize accumulator account metrics: {e}"),
2919        }
2920    }
2921
2922    /// Starts the CheckpointService.
2923    ///
2924    /// This function blocks until the CheckpointBuilder re-builds all checkpoints that had
2925    /// been built before the most recent restart. You can think of this as a WAL replay
2926    /// operation. Upon startup, we may have a number of consensus commits and resulting
2927    /// checkpoints that were built but not committed to disk. We want to reprocess the
2928    /// commits and rebuild the checkpoints before starting normal operation.
2929    pub async fn spawn(
2930        &self,
2931        epoch_store: Arc<AuthorityPerEpochStore>,
2932        consensus_replay_waiter: Option<ReplayWaiter>,
2933    ) {
2934        let (builder, aggregator, state_hasher) = self.state.lock().take_unstarted();
2935
2936        // Clean up state hashes computed after the last built checkpoint
2937        // This prevents ECMH divergence after fork recovery restarts
2938
2939        // Note: there is a rare crash recovery edge case where we write the builder
2940        // summary, but crash before we can bump the highest executed checkpoint.
2941        // If we committed the builder summary, it was certified and unforked, so there
2942        // is no need to clear that state hash. If we do clear it, then checkpoint executor
2943        // will wait forever for checkpoint builder to produce the state hash, which will
2944        // never happen.
2945        let last_persisted_builder_seq = epoch_store
2946            .last_persisted_checkpoint_builder_summary()
2947            .expect("epoch should not have ended")
2948            .map(|s| s.summary.sequence_number);
2949
2950        let last_executed_seq = self
2951            .tables
2952            .get_highest_executed_checkpoint()
2953            .expect("Failed to get highest executed checkpoint")
2954            .map(|checkpoint| *checkpoint.sequence_number());
2955
2956        if let Some(last_committed_seq) = last_persisted_builder_seq.max(last_executed_seq) {
2957            if let Err(e) = builder
2958                .epoch_store
2959                .clear_state_hashes_after_checkpoint(last_committed_seq)
2960            {
2961                error!(
2962                    "Failed to clear state hashes after checkpoint {}: {:?}",
2963                    last_committed_seq, e
2964                );
2965            } else {
2966                info!(
2967                    "Cleared state hashes after checkpoint {} to ensure consistent ECMH computation",
2968                    last_committed_seq
2969                );
2970            }
2971        }
2972
2973        let (builder_finished_tx, builder_finished_rx) = tokio::sync::oneshot::channel();
2974
2975        let state_hasher_task = spawn_monitored_task!(state_hasher.run());
2976        let aggregator_task = spawn_monitored_task!(aggregator.run());
2977
2978        spawn_monitored_task!(async move {
2979            epoch_store
2980                .within_alive_epoch(async move {
2981                    builder.run(consensus_replay_waiter).await;
2982                    builder_finished_tx.send(()).ok();
2983                })
2984                .await
2985                .ok();
2986
2987            // state hasher will terminate as soon as it has finished processing all messages from builder
2988            state_hasher_task
2989                .await
2990                .expect("state hasher should exit normally");
2991
2992            // builder must shut down before aggregator and state_hasher, since it sends
2993            // messages to them
2994            aggregator_task.abort();
2995            aggregator_task.await.ok();
2996        });
2997
2998        // If this times out, the validator may still start up. The worst that can
2999        // happen is that we will crash later on instead of immediately. The eventual
3000        // crash would occur because we may be missing transactions that are below the
3001        // highest_synced_checkpoint watermark, which can cause a crash in
3002        // `CheckpointExecutor::extract_randomness_rounds`.
3003        if tokio::time::timeout(Duration::from_secs(120), async move {
3004            tokio::select! {
3005                _ = builder_finished_rx => { debug!("CheckpointBuilder finished"); }
3006                _ = self.wait_for_rebuilt_checkpoints() => (),
3007            }
3008        })
3009        .await
3010        .is_err()
3011        {
3012            debug_fatal!("Timed out waiting for checkpoints to be rebuilt");
3013        }
3014    }
3015}
3016
3017impl CheckpointService {
3018    /// Waits until all checkpoints had been built before the node restarted
3019    /// are rebuilt. This is required to preserve the invariant that all checkpoints
3020    /// (and their transactions) below the highest_synced_checkpoint watermark are
3021    /// available. Once the checkpoints are constructed, we can be sure that the
3022    /// transactions have also been executed.
3023    pub async fn wait_for_rebuilt_checkpoints(&self) {
3024        let highest_previously_built_seq = self.highest_previously_built_seq;
3025        let mut rx = self.highest_currently_built_seq_tx.subscribe();
3026        let mut highest_currently_built_seq = *rx.borrow_and_update();
3027        info!(
3028            "Waiting for checkpoints to be rebuilt, previously built seq: {highest_previously_built_seq}, currently built seq: {highest_currently_built_seq}"
3029        );
3030        loop {
3031            if highest_currently_built_seq >= highest_previously_built_seq {
3032                info!("Checkpoint rebuild complete");
3033                break;
3034            }
3035            rx.changed().await.unwrap();
3036            highest_currently_built_seq = *rx.borrow_and_update();
3037        }
3038    }
3039
3040    #[cfg(test)]
3041    fn write_and_notify_checkpoint_for_testing(
3042        &self,
3043        epoch_store: &AuthorityPerEpochStore,
3044        checkpoint: PendingCheckpoint,
3045    ) -> SuiResult {
3046        use crate::authority::authority_per_epoch_store::consensus_quarantine::ConsensusCommitOutput;
3047
3048        let mut output = ConsensusCommitOutput::new(0);
3049        epoch_store.write_pending_checkpoint(&mut output, &checkpoint)?;
3050        output.set_default_commit_stats_for_testing();
3051        epoch_store.push_consensus_output_for_tests(output);
3052        self.notify_checkpoint()?;
3053        Ok(())
3054    }
3055}
3056
3057impl CheckpointServiceNotify for CheckpointService {
3058    fn notify_checkpoint_signature(&self, info: &CheckpointSignatureMessage) -> SuiResult {
3059        let sequence = info.summary.sequence_number;
3060        let signer = info.summary.auth_sig().authority.concise();
3061
3062        if let Some(highest_verified_checkpoint) = self
3063            .tables
3064            .get_highest_verified_checkpoint()?
3065            .map(|x| *x.sequence_number())
3066            && sequence <= highest_verified_checkpoint
3067        {
3068            trace!(
3069                checkpoint_seq = sequence,
3070                "Ignore checkpoint signature from {} - already certified", signer,
3071            );
3072            self.metrics
3073                .last_ignored_checkpoint_signature_received
3074                .set(sequence as i64);
3075            return Ok(());
3076        }
3077        trace!(
3078            checkpoint_seq = sequence,
3079            "Received checkpoint signature, digest {} from {}",
3080            info.summary.digest(),
3081            signer,
3082        );
3083        self.metrics
3084            .last_received_checkpoint_signatures
3085            .with_label_values(&[&signer.to_string()])
3086            .set(sequence as i64);
3087        self.signature_sender.send(info.clone()).ok();
3088        Ok(())
3089    }
3090
3091    fn notify_checkpoint(&self) -> SuiResult {
3092        self.notify_builder.notify_one();
3093        Ok(())
3094    }
3095}
3096
3097// test helper
3098pub struct CheckpointServiceNoop {}
3099impl CheckpointServiceNotify for CheckpointServiceNoop {
3100    fn notify_checkpoint_signature(&self, _: &CheckpointSignatureMessage) -> SuiResult {
3101        Ok(())
3102    }
3103
3104    fn notify_checkpoint(&self) -> SuiResult {
3105        Ok(())
3106    }
3107}
3108
3109impl PendingCheckpoint {
3110    pub fn height(&self) -> CheckpointHeight {
3111        self.details.checkpoint_height
3112    }
3113
3114    pub(crate) fn num_roots(&self) -> usize {
3115        self.roots.iter().map(|r| r.tx_roots.len()).sum()
3116    }
3117}
3118
3119pin_project! {
3120    pub struct PollCounter<Fut> {
3121        #[pin]
3122        future: Fut,
3123        count: usize,
3124    }
3125}
3126
3127impl<Fut> PollCounter<Fut> {
3128    pub fn new(future: Fut) -> Self {
3129        Self { future, count: 0 }
3130    }
3131
3132    pub fn count(&self) -> usize {
3133        self.count
3134    }
3135}
3136
3137impl<Fut: Future> Future for PollCounter<Fut> {
3138    type Output = (usize, Fut::Output);
3139
3140    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
3141        let this = self.project();
3142        *this.count += 1;
3143        match this.future.poll(cx) {
3144            Poll::Ready(output) => Poll::Ready((*this.count, output)),
3145            Poll::Pending => Poll::Pending,
3146        }
3147    }
3148}
3149
3150fn poll_count<Fut>(future: Fut) -> PollCounter<Fut> {
3151    PollCounter::new(future)
3152}
3153
3154#[cfg(test)]
3155mod tests {
3156    use super::*;
3157    use crate::authority::test_authority_builder::TestAuthorityBuilder;
3158    use futures::FutureExt as _;
3159    use futures::future::BoxFuture;
3160    use std::collections::HashMap;
3161    use std::ops::Deref;
3162    use sui_macros::sim_test;
3163    use sui_protocol_config::{Chain, ProtocolConfig};
3164    use sui_types::accumulator_event::AccumulatorEvent;
3165    use sui_types::base_types::{SequenceNumber, TransactionEffectsDigest};
3166    use sui_types::crypto::Signature;
3167    use sui_types::effects::{TransactionEffects, TransactionEvents};
3168    use sui_types::messages_checkpoint::SignedCheckpointSummary;
3169    use sui_types::transaction::VerifiedTransaction;
3170    use tokio::sync::mpsc;
3171
3172    #[tokio::test]
3173    async fn test_clear_locally_computed_checkpoints_from_deletes_inclusive_range() {
3174        let store = CheckpointStore::new_for_tests();
3175        let protocol = sui_protocol_config::ProtocolConfig::get_for_max_version_UNSAFE();
3176        for seq in 70u64..=80u64 {
3177            let contents =
3178                sui_types::messages_checkpoint::CheckpointContents::new_with_digests_only_for_tests(
3179                    [sui_types::base_types::ExecutionDigests::new(
3180                        sui_types::digests::TransactionDigest::random(),
3181                        sui_types::digests::TransactionEffectsDigest::ZERO,
3182                    )],
3183                );
3184            let summary = sui_types::messages_checkpoint::CheckpointSummary::new(
3185                &protocol,
3186                0,
3187                seq,
3188                0,
3189                &contents,
3190                None,
3191                sui_types::gas::GasCostSummary::default(),
3192                None,
3193                0,
3194                Vec::new(),
3195                Vec::new(),
3196            );
3197            store
3198                .tables
3199                .locally_computed_checkpoints
3200                .insert(&seq, &summary)
3201                .unwrap();
3202        }
3203
3204        store
3205            .clear_locally_computed_checkpoints_from(76)
3206            .expect("clear should succeed");
3207
3208        // Explicit boundary checks: 75 must remain, 76 must be deleted
3209        assert!(
3210            store
3211                .tables
3212                .locally_computed_checkpoints
3213                .get(&75)
3214                .unwrap()
3215                .is_some()
3216        );
3217        assert!(
3218            store
3219                .tables
3220                .locally_computed_checkpoints
3221                .get(&76)
3222                .unwrap()
3223                .is_none()
3224        );
3225
3226        for seq in 70u64..76u64 {
3227            assert!(
3228                store
3229                    .tables
3230                    .locally_computed_checkpoints
3231                    .get(&seq)
3232                    .unwrap()
3233                    .is_some()
3234            );
3235        }
3236        for seq in 76u64..=80u64 {
3237            assert!(
3238                store
3239                    .tables
3240                    .locally_computed_checkpoints
3241                    .get(&seq)
3242                    .unwrap()
3243                    .is_none()
3244            );
3245        }
3246    }
3247
3248    #[tokio::test]
3249    async fn test_fork_detection_storage() {
3250        let store = CheckpointStore::new_for_tests();
3251        // checkpoint fork
3252        let seq_num = 42;
3253        let digest = CheckpointDigest::random();
3254
3255        assert!(store.get_checkpoint_fork_detected().unwrap().is_none());
3256
3257        store
3258            .record_checkpoint_fork_detected(seq_num, digest)
3259            .unwrap();
3260
3261        let retrieved = store.get_checkpoint_fork_detected().unwrap();
3262        assert!(retrieved.is_some());
3263        let (retrieved_seq, retrieved_digest) = retrieved.unwrap();
3264        assert_eq!(retrieved_seq, seq_num);
3265        assert_eq!(retrieved_digest, digest);
3266
3267        store.clear_checkpoint_fork_detected().unwrap();
3268        assert!(store.get_checkpoint_fork_detected().unwrap().is_none());
3269
3270        // txn fork
3271        let tx_digest = TransactionDigest::random();
3272        let expected_effects = TransactionEffectsDigest::random();
3273        let actual_effects = TransactionEffectsDigest::random();
3274
3275        assert!(store.get_transaction_fork_detected().unwrap().is_none());
3276
3277        store
3278            .record_transaction_fork_detected(tx_digest, expected_effects, actual_effects)
3279            .unwrap();
3280
3281        let retrieved = store.get_transaction_fork_detected().unwrap();
3282        assert!(retrieved.is_some());
3283        let (retrieved_tx, retrieved_expected, retrieved_actual) = retrieved.unwrap();
3284        assert_eq!(retrieved_tx, tx_digest);
3285        assert_eq!(retrieved_expected, expected_effects);
3286        assert_eq!(retrieved_actual, actual_effects);
3287
3288        store.clear_transaction_fork_detected().unwrap();
3289        assert!(store.get_transaction_fork_detected().unwrap().is_none());
3290    }
3291
3292    #[sim_test]
3293    pub async fn checkpoint_builder_test() {
3294        telemetry_subscribers::init_for_testing();
3295
3296        let mut protocol_config =
3297            ProtocolConfig::get_for_version(ProtocolVersion::max(), Chain::Unknown);
3298        protocol_config.disable_accumulators_for_testing();
3299        let state = TestAuthorityBuilder::new()
3300            .with_protocol_config(protocol_config)
3301            .build()
3302            .await;
3303
3304        let dummy_tx = VerifiedTransaction::new_authenticator_state_update(
3305            0,
3306            0,
3307            vec![],
3308            SequenceNumber::new(),
3309        );
3310
3311        for i in 0..20 {
3312            state
3313                .database_for_testing()
3314                .perpetual_tables
3315                .transactions
3316                .insert(&d(i), dummy_tx.serializable_ref())
3317                .unwrap();
3318        }
3319
3320        let mut store = HashMap::<TransactionDigest, TransactionEffects>::new();
3321        commit_cert_for_test(
3322            &mut store,
3323            state.clone(),
3324            d(1),
3325            vec![d(2), d(3)],
3326            GasCostSummary::new(11, 12, 11, 1),
3327        );
3328        commit_cert_for_test(
3329            &mut store,
3330            state.clone(),
3331            d(2),
3332            vec![d(3), d(4)],
3333            GasCostSummary::new(21, 22, 21, 1),
3334        );
3335        commit_cert_for_test(
3336            &mut store,
3337            state.clone(),
3338            d(3),
3339            vec![],
3340            GasCostSummary::new(31, 32, 31, 1),
3341        );
3342        commit_cert_for_test(
3343            &mut store,
3344            state.clone(),
3345            d(4),
3346            vec![],
3347            GasCostSummary::new(41, 42, 41, 1),
3348        );
3349        for i in [5, 6, 7, 10, 11, 12, 13] {
3350            commit_cert_for_test(
3351                &mut store,
3352                state.clone(),
3353                d(i),
3354                vec![],
3355                GasCostSummary::new(41, 42, 41, 1),
3356            );
3357        }
3358        for i in [15, 16, 17] {
3359            commit_cert_for_test(
3360                &mut store,
3361                state.clone(),
3362                d(i),
3363                vec![],
3364                GasCostSummary::new(51, 52, 51, 1),
3365            );
3366        }
3367        let all_digests: Vec<_> = store.keys().copied().collect();
3368        for digest in all_digests {
3369            let signature = Signature::Ed25519SuiSignature(Default::default()).into();
3370            state
3371                .epoch_store_for_testing()
3372                .test_insert_user_signature(digest, vec![(signature, None)]);
3373        }
3374
3375        let (output, mut result) = mpsc::channel::<(CheckpointContents, CheckpointSummary)>(10);
3376        let (certified_output, mut certified_result) =
3377            mpsc::channel::<CertifiedCheckpointSummary>(10);
3378        let store = Arc::new(store);
3379
3380        let ckpt_dir = tempfile::tempdir().unwrap();
3381        let checkpoint_store =
3382            CheckpointStore::new(ckpt_dir.path(), Arc::new(PrunerWatermarks::default()));
3383        let epoch_store = state.epoch_store_for_testing();
3384
3385        let global_state_hasher = Arc::new(GlobalStateHasher::new_for_tests(
3386            state.get_global_state_hash_store().clone(),
3387        ));
3388
3389        let checkpoint_service = CheckpointService::build(
3390            state.clone(),
3391            checkpoint_store,
3392            epoch_store.clone(),
3393            store,
3394            Arc::downgrade(&global_state_hasher),
3395            Box::new(output),
3396            Box::new(certified_output),
3397            CheckpointMetrics::new_for_tests(),
3398        );
3399        checkpoint_service.spawn(epoch_store.clone(), None).await;
3400
3401        checkpoint_service
3402            .write_and_notify_checkpoint_for_testing(&epoch_store, p(0, vec![4], 0))
3403            .unwrap();
3404        checkpoint_service
3405            .write_and_notify_checkpoint_for_testing(&epoch_store, p(1, vec![1, 3], 2000))
3406            .unwrap();
3407        checkpoint_service
3408            .write_and_notify_checkpoint_for_testing(&epoch_store, p(2, vec![10, 11, 12, 13], 3000))
3409            .unwrap();
3410        checkpoint_service
3411            .write_and_notify_checkpoint_for_testing(&epoch_store, p(3, vec![15, 16, 17], 4000))
3412            .unwrap();
3413        checkpoint_service
3414            .write_and_notify_checkpoint_for_testing(&epoch_store, p(4, vec![5], 4001))
3415            .unwrap();
3416        checkpoint_service
3417            .write_and_notify_checkpoint_for_testing(&epoch_store, p(5, vec![6], 5000))
3418            .unwrap();
3419
3420        let (c1c, c1s) = result.recv().await.unwrap();
3421        let (c2c, c2s) = result.recv().await.unwrap();
3422
3423        let c1t = c1c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3424        let c2t = c2c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3425        assert_eq!(c1t, vec![d(4)]);
3426        assert_eq!(c1s.previous_digest, None);
3427        assert_eq!(c1s.sequence_number, 0);
3428        assert_eq!(
3429            c1s.epoch_rolling_gas_cost_summary,
3430            GasCostSummary::new(41, 42, 41, 1)
3431        );
3432
3433        // Causal order places d(3) before d(1), which depends on it.
3434        assert_eq!(c2t, vec![d(3), d(1)]);
3435        assert_eq!(c2s.previous_digest, Some(c1s.digest()));
3436        assert_eq!(c2s.sequence_number, 1);
3437        assert_eq!(
3438            c2s.epoch_rolling_gas_cost_summary,
3439            GasCostSummary::new(83, 86, 83, 3)
3440        );
3441
3442        // Each pending checkpoint produces exactly one checkpoint; splitting is
3443        // done in the consensus handler.
3444        let (c3c, c3s) = result.recv().await.unwrap();
3445        let c3t = c3c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3446        assert_eq!(c3s.sequence_number, 2);
3447        assert_eq!(c3s.previous_digest, Some(c2s.digest()));
3448        assert_eq!(c3t, vec![d(10), d(11), d(12), d(13)]);
3449
3450        let (c4c, c4s) = result.recv().await.unwrap();
3451        let c4t = c4c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3452        assert_eq!(c4s.sequence_number, 3);
3453        assert_eq!(c4s.previous_digest, Some(c3s.digest()));
3454        assert_eq!(c4t, vec![d(15), d(16), d(17)]);
3455
3456        let (c5c, c5s) = result.recv().await.unwrap();
3457        let c5t = c5c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3458        assert_eq!(c5s.sequence_number, 4);
3459        assert_eq!(c5s.previous_digest, Some(c4s.digest()));
3460        assert_eq!(c5t, vec![d(5)]);
3461
3462        let (c6c, c6s) = result.recv().await.unwrap();
3463        let c6t = c6c.iter().map(|d| d.transaction).collect::<Vec<_>>();
3464        assert_eq!(c6s.sequence_number, 5);
3465        assert_eq!(c6s.previous_digest, Some(c5s.digest()));
3466        assert_eq!(c6t, vec![d(6)]);
3467
3468        let c1ss = SignedCheckpointSummary::new(c1s.epoch, c1s, state.secret.deref(), state.name);
3469        let c2ss = SignedCheckpointSummary::new(c2s.epoch, c2s, state.secret.deref(), state.name);
3470
3471        checkpoint_service
3472            .notify_checkpoint_signature(&CheckpointSignatureMessage { summary: c2ss })
3473            .unwrap();
3474        checkpoint_service
3475            .notify_checkpoint_signature(&CheckpointSignatureMessage { summary: c1ss })
3476            .unwrap();
3477
3478        let c1sc = certified_result.recv().await.unwrap();
3479        let c2sc = certified_result.recv().await.unwrap();
3480        assert_eq!(c1sc.sequence_number, 0);
3481        assert_eq!(c2sc.sequence_number, 1);
3482    }
3483
3484    impl TransactionCacheRead for HashMap<TransactionDigest, TransactionEffects> {
3485        fn notify_read_executed_effects_may_fail(
3486            &self,
3487            _: &str,
3488            digests: &[TransactionDigest],
3489        ) -> BoxFuture<'_, SuiResult<Vec<TransactionEffects>>> {
3490            std::future::ready(Ok(digests
3491                .iter()
3492                .map(|d| self.get(d).expect("effects not found").clone())
3493                .collect()))
3494            .boxed()
3495        }
3496
3497        fn notify_read_executed_effects_digests(
3498            &self,
3499            _: &str,
3500            digests: &[TransactionDigest],
3501        ) -> BoxFuture<'_, Vec<TransactionEffectsDigest>> {
3502            std::future::ready(
3503                digests
3504                    .iter()
3505                    .map(|d| {
3506                        self.get(d)
3507                            .map(|fx| fx.digest())
3508                            .expect("effects not found")
3509                    })
3510                    .collect(),
3511            )
3512            .boxed()
3513        }
3514
3515        fn multi_get_executed_effects(
3516            &self,
3517            digests: &[TransactionDigest],
3518        ) -> Vec<Option<TransactionEffects>> {
3519            digests.iter().map(|d| self.get(d).cloned()).collect()
3520        }
3521
3522        // Unimplemented methods - its unfortunate to have this big blob of useless code, but it wasn't
3523        // worth it to keep EffectsNotifyRead around just for these tests, as it caused a ton of
3524        // complication in non-test code. (e.g. had to implement EFfectsNotifyRead for all
3525        // ExecutionCacheRead implementors).
3526
3527        fn multi_get_transaction_blocks(
3528            &self,
3529            _: &[TransactionDigest],
3530        ) -> Vec<Option<Arc<VerifiedTransaction>>> {
3531            unimplemented!()
3532        }
3533
3534        fn multi_get_executed_effects_digests(
3535            &self,
3536            _: &[TransactionDigest],
3537        ) -> Vec<Option<TransactionEffectsDigest>> {
3538            unimplemented!()
3539        }
3540
3541        fn multi_get_effects(
3542            &self,
3543            _: &[TransactionEffectsDigest],
3544        ) -> Vec<Option<TransactionEffects>> {
3545            unimplemented!()
3546        }
3547
3548        fn multi_get_events(&self, _: &[TransactionDigest]) -> Vec<Option<TransactionEvents>> {
3549            unimplemented!()
3550        }
3551
3552        fn take_accumulator_events(&self, _: &TransactionDigest) -> Option<Vec<AccumulatorEvent>> {
3553            unimplemented!()
3554        }
3555
3556        fn get_unchanged_loaded_runtime_objects(
3557            &self,
3558            _digest: &TransactionDigest,
3559        ) -> Option<Vec<sui_types::storage::ObjectKey>> {
3560            unimplemented!()
3561        }
3562
3563        fn transaction_executed_in_last_epoch(&self, _: &TransactionDigest, _: EpochId) -> bool {
3564            unimplemented!()
3565        }
3566    }
3567
3568    #[async_trait::async_trait]
3569    impl CheckpointOutput for mpsc::Sender<(CheckpointContents, CheckpointSummary)> {
3570        async fn checkpoint_created(
3571            &self,
3572            summary: &CheckpointSummary,
3573            contents: &CheckpointContents,
3574            _epoch_store: &Arc<AuthorityPerEpochStore>,
3575            _checkpoint_store: &Arc<CheckpointStore>,
3576        ) -> SuiResult {
3577            self.try_send((contents.clone(), summary.clone())).unwrap();
3578            Ok(())
3579        }
3580    }
3581
3582    #[async_trait::async_trait]
3583    impl CertifiedCheckpointOutput for mpsc::Sender<CertifiedCheckpointSummary> {
3584        async fn certified_checkpoint_created(
3585            &self,
3586            summary: &CertifiedCheckpointSummary,
3587        ) -> SuiResult {
3588            self.try_send(summary.clone()).unwrap();
3589            Ok(())
3590        }
3591    }
3592
3593    fn p(i: u64, t: Vec<u8>, timestamp_ms: u64) -> PendingCheckpoint {
3594        PendingCheckpoint {
3595            roots: vec![CheckpointRoots {
3596                tx_roots: t
3597                    .into_iter()
3598                    .map(|t| TransactionKey::Digest(d(t)))
3599                    .collect(),
3600                settlement_root: None,
3601                height: i,
3602            }],
3603            details: PendingCheckpointInfo {
3604                timestamp_ms,
3605                last_of_epoch: false,
3606                checkpoint_height: i,
3607                consensus_commit_ref: CommitRef::default(),
3608                rejected_transactions_digest: Digest::default(),
3609                checkpoint_seq: i,
3610            },
3611        }
3612    }
3613
3614    fn d(i: u8) -> TransactionDigest {
3615        let mut bytes: [u8; 32] = Default::default();
3616        bytes[0] = i;
3617        TransactionDigest::new(bytes)
3618    }
3619
3620    fn e(
3621        transaction_digest: TransactionDigest,
3622        dependencies: Vec<TransactionDigest>,
3623        gas_used: GasCostSummary,
3624    ) -> TransactionEffects {
3625        let mut effects = TransactionEffects::default();
3626        *effects.transaction_digest_mut_for_testing() = transaction_digest;
3627        *effects.dependencies_mut_for_testing() = dependencies;
3628        *effects.gas_cost_summary_mut_for_testing() = gas_used;
3629        effects
3630    }
3631
3632    fn commit_cert_for_test(
3633        store: &mut HashMap<TransactionDigest, TransactionEffects>,
3634        state: Arc<AuthorityState>,
3635        digest: TransactionDigest,
3636        dependencies: Vec<TransactionDigest>,
3637        gas_used: GasCostSummary,
3638    ) {
3639        let epoch_store = state.epoch_store_for_testing();
3640        let effects = e(digest, dependencies, gas_used);
3641        store.insert(digest, effects.clone());
3642        epoch_store.insert_executed_in_epoch(&digest);
3643    }
3644}