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