Skip to main content

iota_core/checkpoints/
mod.rs

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