Skip to main content

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