Skip to main content

sui_core/authority/
authority_store_tables.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::*;
5use crate::authority::authority_store::LockDetailsWrapperDeprecated;
6#[cfg(tidehunter)]
7use crate::authority::epoch_marker_key::EPOCH_MARKER_KEY_SIZE;
8use crate::authority::epoch_marker_key::EpochMarkerKey;
9use serde::{Deserialize, Serialize};
10use std::path::Path;
11use std::sync::atomic::AtomicU64;
12use sui_types::base_types::SequenceNumber;
13use sui_types::effects::{TransactionEffects, TransactionEvents};
14use sui_types::global_state_hash::GlobalStateHash;
15use sui_types::storage::MarkerValue;
16use typed_store::metrics::SamplingInterval;
17use typed_store::rocks::{DBBatch, DBMap, MetricConf};
18#[cfg(not(tidehunter))]
19use typed_store::rocks::{DBMapTableConfigMap, DBOptions, default_db_options, read_size_from_env};
20use typed_store::traits::Map;
21
22use crate::authority::authority_store_types::{
23    StoreObject, StoreObjectValue, StoreObjectWrapper, get_store_object, try_construct_object,
24};
25use crate::authority::epoch_start_configuration::EpochStartConfiguration;
26use typed_store::{DBMapUtils, DbIterator};
27
28#[cfg(not(tidehunter))]
29const ENV_VAR_OBJECTS_BLOCK_CACHE_SIZE: &str = "OBJECTS_BLOCK_CACHE_MB";
30#[cfg(not(tidehunter))]
31pub(crate) const ENV_VAR_LOCKS_BLOCK_CACHE_SIZE: &str = "LOCKS_BLOCK_CACHE_MB";
32#[cfg(not(tidehunter))]
33const ENV_VAR_TRANSACTIONS_BLOCK_CACHE_SIZE: &str = "TRANSACTIONS_BLOCK_CACHE_MB";
34#[cfg(not(tidehunter))]
35const ENV_VAR_EFFECTS_BLOCK_CACHE_SIZE: &str = "EFFECTS_BLOCK_CACHE_MB";
36
37/// Options to apply to every column family of the `perpetual` DB.
38#[derive(Default)]
39pub struct AuthorityPerpetualTablesOptions {
40    /// Whether to enable write stalling on all column families.
41    pub enable_write_stall: bool,
42    /// On tidehunter, attach the per-keyspace objects compactor that retains
43    /// only the latest version per ObjectID. Mutually exclusive with the
44    /// object pruner — see `AuthorityStorePruner::new`.
45    pub enable_objects_compactor: bool,
46}
47
48impl AuthorityPerpetualTablesOptions {
49    #[cfg(not(tidehunter))]
50    fn apply_to(&self, mut db_options: DBOptions) -> DBOptions {
51        if !self.enable_write_stall {
52            db_options = db_options.disable_write_throttling();
53        }
54        db_options
55    }
56}
57
58/// AuthorityPerpetualTables contains data that must be preserved from one epoch to the next.
59#[derive(DBMapUtils)]
60#[cfg_attr(tidehunter, tidehunter)]
61pub struct AuthorityPerpetualTables {
62    /// This is a map between the object (ID, version) and the latest state of the object, namely the
63    /// state that is needed to process new transactions.
64    /// State is represented by `StoreObject` enum, which is either a move module or a move object.
65    ///
66    /// Note that while this map can store all versions of an object, we will eventually
67    /// prune old object versions from the db.
68    ///
69    /// IMPORTANT: object versions must *only* be pruned if they appear as inputs in some
70    /// TransactionEffects. Simply pruning all objects but the most recent is an error!
71    /// This is because there can be partially executed transactions whose effects have not yet
72    /// been written out, and which must be retried. But, they cannot be retried unless their input
73    /// objects are still accessible!
74    pub(crate) objects: DBMap<ObjectKey, StoreObjectWrapper>,
75
76    /// DEPRECATED: markers of live address-owned object refs. Nothing reads this table
77    /// anymore; writes are still maintained so that rolling back to a previous binary
78    /// finds a correctly maintained table. The table (and its writes) will be removed
79    /// entirely in a follow-up once this version is deployed everywhere.
80    #[rename = "owned_object_transaction_locks"]
81    pub(crate) live_owned_object_markers: DBMap<ObjectRef, Option<LockDetailsWrapperDeprecated>>,
82
83    /// This is a map between the transaction digest and the corresponding transaction that's known to be
84    /// executable. This means that it may have been executed locally, or it may have been synced through
85    /// state-sync but hasn't been executed yet.
86    pub(crate) transactions: DBMap<TransactionDigest, TrustedTransaction>,
87
88    /// A map between the transaction digest of a certificate to the effects of its execution.
89    /// We store effects into this table in two different cases:
90    /// 1. When a transaction is synced through state_sync, we store the effects here. These effects
91    ///    are known to be final in the network, but may not have been executed locally yet.
92    /// 2. When the transaction is executed locally on this node, we store the effects here. This means that
93    ///    it's possible to store the same effects twice (once for the synced transaction, and once for the executed).
94    ///
95    /// It's also possible for the effects to be reverted if the transaction didn't make it into the epoch.
96    pub(crate) effects: DBMap<TransactionEffectsDigest, TransactionEffects>,
97
98    /// Transactions that have been executed locally on this node. We need this table since the `effects` table
99    /// doesn't say anything about the execution status of the transaction on this node. When we wait for transactions
100    /// to be executed, we wait for them to appear in this table. When we revert transactions, we remove them from both
101    /// tables.
102    pub(crate) executed_effects: DBMap<TransactionDigest, TransactionEffectsDigest>,
103
104    // Events keyed by the digest of the transaction that produced them.
105    pub(crate) events_2: DBMap<TransactionDigest, TransactionEvents>,
106
107    // Loaded (and unchanged) runtime object references.
108    pub(crate) unchanged_loaded_runtime_objects: DBMap<TransactionDigest, Vec<ObjectKey>>,
109
110    /// DEPRECATED in favor of the table of the same name in authority_per_epoch_store.
111    /// Please do not add new accessors/callsites.
112    /// When transaction is executed via checkpoint executor, we store association here
113    pub(crate) executed_transactions_to_checkpoint:
114        DBMap<TransactionDigest, (EpochId, CheckpointSequenceNumber)>,
115
116    // Finalized root state hash for epoch, to be included in CheckpointSummary
117    // of last checkpoint of epoch. These values should only ever be written once
118    // and never changed
119    pub(crate) root_state_hash_by_epoch:
120        DBMap<EpochId, (CheckpointSequenceNumber, GlobalStateHash)>,
121
122    /// Parameters of the system fixed at the epoch start
123    pub(crate) epoch_start_configuration: DBMap<(), EpochStartConfiguration>,
124
125    /// A singleton table that stores latest pruned checkpoint. Used to keep objects pruner progress
126    pub(crate) pruned_checkpoint: DBMap<(), CheckpointSequenceNumber>,
127
128    /// Expected total amount of SUI in the network. This is expected to remain constant
129    /// throughout the lifetime of the network. We check it at the end of each epoch if
130    /// expensive checks are enabled. We cannot use 10B today because in tests we often
131    /// inject extra gas objects into genesis.
132    pub(crate) expected_network_sui_amount: DBMap<(), u64>,
133
134    /// Expected imbalance between storage fund balance and the sum of storage rebate of all live objects.
135    /// This could be non-zero due to bugs in earlier protocol versions.
136    /// This number is the result of storage_fund_balance - sum(storage_rebate).
137    pub(crate) expected_storage_fund_imbalance: DBMap<(), i64>,
138
139    /// Table that stores the set of received objects and deleted objects and the version at
140    /// which they were received. This is used to prevent possible race conditions around receiving
141    /// objects (since they are not locked by the transaction manager) and for tracking shared
142    /// objects that have been deleted. This table is meant to be pruned per-epoch, and all
143    /// previous epochs other than the current epoch may be pruned safely.
144    pub(crate) object_per_epoch_marker_table: DBMap<(EpochId, ObjectKey), MarkerValue>,
145    pub(crate) object_per_epoch_marker_table_v2: DBMap<EpochMarkerKey, MarkerValue>,
146
147    /// Tracks executed transaction digests across epochs.
148    /// Used to support address balance gas payments feature.
149    /// This table uses epoch-prefixed keys to support efficient pruning via range delete.
150    pub(crate) executed_transaction_digests: DBMap<(EpochId, TransactionDigest), ()>,
151
152    /// A singleton table recording the highest checkpoint whose transaction
153    /// outputs (objects, effects, etc.) are durably committed to this store.
154    /// It is written in the *same* atomic batch as those outputs (see
155    /// `AuthorityStore::build_db_batch` and the checkpoint executor), so it is
156    /// always consistent with the live object set even after an unclean stop.
157    ///
158    /// This differs from the checkpoint store's `highest_executed` watermark,
159    /// which is bumped in a separate write after the outputs are committed: an
160    /// abrupt stop can leave the object writes durable while `highest_executed`
161    /// still lags. Consumers that read the live object set directly (e.g. the
162    /// embedded rpc-store's bulk restore) use this watermark instead, so they
163    /// never observe objects beyond the checkpoint they think they are at.
164    ///
165    /// IMPORTANT: TideHunter keyspaces are order-sensitive once written to disk.
166    /// Keep new keyspaces append-only to preserve compatibility with existing DBs.
167    pub(crate) highest_committed_checkpoint: DBMap<(), CheckpointSequenceNumber>,
168}
169
170impl AuthorityPerpetualTables {
171    pub fn path(parent_path: &Path) -> PathBuf {
172        parent_path.join("perpetual")
173    }
174
175    #[cfg(not(tidehunter))]
176    pub fn open(
177        parent_path: &Path,
178        db_options_override: Option<AuthorityPerpetualTablesOptions>,
179        _pruner_watermark: Option<Arc<AtomicU64>>,
180    ) -> Self {
181        let db_options_override = db_options_override.unwrap_or_default();
182        let db_options = db_options_override
183            .apply_to(default_db_options().optimize_db_for_write_throughput(4, false));
184        let table_options = DBMapTableConfigMap::new(BTreeMap::from([
185            (
186                "objects".to_string(),
187                objects_table_config(db_options.clone()),
188            ),
189            (
190                "owned_object_transaction_locks".to_string(),
191                owned_object_transaction_locks_table_config(db_options.clone()),
192            ),
193            (
194                "transactions".to_string(),
195                transactions_table_config(db_options.clone()),
196            ),
197            (
198                "effects".to_string(),
199                effects_table_config(db_options.clone()),
200            ),
201        ]));
202
203        Self::open_tables_read_write(
204            Self::path(parent_path),
205            MetricConf::new("perpetual")
206                .with_sampling(SamplingInterval::new(Duration::from_secs(60), 0)),
207            Some(db_options.options),
208            Some(table_options),
209        )
210    }
211
212    #[cfg(tidehunter)]
213    pub fn open(
214        parent_path: &Path,
215        db_options_override: Option<AuthorityPerpetualTablesOptions>,
216        pruner_watermark: Option<Arc<AtomicU64>>,
217    ) -> Self {
218        use crate::authority::authority_store_pruner::apply_relocation_filter;
219        tracing::warn!("AuthorityPerpetualTables using tidehunter");
220        use typed_store::tidehunter_util::{
221            Bytes, Decision, KeyIndexing, KeySpaceConfig, KeyType, ThConfig,
222            default_cells_per_mutex, default_max_dirty_keys, default_mutex_count,
223            default_value_cache_size,
224        };
225        let mutexes = default_mutex_count() * 2;
226        let transaction_mutexes = mutexes * 4;
227        let value_cache_size = default_value_cache_size();
228        // effectively disables pruning if not set
229        let pruner_watermark = pruner_watermark.unwrap_or(Arc::new(AtomicU64::new(0)));
230
231        let bloom_config = KeySpaceConfig::new().with_bloom_filter(0.001, 32_000);
232        let objects_compactor = |iter: &mut dyn DoubleEndedIterator<Item = &Bytes>| {
233            let mut retain = HashSet::new();
234            let mut previous: Option<&[u8]> = None;
235            const OID_SIZE: usize = 32;
236            for key in iter.rev() {
237                if let Some(prev) = previous
238                    && prev == &key[..OID_SIZE]
239                {
240                    continue;
241                }
242                previous = Some(&key[..OID_SIZE]);
243                retain.insert(key.clone());
244            }
245            retain
246        };
247        let mut digest_prefix = vec![0; 8];
248        digest_prefix[7] = 32;
249        let uniform_key = KeyType::uniform(default_cells_per_mutex());
250        let epoch_prefix_key = KeyType::from_prefix_bits(9 * 8 + 4);
251        // TransactionDigest is serialized with an 8-byte prefix, so we include it in the key calculation
252        let epoch_tx_digest_prefix_key =
253            KeyType::from_prefix_bits((8/*EpochId*/ + 8/*TransactionDigest prefix*/) * 8 + 12);
254        let object_indexing = KeyIndexing::fixed(32 + 8); //  KeyIndexing::key_reduction(32 + 8, 16..(32 + 8));
255        // todo can figure way to scramble off 8 bytes in the middle
256        let obj_ref_size = 32 + 8 + 32 + 8;
257        let owned_object_transaction_locks_indexing =
258            KeyIndexing::key_reduction(obj_ref_size, 16..(obj_ref_size - 16));
259
260        let mut objects_config = KeySpaceConfig::new()
261            .with_max_dirty_keys(16 * default_max_dirty_keys())
262            .with_value_cache_size(value_cache_size);
263        if matches!(db_options_override, Some(options) if options.enable_objects_compactor) {
264            objects_config = objects_config.with_compactor(Box::new(objects_compactor));
265        }
266
267        let configs = vec![
268            (
269                "objects".to_string(),
270                ThConfig::new_with_config_indexing(
271                    object_indexing,
272                    mutexes * 4,
273                    KeyType::uniform(1),
274                    objects_config,
275                ),
276            ),
277            (
278                "owned_object_transaction_locks".to_string(),
279                ThConfig::new_with_config_indexing(
280                    owned_object_transaction_locks_indexing,
281                    mutexes * 16,
282                    KeyType::uniform(default_cells_per_mutex()),
283                    bloom_config
284                        .clone()
285                        .with_max_dirty_keys(16 * default_max_dirty_keys()),
286                ),
287            ),
288            (
289                "transactions".to_string(),
290                ThConfig::new_with_rm_prefix_indexing(
291                    KeyIndexing::key_reduction(32, 0..16),
292                    transaction_mutexes,
293                    uniform_key,
294                    KeySpaceConfig::new()
295                        .with_value_cache_size(value_cache_size)
296                        .with_relocation_filter(|_, _| Decision::Remove),
297                    digest_prefix.clone(),
298                ),
299            ),
300            (
301                "effects".to_string(),
302                ThConfig::new_with_rm_prefix_indexing(
303                    KeyIndexing::key_reduction(32, 0..16),
304                    transaction_mutexes,
305                    uniform_key,
306                    apply_relocation_filter(
307                        bloom_config.clone().with_value_cache_size(value_cache_size),
308                        pruner_watermark.clone(),
309                        |effects: TransactionEffects| effects.executed_epoch(),
310                        false,
311                    ),
312                    digest_prefix.clone(),
313                ),
314            ),
315            (
316                "executed_effects".to_string(),
317                ThConfig::new_with_rm_prefix_indexing(
318                    KeyIndexing::key_reduction(32, 0..16),
319                    transaction_mutexes,
320                    uniform_key,
321                    bloom_config
322                        .clone()
323                        .with_value_cache_size(value_cache_size)
324                        .with_relocation_filter(|_, _| Decision::Remove),
325                    digest_prefix.clone(),
326                ),
327            ),
328            (
329                "events".to_string(),
330                ThConfig::new_with_rm_prefix(
331                    32 + 8,
332                    mutexes,
333                    uniform_key,
334                    KeySpaceConfig::default().with_relocation_filter(|_, _| Decision::Remove),
335                    digest_prefix.clone(),
336                ),
337            ),
338            (
339                "events_2".to_string(),
340                ThConfig::new_with_rm_prefix(
341                    32,
342                    mutexes,
343                    uniform_key,
344                    KeySpaceConfig::default().with_relocation_filter(|_, _| Decision::Remove),
345                    digest_prefix.clone(),
346                ),
347            ),
348            (
349                "unchanged_loaded_runtime_objects".to_string(),
350                ThConfig::new_with_rm_prefix(
351                    32,
352                    mutexes,
353                    uniform_key,
354                    KeySpaceConfig::default().with_relocation_filter(|_, _| Decision::Remove),
355                    digest_prefix.clone(),
356                ),
357            ),
358            (
359                "executed_transactions_to_checkpoint".to_string(),
360                ThConfig::new_with_rm_prefix(
361                    32,
362                    mutexes,
363                    uniform_key,
364                    apply_relocation_filter(
365                        KeySpaceConfig::default(),
366                        pruner_watermark.clone(),
367                        |(epoch_id, _): (EpochId, CheckpointSequenceNumber)| epoch_id,
368                        false,
369                    ),
370                    digest_prefix.clone(),
371                ),
372            ),
373            (
374                "root_state_hash_by_epoch".to_string(),
375                ThConfig::new(8, 1, KeyType::uniform(1)),
376            ),
377            (
378                "epoch_start_configuration".to_string(),
379                ThConfig::new(0, 1, KeyType::uniform(1)),
380            ),
381            (
382                "pruned_checkpoint".to_string(),
383                ThConfig::new(0, 1, KeyType::uniform(1)),
384            ),
385            (
386                "expected_network_sui_amount".to_string(),
387                ThConfig::new(0, 1, KeyType::uniform(1)),
388            ),
389            (
390                "expected_storage_fund_imbalance".to_string(),
391                ThConfig::new(0, 1, KeyType::uniform(1)),
392            ),
393            (
394                "object_per_epoch_marker_table".to_string(),
395                ThConfig::new_with_config_indexing(
396                    KeyIndexing::VariableLength,
397                    mutexes,
398                    epoch_prefix_key,
399                    apply_relocation_filter(
400                        KeySpaceConfig::default(),
401                        pruner_watermark.clone(),
402                        |(epoch_id, _): (EpochId, ObjectKey)| epoch_id,
403                        true,
404                    ),
405                ),
406            ),
407            (
408                "object_per_epoch_marker_table_v2".to_string(),
409                ThConfig::new_with_config_indexing(
410                    KeyIndexing::fixed(EPOCH_MARKER_KEY_SIZE),
411                    mutexes,
412                    epoch_prefix_key,
413                    apply_relocation_filter(
414                        bloom_config.clone(),
415                        pruner_watermark.clone(),
416                        |k: EpochMarkerKey| k.0,
417                        true,
418                    ),
419                ),
420            ),
421            (
422                "executed_transaction_digests".to_string(),
423                ThConfig::new_with_config_indexing(
424                    // EpochId + (TransactionDigest)
425                    KeyIndexing::fixed(8 + (32 + 8)),
426                    transaction_mutexes,
427                    epoch_tx_digest_prefix_key,
428                    apply_relocation_filter(
429                        bloom_config.clone(),
430                        pruner_watermark.clone(),
431                        |(epoch_id, _): (EpochId, TransactionDigest)| epoch_id,
432                        true,
433                    ),
434                ),
435            ),
436            (
437                "highest_committed_checkpoint".to_string(),
438                ThConfig::new(0, 1, KeyType::uniform(1)),
439            ),
440        ];
441        Self::open_tables_read_write(
442            Self::path(parent_path),
443            MetricConf::new("perpetual")
444                .with_sampling(SamplingInterval::new(Duration::from_secs(60), 0)),
445            configs.into_iter().collect(),
446        )
447    }
448
449    #[cfg(not(tidehunter))]
450    pub fn open_readonly(parent_path: &Path) -> AuthorityPerpetualTablesReadOnly {
451        Self::get_read_only_handle(
452            Self::path(parent_path),
453            None,
454            None,
455            MetricConf::new("perpetual_readonly"),
456        )
457    }
458
459    #[cfg(tidehunter)]
460    pub fn open_readonly(parent_path: &Path) -> Self {
461        Self::open(parent_path, None, None)
462    }
463
464    #[cfg(tidehunter)]
465    pub fn force_rebuild_control_region(&self) -> anyhow::Result<()> {
466        self.objects.db.force_rebuild_control_region()
467    }
468
469    /// Wait for tidehunter background threads to finish before allowing the caller
470    /// to e.g. rename the database directory. Consumes `Arc<Self>` so all internal
471    /// `Arc<Database>` clones held by the column family `DBMap`s are released as
472    /// part of the drop, leaving only the `Arc<Database>` extracted here.
473    #[cfg(tidehunter)]
474    pub fn wait_for_tidehunter_background_threads(self: Arc<Self>) {
475        let strong = Arc::strong_count(&self);
476        if strong != 1 {
477            println!(
478                "WARNING: wait_for_tidehunter_background_threads called with Arc<AuthorityPerpetualTables> strong_count={} (expected 1); other clones will keep DBMap.db Arc<Database> alive past drop(self) and the inner Database wait will warn/timeout",
479                strong,
480            );
481        }
482        let db = self.objects.db.clone();
483        drop(self);
484        db.wait_for_tidehunter_background_threads();
485    }
486
487    // This is used by indexer to find the correct version of dynamic field child object.
488    // We do not store the version of the child object, but because of lamport timestamp,
489    // we know the child must have version number less then or eq to the parent.
490    pub fn find_object_lt_or_eq_version(
491        &self,
492        object_id: ObjectID,
493        version: SequenceNumber,
494    ) -> SuiResult<Option<Object>> {
495        let mut iter = self.objects.reversed_safe_iter_with_bounds(
496            Some(ObjectKey::min_for_id(&object_id)),
497            Some(ObjectKey(object_id, version)),
498        )?;
499        match iter.next() {
500            Some(Ok((key, o))) => self.object(&key, o),
501            Some(Err(e)) => Err(e.into()),
502            None => Ok(None),
503        }
504    }
505
506    fn construct_object(
507        &self,
508        object_key: &ObjectKey,
509        store_object: StoreObjectValue,
510    ) -> Result<Object, SuiError> {
511        try_construct_object(object_key, store_object)
512    }
513
514    // Constructs `sui_types::object::Object` from `StoreObjectWrapper`.
515    // Returns `None` if object was deleted/wrapped
516    pub fn object(
517        &self,
518        object_key: &ObjectKey,
519        store_object: StoreObjectWrapper,
520    ) -> Result<Option<Object>, SuiError> {
521        let StoreObject::Value(store_object) = store_object.migrate().into_inner() else {
522            return Ok(None);
523        };
524        Ok(Some(self.construct_object(object_key, *store_object)?))
525    }
526
527    pub fn object_reference(
528        &self,
529        object_key: &ObjectKey,
530        store_object: StoreObjectWrapper,
531    ) -> Result<ObjectRef, SuiError> {
532        let obj_ref = match store_object.migrate().into_inner() {
533            StoreObject::Value(object) => self
534                .construct_object(object_key, *object)?
535                .compute_object_reference(),
536            StoreObject::Deleted => (
537                object_key.0,
538                object_key.1,
539                ObjectDigest::OBJECT_DIGEST_DELETED,
540            ),
541            StoreObject::Wrapped => (
542                object_key.0,
543                object_key.1,
544                ObjectDigest::OBJECT_DIGEST_WRAPPED,
545            ),
546        };
547        Ok(obj_ref)
548    }
549
550    pub fn tombstone_reference(
551        &self,
552        object_key: &ObjectKey,
553        store_object: &StoreObjectWrapper,
554    ) -> Result<Option<ObjectRef>, SuiError> {
555        let obj_ref = match store_object.inner() {
556            StoreObject::Deleted => Some((
557                object_key.0,
558                object_key.1,
559                ObjectDigest::OBJECT_DIGEST_DELETED,
560            )),
561            StoreObject::Wrapped => Some((
562                object_key.0,
563                object_key.1,
564                ObjectDigest::OBJECT_DIGEST_WRAPPED,
565            )),
566            _ => None,
567        };
568        Ok(obj_ref)
569    }
570
571    pub fn get_latest_object_ref_or_tombstone(
572        &self,
573        object_id: ObjectID,
574    ) -> Result<Option<ObjectRef>, SuiError> {
575        let mut iterator = self.objects.reversed_safe_iter_with_bounds(
576            Some(ObjectKey::min_for_id(&object_id)),
577            Some(ObjectKey::max_for_id(&object_id)),
578        )?;
579
580        if let Some(Ok((object_key, value))) = iterator.next()
581            && object_key.0 == object_id
582        {
583            return Ok(Some(self.object_reference(&object_key, value)?));
584        }
585        Ok(None)
586    }
587
588    pub fn get_latest_object_or_tombstone(
589        &self,
590        object_id: ObjectID,
591    ) -> Result<Option<(ObjectKey, StoreObjectWrapper)>, SuiError> {
592        let mut iterator = self.objects.reversed_safe_iter_with_bounds(
593            Some(ObjectKey::min_for_id(&object_id)),
594            Some(ObjectKey::max_for_id(&object_id)),
595        )?;
596
597        if let Some(Ok((object_key, value))) = iterator.next()
598            && object_key.0 == object_id
599        {
600            return Ok(Some((object_key, value)));
601        }
602        Ok(None)
603    }
604
605    pub fn get_recovery_epoch_at_restart(&self) -> SuiResult<EpochId> {
606        Ok(self
607            .epoch_start_configuration
608            .get(&())?
609            .expect("Must have current epoch.")
610            .epoch_start_state()
611            .epoch())
612    }
613
614    pub fn set_epoch_start_configuration(
615        &self,
616        epoch_start_configuration: &EpochStartConfiguration,
617    ) -> SuiResult {
618        let mut wb = self.epoch_start_configuration.batch();
619        wb.insert_batch(
620            &self.epoch_start_configuration,
621            std::iter::once(((), epoch_start_configuration)),
622        )?;
623        wb.write()?;
624        Ok(())
625    }
626
627    pub fn get_highest_pruned_checkpoint(
628        &self,
629    ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
630        self.pruned_checkpoint.get(&())
631    }
632
633    pub fn set_highest_pruned_checkpoint(
634        &self,
635        wb: &mut DBBatch,
636        checkpoint_number: CheckpointSequenceNumber,
637    ) -> SuiResult {
638        wb.insert_batch(&self.pruned_checkpoint, [((), checkpoint_number)])?;
639        Ok(())
640    }
641
642    pub fn get_highest_committed_checkpoint(
643        &self,
644    ) -> Result<Option<CheckpointSequenceNumber>, TypedStoreError> {
645        self.highest_committed_checkpoint.get(&())
646    }
647
648    /// Stage the highest-committed-checkpoint watermark into `wb`. Must be
649    /// called on the same batch that commits the checkpoint's transaction
650    /// outputs so the watermark and the outputs land atomically.
651    pub fn set_highest_committed_checkpoint(
652        &self,
653        wb: &mut DBBatch,
654        checkpoint_number: CheckpointSequenceNumber,
655    ) -> SuiResult {
656        wb.insert_batch(
657            &self.highest_committed_checkpoint,
658            [((), checkpoint_number)],
659        )?;
660        Ok(())
661    }
662
663    pub fn get_transaction(
664        &self,
665        digest: &TransactionDigest,
666    ) -> SuiResult<Option<TrustedTransaction>> {
667        let Some(transaction) = self.transactions.get(digest)? else {
668            return Ok(None);
669        };
670        Ok(Some(transaction))
671    }
672
673    pub fn list_transactions_from(
674        &self,
675        start: Option<TransactionDigest>,
676        limit: usize,
677    ) -> Result<Vec<TransactionDigest>, typed_store::TypedStoreError> {
678        let iter = self.transactions.safe_iter_with_bounds(start, None);
679        let mut result = Vec::with_capacity(limit);
680        for item in iter.take(limit) {
681            let (digest, _) = item?;
682            result.push(digest);
683        }
684        Ok(result)
685    }
686
687    pub fn get_executed_effects_digest(
688        &self,
689        tx_digest: &TransactionDigest,
690    ) -> Result<Option<TransactionEffectsDigest>, typed_store::TypedStoreError> {
691        self.executed_effects.get(tx_digest)
692    }
693
694    pub fn get_effects_by_digest(
695        &self,
696        effects_digest: &TransactionEffectsDigest,
697    ) -> Result<Option<TransactionEffects>, typed_store::TypedStoreError> {
698        self.effects.get(effects_digest)
699    }
700
701    /// Batch insert executed transaction digests for a given epoch.
702    /// Used by formal snapshot restore to backfill transaction digests from the previous epoch.
703    pub fn insert_executed_transaction_digests_batch(
704        &self,
705        epoch: EpochId,
706        digests: impl Iterator<Item = TransactionDigest>,
707    ) -> SuiResult {
708        let mut batch = self.executed_transaction_digests.batch();
709        batch.insert_batch(
710            &self.executed_transaction_digests,
711            digests.map(|digest| ((epoch, digest), ())),
712        )?;
713        batch.write()?;
714        Ok(())
715    }
716
717    pub fn get_effects(&self, digest: &TransactionDigest) -> SuiResult<Option<TransactionEffects>> {
718        let Some(effect_digest) = self.executed_effects.get(digest)? else {
719            return Ok(None);
720        };
721        Ok(self.effects.get(&effect_digest)?)
722    }
723
724    pub(crate) fn was_transaction_executed_in_last_epoch(
725        &self,
726        digest: &TransactionDigest,
727        current_epoch: EpochId,
728    ) -> bool {
729        if current_epoch == 0 {
730            return false;
731        }
732        self.executed_transaction_digests
733            .contains_key(&(current_epoch - 1, *digest))
734            .expect("db error")
735    }
736
737    // DEPRECATED as the backing table has been moved to authority_per_epoch_store.
738    // Please do not add new accessors/callsites.
739    pub fn get_checkpoint_sequence_number(
740        &self,
741        digest: &TransactionDigest,
742    ) -> SuiResult<Option<(EpochId, CheckpointSequenceNumber)>> {
743        Ok(self.executed_transactions_to_checkpoint.get(digest)?)
744    }
745
746    pub fn set_highest_pruned_checkpoint_without_wb(
747        &self,
748        checkpoint_number: CheckpointSequenceNumber,
749    ) -> SuiResult {
750        let mut wb = self.pruned_checkpoint.batch();
751        self.set_highest_pruned_checkpoint(&mut wb, checkpoint_number)?;
752        wb.write()?;
753        Ok(())
754    }
755
756    pub fn database_is_empty(&self) -> SuiResult<bool> {
757        Ok(self.objects.safe_iter().next().is_none())
758    }
759
760    pub fn iter_live_object_set(&self, include_wrapped_object: bool) -> LiveSetIter<'_> {
761        LiveSetIter {
762            iter: Box::new(self.objects.safe_iter()),
763            tables: self,
764            prev: None,
765            include_wrapped_object,
766        }
767    }
768
769    pub fn range_iter_live_object_set(
770        &self,
771        lower_bound: Option<ObjectID>,
772        upper_bound: Option<ObjectID>,
773        include_wrapped_object: bool,
774    ) -> LiveSetIter<'_> {
775        let lower_bound = lower_bound.as_ref().map(ObjectKey::min_for_id);
776        let upper_bound = upper_bound.as_ref().map(ObjectKey::max_for_id);
777
778        LiveSetIter {
779            iter: Box::new(self.objects.safe_iter_with_bounds(lower_bound, upper_bound)),
780            tables: self,
781            prev: None,
782            include_wrapped_object,
783        }
784    }
785
786    pub fn checkpoint_db(&self, path: &Path) -> SuiResult {
787        // This checkpoints the entire db and not just objects table
788        self.objects.checkpoint_db(path).map_err(Into::into)
789    }
790
791    pub fn insert_root_state_hash(
792        &self,
793        epoch: EpochId,
794        last_checkpoint_of_epoch: CheckpointSequenceNumber,
795        hash: GlobalStateHash,
796    ) -> SuiResult {
797        self.root_state_hash_by_epoch
798            .insert(&epoch, &(last_checkpoint_of_epoch, hash))?;
799        Ok(())
800    }
801
802    pub fn insert_object_test_only(&self, object: Object) -> SuiResult {
803        let object_reference = object.compute_object_reference();
804        let wrapper = get_store_object(object);
805        let mut wb = self.objects.batch();
806        wb.insert_batch(
807            &self.objects,
808            std::iter::once((ObjectKey::from(object_reference), wrapper)),
809        )?;
810        wb.write()?;
811        Ok(())
812    }
813
814    // fallible get object methods for sui-tool, which may need to attempt to read a corrupted database
815    pub fn get_object_fallible(&self, object_id: &ObjectID) -> SuiResult<Option<Object>> {
816        let obj_entry = self
817            .objects
818            .reversed_safe_iter_with_bounds(None, Some(ObjectKey::max_for_id(object_id)))?
819            .next();
820
821        match obj_entry.transpose()? {
822            Some((ObjectKey(obj_id, version), obj)) if obj_id == *object_id => {
823                Ok(self.object(&ObjectKey(obj_id, version), obj)?)
824            }
825            _ => Ok(None),
826        }
827    }
828
829    pub fn get_object_by_key_fallible(
830        &self,
831        object_id: &ObjectID,
832        version: VersionNumber,
833    ) -> SuiResult<Option<Object>> {
834        Ok(self
835            .objects
836            .get(&ObjectKey(*object_id, version))?
837            .and_then(|object| {
838                self.object(&ObjectKey(*object_id, version), object)
839                    .expect("object construction error")
840            }))
841    }
842}
843
844impl ObjectStore for AuthorityPerpetualTables {
845    /// Read an object and return it, or Ok(None) if the object was not found.
846    fn get_object(&self, object_id: &ObjectID) -> Option<Object> {
847        self.get_object_fallible(object_id).expect("db error")
848    }
849
850    fn get_object_by_key(&self, object_id: &ObjectID, version: VersionNumber) -> Option<Object> {
851        self.get_object_by_key_fallible(object_id, version)
852            .expect("db error")
853    }
854}
855
856pub struct LiveSetIter<'a> {
857    iter: DbIterator<'a, (ObjectKey, StoreObjectWrapper)>,
858    tables: &'a AuthorityPerpetualTables,
859    prev: Option<(ObjectKey, StoreObjectWrapper)>,
860    /// Whether a wrapped object is considered as a live object.
861    include_wrapped_object: bool,
862}
863
864#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)]
865pub enum LiveObject {
866    Normal(Object),
867    Wrapped(ObjectKey),
868}
869
870impl LiveObject {
871    pub fn object_id(&self) -> ObjectID {
872        match self {
873            LiveObject::Normal(obj) => obj.id(),
874            LiveObject::Wrapped(key) => key.0,
875        }
876    }
877
878    pub fn version(&self) -> SequenceNumber {
879        match self {
880            LiveObject::Normal(obj) => obj.version(),
881            LiveObject::Wrapped(key) => key.1,
882        }
883    }
884
885    pub fn object_reference(&self) -> ObjectRef {
886        match self {
887            LiveObject::Normal(obj) => obj.compute_object_reference(),
888            LiveObject::Wrapped(key) => (key.0, key.1, ObjectDigest::OBJECT_DIGEST_WRAPPED),
889        }
890    }
891}
892
893impl LiveSetIter<'_> {
894    fn store_object_wrapper_to_live_object(
895        &self,
896        object_key: ObjectKey,
897        store_object: StoreObjectWrapper,
898    ) -> Option<LiveObject> {
899        match store_object.migrate().into_inner() {
900            StoreObject::Value(object) => {
901                let object = self
902                    .tables
903                    .construct_object(&object_key, *object)
904                    .expect("Constructing object from store cannot fail");
905                Some(LiveObject::Normal(object))
906            }
907            StoreObject::Wrapped => {
908                if self.include_wrapped_object {
909                    Some(LiveObject::Wrapped(object_key))
910                } else {
911                    None
912                }
913            }
914            StoreObject::Deleted => None,
915        }
916    }
917}
918
919impl Iterator for LiveSetIter<'_> {
920    type Item = LiveObject;
921
922    fn next(&mut self) -> Option<Self::Item> {
923        loop {
924            if let Some(Ok((next_key, next_value))) = self.iter.next() {
925                let prev = self.prev.take();
926                self.prev = Some((next_key, next_value));
927
928                if let Some((prev_key, prev_value)) = prev
929                    && prev_key.0 != next_key.0
930                {
931                    let live_object =
932                        self.store_object_wrapper_to_live_object(prev_key, prev_value);
933                    if live_object.is_some() {
934                        return live_object;
935                    }
936                }
937                continue;
938            }
939            if let Some((key, value)) = self.prev.take() {
940                let live_object = self.store_object_wrapper_to_live_object(key, value);
941                if live_object.is_some() {
942                    return live_object;
943                }
944            }
945            return None;
946        }
947    }
948}
949
950// These functions are used to initialize the DB tables
951#[cfg(not(tidehunter))]
952fn owned_object_transaction_locks_table_config(db_options: DBOptions) -> DBOptions {
953    DBOptions {
954        options: db_options
955            .clone()
956            .optimize_for_write_throughput()
957            .optimize_for_read(read_size_from_env(ENV_VAR_LOCKS_BLOCK_CACHE_SIZE).unwrap_or(1024))
958            .options,
959        rw_options: db_options.rw_options.set_ignore_range_deletions(false),
960    }
961}
962
963#[cfg(not(tidehunter))]
964fn objects_table_config(db_options: DBOptions) -> DBOptions {
965    db_options
966        .optimize_for_write_throughput()
967        .optimize_for_read(read_size_from_env(ENV_VAR_OBJECTS_BLOCK_CACHE_SIZE).unwrap_or(5 * 1024))
968}
969
970#[cfg(not(tidehunter))]
971fn transactions_table_config(db_options: DBOptions) -> DBOptions {
972    db_options
973        .optimize_for_write_throughput()
974        .optimize_for_point_lookup(
975            read_size_from_env(ENV_VAR_TRANSACTIONS_BLOCK_CACHE_SIZE).unwrap_or(512),
976        )
977}
978
979#[cfg(not(tidehunter))]
980fn effects_table_config(db_options: DBOptions) -> DBOptions {
981    db_options
982        .optimize_for_write_throughput()
983        .optimize_for_point_lookup(
984            read_size_from_env(ENV_VAR_EFFECTS_BLOCK_CACHE_SIZE).unwrap_or(1024),
985        )
986}