Skip to main content

sui_core/authority/
authority_store.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::Arc;
5use std::{iter, mem, thread};
6
7use crate::authority::authority_per_epoch_store::AuthorityPerEpochStore;
8use crate::authority::authority_store_pruner::{
9    AuthorityStorePruner, AuthorityStorePruningMetrics, EPOCH_DURATION_MS_FOR_TESTING,
10};
11use crate::authority::authority_store_types::{StoreObject, StoreObjectWrapper, get_store_object};
12use crate::authority::epoch_marker_key::EpochMarkerKey;
13use crate::authority::epoch_start_configuration::{EpochFlag, EpochStartConfiguration};
14use crate::global_state_hasher::GlobalStateHashStore;
15use crate::transaction_outputs::TransactionOutputs;
16use fastcrypto::hash::{HashFunction, MultisetHash, Sha3_256};
17use futures::stream::FuturesUnordered;
18use move_core_types::account_address::AccountAddress;
19use move_core_types::resolver::{ModuleResolver, SerializedPackage};
20use sui_config::node::AuthorityStorePruningConfig;
21use sui_macros::fail_point_arg;
22use sui_package_resolver::{PackageStoreWithLruCache, SyncResolver};
23use sui_types::execution::TypeLayoutStore;
24use sui_types::global_state_hash::GlobalStateHash;
25use sui_types::message_envelope::Message;
26use sui_types::storage::{
27    BackingPackageStore, FullObjectKey, MarkerValue, ObjectKey, ObjectOrTombstone, ObjectStore,
28    get_module, get_package,
29};
30use sui_types::sui_system_state::get_sui_system_state;
31use sui_types::{base_types::SequenceNumber, fp_ensure};
32use tokio::time::Instant;
33use tracing::{debug, info, trace};
34use typed_store::traits::Map;
35use typed_store::{TypedStoreError, rocks::DBBatch};
36
37use super::authority_store_tables::LiveObject;
38use super::{authority_store_tables::AuthorityPerpetualTables, *};
39use mysten_common::sync::notify_read::NotifyRead;
40use sui_types::effects::{TransactionEffects, TransactionEvents};
41use sui_types::gas_coin::TOTAL_SUPPLY_MIST;
42
43struct AuthorityStoreMetrics {
44    sui_conservation_check_latency: IntGauge,
45    sui_conservation_live_object_count: IntGauge,
46    sui_conservation_live_object_size: IntGauge,
47    sui_conservation_imbalance: IntGauge,
48    sui_conservation_storage_fund: IntGauge,
49    sui_conservation_storage_fund_imbalance: IntGauge,
50    epoch_flags: IntGaugeVec,
51}
52
53impl AuthorityStoreMetrics {
54    pub fn new(registry: &Registry) -> Self {
55        Self {
56            sui_conservation_check_latency: register_int_gauge_with_registry!(
57                "sui_conservation_check_latency",
58                "Number of seconds took to scan all live objects in the store for SUI conservation check",
59                registry,
60            ).unwrap(),
61            sui_conservation_live_object_count: register_int_gauge_with_registry!(
62                "sui_conservation_live_object_count",
63                "Number of live objects in the store",
64                registry,
65            ).unwrap(),
66            sui_conservation_live_object_size: register_int_gauge_with_registry!(
67                "sui_conservation_live_object_size",
68                "Size in bytes of live objects in the store",
69                registry,
70            ).unwrap(),
71            sui_conservation_imbalance: register_int_gauge_with_registry!(
72                "sui_conservation_imbalance",
73                "Total amount of SUI in the network - 10B * 10^9. This delta shows the amount of imbalance",
74                registry,
75            ).unwrap(),
76            sui_conservation_storage_fund: register_int_gauge_with_registry!(
77                "sui_conservation_storage_fund",
78                "Storage Fund pool balance (only includes the storage fund proper that represents object storage)",
79                registry,
80            ).unwrap(),
81            sui_conservation_storage_fund_imbalance: register_int_gauge_with_registry!(
82                "sui_conservation_storage_fund_imbalance",
83                "Imbalance of storage fund, computed with storage_fund_balance - total_object_storage_rebates",
84                registry,
85            ).unwrap(),
86            epoch_flags: register_int_gauge_vec_with_registry!(
87                "epoch_flags",
88                "Local flags of the currently running epoch",
89                &["flag"],
90                registry,
91            ).unwrap(),
92        }
93    }
94}
95
96/// ALL_OBJ_VER determines whether we want to store all past
97/// versions of every object in the store. Authority doesn't store
98/// them, but other entities such as replicas will.
99/// S is a template on Authority signature state. This allows SuiDataStore to be used on either
100/// authorities or non-authorities. Specifically, when storing transactions and effects,
101/// S allows SuiDataStore to either store the authority signed version or unsigned version.
102pub struct AuthorityStore {
103    pub(crate) perpetual_tables: Arc<AuthorityPerpetualTables>,
104
105    pub(crate) root_state_notify_read:
106        NotifyRead<EpochId, (CheckpointSequenceNumber, GlobalStateHash)>,
107
108    /// Whether to enable expensive SUI conservation check at epoch boundaries.
109    enable_epoch_sui_conservation_check: bool,
110
111    metrics: AuthorityStoreMetrics,
112}
113
114pub type ExecutionLockReadGuard<'a> = tokio::sync::RwLockReadGuard<'a, EpochId>;
115pub type ExecutionLockWriteGuard<'a> = tokio::sync::RwLockWriteGuard<'a, EpochId>;
116
117impl AuthorityStore {
118    /// Open an authority store by directory path.
119    /// If the store is empty, initialize it using genesis.
120    pub async fn open(
121        perpetual_tables: Arc<AuthorityPerpetualTables>,
122        genesis: &Genesis,
123        config: &NodeConfig,
124        registry: &Registry,
125    ) -> SuiResult<Arc<Self>> {
126        let enable_epoch_sui_conservation_check = config
127            .expensive_safety_check_config
128            .enable_epoch_sui_conservation_check();
129
130        let epoch_start_configuration = if perpetual_tables.database_is_empty()? {
131            info!("Creating new epoch start config from genesis");
132
133            #[allow(unused_mut)]
134            let mut initial_epoch_flags = EpochFlag::default_flags_for_new_epoch(config);
135            fail_point_arg!("initial_epoch_flags", |flags: Vec<EpochFlag>| {
136                info!("Setting initial epoch flags to {:?}", flags);
137                initial_epoch_flags = flags;
138            });
139
140            let epoch_start_configuration = EpochStartConfiguration::new(
141                genesis.sui_system_object().into_epoch_start_state(),
142                *genesis.checkpoint().digest(),
143                &genesis.objects(),
144                initial_epoch_flags,
145            )?;
146            perpetual_tables.set_epoch_start_configuration(&epoch_start_configuration)?;
147            epoch_start_configuration
148        } else {
149            info!("Loading epoch start config from DB");
150            perpetual_tables
151                .epoch_start_configuration
152                .get(&())?
153                .expect("Epoch start configuration must be set in non-empty DB")
154        };
155        let cur_epoch = perpetual_tables.get_recovery_epoch_at_restart()?;
156        info!("Epoch start config: {:?}", epoch_start_configuration);
157        info!("Cur epoch: {:?}", cur_epoch);
158        let this = Self::open_inner(
159            genesis,
160            perpetual_tables,
161            enable_epoch_sui_conservation_check,
162            registry,
163        )
164        .await?;
165        this.update_epoch_flags_metrics(&[], epoch_start_configuration.flags());
166        Ok(this)
167    }
168
169    pub fn update_epoch_flags_metrics(&self, old: &[EpochFlag], new: &[EpochFlag]) {
170        for flag in old {
171            self.metrics
172                .epoch_flags
173                .with_label_values(&[&flag.to_string()])
174                .set(0);
175        }
176        for flag in new {
177            self.metrics
178                .epoch_flags
179                .with_label_values(&[&flag.to_string()])
180                .set(1);
181        }
182    }
183
184    // NB: This must only be called at time of reconfiguration. We take the execution lock write
185    // guard as an argument to ensure that this is the case.
186    pub fn clear_object_per_epoch_marker_table(
187        &self,
188        _execution_guard: &ExecutionLockWriteGuard<'_>,
189    ) -> SuiResult<()> {
190        // We can safely delete all entries in the per epoch marker table since this is only called
191        // at epoch boundaries (during reconfiguration). Therefore any entries that currently
192        // exist can be removed. Because of this we can use the `schedule_delete_all` method.
193        self.perpetual_tables
194            .object_per_epoch_marker_table
195            .schedule_delete_all()?;
196        #[cfg(not(tidehunter))]
197        {
198            self.perpetual_tables
199                .object_per_epoch_marker_table_v2
200                .schedule_delete_all()?;
201        }
202        #[cfg(tidehunter)]
203        {
204            self.perpetual_tables
205                .object_per_epoch_marker_table_v2
206                .drop_cells_in_range_raw(&EpochMarkerKey::MIN_KEY, &EpochMarkerKey::MAX_KEY)?;
207        }
208        Ok(())
209    }
210
211    pub async fn open_with_committee_for_testing(
212        perpetual_tables: Arc<AuthorityPerpetualTables>,
213        committee: &Committee,
214        genesis: &Genesis,
215    ) -> SuiResult<Arc<Self>> {
216        // TODO: Since we always start at genesis, the committee should be technically the same
217        // as the genesis committee.
218        assert_eq!(committee.epoch, 0);
219        Self::open_inner(genesis, perpetual_tables, true, &Registry::new()).await
220    }
221
222    async fn open_inner(
223        genesis: &Genesis,
224        perpetual_tables: Arc<AuthorityPerpetualTables>,
225        enable_epoch_sui_conservation_check: bool,
226        registry: &Registry,
227    ) -> SuiResult<Arc<Self>> {
228        let store = Arc::new(Self {
229            perpetual_tables,
230            root_state_notify_read: NotifyRead::<
231                EpochId,
232                (CheckpointSequenceNumber, GlobalStateHash),
233            >::new(),
234            enable_epoch_sui_conservation_check,
235            metrics: AuthorityStoreMetrics::new(registry),
236        });
237        // Only initialize an empty database.
238        if store
239            .database_is_empty()
240            .expect("Database read should not fail at init.")
241        {
242            store
243                .bulk_insert_genesis_objects(genesis.objects())
244                .expect("Cannot bulk insert genesis objects");
245
246            // insert txn and effects of genesis
247            let transaction = VerifiedTransaction::new_unchecked(genesis.transaction().clone());
248
249            store
250                .perpetual_tables
251                .transactions
252                .insert(transaction.digest(), transaction.serializable_ref())
253                .unwrap();
254
255            store
256                .perpetual_tables
257                .effects
258                .insert(&genesis.effects().digest(), genesis.effects())
259                .unwrap();
260            // We don't insert the effects to executed_effects yet because the genesis tx hasn't but will be executed.
261            // This is important for fullnodes to be able to generate indexing data right now.
262
263            if genesis.effects().events_digest().is_some() {
264                store
265                    .perpetual_tables
266                    .events_2
267                    .insert(transaction.digest(), genesis.events())
268                    .unwrap();
269            }
270        }
271
272        Ok(store)
273    }
274
275    /// Open authority store without any operations that require
276    /// genesis, such as constructing EpochStartConfiguration
277    /// or inserting genesis objects.
278    pub fn open_no_genesis(
279        perpetual_tables: Arc<AuthorityPerpetualTables>,
280        enable_epoch_sui_conservation_check: bool,
281        registry: &Registry,
282    ) -> SuiResult<Arc<Self>> {
283        let store = Arc::new(Self {
284            perpetual_tables,
285            root_state_notify_read: NotifyRead::<
286                EpochId,
287                (CheckpointSequenceNumber, GlobalStateHash),
288            >::new(),
289            enable_epoch_sui_conservation_check,
290            metrics: AuthorityStoreMetrics::new(registry),
291        });
292        Ok(store)
293    }
294
295    pub fn get_recovery_epoch_at_restart(&self) -> SuiResult<EpochId> {
296        self.perpetual_tables.get_recovery_epoch_at_restart()
297    }
298
299    pub fn get_effects(
300        &self,
301        effects_digest: &TransactionEffectsDigest,
302    ) -> SuiResult<Option<TransactionEffects>> {
303        Ok(self.perpetual_tables.effects.get(effects_digest)?)
304    }
305
306    pub fn get_events(
307        &self,
308        digest: &TransactionDigest,
309    ) -> Result<Option<TransactionEvents>, TypedStoreError> {
310        self.perpetual_tables.events_2.get(digest)
311    }
312
313    pub fn multi_get_events(
314        &self,
315        event_digests: &[TransactionDigest],
316    ) -> SuiResult<Vec<Option<TransactionEvents>>> {
317        Ok(event_digests
318            .iter()
319            .map(|digest| self.get_events(digest))
320            .collect::<Result<Vec<_>, _>>()?)
321    }
322
323    pub fn get_unchanged_loaded_runtime_objects(
324        &self,
325        digest: &TransactionDigest,
326    ) -> Result<Option<Vec<ObjectKey>>, TypedStoreError> {
327        self.perpetual_tables
328            .unchanged_loaded_runtime_objects
329            .get(digest)
330    }
331
332    pub fn multi_get_unchanged_loaded_runtime_objects(
333        &self,
334        digests: &[TransactionDigest],
335    ) -> Result<Vec<Option<Vec<ObjectKey>>>, TypedStoreError> {
336        self.perpetual_tables
337            .unchanged_loaded_runtime_objects
338            .multi_get(digests)
339    }
340
341    pub fn multi_get_effects<'a>(
342        &self,
343        effects_digests: impl Iterator<Item = &'a TransactionEffectsDigest>,
344    ) -> Result<Vec<Option<TransactionEffects>>, TypedStoreError> {
345        self.perpetual_tables.effects.multi_get(effects_digests)
346    }
347
348    pub fn get_executed_effects(
349        &self,
350        tx_digest: &TransactionDigest,
351    ) -> Result<Option<TransactionEffects>, TypedStoreError> {
352        let effects_digest = self.perpetual_tables.executed_effects.get(tx_digest)?;
353        match effects_digest {
354            Some(digest) => Ok(self.perpetual_tables.effects.get(&digest)?),
355            None => Ok(None),
356        }
357    }
358
359    /// Given a list of transaction digests, returns a list of the corresponding effects only if they have been
360    /// executed. For transactions that have not been executed, None is returned.
361    pub fn multi_get_executed_effects_digests(
362        &self,
363        digests: &[TransactionDigest],
364    ) -> Result<Vec<Option<TransactionEffectsDigest>>, TypedStoreError> {
365        self.perpetual_tables.executed_effects.multi_get(digests)
366    }
367
368    /// Given a list of transaction digests, returns a list of the corresponding effects only if they have been
369    /// executed. For transactions that have not been executed, None is returned.
370    pub fn multi_get_executed_effects(
371        &self,
372        digests: &[TransactionDigest],
373    ) -> Result<Vec<Option<TransactionEffects>>, TypedStoreError> {
374        let executed_effects_digests = self.perpetual_tables.executed_effects.multi_get(digests)?;
375        let effects = self.multi_get_effects(executed_effects_digests.iter().flatten())?;
376        let mut tx_to_effects_map = effects
377            .into_iter()
378            .flatten()
379            .map(|effects| (*effects.transaction_digest(), effects))
380            .collect::<HashMap<_, _>>();
381        Ok(digests
382            .iter()
383            .map(|digest| tx_to_effects_map.remove(digest))
384            .collect())
385    }
386
387    pub fn is_tx_already_executed(&self, digest: &TransactionDigest) -> SuiResult<bool> {
388        Ok(self
389            .perpetual_tables
390            .executed_effects
391            .contains_key(digest)?)
392    }
393
394    pub fn get_marker_value(
395        &self,
396        object_key: FullObjectKey,
397        epoch_id: EpochId,
398    ) -> SuiResult<Option<MarkerValue>> {
399        Ok(self
400            .perpetual_tables
401            .object_per_epoch_marker_table_v2
402            .get(&EpochMarkerKey(epoch_id, object_key))?)
403    }
404
405    pub fn get_latest_marker(
406        &self,
407        object_id: FullObjectID,
408        epoch_id: EpochId,
409    ) -> SuiResult<Option<(SequenceNumber, MarkerValue)>> {
410        let min_key = EpochMarkerKey(epoch_id, FullObjectKey::min_for_id(&object_id));
411        let max_key = EpochMarkerKey(epoch_id, FullObjectKey::max_for_id(&object_id));
412
413        let marker_entry = self
414            .perpetual_tables
415            .object_per_epoch_marker_table_v2
416            .reversed_safe_iter_with_bounds(Some(min_key), Some(max_key))?
417            .next();
418        match marker_entry {
419            Some(Ok((EpochMarkerKey(epoch, key), marker))) => {
420                // because of the iterator bounds these cannot fail
421                assert_eq!(epoch, epoch_id);
422                assert_eq!(key.id(), object_id);
423                Ok(Some((key.version(), marker)))
424            }
425            Some(Err(e)) => Err(e.into()),
426            None => Ok(None),
427        }
428    }
429
430    // DEPRECATED -- use function of same name in AuthorityPerEpochStore
431    pub fn deprecated_insert_finalized_transactions(
432        &self,
433        digests: &[TransactionDigest],
434        epoch: EpochId,
435        sequence: CheckpointSequenceNumber,
436    ) -> SuiResult {
437        let mut batch = self
438            .perpetual_tables
439            .executed_transactions_to_checkpoint
440            .batch();
441        batch.insert_batch(
442            &self.perpetual_tables.executed_transactions_to_checkpoint,
443            digests.iter().map(|d| (*d, (epoch, sequence))),
444        )?;
445        batch.write()?;
446        trace!("Transactions {digests:?} finalized at checkpoint {sequence} epoch {epoch}");
447        Ok(())
448    }
449
450    // DEPRECATED -- use function of same name in AuthorityPerEpochStore
451    pub fn deprecated_get_transaction_checkpoint(
452        &self,
453        digest: &TransactionDigest,
454    ) -> SuiResult<Option<(EpochId, CheckpointSequenceNumber)>> {
455        Ok(self
456            .perpetual_tables
457            .executed_transactions_to_checkpoint
458            .get(digest)?)
459    }
460
461    // DEPRECATED -- use function of same name in AuthorityPerEpochStore
462    pub fn deprecated_multi_get_transaction_checkpoint(
463        &self,
464        digests: &[TransactionDigest],
465    ) -> SuiResult<Vec<Option<(EpochId, CheckpointSequenceNumber)>>> {
466        Ok(self
467            .perpetual_tables
468            .executed_transactions_to_checkpoint
469            .multi_get(digests)?
470            .into_iter()
471            .collect())
472    }
473
474    /// Returns true if there are no objects in the database
475    pub fn database_is_empty(&self) -> SuiResult<bool> {
476        self.perpetual_tables.database_is_empty()
477    }
478
479    pub fn object_exists_by_key(
480        &self,
481        object_id: &ObjectID,
482        version: VersionNumber,
483    ) -> SuiResult<bool> {
484        Ok(self
485            .perpetual_tables
486            .objects
487            .contains_key(&ObjectKey(*object_id, version))?)
488    }
489
490    pub fn multi_object_exists_by_key(&self, object_keys: &[ObjectKey]) -> SuiResult<Vec<bool>> {
491        Ok(self
492            .perpetual_tables
493            .objects
494            .multi_contains_keys(object_keys.to_vec())?
495            .into_iter()
496            .collect())
497    }
498
499    fn get_object_ref_prior_to_key(
500        &self,
501        object_id: &ObjectID,
502        version: VersionNumber,
503    ) -> Result<Option<ObjectRef>, SuiError> {
504        let Some(prior_version) = version.one_before() else {
505            return Ok(None);
506        };
507        let mut iterator = self
508            .perpetual_tables
509            .objects
510            .reversed_safe_iter_with_bounds(
511                Some(ObjectKey::min_for_id(object_id)),
512                Some(ObjectKey(*object_id, prior_version)),
513            )?;
514
515        if let Some((object_key, value)) = iterator.next().transpose()?
516            && object_key.0 == *object_id
517        {
518            return Ok(Some(
519                self.perpetual_tables.object_reference(&object_key, value)?,
520            ));
521        }
522        Ok(None)
523    }
524
525    pub fn multi_get_objects_by_key(
526        &self,
527        object_keys: &[ObjectKey],
528    ) -> Result<Vec<Option<Object>>, SuiError> {
529        let wrappers = self
530            .perpetual_tables
531            .objects
532            .multi_get(object_keys.to_vec())?;
533        let mut ret = vec![];
534
535        for (idx, w) in wrappers.into_iter().enumerate() {
536            ret.push(
537                w.map(|object| self.perpetual_tables.object(&object_keys[idx], object))
538                    .transpose()?
539                    .flatten(),
540            );
541        }
542        Ok(ret)
543    }
544
545    /// Get many objects
546    pub fn get_objects(&self, objects: &[ObjectID]) -> Result<Vec<Option<Object>>, SuiError> {
547        let mut result = Vec::new();
548        for id in objects {
549            result.push(self.get_object(id));
550        }
551        Ok(result)
552    }
553
554    // Methods to mutate the store
555
556    /// Insert a genesis object.
557    /// TODO: delete this method entirely (still used by authority_tests.rs)
558    pub(crate) fn insert_genesis_object(&self, object: Object) -> SuiResult {
559        // We only side load objects with a genesis parent transaction.
560        debug_assert!(object.previous_transaction == TransactionDigest::genesis_marker());
561        let object_ref = object.compute_object_reference();
562        self.insert_object_direct(object_ref, &object)
563    }
564
565    /// Insert an object directly into the store, and also update relevant tables
566    /// NOTE: does not handle transaction lock.
567    /// This is used to insert genesis objects
568    fn insert_object_direct(&self, object_ref: ObjectRef, object: &Object) -> SuiResult {
569        let mut write_batch = self.perpetual_tables.objects.batch();
570
571        // Insert object
572        let store_object = get_store_object(object.clone());
573        write_batch.insert_batch(
574            &self.perpetual_tables.objects,
575            std::iter::once((ObjectKey::from(object_ref), store_object)),
576        )?;
577
578        write_batch.write()?;
579
580        Ok(())
581    }
582
583    /// This function should only be used for initializing genesis and should remain private.
584    #[instrument(level = "debug", skip_all)]
585    pub(crate) fn bulk_insert_genesis_objects(&self, objects: &[Object]) -> SuiResult<()> {
586        let mut batch = self.perpetual_tables.objects.batch();
587        let ref_and_objects: Vec<_> = objects
588            .iter()
589            .map(|o| (o.compute_object_reference(), o))
590            .collect();
591
592        batch.insert_batch(
593            &self.perpetual_tables.objects,
594            ref_and_objects
595                .iter()
596                .map(|(oref, o)| (ObjectKey::from(oref), get_store_object((*o).clone()))),
597        )?;
598
599        batch.write()?;
600
601        Ok(())
602    }
603
604    pub async fn bulk_insert_live_objects(
605        perpetual_db: Arc<AuthorityPerpetualTables>,
606        objects: Vec<LiveObject>,
607        expected_sha3_digest: &[u8; 32],
608        num_parallel_chunks: usize,
609    ) -> SuiResult<()> {
610        // Verify SHA3 over the full object set before inserting.
611        let mut hasher = Sha3_256::default();
612        for object in &objects {
613            hasher.update(object.object_reference().2.inner());
614        }
615        let sha3_digest = hasher.finalize().digest;
616        if *expected_sha3_digest != sha3_digest {
617            error!(
618                "Sha does not match! expected: {:?}, actual: {:?}",
619                expected_sha3_digest, sha3_digest
620            );
621            return Err(SuiError::from("Sha does not match"));
622        }
623
624        let chunk_size = objects.len().div_ceil(num_parallel_chunks).max(1);
625        let mut remaining = objects;
626        let mut handles = Vec::new();
627        while !remaining.is_empty() {
628            let take = chunk_size.min(remaining.len());
629            let chunk: Vec<LiveObject> = remaining.drain(..take).collect();
630            let db = perpetual_db.clone();
631            handles.push(tokio::task::spawn_blocking(move || {
632                Self::insert_objects_chunk(db, chunk)
633            }));
634        }
635        for handle in handles {
636            handle.await.expect("insert task panicked")?;
637        }
638        Ok(())
639    }
640
641    fn insert_objects_chunk(
642        perpetual_db: Arc<AuthorityPerpetualTables>,
643        objects: Vec<LiveObject>,
644    ) -> SuiResult<()> {
645        let mut batch = perpetual_db.objects.batch();
646        let mut written = 0usize;
647        const MAX_BATCH_SIZE: usize = 100_000;
648        for object in objects {
649            match object {
650                LiveObject::Normal(object) => {
651                    let object_key = ObjectKey::from(object.compute_object_reference());
652                    let store_object_wrapper = get_store_object(object);
653                    batch.insert_batch(
654                        &perpetual_db.objects,
655                        std::iter::once((object_key, store_object_wrapper)),
656                    )?;
657                }
658                LiveObject::Wrapped(object_key) => {
659                    batch.insert_batch(
660                        &perpetual_db.objects,
661                        std::iter::once::<(ObjectKey, StoreObjectWrapper)>((
662                            object_key,
663                            StoreObject::Wrapped.into(),
664                        )),
665                    )?;
666                }
667            }
668            written += 1;
669            if written > MAX_BATCH_SIZE {
670                batch.write()?;
671                batch = perpetual_db.objects.batch();
672                written = 0;
673            }
674        }
675        batch.write()?;
676        Ok(())
677    }
678
679    pub fn set_epoch_start_configuration(
680        &self,
681        epoch_start_configuration: &EpochStartConfiguration,
682    ) -> SuiResult {
683        self.perpetual_tables
684            .set_epoch_start_configuration(epoch_start_configuration)?;
685        Ok(())
686    }
687
688    pub fn get_epoch_start_configuration(&self) -> SuiResult<Option<EpochStartConfiguration>> {
689        Ok(self.perpetual_tables.epoch_start_configuration.get(&())?)
690    }
691
692    /// Updates the state resulting from the execution of a certificate.
693    ///
694    /// Internally it checks that all locks for active inputs are at the correct
695    /// version, and then writes objects, certificates, parents and clean up locks atomically.
696    #[instrument(level = "debug", skip_all)]
697    pub fn build_db_batch(
698        &self,
699        epoch_id: EpochId,
700        tx_outputs: &[Arc<TransactionOutputs>],
701    ) -> SuiResult<DBBatch> {
702        let mut write_batch = self.perpetual_tables.transactions.batch();
703        for outputs in tx_outputs {
704            self.write_one_transaction_outputs(&mut write_batch, epoch_id, outputs)?;
705        }
706        // test crashing before writing the batch
707        fail_point!("crash");
708
709        trace!(
710            "built batch for committed transactions: {:?}",
711            tx_outputs
712                .iter()
713                .map(|tx| tx.transaction.digest())
714                .collect::<Vec<_>>()
715        );
716
717        // test crashing before notifying
718        fail_point!("crash");
719
720        Ok(write_batch)
721    }
722
723    fn write_one_transaction_outputs(
724        &self,
725        write_batch: &mut DBBatch,
726        epoch_id: EpochId,
727        tx_outputs: &TransactionOutputs,
728    ) -> SuiResult {
729        let TransactionOutputs {
730            transaction,
731            effects,
732            markers,
733            wrapped,
734            deleted,
735            written,
736            events,
737            unchanged_loaded_runtime_objects,
738            ..
739        } = tx_outputs;
740
741        let effects_digest = effects.digest();
742        let transaction_digest = transaction.digest();
743        // effects must be inserted before the corresponding dependent entries
744        // because they carry epoch information necessary for correct pruning via relocation filters
745        write_batch
746            .insert_batch(
747                &self.perpetual_tables.effects,
748                [(effects_digest, effects.clone())],
749            )?
750            .insert_batch(
751                &self.perpetual_tables.executed_effects,
752                [(transaction_digest, effects_digest)],
753            )?;
754
755        // Store the certificate indexed by transaction digest
756        write_batch.insert_batch(
757            &self.perpetual_tables.transactions,
758            iter::once((transaction_digest, transaction.serializable_ref())),
759        )?;
760
761        write_batch.insert_batch(
762            &self.perpetual_tables.executed_transaction_digests,
763            [((epoch_id, *transaction_digest), ())],
764        )?;
765
766        // Add batched writes for objects and locks.
767        write_batch.insert_batch(
768            &self.perpetual_tables.object_per_epoch_marker_table_v2,
769            markers
770                .iter()
771                .map(|(key, marker_value)| (EpochMarkerKey(epoch_id, *key), *marker_value)),
772        )?;
773        write_batch.insert_batch(
774            &self.perpetual_tables.objects,
775            deleted
776                .iter()
777                .map(|key| (key, StoreObject::Deleted))
778                .chain(wrapped.iter().map(|key| (key, StoreObject::Wrapped)))
779                .map(|(key, store_object)| (key, StoreObjectWrapper::from(store_object))),
780        )?;
781
782        // Insert each output object into the stores
783        let new_objects = written.iter().map(|(id, new_object)| {
784            let version = new_object.version();
785            trace!(?id, ?version, "writing object");
786            let store_object = get_store_object(new_object.clone());
787            (ObjectKey(*id, version), store_object)
788        });
789
790        write_batch.insert_batch(&self.perpetual_tables.objects, new_objects)?;
791
792        // Write events into the new table keyed off of transaction_digest
793        if effects.events_digest().is_some() {
794            write_batch.insert_batch(
795                &self.perpetual_tables.events_2,
796                [(transaction_digest, events)],
797            )?;
798        }
799
800        // Write unchanged_loaded_runtime_objects
801        if !unchanged_loaded_runtime_objects.is_empty() {
802            write_batch.insert_batch(
803                &self.perpetual_tables.unchanged_loaded_runtime_objects,
804                [(transaction_digest, unchanged_loaded_runtime_objects)],
805            )?;
806        }
807
808        debug!(effects_digest = ?effects.digest(), "commit_certificate finished");
809
810        Ok(())
811    }
812
813    /// Commits transactions only (not effects or other transaction outputs) to the db.
814    /// See ExecutionCache::persist_transaction for more info
815    pub(crate) fn persist_transaction(&self, tx: &VerifiedExecutableTransaction) -> SuiResult {
816        let mut batch = self.perpetual_tables.transactions.batch();
817        batch.insert_batch(
818            &self.perpetual_tables.transactions,
819            [(tx.digest(), tx.clone().into_unsigned().serializable_ref())],
820        )?;
821        batch.write()?;
822        Ok(())
823    }
824
825    /// Return the object with version less then or eq to the provided seq number.
826    /// This is used by indexer to find the correct version of dynamic field child object.
827    /// We do not store the version of the child object, but because of lamport timestamp,
828    /// we know the child must have version number less then or eq to the parent.
829    pub fn find_object_lt_or_eq_version(
830        &self,
831        object_id: ObjectID,
832        version: SequenceNumber,
833    ) -> SuiResult<Option<Object>> {
834        self.perpetual_tables
835            .find_object_lt_or_eq_version(object_id, version)
836    }
837
838    /// Returns the latest object reference we have for this object_id in the objects table.
839    ///
840    /// The method may also return the reference to a deleted object with a digest of
841    /// ObjectDigest::deleted() or ObjectDigest::wrapped() and lamport version
842    /// of a transaction that deleted the object.
843    /// Note that a deleted object may re-appear if the deletion was the result of the object
844    /// being wrapped in another object.
845    ///
846    /// If no entry for the object_id is found, return None.
847    pub fn get_latest_object_ref_or_tombstone(
848        &self,
849        object_id: ObjectID,
850    ) -> Result<Option<ObjectRef>, SuiError> {
851        self.perpetual_tables
852            .get_latest_object_ref_or_tombstone(object_id)
853    }
854
855    /// Returns the latest object we have for this object_id in the objects table.
856    ///
857    /// If no entry for the object_id is found, return None.
858    pub fn get_latest_object_or_tombstone(
859        &self,
860        object_id: ObjectID,
861    ) -> Result<Option<(ObjectKey, ObjectOrTombstone)>, SuiError> {
862        let Some((object_key, store_object)) = self
863            .perpetual_tables
864            .get_latest_object_or_tombstone(object_id)?
865        else {
866            return Ok(None);
867        };
868
869        if let Some(object_ref) = self
870            .perpetual_tables
871            .tombstone_reference(&object_key, &store_object)?
872        {
873            return Ok(Some((object_key, ObjectOrTombstone::Tombstone(object_ref))));
874        }
875
876        let object = self
877            .perpetual_tables
878            .object(&object_key, store_object)?
879            .expect("Non tombstone store object could not be converted to object");
880
881        Ok(Some((object_key, ObjectOrTombstone::Object(object))))
882    }
883
884    pub fn insert_transaction_and_effects(
885        &self,
886        transaction: &VerifiedTransaction,
887        transaction_effects: &TransactionEffects,
888    ) -> Result<(), TypedStoreError> {
889        let mut write_batch = self.perpetual_tables.transactions.batch();
890        // effects must be inserted before the corresponding transaction entry
891        // because they carry epoch information necessary for correct pruning via relocation filters
892        write_batch
893            .insert_batch(
894                &self.perpetual_tables.effects,
895                [(transaction_effects.digest(), transaction_effects)],
896            )?
897            .insert_batch(
898                &self.perpetual_tables.transactions,
899                [(transaction.digest(), transaction.serializable_ref())],
900            )?;
901
902        write_batch.write()?;
903        Ok(())
904    }
905
906    pub fn multi_insert_transaction_and_effects<'a>(
907        &self,
908        transactions: impl Iterator<Item = &'a VerifiedExecutionData>,
909    ) -> Result<(), TypedStoreError> {
910        let mut write_batch = self.perpetual_tables.transactions.batch();
911        for tx in transactions {
912            write_batch
913                .insert_batch(
914                    &self.perpetual_tables.effects,
915                    [(tx.effects.digest(), &tx.effects)],
916                )?
917                .insert_batch(
918                    &self.perpetual_tables.transactions,
919                    [(tx.transaction.digest(), tx.transaction.serializable_ref())],
920                )?;
921        }
922
923        write_batch.write()?;
924        Ok(())
925    }
926
927    pub fn multi_get_transaction_blocks(
928        &self,
929        tx_digests: &[TransactionDigest],
930    ) -> Result<Vec<Option<VerifiedTransaction>>, TypedStoreError> {
931        self.perpetual_tables
932            .transactions
933            .multi_get(tx_digests)
934            .map(|v| v.into_iter().map(|v| v.map(|v| v.into())).collect())
935    }
936
937    pub fn get_transaction_block(
938        &self,
939        tx_digest: &TransactionDigest,
940    ) -> Result<Option<VerifiedTransaction>, TypedStoreError> {
941        self.perpetual_tables
942            .transactions
943            .get(tx_digest)
944            .map(|v| v.map(|v| v.into()))
945    }
946
947    pub fn list_transactions_from(
948        &self,
949        start: Option<TransactionDigest>,
950        limit: usize,
951    ) -> Result<Vec<TransactionDigest>, TypedStoreError> {
952        self.perpetual_tables.list_transactions_from(start, limit)
953    }
954
955    pub fn get_executed_effects_digest_for_tx(
956        &self,
957        tx_digest: &TransactionDigest,
958    ) -> Result<Option<TransactionEffectsDigest>, TypedStoreError> {
959        self.perpetual_tables.get_executed_effects_digest(tx_digest)
960    }
961
962    /// This function reads the DB directly to get the system state object.
963    /// If reconfiguration is happening at the same time, there is no guarantee whether we would be getting
964    /// the old or the new system state object.
965    /// Hence this function should only be called during RPC reads where data race is not a major concern.
966    /// In general we should avoid this as much as possible.
967    /// If the intent is for testing, you can use AuthorityState:: get_sui_system_state_object_for_testing.
968    pub fn get_sui_system_state_object_unsafe(&self) -> SuiResult<SuiSystemState> {
969        get_sui_system_state(self.perpetual_tables.as_ref())
970    }
971
972    pub fn expensive_check_sui_conservation<T>(
973        self: &Arc<Self>,
974        type_layout_store: T,
975        old_epoch_store: &AuthorityPerEpochStore,
976    ) -> SuiResult
977    where
978        T: TypeLayoutStore + Send + Sync + Copy,
979    {
980        if !self.enable_epoch_sui_conservation_check {
981            return Ok(());
982        }
983
984        let package_store = PackageStoreWithLruCache::new(type_layout_store);
985        info!("Starting SUI conservation check. This may take a while..");
986        let cur_time = Instant::now();
987        let mut pending_objects = vec![];
988        let mut count = 0;
989        let mut size = 0;
990        let (mut total_sui, mut total_storage_rebate) = thread::scope(|s| {
991            let pending_tasks = FuturesUnordered::new();
992            for o in self.iter_live_object_set(false) {
993                match o {
994                    LiveObject::Normal(object) => {
995                        size += object.object_size_for_gas_metering();
996                        count += 1;
997                        pending_objects.push(object);
998                        if count % 1_000_000 == 0 {
999                            let mut task_objects = vec![];
1000                            mem::swap(&mut pending_objects, &mut task_objects);
1001                            let package_store = &package_store;
1002                            pending_tasks.push(s.spawn(move || {
1003                                let mut layout_resolver = SyncResolver::new(package_store);
1004                                let mut total_storage_rebate = 0;
1005                                let mut total_sui = 0;
1006                                for object in task_objects {
1007                                    total_storage_rebate += object.storage_rebate;
1008                                    // get_total_sui includes storage rebate, however all storage rebate is
1009                                    // also stored in the storage fund, so we need to subtract it here.
1010                                    let object_contained_sui = match object
1011                                        .get_total_sui(&mut layout_resolver)
1012                                    {
1013                                        Ok(sui) => sui,
1014                                        Err(e)
1015                                            if old_epoch_store.get_chain()
1016                                                == sui_protocol_config::Chain::Testnet =>
1017                                        {
1018                                            error!(
1019                                                "Error calculating total SUI for object {:?}: {:?}",
1020                                                object.compute_object_reference(),
1021                                                e
1022                                            );
1023                                            0
1024                                        }
1025                                        Err(e) => panic!(
1026                                            "Error calculating total SUI for object {:?}: {:?}",
1027                                            object.compute_object_reference(),
1028                                            e
1029                                        ),
1030                                    };
1031                                    total_sui += object_contained_sui - object.storage_rebate;
1032                                }
1033                                if count % 50_000_000 == 0 {
1034                                    info!("Processed {} objects", count);
1035                                }
1036                                (total_sui, total_storage_rebate)
1037                            }));
1038                        }
1039                    }
1040                    LiveObject::Wrapped(_) => {
1041                        unreachable!("Explicitly asked to not include wrapped tombstones")
1042                    }
1043                }
1044            }
1045            pending_tasks.into_iter().fold((0, 0), |init, result| {
1046                let result = result.join().unwrap();
1047                (init.0 + result.0, init.1 + result.1)
1048            })
1049        });
1050        let mut layout_resolver = SyncResolver::new(&package_store);
1051        for object in pending_objects {
1052            total_storage_rebate += object.storage_rebate;
1053            total_sui +=
1054                object.get_total_sui(&mut layout_resolver).unwrap() - object.storage_rebate;
1055        }
1056        info!(
1057            "Scanned {} live objects, took {:?}",
1058            count,
1059            cur_time.elapsed()
1060        );
1061        self.metrics
1062            .sui_conservation_live_object_count
1063            .set(count as i64);
1064        self.metrics
1065            .sui_conservation_live_object_size
1066            .set(size as i64);
1067        self.metrics
1068            .sui_conservation_check_latency
1069            .set(cur_time.elapsed().as_secs() as i64);
1070
1071        // It is safe to call this function because we are in the middle of reconfiguration.
1072        let system_state = self
1073            .get_sui_system_state_object_unsafe()
1074            .expect("Reading sui system state object cannot fail")
1075            .into_sui_system_state_summary();
1076        let storage_fund_balance = system_state.storage_fund_total_object_storage_rebates;
1077        info!(
1078            "Total SUI amount in the network: {}, storage fund balance: {}, total storage rebate: {} at beginning of epoch {}",
1079            total_sui, storage_fund_balance, total_storage_rebate, system_state.epoch
1080        );
1081
1082        let imbalance = (storage_fund_balance as i64) - (total_storage_rebate as i64);
1083        self.metrics
1084            .sui_conservation_storage_fund
1085            .set(storage_fund_balance as i64);
1086        self.metrics
1087            .sui_conservation_storage_fund_imbalance
1088            .set(imbalance);
1089        self.metrics
1090            .sui_conservation_imbalance
1091            .set((total_sui as i128 - TOTAL_SUPPLY_MIST as i128) as i64);
1092
1093        if let Some(expected_imbalance) = self
1094            .perpetual_tables
1095            .expected_storage_fund_imbalance
1096            .get(&())
1097            .expect("DB read cannot fail")
1098        {
1099            fp_ensure!(
1100                imbalance == expected_imbalance,
1101                SuiError::from(
1102                    format!(
1103                        "Inconsistent state detected at epoch {}: total storage rebate: {}, storage fund balance: {}, expected imbalance: {}",
1104                        system_state.epoch, total_storage_rebate, storage_fund_balance, expected_imbalance
1105                    ).as_str()
1106                )
1107            );
1108        } else {
1109            self.perpetual_tables
1110                .expected_storage_fund_imbalance
1111                .insert(&(), &imbalance)
1112                .expect("DB write cannot fail");
1113        }
1114
1115        if let Some(expected_sui) = self
1116            .perpetual_tables
1117            .expected_network_sui_amount
1118            .get(&())
1119            .expect("DB read cannot fail")
1120        {
1121            fp_ensure!(
1122                total_sui == expected_sui,
1123                SuiError::from(
1124                    format!(
1125                        "Inconsistent state detected at epoch {}: total sui: {}, expecting {}",
1126                        system_state.epoch, total_sui, expected_sui
1127                    )
1128                    .as_str()
1129                )
1130            );
1131        } else {
1132            self.perpetual_tables
1133                .expected_network_sui_amount
1134                .insert(&(), &total_sui)
1135                .expect("DB write cannot fail");
1136        }
1137
1138        Ok(())
1139    }
1140
1141    /// This is a temporary method to be used when we enable simplified_unwrap_then_delete.
1142    /// It re-accumulates state hash for the new epoch if simplified_unwrap_then_delete is enabled.
1143    #[instrument(level = "error", skip_all)]
1144    pub fn maybe_reaccumulate_state_hash(
1145        &self,
1146        cur_epoch_store: &AuthorityPerEpochStore,
1147        new_protocol_version: ProtocolVersion,
1148    ) {
1149        let old_simplified_unwrap_then_delete = cur_epoch_store
1150            .protocol_config()
1151            .simplified_unwrap_then_delete();
1152        let new_simplified_unwrap_then_delete =
1153            ProtocolConfig::get_for_version(new_protocol_version, cur_epoch_store.get_chain())
1154                .simplified_unwrap_then_delete();
1155        // If in the new epoch the simplified_unwrap_then_delete is enabled for the first time,
1156        // we re-accumulate state root.
1157        let should_reaccumulate =
1158            !old_simplified_unwrap_then_delete && new_simplified_unwrap_then_delete;
1159        if !should_reaccumulate {
1160            return;
1161        }
1162        info!(
1163            "[Re-accumulate] simplified_unwrap_then_delete is enabled in the new protocol version, re-accumulating state hash"
1164        );
1165        let cur_time = Instant::now();
1166        std::thread::scope(|s| {
1167            let pending_tasks = FuturesUnordered::new();
1168            // Shard the object IDs into different ranges so that we can process them in parallel.
1169            // We divide the range into 2^BITS number of ranges. To do so we use the highest BITS bits
1170            // to mark the starting/ending point of the range. For example, when BITS = 5, we
1171            // divide the range into 32 ranges, and the first few ranges are:
1172            // 00000000_.... to 00000111_....
1173            // 00001000_.... to 00001111_....
1174            // 00010000_.... to 00010111_....
1175            // and etc.
1176            const BITS: u8 = 5;
1177            for index in 0u8..(1 << BITS) {
1178                pending_tasks.push(s.spawn(move || {
1179                    let mut id_bytes = [0; ObjectID::LENGTH];
1180                    id_bytes[0] = index << (8 - BITS);
1181                    let start_id = ObjectID::new(id_bytes);
1182
1183                    id_bytes[0] |= (1 << (8 - BITS)) - 1;
1184                    for element in id_bytes.iter_mut().skip(1) {
1185                        *element = u8::MAX;
1186                    }
1187                    let end_id = ObjectID::new(id_bytes);
1188
1189                    info!(
1190                        "[Re-accumulate] Scanning object ID range {:?}..{:?}",
1191                        start_id, end_id
1192                    );
1193                    let mut prev = (
1194                        ObjectKey::min_for_id(&ObjectID::ZERO),
1195                        StoreObjectWrapper::V1(StoreObject::Deleted),
1196                    );
1197                    let mut object_scanned: u64 = 0;
1198                    let mut wrapped_objects_to_remove = vec![];
1199                    for db_result in self.perpetual_tables.objects.safe_range_iter(
1200                        ObjectKey::min_for_id(&start_id)..=ObjectKey::max_for_id(&end_id),
1201                    ) {
1202                        match db_result {
1203                            Ok((object_key, object)) => {
1204                                object_scanned += 1;
1205                                if object_scanned.is_multiple_of(100000) {
1206                                    info!(
1207                                        "[Re-accumulate] Task {}: object scanned: {}",
1208                                        index, object_scanned,
1209                                    );
1210                                }
1211                                if matches!(prev.1.inner(), StoreObject::Wrapped)
1212                                    && object_key.0 != prev.0.0
1213                                {
1214                                    wrapped_objects_to_remove
1215                                        .push(WrappedObject::new(prev.0.0, prev.0.1));
1216                                }
1217
1218                                prev = (object_key, object);
1219                            }
1220                            Err(err) => {
1221                                warn!("Object iterator encounter RocksDB error {:?}", err);
1222                                return Err(err);
1223                            }
1224                        }
1225                    }
1226                    if matches!(prev.1.inner(), StoreObject::Wrapped) {
1227                        wrapped_objects_to_remove.push(WrappedObject::new(prev.0.0, prev.0.1));
1228                    }
1229                    info!(
1230                        "[Re-accumulate] Task {}: object scanned: {}, wrapped objects: {}",
1231                        index,
1232                        object_scanned,
1233                        wrapped_objects_to_remove.len(),
1234                    );
1235                    Ok((wrapped_objects_to_remove, object_scanned))
1236                }));
1237            }
1238            let (last_checkpoint_of_epoch, cur_accumulator) = self
1239                .get_root_state_hash_for_epoch(cur_epoch_store.epoch())
1240                .expect("read cannot fail")
1241                .expect("accumulator must exist");
1242            let (accumulator, total_objects_scanned, total_wrapped_objects) =
1243                pending_tasks.into_iter().fold(
1244                    (cur_accumulator, 0u64, 0usize),
1245                    |(mut accumulator, total_objects_scanned, total_wrapped_objects), task| {
1246                        let (wrapped_objects_to_remove, object_scanned) =
1247                            task.join().unwrap().unwrap();
1248                        accumulator.remove_all(
1249                            wrapped_objects_to_remove
1250                                .iter()
1251                                .map(|wrapped| bcs::to_bytes(wrapped).unwrap().to_vec())
1252                                .collect::<Vec<Vec<u8>>>(),
1253                        );
1254                        (
1255                            accumulator,
1256                            total_objects_scanned + object_scanned,
1257                            total_wrapped_objects + wrapped_objects_to_remove.len(),
1258                        )
1259                    },
1260                );
1261            info!(
1262                "[Re-accumulate] Total objects scanned: {}, total wrapped objects: {}",
1263                total_objects_scanned, total_wrapped_objects,
1264            );
1265            info!(
1266                "[Re-accumulate] New accumulator value: {:?}",
1267                accumulator.digest()
1268            );
1269            self.insert_state_hash_for_epoch(
1270                cur_epoch_store.epoch(),
1271                &last_checkpoint_of_epoch,
1272                &accumulator,
1273            )
1274            .unwrap();
1275        });
1276        info!(
1277            "[Re-accumulate] Re-accumulating took {}seconds",
1278            cur_time.elapsed().as_secs()
1279        );
1280    }
1281
1282    pub async fn prune_objects_and_compact_for_testing(
1283        &self,
1284        checkpoint_store: &Arc<CheckpointStore>,
1285    ) {
1286        let pruning_config = AuthorityStorePruningConfig {
1287            num_epochs_to_retain: 0,
1288            ..Default::default()
1289        };
1290        let _ = AuthorityStorePruner::prune_objects_for_eligible_epochs(
1291            &self.perpetual_tables,
1292            checkpoint_store,
1293            None,
1294            &mut sui_rpc_store::RetractionCursors::default(),
1295            pruning_config,
1296            AuthorityStorePruningMetrics::new_for_test(),
1297            EPOCH_DURATION_MS_FOR_TESTING,
1298        )
1299        .await;
1300        let _ = AuthorityStorePruner::compact(&self.perpetual_tables);
1301    }
1302
1303    pub fn remove_executed_effects_for_testing(
1304        &self,
1305        tx_digest: &TransactionDigest,
1306    ) -> anyhow::Result<()> {
1307        let effects_digest = self.perpetual_tables.executed_effects.get(tx_digest)?;
1308        if let Some(effects_digest) = effects_digest {
1309            self.perpetual_tables.executed_effects.remove(tx_digest)?;
1310            self.perpetual_tables.effects.remove(&effects_digest)?;
1311        }
1312        Ok(())
1313    }
1314
1315    #[cfg(test)]
1316    pub async fn prune_objects_immediately_for_testing(
1317        &self,
1318        transaction_effects: Vec<TransactionEffects>,
1319    ) -> anyhow::Result<()> {
1320        let mut wb = self.perpetual_tables.objects.batch();
1321
1322        let mut object_keys_to_prune = vec![];
1323        for effects in &transaction_effects {
1324            for (object_id, seq_number) in effects.modified_at_versions() {
1325                info!("Pruning object {:?} version {:?}", object_id, seq_number);
1326                object_keys_to_prune.push(ObjectKey(object_id, seq_number));
1327            }
1328        }
1329
1330        wb.delete_batch(&self.perpetual_tables.objects, object_keys_to_prune)?;
1331        wb.write()?;
1332        Ok(())
1333    }
1334
1335    // Counts the number of versions exist in object store for `object_id`. This includes tombstone.
1336    #[cfg(msim)]
1337    pub fn count_object_versions(&self, object_id: ObjectID) -> usize {
1338        self.perpetual_tables
1339            .objects
1340            .safe_iter_with_bounds(
1341                Some(ObjectKey(object_id, VersionNumber::MIN)),
1342                Some(ObjectKey(object_id, VersionNumber::MAX)),
1343            )
1344            .collect::<Result<Vec<_>, _>>()
1345            .unwrap()
1346            .len()
1347    }
1348}
1349
1350impl GlobalStateHashStore for AuthorityStore {
1351    fn get_object_ref_prior_to_key_deprecated(
1352        &self,
1353        object_id: &ObjectID,
1354        version: VersionNumber,
1355    ) -> SuiResult<Option<ObjectRef>> {
1356        self.get_object_ref_prior_to_key(object_id, version)
1357    }
1358
1359    fn get_root_state_hash_for_epoch(
1360        &self,
1361        epoch: EpochId,
1362    ) -> SuiResult<Option<(CheckpointSequenceNumber, GlobalStateHash)>> {
1363        self.perpetual_tables
1364            .root_state_hash_by_epoch
1365            .get(&epoch)
1366            .map_err(Into::into)
1367    }
1368
1369    fn insert_state_hash_for_epoch(
1370        &self,
1371        epoch: EpochId,
1372        last_checkpoint_of_epoch: &CheckpointSequenceNumber,
1373        acc: &GlobalStateHash,
1374    ) -> SuiResult {
1375        self.perpetual_tables
1376            .root_state_hash_by_epoch
1377            .insert(&epoch, &(*last_checkpoint_of_epoch, acc.clone()))?;
1378        self.root_state_notify_read
1379            .notify(&epoch, &(*last_checkpoint_of_epoch, acc.clone()));
1380
1381        Ok(())
1382    }
1383
1384    fn iter_live_object_set(
1385        &self,
1386        include_wrapped_object: bool,
1387    ) -> Box<dyn Iterator<Item = LiveObject> + '_> {
1388        Box::new(
1389            self.perpetual_tables
1390                .iter_live_object_set(include_wrapped_object),
1391        )
1392    }
1393}
1394
1395impl ObjectStore for AuthorityStore {
1396    /// Read an object and return it, or Ok(None) if the object was not found.
1397    fn get_object(&self, object_id: &ObjectID) -> Option<Object> {
1398        self.perpetual_tables.as_ref().get_object(object_id)
1399    }
1400
1401    fn get_object_by_key(&self, object_id: &ObjectID, version: VersionNumber) -> Option<Object> {
1402        self.perpetual_tables.get_object_by_key(object_id, version)
1403    }
1404}
1405
1406/// A wrapper to make Orphan Rule happy
1407pub struct ResolverWrapper {
1408    pub resolver: Arc<dyn BackingPackageStore + Send + Sync>,
1409    pub metrics: Arc<ResolverMetrics>,
1410}
1411
1412impl ResolverWrapper {
1413    pub fn new(
1414        resolver: Arc<dyn BackingPackageStore + Send + Sync>,
1415        metrics: Arc<ResolverMetrics>,
1416    ) -> Self {
1417        metrics.module_cache_size.set(0);
1418        ResolverWrapper { resolver, metrics }
1419    }
1420
1421    fn inc_cache_size_gauge(&self) {
1422        // reset the gauge after a restart of the cache
1423        let current = self.metrics.module_cache_size.get();
1424        self.metrics.module_cache_size.set(current + 1);
1425    }
1426}
1427
1428impl ModuleResolver for ResolverWrapper {
1429    type Error = SuiError;
1430    fn get_module(&self, module_id: &ModuleId) -> Result<Option<Vec<u8>>, Self::Error> {
1431        self.inc_cache_size_gauge();
1432        get_module(&*self.resolver, module_id)
1433    }
1434
1435    fn get_packages_static<const N: usize>(
1436        &self,
1437        ids: [AccountAddress; N],
1438    ) -> Result<[Option<SerializedPackage>; N], Self::Error> {
1439        let mut packages = [const { None }; N];
1440        for (i, id) in ids.iter().enumerate() {
1441            packages[i] = get_package(&*self.resolver, &ObjectID::from(*id))?;
1442        }
1443        Ok(packages)
1444    }
1445
1446    fn get_packages<'a>(
1447        &self,
1448        ids: impl ExactSizeIterator<Item = &'a AccountAddress>,
1449    ) -> Result<Vec<Option<SerializedPackage>>, Self::Error> {
1450        ids.map(|id| get_package(&*self.resolver, &ObjectID::from(*id)))
1451            .collect()
1452    }
1453}
1454
1455#[cfg(test)]
1456pub type SuiLockResult = SuiResult<ObjectLockStatus>;
1457
1458#[cfg(test)]
1459#[derive(Debug, PartialEq, Eq)]
1460pub enum ObjectLockStatus {
1461    Initialized,
1462    LockedToTx { locked_by_tx: LockDetailsDeprecated },
1463    LockedAtDifferentVersion { locked_ref: ObjectRef },
1464}
1465
1466#[cfg(test)]
1467#[derive(Clone, Debug, PartialEq, Eq)]
1468pub struct LockDetailsV1Deprecated {
1469    pub epoch: EpochId,
1470    pub tx_digest: TransactionDigest,
1471}
1472
1473#[cfg(test)]
1474pub type LockDetailsDeprecated = LockDetailsV1Deprecated;