Skip to main content

sui_core/
execution_cache.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::accumulators::funds_read::AccountFundsRead;
5use crate::authority::AuthorityStore;
6use crate::authority::authority_per_epoch_store::AuthorityPerEpochStore;
7use crate::authority::authority_store::ExecutionLockWriteGuard;
8#[cfg(test)]
9use crate::authority::authority_store::SuiLockResult;
10use crate::authority::backpressure::BackpressureManager;
11use crate::authority::epoch_start_configuration::EpochFlag;
12use crate::authority::epoch_start_configuration::EpochStartConfiguration;
13use crate::global_state_hasher::GlobalStateHashStore;
14use crate::transaction_outputs::TransactionOutputs;
15use either::Either;
16use itertools::Itertools;
17use mysten_common::ZipDebugEqIteratorExt;
18use sui_types::accumulator_event::AccumulatorEvent;
19use sui_types::bridge::Bridge;
20
21use futures::{FutureExt, future::BoxFuture};
22use prometheus::Registry;
23use std::collections::HashSet;
24use std::path::Path;
25use std::sync::Arc;
26use sui_config::ExecutionCacheConfig;
27use sui_protocol_config::ProtocolVersion;
28use sui_types::base_types::{FullObjectID, VerifiedExecutionData};
29use sui_types::digests::{TransactionDigest, TransactionEffectsDigest};
30use sui_types::effects::{TransactionEffects, TransactionEvents};
31use sui_types::error::{SuiError, SuiErrorKind, SuiResult, UserInputError};
32use sui_types::executable_transaction::VerifiedExecutableTransaction;
33use sui_types::messages_checkpoint::CheckpointSequenceNumber;
34use sui_types::object::Object;
35use sui_types::storage::{
36    BackingPackageStore, BackingStore, FullObjectKey, MarkerValue, ObjectKey, ObjectOrTombstone,
37    ObjectStore, PackageObject, ParentSync, RuntimeObjectResolver,
38};
39use sui_types::sui_system_state::SuiSystemState;
40use sui_types::transaction::VerifiedTransaction;
41use sui_types::{
42    base_types::{EpochId, ObjectID, ObjectRef, SequenceNumber},
43    object::Owner,
44    storage::InputKey,
45};
46use typed_store::rocks::DBBatch;
47
48pub(crate) mod cache_types;
49pub mod metrics;
50mod object_locks;
51pub mod writeback_cache;
52
53pub use writeback_cache::WritebackCache;
54
55use metrics::ExecutionCacheMetrics;
56
57// If you have Arc<ExecutionCache>, you cannot return a reference to it as
58// an &Arc<dyn ExecutionCacheRead> (for example), because the trait object is a fat pointer.
59// So, in order to be able to return &Arc<dyn T>, we create all the converted trait objects
60// (aka fat pointers) up front and return references to them.
61#[derive(Clone)]
62pub struct ExecutionCacheTraitPointers {
63    pub object_cache_reader: Arc<dyn ObjectCacheRead>,
64    pub transaction_cache_reader: Arc<dyn TransactionCacheRead>,
65    pub cache_writer: Arc<dyn ExecutionCacheWrite>,
66    pub backing_store: Arc<dyn BackingStore + Send + Sync>,
67    pub runtime_object_resolver: Arc<dyn RuntimeObjectResolver + Send + Sync>,
68    pub backing_package_store: Arc<dyn BackingPackageStore + Send + Sync>,
69    pub object_store: Arc<dyn ObjectStore + Send + Sync>,
70    pub reconfig_api: Arc<dyn ExecutionCacheReconfigAPI>,
71    pub global_state_hash_store: Arc<dyn GlobalStateHashStore>,
72    pub checkpoint_cache: Arc<dyn CheckpointCache>,
73    pub state_sync_store: Arc<dyn StateSyncAPI>,
74    pub cache_commit: Arc<dyn ExecutionCacheCommit>,
75    pub testing_api: Arc<dyn TestingAPI>,
76    pub account_funds_read: Arc<dyn AccountFundsRead>,
77}
78
79impl ExecutionCacheTraitPointers {
80    pub fn new<T>(cache: Arc<T>) -> Self
81    where
82        T: ObjectCacheRead
83            + TransactionCacheRead
84            + ExecutionCacheWrite
85            + BackingStore
86            + BackingPackageStore
87            + ObjectStore
88            + ExecutionCacheReconfigAPI
89            + GlobalStateHashStore
90            + CheckpointCache
91            + StateSyncAPI
92            + ExecutionCacheCommit
93            + TestingAPI
94            + AccountFundsRead
95            + 'static,
96    {
97        Self {
98            object_cache_reader: cache.clone(),
99            transaction_cache_reader: cache.clone(),
100            cache_writer: cache.clone(),
101            backing_store: cache.clone(),
102            runtime_object_resolver: cache.clone(),
103            backing_package_store: cache.clone(),
104            object_store: cache.clone(),
105            reconfig_api: cache.clone(),
106            global_state_hash_store: cache.clone(),
107            checkpoint_cache: cache.clone(),
108            state_sync_store: cache.clone(),
109            cache_commit: cache.clone(),
110            testing_api: cache.clone(),
111            account_funds_read: cache.clone(),
112        }
113    }
114}
115
116pub fn build_execution_cache(
117    cache_config: &ExecutionCacheConfig,
118    prometheus_registry: &Registry,
119    store: &Arc<AuthorityStore>,
120    backpressure_manager: Arc<BackpressureManager>,
121) -> ExecutionCacheTraitPointers {
122    let execution_cache_metrics = Arc::new(ExecutionCacheMetrics::new(prometheus_registry));
123
124    ExecutionCacheTraitPointers::new(
125        WritebackCache::new(
126            cache_config,
127            store.clone(),
128            execution_cache_metrics,
129            backpressure_manager,
130        )
131        .into(),
132    )
133}
134
135/// Should only be used for sui-tool or tests. Nodes must use build_execution_cache which
136/// uses the epoch_start_config to prevent cache impl from switching except at epoch boundaries.
137pub fn build_execution_cache_from_env(
138    prometheus_registry: &Registry,
139    store: &Arc<AuthorityStore>,
140) -> ExecutionCacheTraitPointers {
141    let execution_cache_metrics = Arc::new(ExecutionCacheMetrics::new(prometheus_registry));
142
143    ExecutionCacheTraitPointers::new(
144        WritebackCache::new(
145            &Default::default(),
146            store.clone(),
147            execution_cache_metrics,
148            BackpressureManager::new_for_tests(),
149        )
150        .into(),
151    )
152}
153
154pub type Batch = (Vec<Arc<TransactionOutputs>>, DBBatch);
155
156pub trait ExecutionCacheCommit: Send + Sync {
157    /// Build a DBBatch containing the given transaction outputs.
158    fn build_db_batch(&self, epoch: EpochId, digests: &[TransactionDigest]) -> Batch;
159
160    /// Stage the highest-committed-checkpoint watermark into `batch` so it is
161    /// written atomically with that checkpoint's transaction outputs. Called by
162    /// CheckpointExecutor between [`Self::build_db_batch`] and
163    /// [`Self::commit_transaction_outputs`]. Unlike the checkpoint store's
164    /// separately-bumped `highest_executed` watermark, this stays consistent
165    /// with the durable object set across an unclean stop.
166    fn set_highest_committed_checkpoint_in_batch(
167        &self,
168        batch: &mut Batch,
169        checkpoint: CheckpointSequenceNumber,
170    );
171
172    /// Durably commit the outputs of the given transactions to the database.
173    /// Will be called by CheckpointExecutor to ensure that transaction outputs are
174    /// written durably before marking a checkpoint as finalized.
175    fn commit_transaction_outputs(
176        &self,
177        epoch: EpochId,
178        batch: Batch,
179        digests: &[TransactionDigest],
180    );
181
182    /// Durably commit a transaction to the database. Used to store any transactions
183    /// that cannot be reconstructed at start-up by consensus replay. Currently the only
184    /// case of this is RandomnessStateUpdate.
185    fn persist_transaction(&self, transaction: &VerifiedExecutableTransaction);
186
187    // Number of pending uncommitted transactions
188    fn approximate_pending_transaction_count(&self) -> u64;
189}
190
191pub trait ObjectCacheRead: Send + Sync {
192    fn get_package_object(&self, id: &ObjectID) -> SuiResult<Option<PackageObject>>;
193    fn force_reload_system_packages(&self, system_package_ids: &[ObjectID]);
194
195    fn get_object(&self, id: &ObjectID) -> Option<Object>;
196
197    fn get_objects(&self, objects: &[ObjectID]) -> Vec<Option<Object>> {
198        let mut ret = Vec::with_capacity(objects.len());
199        for object_id in objects {
200            ret.push(self.get_object(object_id));
201        }
202        ret
203    }
204
205    fn get_latest_object_ref_or_tombstone(&self, object_id: ObjectID) -> Option<ObjectRef>;
206
207    fn get_latest_object_or_tombstone(
208        &self,
209        object_id: ObjectID,
210    ) -> Option<(ObjectKey, ObjectOrTombstone)>;
211
212    fn get_object_by_key(&self, object_id: &ObjectID, version: SequenceNumber) -> Option<Object>;
213
214    fn multi_get_objects_by_key(&self, object_keys: &[ObjectKey]) -> Vec<Option<Object>>;
215
216    fn object_exists_by_key(&self, object_id: &ObjectID, version: SequenceNumber) -> bool;
217
218    fn multi_object_exists_by_key(&self, object_keys: &[ObjectKey]) -> Vec<bool>;
219
220    /// Load a list of objects from the store by object reference.
221    /// If they exist in the store, they are returned directly.
222    /// If any object missing, we try to figure out the best error to return.
223    /// If the object we are asking is currently locked at a future version, we know this
224    /// transaction is out-of-date and we return a ObjectVersionUnavailableForConsumption,
225    /// which indicates this is not retriable.
226    /// Otherwise, we return a ObjectNotFound error, which indicates this is retriable.
227    fn multi_get_objects_with_more_accurate_error_return(
228        &self,
229        object_refs: &[ObjectRef],
230    ) -> Result<Vec<Object>, SuiError> {
231        let objects = self
232            .multi_get_objects_by_key(&object_refs.iter().map(ObjectKey::from).collect::<Vec<_>>());
233        let mut result = Vec::new();
234        for (object_opt, object_ref) in objects.into_iter().zip_debug_eq(object_refs) {
235            match object_opt {
236                None => {
237                    let live_objref = self._get_live_objref(object_ref.0)?;
238                    let error = if live_objref.1 >= object_ref.1 {
239                        UserInputError::ObjectVersionUnavailableForConsumption {
240                            provided_obj_ref: *object_ref,
241                            current_version: live_objref.1,
242                        }
243                    } else {
244                        UserInputError::ObjectNotFound {
245                            object_id: object_ref.0,
246                            version: Some(object_ref.1),
247                        }
248                    };
249                    return Err(SuiErrorKind::UserInputError { error }.into());
250                }
251                Some(object) => {
252                    result.push(object);
253                }
254            }
255        }
256        assert_eq!(result.len(), object_refs.len());
257        Ok(result)
258    }
259
260    /// Used by execution scheduler to determine if input objects are ready. Distinct from multi_get_object_by_key
261    /// because it also consults markers to handle the case where an object will never become available (e.g.
262    /// because it has been received by some other transaction already).
263    fn multi_input_objects_available(
264        &self,
265        keys: &[InputKey],
266        receiving_objects: &HashSet<InputKey>,
267        epoch: EpochId,
268    ) -> Vec<bool> {
269        let mut results = vec![false; keys.len()];
270        let non_canceled_keys = keys.iter().enumerate().filter(|(idx, key)| {
271            if key.is_cancelled() {
272                // Shared objects in canceled transactions are always available.
273                results[*idx] = true;
274                false
275            } else {
276                true
277            }
278        });
279        let (move_object_keys, package_object_keys): (Vec<_>, Vec<_>) = non_canceled_keys
280            .partition_map(|(idx, key)| match key {
281                InputKey::VersionedObject { id, version } => Either::Left((idx, (id, version))),
282                InputKey::Package { id } => Either::Right((idx, id)),
283            });
284
285        for ((idx, (id, version)), has_key) in move_object_keys.iter().zip_debug_eq(
286            self.multi_object_exists_by_key(
287                &move_object_keys
288                    .iter()
289                    .map(|(_, k)| ObjectKey(k.0.id(), *k.1))
290                    .collect::<Vec<_>>(),
291            ),
292        ) {
293            // If the key exists at the specified version, then the object is available.
294            if has_key {
295                results[*idx] = true;
296            } else if receiving_objects.contains(&InputKey::VersionedObject {
297                id: **id,
298                version: **version,
299            }) {
300                // There could be a more recent version of this object, and the object at the
301                // specified version could have already been pruned. In such a case `has_key` will
302                // be false, but since this is a receiving object we should mark it as available if
303                // we can determine that an object with a version greater than or equal to the
304                // specified version exists or was deleted. We will then let mark it as available
305                // to let the transaction through so it can fail at execution.
306                let is_available = self
307                    .get_object(&id.id())
308                    .map(|obj| obj.version() >= **version)
309                    .unwrap_or(false)
310                    || self.fastpath_stream_ended_at_version_or_after(id.id(), **version, epoch);
311                results[*idx] = is_available;
312            } else {
313                // If the object is an already-removed consensus object, mark it as available if the
314                // version for that object is in the marker table.
315                let is_consensus_stream_ended = self
316                    .get_consensus_stream_end_tx_digest(FullObjectKey::new(**id, **version), epoch)
317                    .is_some();
318                results[*idx] = is_consensus_stream_ended;
319            }
320        }
321
322        package_object_keys.into_iter().for_each(|(idx, id)| {
323            // get_package_object() only errors when the object is not a package, so returning false on error.
324            // Error is possible when this gets called on uncertified transactions.
325            results[idx] = self.get_package_object(id).is_ok_and(|p| p.is_some());
326        });
327
328        results
329    }
330
331    fn multi_input_objects_available_cache_only(&self, keys: &[InputKey]) -> Vec<bool>;
332
333    /// Return the object with version less then or eq to the provided seq number.
334    /// This is used by indexer to find the correct version of dynamic field child object.
335    /// We do not store the version of the child object, but because of lamport timestamp,
336    /// we know the child must have version number less then or eq to the parent.
337    fn find_object_lt_or_eq_version(
338        &self,
339        object_id: ObjectID,
340        version: SequenceNumber,
341    ) -> Option<Object>;
342
343    /// Test-only: production code no longer reads owned-object lock status by ref.
344    #[cfg(test)]
345    fn get_lock(&self, obj_ref: ObjectRef, epoch_store: &AuthorityPerEpochStore) -> SuiLockResult;
346
347    // This method is considered "private" - only used by multi_get_objects_with_more_accurate_error_return
348    fn _get_live_objref(&self, object_id: ObjectID) -> SuiResult<ObjectRef>;
349
350    fn get_sui_system_state_object_unsafe(&self) -> SuiResult<SuiSystemState>;
351
352    fn get_bridge_object_unsafe(&self) -> SuiResult<Bridge>;
353
354    // Marker methods
355
356    /// Get the marker at a specific version
357    fn get_marker_value(&self, object_key: FullObjectKey, epoch_id: EpochId)
358    -> Option<MarkerValue>;
359
360    /// Get the latest marker for a given object.
361    fn get_latest_marker(
362        &self,
363        object_id: FullObjectID,
364        epoch_id: EpochId,
365    ) -> Option<(SequenceNumber, MarkerValue)>;
366
367    /// If the given consensus object stream was ended, return related
368    /// version and transaction digest.
369    fn get_last_consensus_stream_end_info(
370        &self,
371        object_id: FullObjectID,
372        epoch_id: EpochId,
373    ) -> Option<(SequenceNumber, TransactionDigest)> {
374        match self.get_latest_marker(object_id, epoch_id) {
375            Some((version, MarkerValue::ConsensusStreamEnded(digest))) => Some((version, digest)),
376            _ => None,
377        }
378    }
379
380    /// If the given consensus object stream was ended at the specified version,
381    /// return related transaction digest.
382    fn get_consensus_stream_end_tx_digest(
383        &self,
384        object_key: FullObjectKey,
385        epoch_id: EpochId,
386    ) -> Option<TransactionDigest> {
387        match self.get_marker_value(object_key, epoch_id) {
388            Some(MarkerValue::ConsensusStreamEnded(digest)) => Some(digest),
389            _ => None,
390        }
391    }
392
393    fn have_received_object_at_version(
394        &self,
395        object_key: FullObjectKey,
396        epoch_id: EpochId,
397    ) -> bool {
398        matches!(
399            self.get_marker_value(object_key, epoch_id),
400            Some(MarkerValue::Received)
401        )
402    }
403
404    fn fastpath_stream_ended_at_version_or_after(
405        &self,
406        object_id: ObjectID,
407        version: SequenceNumber,
408        epoch_id: EpochId,
409    ) -> bool {
410        let full_id = FullObjectID::Fastpath(object_id); // function explicitly assumes "fastpath"
411        matches!(
412            self.get_latest_marker(full_id, epoch_id),
413            Some((marker_version, MarkerValue::FastpathStreamEnded)) if marker_version >= version
414        )
415    }
416
417    /// Return the watermark for the highest checkpoint for which we've pruned objects.
418    fn get_highest_pruned_checkpoint(&self) -> Option<CheckpointSequenceNumber>;
419
420    /// Given a list of input and receiving objects for a transaction,
421    /// wait until all of them become available, so that the transaction
422    /// can start execution.
423    /// `input_and_receiving_keys` contains both input objects and receiving
424    /// input objects, including canceled objects.
425    /// TODO: Eventually this can return the objects read results,
426    /// so that execution does not need to load them again.
427    fn notify_read_input_objects<'a>(
428        &'a self,
429        input_and_receiving_keys: &'a [InputKey],
430        receiving_keys: &'a HashSet<InputKey>,
431        epoch: EpochId,
432    ) -> BoxFuture<'a, ()>;
433}
434
435pub trait TransactionCacheRead: Send + Sync {
436    fn multi_get_transaction_blocks(
437        &self,
438        digests: &[TransactionDigest],
439    ) -> Vec<Option<Arc<VerifiedTransaction>>>;
440
441    fn get_transaction_block(
442        &self,
443        digest: &TransactionDigest,
444    ) -> Option<Arc<VerifiedTransaction>> {
445        self.multi_get_transaction_blocks(&[*digest])
446            .pop()
447            .expect("multi-get must return correct number of items")
448    }
449
450    fn multi_get_executed_effects_digests(
451        &self,
452        digests: &[TransactionDigest],
453    ) -> Vec<Option<TransactionEffectsDigest>>;
454
455    fn is_tx_already_executed(&self, digest: &TransactionDigest) -> bool {
456        self.multi_get_executed_effects_digests(&[*digest])
457            .pop()
458            .expect("multi-get must return correct number of items")
459            .is_some()
460    }
461
462    fn multi_get_executed_effects(
463        &self,
464        digests: &[TransactionDigest],
465    ) -> Vec<Option<TransactionEffects>> {
466        let effects_digests = self.multi_get_executed_effects_digests(digests);
467        assert_eq!(effects_digests.len(), digests.len());
468
469        let mut results = vec![None; digests.len()];
470        let mut fetch_digests = Vec::with_capacity(digests.len());
471        let mut fetch_indices = Vec::with_capacity(digests.len());
472
473        for (i, digest) in effects_digests.into_iter().enumerate() {
474            if let Some(digest) = digest {
475                fetch_digests.push(digest);
476                fetch_indices.push(i);
477            }
478        }
479
480        let effects = self.multi_get_effects(&fetch_digests);
481        for (i, effects) in fetch_indices.into_iter().zip_debug_eq(effects) {
482            results[i] = effects;
483        }
484
485        results
486    }
487
488    fn get_executed_effects(&self, digest: &TransactionDigest) -> Option<TransactionEffects> {
489        self.multi_get_executed_effects(&[*digest])
490            .pop()
491            .expect("multi-get must return correct number of items")
492    }
493
494    fn transaction_executed_in_last_epoch(
495        &self,
496        digest: &TransactionDigest,
497        current_epoch: EpochId,
498    ) -> bool;
499
500    fn multi_get_effects(
501        &self,
502        digests: &[TransactionEffectsDigest],
503    ) -> Vec<Option<TransactionEffects>>;
504
505    fn get_effects(&self, digest: &TransactionEffectsDigest) -> Option<TransactionEffects> {
506        self.multi_get_effects(&[*digest])
507            .pop()
508            .expect("multi-get must return correct number of items")
509    }
510
511    fn multi_get_events(&self, digests: &[TransactionDigest]) -> Vec<Option<TransactionEvents>>;
512
513    fn get_events(&self, digest: &TransactionDigest) -> Option<TransactionEvents> {
514        self.multi_get_events(&[*digest])
515            .pop()
516            .expect("multi-get must return correct number of items")
517    }
518
519    fn get_unchanged_loaded_runtime_objects(
520        &self,
521        digest: &TransactionDigest,
522    ) -> Option<Vec<ObjectKey>>;
523
524    fn multi_get_unchanged_loaded_runtime_objects(
525        &self,
526        digests: &[TransactionDigest],
527    ) -> Vec<Option<Vec<ObjectKey>>> {
528        digests
529            .iter()
530            .map(|digest| self.get_unchanged_loaded_runtime_objects(digest))
531            .collect()
532    }
533
534    fn take_accumulator_events(&self, digest: &TransactionDigest) -> Option<Vec<AccumulatorEvent>>;
535
536    fn notify_read_executed_effects_digests<'a>(
537        &'a self,
538        task_name: &'static str,
539        digests: &'a [TransactionDigest],
540    ) -> BoxFuture<'a, Vec<TransactionEffectsDigest>>;
541
542    /// Wait until the effects of the given transactions are available and return them.
543    /// WARNING: If calling this on a transaction that could be reverted, you must be
544    /// sure that this function cannot be called during reconfiguration. The best way to
545    /// do this is to wrap your future in EpochStore::within_alive_epoch. Holding an
546    /// ExecutionLockReadGuard would also prevent reconfig from happening while waiting,
547    /// but this is very dangerous, as it could prevent reconfiguration from ever
548    /// occurring!
549    ///
550    /// This function panics if any of the requested effects are not found. Use this in
551    /// critical paths where effects are expected to exist (e.g., checkpoint building,
552    /// consensus commit processing). For non-critical paths where effects may have been
553    /// pruned (e.g., serving historical data to clients), use `notify_read_executed_effects_may_fail`.
554    fn notify_read_executed_effects<'a>(
555        &'a self,
556        task_name: &'static str,
557        digests: &'a [TransactionDigest],
558    ) -> BoxFuture<'a, Vec<TransactionEffects>> {
559        async move {
560            self.notify_read_executed_effects_may_fail(task_name, digests)
561                .await
562                .unwrap_or_else(|e| panic!("effects must exist: {e}"))
563        }
564        .boxed()
565    }
566
567    /// Returns an error if any of the requested effects have been pruned from the database.
568    /// Use this in non-critical paths where effects may not exist (e.g., serving historical
569    /// data that may have been pruned). For critical paths where effects must exist,
570    /// use `notify_read_executed_effects`.
571    fn notify_read_executed_effects_may_fail<'a>(
572        &'a self,
573        task_name: &'static str,
574        digests: &'a [TransactionDigest],
575    ) -> BoxFuture<'a, SuiResult<Vec<TransactionEffects>>> {
576        async move {
577            let effects_digests = self
578                .notify_read_executed_effects_digests(task_name, digests)
579                .await;
580            self.multi_get_effects(&effects_digests)
581                .into_iter()
582                .zip_debug_eq(digests)
583                .map(|(e, digest)| {
584                    e.ok_or_else(|| {
585                        SuiError::from(SuiErrorKind::TransactionEffectsNotFound { digest: *digest })
586                    })
587                })
588                .collect()
589        }
590        .boxed()
591    }
592}
593
594pub trait ExecutionCacheWrite: Send + Sync {
595    /// Write the output of a transaction.
596    ///
597    /// Because of the child object consistency rule (readers that observe parents must observe all
598    /// children of that parent, up to the parent's version bound), implementations of this method
599    /// must not write any top-level (address-owned or shared) objects before they have written all
600    /// of the object-owned objects (i.e. child objects) in the `objects` list.
601    ///
602    /// In the future, we may modify this method to expose finer-grained information about
603    /// parent/child relationships. (This may be especially necessary for distributed object
604    /// storage, but is unlikely to be an issue before we tackle that problem).
605    ///
606    /// This function may evict the mutable input objects (and successfully received objects) of
607    /// transaction from the cache, since they cannot be read by any other transaction.
608    ///
609    /// Any write performed by this method immediately notifies any waiter that has previously
610    /// called notify_read_objects_for_execution or notify_read_objects_for_signing for the object
611    /// in question.
612    fn write_transaction_outputs(&self, epoch_id: EpochId, tx_outputs: Arc<TransactionOutputs>);
613
614    /// Validate owned object versions and digests without acquiring locks.
615    /// Used to validate transaction input before submitting or voting to accept the transaction.
616    fn validate_owned_object_versions(&self, owned_input_objects: &[ObjectRef]) -> SuiResult;
617
618    /// Write an object entry directly to the cache for testing.
619    /// This allows us to write an object without constructing the entire
620    /// transaction outputs.
621    #[cfg(test)]
622    fn write_object_entry_for_test(&self, object: Object);
623}
624
625pub trait CheckpointCache: Send + Sync {
626    // TODO: In addition to the deprecated methods below, this will eventually include access
627    // to the CheckpointStore
628
629    // DEPRECATED METHODS
630    fn deprecated_get_transaction_checkpoint(
631        &self,
632        digest: &TransactionDigest,
633    ) -> Option<(EpochId, CheckpointSequenceNumber)>;
634
635    fn deprecated_multi_get_transaction_checkpoint(
636        &self,
637        digests: &[TransactionDigest],
638    ) -> Vec<Option<(EpochId, CheckpointSequenceNumber)>>;
639
640    fn deprecated_insert_finalized_transactions(
641        &self,
642        digests: &[TransactionDigest],
643        epoch: EpochId,
644        sequence: CheckpointSequenceNumber,
645    );
646}
647
648pub trait ExecutionCacheReconfigAPI: Send + Sync {
649    fn insert_genesis_object(&self, object: Object);
650    fn bulk_insert_genesis_objects(&self, objects: &[Object]);
651
652    fn set_epoch_start_configuration(&self, epoch_start_config: &EpochStartConfiguration);
653
654    fn update_epoch_flags_metrics(&self, old: &[EpochFlag], new: &[EpochFlag]);
655
656    fn clear_state_end_of_epoch(&self, execution_guard: &ExecutionLockWriteGuard<'_>);
657
658    fn expensive_check_sui_conservation(
659        &self,
660        old_epoch_store: &AuthorityPerEpochStore,
661    ) -> SuiResult;
662
663    fn checkpoint_db(&self, path: &Path) -> SuiResult;
664
665    /// This is a temporary method to be used when we enable simplified_unwrap_then_delete.
666    /// It re-accumulates state hash for the new epoch if simplified_unwrap_then_delete is enabled.
667    fn maybe_reaccumulate_state_hash(
668        &self,
669        cur_epoch_store: &AuthorityPerEpochStore,
670        new_protocol_version: ProtocolVersion,
671    );
672
673    /// Reconfigure the cache itself.
674    /// TODO: this is only needed for ProxyCache to switch between cache impls. It can be removed
675    /// once WritebackCache is the sole cache impl.
676    fn reconfigure_cache<'a>(
677        &'a self,
678        epoch_start_config: &'a EpochStartConfiguration,
679    ) -> BoxFuture<'a, ()>;
680}
681
682// StateSyncAPI is for writing any data that was not the result of transaction execution,
683// but that arrived via state sync. The fact that it came via state sync implies that it
684// is certified output, and can be immediately persisted to the store.
685pub trait StateSyncAPI: Send + Sync {
686    fn insert_transaction_and_effects(
687        &self,
688        transaction: &VerifiedTransaction,
689        transaction_effects: &TransactionEffects,
690    );
691
692    fn multi_insert_transaction_and_effects(
693        &self,
694        transactions_and_effects: &[VerifiedExecutionData],
695    );
696}
697
698pub trait TestingAPI: Send + Sync {
699    fn database_for_testing(&self) -> Arc<AuthorityStore>;
700
701    fn cache_for_testing(&self) -> &WritebackCache;
702}
703
704macro_rules! implement_storage_traits {
705    ($implementor: ident) => {
706        impl ObjectStore for $implementor {
707            fn get_object(&self, object_id: &ObjectID) -> Option<Object> {
708                ObjectCacheRead::get_object(self, object_id)
709            }
710
711            fn get_object_by_key(
712                &self,
713                object_id: &ObjectID,
714                version: sui_types::base_types::VersionNumber,
715            ) -> Option<Object> {
716                ObjectCacheRead::get_object_by_key(self, object_id, version)
717            }
718        }
719
720        impl RuntimeObjectResolver for $implementor {
721            fn read_child_object(
722                &self,
723                parent: &ObjectID,
724                child: &ObjectID,
725                child_version_upper_bound: SequenceNumber,
726            ) -> SuiResult<Option<Object>> {
727                let Some(child_object) =
728                    self.find_object_lt_or_eq_version(*child, child_version_upper_bound)
729                else {
730                    return Ok(None);
731                };
732
733                let parent = *parent;
734                if child_object.owner != Owner::ObjectOwner(parent.into()) {
735                    return Err(SuiErrorKind::InvalidChildObjectAccess {
736                        object: *child,
737                        given_parent: parent,
738                        actual_owner: child_object.owner.clone(),
739                    }
740                    .into());
741                }
742                Ok(Some(child_object))
743            }
744
745            fn get_object_received_at_version(
746                &self,
747                owner: &ObjectID,
748                receiving_object_id: &ObjectID,
749                receive_object_at_version: SequenceNumber,
750                epoch_id: EpochId,
751            ) -> SuiResult<Option<Object>> {
752                let Some(recv_object) = ObjectCacheRead::get_object_by_key(
753                    self,
754                    receiving_object_id,
755                    receive_object_at_version,
756                ) else {
757                    return Ok(None);
758                };
759
760                // Check for:
761                // * Invalid access -- treat as the object does not exist. Or;
762                // * If we've already received the object at the version -- then treat it as though it doesn't exist.
763                // These two cases must remain indisguishable to the caller otherwise we risk forks in
764                // transaction replay due to possible reordering of transactions during replay.
765                if recv_object.owner != Owner::AddressOwner((*owner).into())
766                    || self.have_received_object_at_version(
767                        // TODO: Add support for receiving consensus objects. For now this assumes fastpath.
768                        FullObjectKey::new(
769                            FullObjectID::new(*receiving_object_id, None),
770                            receive_object_at_version,
771                        ),
772                        epoch_id,
773                    )
774                {
775                    return Ok(None);
776                }
777
778                Ok(Some(recv_object))
779            }
780        }
781
782        impl BackingPackageStore for $implementor {
783            fn get_package_object(
784                &self,
785                package_id: &ObjectID,
786            ) -> SuiResult<Option<PackageObject>> {
787                ObjectCacheRead::get_package_object(self, package_id)
788            }
789        }
790
791        impl ParentSync for $implementor {
792            fn get_latest_parent_entry_ref_deprecated(
793                &self,
794                object_id: ObjectID,
795            ) -> Option<ObjectRef> {
796                ObjectCacheRead::get_latest_object_ref_or_tombstone(self, object_id)
797            }
798        }
799    };
800}
801
802// Implement traits for a cache implementation that always go directly to the store.
803macro_rules! implement_passthrough_traits {
804    ($implementor: ident) => {
805        impl CheckpointCache for $implementor {
806            fn deprecated_get_transaction_checkpoint(
807                &self,
808                digest: &TransactionDigest,
809            ) -> Option<(EpochId, CheckpointSequenceNumber)> {
810                self.store
811                    .deprecated_get_transaction_checkpoint(digest)
812                    .expect("db error")
813            }
814
815            fn deprecated_multi_get_transaction_checkpoint(
816                &self,
817                digests: &[TransactionDigest],
818            ) -> Vec<Option<(EpochId, CheckpointSequenceNumber)>> {
819                self.store
820                    .deprecated_multi_get_transaction_checkpoint(digests)
821                    .expect("db error")
822            }
823
824            fn deprecated_insert_finalized_transactions(
825                &self,
826                digests: &[TransactionDigest],
827                epoch: EpochId,
828                sequence: CheckpointSequenceNumber,
829            ) {
830                self.store
831                    .deprecated_insert_finalized_transactions(digests, epoch, sequence)
832                    .expect("db error");
833            }
834        }
835
836        impl ExecutionCacheReconfigAPI for $implementor {
837            fn insert_genesis_object(&self, object: Object) {
838                self.insert_genesis_object_impl(object)
839            }
840
841            fn bulk_insert_genesis_objects(&self, objects: &[Object]) {
842                self.bulk_insert_genesis_objects_impl(objects)
843            }
844
845            fn set_epoch_start_configuration(&self, epoch_start_config: &EpochStartConfiguration) {
846                self.store
847                    .set_epoch_start_configuration(epoch_start_config)
848                    .expect("db error");
849            }
850
851            fn update_epoch_flags_metrics(&self, old: &[EpochFlag], new: &[EpochFlag]) {
852                self.store.update_epoch_flags_metrics(old, new)
853            }
854
855            fn clear_state_end_of_epoch(&self, execution_guard: &ExecutionLockWriteGuard<'_>) {
856                self.clear_state_end_of_epoch_impl(execution_guard)
857            }
858
859            fn expensive_check_sui_conservation(
860                &self,
861                old_epoch_store: &AuthorityPerEpochStore,
862            ) -> SuiResult {
863                self.store
864                    .expensive_check_sui_conservation(self, old_epoch_store)
865            }
866
867            fn checkpoint_db(&self, path: &std::path::Path) -> SuiResult {
868                self.store.perpetual_tables.checkpoint_db(path)
869            }
870
871            fn maybe_reaccumulate_state_hash(
872                &self,
873                cur_epoch_store: &AuthorityPerEpochStore,
874                new_protocol_version: ProtocolVersion,
875            ) {
876                self.store
877                    .maybe_reaccumulate_state_hash(cur_epoch_store, new_protocol_version)
878            }
879
880            fn reconfigure_cache<'a>(
881                &'a self,
882                _: &'a EpochStartConfiguration,
883            ) -> BoxFuture<'a, ()> {
884                // Since we now use WritebackCache directly at startup (if the epoch flag is set),
885                // this can be called at reconfiguration time. It is a no-op.
886                // TODO: remove this once we completely remove ProxyCache.
887                std::future::ready(()).boxed()
888            }
889        }
890
891        impl TestingAPI for $implementor {
892            fn database_for_testing(&self) -> Arc<AuthorityStore> {
893                self.store.clone()
894            }
895
896            fn cache_for_testing(&self) -> &WritebackCache {
897                self
898            }
899        }
900    };
901}
902
903use implement_passthrough_traits;
904
905implement_storage_traits!(WritebackCache);
906
907pub trait ExecutionCacheAPI:
908    ObjectCacheRead
909    + ExecutionCacheWrite
910    + ExecutionCacheCommit
911    + ExecutionCacheReconfigAPI
912    + CheckpointCache
913    + StateSyncAPI
914{
915}