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, SuiLockResult};
8use crate::authority::backpressure::BackpressureManager;
9use crate::authority::epoch_start_configuration::EpochFlag;
10use crate::authority::epoch_start_configuration::EpochStartConfiguration;
11use crate::global_state_hasher::GlobalStateHashStore;
12use crate::transaction_outputs::TransactionOutputs;
13use either::Either;
14use itertools::Itertools;
15use mysten_common::ZipDebugEqIteratorExt;
16use sui_types::accumulator_event::AccumulatorEvent;
17use sui_types::bridge::Bridge;
18
19use futures::{FutureExt, future::BoxFuture};
20use prometheus::Registry;
21use std::collections::HashSet;
22use std::path::Path;
23use std::sync::Arc;
24use sui_config::ExecutionCacheConfig;
25use sui_protocol_config::ProtocolVersion;
26use sui_types::base_types::{FullObjectID, VerifiedExecutionData};
27use sui_types::digests::{TransactionDigest, TransactionEffectsDigest};
28use sui_types::effects::{TransactionEffects, TransactionEvents};
29use sui_types::error::{SuiError, SuiErrorKind, SuiResult, UserInputError};
30use sui_types::executable_transaction::VerifiedExecutableTransaction;
31use sui_types::messages_checkpoint::CheckpointSequenceNumber;
32use sui_types::object::Object;
33use sui_types::storage::{
34    BackingPackageStore, BackingStore, ChildObjectResolver, FullObjectKey, MarkerValue, ObjectKey,
35    ObjectOrTombstone, ObjectStore, PackageObject, ParentSync,
36};
37use sui_types::sui_system_state::SuiSystemState;
38use sui_types::transaction::VerifiedTransaction;
39use sui_types::{
40    base_types::{EpochId, ObjectID, ObjectRef, SequenceNumber},
41    object::Owner,
42    storage::InputKey,
43};
44use typed_store::rocks::DBBatch;
45
46pub(crate) mod cache_types;
47pub mod metrics;
48mod object_locks;
49pub mod writeback_cache;
50
51pub use writeback_cache::WritebackCache;
52
53use metrics::ExecutionCacheMetrics;
54
55// If you have Arc<ExecutionCache>, you cannot return a reference to it as
56// an &Arc<dyn ExecutionCacheRead> (for example), because the trait object is a fat pointer.
57// So, in order to be able to return &Arc<dyn T>, we create all the converted trait objects
58// (aka fat pointers) up front and return references to them.
59#[derive(Clone)]
60pub struct ExecutionCacheTraitPointers {
61    pub object_cache_reader: Arc<dyn ObjectCacheRead>,
62    pub transaction_cache_reader: Arc<dyn TransactionCacheRead>,
63    pub cache_writer: Arc<dyn ExecutionCacheWrite>,
64    pub backing_store: Arc<dyn BackingStore + Send + Sync>,
65    pub child_object_resolver: Arc<dyn ChildObjectResolver + Send + Sync>,
66    pub backing_package_store: Arc<dyn BackingPackageStore + Send + Sync>,
67    pub object_store: Arc<dyn ObjectStore + Send + Sync>,
68    pub reconfig_api: Arc<dyn ExecutionCacheReconfigAPI>,
69    pub global_state_hash_store: Arc<dyn GlobalStateHashStore>,
70    pub checkpoint_cache: Arc<dyn CheckpointCache>,
71    pub state_sync_store: Arc<dyn StateSyncAPI>,
72    pub cache_commit: Arc<dyn ExecutionCacheCommit>,
73    pub testing_api: Arc<dyn TestingAPI>,
74    pub account_funds_read: Arc<dyn AccountFundsRead>,
75}
76
77impl ExecutionCacheTraitPointers {
78    pub fn new<T>(cache: Arc<T>) -> Self
79    where
80        T: ObjectCacheRead
81            + TransactionCacheRead
82            + ExecutionCacheWrite
83            + BackingStore
84            + BackingPackageStore
85            + ObjectStore
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            child_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        for ((idx, (id, version)), has_key) in move_object_keys.iter().zip_debug_eq(
284            self.multi_object_exists_by_key(
285                &move_object_keys
286                    .iter()
287                    .map(|(_, k)| ObjectKey(k.0.id(), *k.1))
288                    .collect::<Vec<_>>(),
289            )
290            .into_iter(),
291        ) {
292            // If the key exists at the specified version, then the object is available.
293            if has_key {
294                results[*idx] = true;
295            } else if receiving_objects.contains(&InputKey::VersionedObject {
296                id: **id,
297                version: **version,
298            }) {
299                // There could be a more recent version of this object, and the object at the
300                // specified version could have already been pruned. In such a case `has_key` will
301                // be false, but since this is a receiving object we should mark it as available if
302                // we can determine that an object with a version greater than or equal to the
303                // specified version exists or was deleted. We will then let mark it as available
304                // to let the transaction through so it can fail at execution.
305                let is_available = self
306                    .get_object(&id.id())
307                    .map(|obj| obj.version() >= **version)
308                    .unwrap_or(false)
309                    || self.fastpath_stream_ended_at_version_or_after(id.id(), **version, epoch);
310                results[*idx] = is_available;
311            } else {
312                // If the object is an already-removed consensus object, mark it as available if the
313                // version for that object is in the marker table.
314                let is_consensus_stream_ended = self
315                    .get_consensus_stream_end_tx_digest(FullObjectKey::new(**id, **version), epoch)
316                    .is_some();
317                results[*idx] = is_consensus_stream_ended;
318            }
319        }
320
321        package_object_keys.into_iter().for_each(|(idx, id)| {
322            // get_package_object() only errors when the object is not a package, so returning false on error.
323            // Error is possible when this gets called on uncertified transactions.
324            results[idx] = self.get_package_object(id).is_ok_and(|p| p.is_some());
325        });
326
327        results
328    }
329
330    fn multi_input_objects_available_cache_only(&self, keys: &[InputKey]) -> Vec<bool>;
331
332    /// Return the object with version less then or eq to the provided seq number.
333    /// This is used by indexer to find the correct version of dynamic field child object.
334    /// We do not store the version of the child object, but because of lamport timestamp,
335    /// we know the child must have version number less then or eq to the parent.
336    fn find_object_lt_or_eq_version(
337        &self,
338        object_id: ObjectID,
339        version: SequenceNumber,
340    ) -> Option<Object>;
341
342    fn get_lock(&self, obj_ref: ObjectRef, epoch_store: &AuthorityPerEpochStore) -> SuiLockResult;
343
344    // This method is considered "private" - only used by multi_get_objects_with_more_accurate_error_return
345    fn _get_live_objref(&self, object_id: ObjectID) -> SuiResult<ObjectRef>;
346
347    // Check that the given set of objects are live at the given version. This is used as a
348    // safety check before execution, and could potentially be deleted or changed to a debug_assert
349    fn check_owned_objects_are_live(&self, owned_object_refs: &[ObjectRef]) -> SuiResult;
350
351    fn get_sui_system_state_object_unsafe(&self) -> SuiResult<SuiSystemState>;
352
353    fn get_bridge_object_unsafe(&self) -> SuiResult<Bridge>;
354
355    // Marker methods
356
357    /// Get the marker at a specific version
358    fn get_marker_value(&self, object_key: FullObjectKey, epoch_id: EpochId)
359    -> Option<MarkerValue>;
360
361    /// Get the latest marker for a given object.
362    fn get_latest_marker(
363        &self,
364        object_id: FullObjectID,
365        epoch_id: EpochId,
366    ) -> Option<(SequenceNumber, MarkerValue)>;
367
368    /// If the given consensus object stream was ended, return related
369    /// version and transaction digest.
370    fn get_last_consensus_stream_end_info(
371        &self,
372        object_id: FullObjectID,
373        epoch_id: EpochId,
374    ) -> Option<(SequenceNumber, TransactionDigest)> {
375        match self.get_latest_marker(object_id, epoch_id) {
376            Some((version, MarkerValue::ConsensusStreamEnded(digest))) => Some((version, digest)),
377            _ => None,
378        }
379    }
380
381    /// If the given consensus object stream was ended at the specified version,
382    /// return related transaction digest.
383    fn get_consensus_stream_end_tx_digest(
384        &self,
385        object_key: FullObjectKey,
386        epoch_id: EpochId,
387    ) -> Option<TransactionDigest> {
388        match self.get_marker_value(object_key, epoch_id) {
389            Some(MarkerValue::ConsensusStreamEnded(digest)) => Some(digest),
390            _ => None,
391        }
392    }
393
394    fn have_received_object_at_version(
395        &self,
396        object_key: FullObjectKey,
397        epoch_id: EpochId,
398    ) -> bool {
399        matches!(
400            self.get_marker_value(object_key, epoch_id),
401            Some(MarkerValue::Received)
402        )
403    }
404
405    fn fastpath_stream_ended_at_version_or_after(
406        &self,
407        object_id: ObjectID,
408        version: SequenceNumber,
409        epoch_id: EpochId,
410    ) -> bool {
411        let full_id = FullObjectID::Fastpath(object_id); // function explicitly assumes "fastpath"
412        matches!(
413            self.get_latest_marker(full_id, epoch_id),
414            Some((marker_version, MarkerValue::FastpathStreamEnded)) if marker_version >= version
415        )
416    }
417
418    /// Return the watermark for the highest checkpoint for which we've pruned objects.
419    fn get_highest_pruned_checkpoint(&self) -> Option<CheckpointSequenceNumber>;
420
421    /// Given a list of input and receiving objects for a transaction,
422    /// wait until all of them become available, so that the transaction
423    /// can start execution.
424    /// `input_and_receiving_keys` contains both input objects and receiving
425    /// input objects, including canceled objects.
426    /// TODO: Eventually this can return the objects read results,
427    /// so that execution does not need to load them again.
428    fn notify_read_input_objects<'a>(
429        &'a self,
430        input_and_receiving_keys: &'a [InputKey],
431        receiving_keys: &'a HashSet<InputKey>,
432        epoch: EpochId,
433    ) -> BoxFuture<'a, ()>;
434}
435
436pub trait TransactionCacheRead: Send + Sync {
437    fn multi_get_transaction_blocks(
438        &self,
439        digests: &[TransactionDigest],
440    ) -> Vec<Option<Arc<VerifiedTransaction>>>;
441
442    fn get_transaction_block(
443        &self,
444        digest: &TransactionDigest,
445    ) -> Option<Arc<VerifiedTransaction>> {
446        self.multi_get_transaction_blocks(&[*digest])
447            .pop()
448            .expect("multi-get must return correct number of items")
449    }
450
451    fn multi_get_executed_effects_digests(
452        &self,
453        digests: &[TransactionDigest],
454    ) -> Vec<Option<TransactionEffectsDigest>>;
455
456    fn is_tx_already_executed(&self, digest: &TransactionDigest) -> bool {
457        self.multi_get_executed_effects_digests(&[*digest])
458            .pop()
459            .expect("multi-get must return correct number of items")
460            .is_some()
461    }
462
463    fn multi_get_executed_effects(
464        &self,
465        digests: &[TransactionDigest],
466    ) -> Vec<Option<TransactionEffects>> {
467        let effects_digests = self.multi_get_executed_effects_digests(digests);
468        assert_eq!(effects_digests.len(), digests.len());
469
470        let mut results = vec![None; digests.len()];
471        let mut fetch_digests = Vec::with_capacity(digests.len());
472        let mut fetch_indices = Vec::with_capacity(digests.len());
473
474        for (i, digest) in effects_digests.into_iter().enumerate() {
475            if let Some(digest) = digest {
476                fetch_digests.push(digest);
477                fetch_indices.push(i);
478            }
479        }
480
481        let effects = self.multi_get_effects(&fetch_digests);
482        for (i, effects) in fetch_indices.into_iter().zip_debug_eq(effects.into_iter()) {
483            results[i] = effects;
484        }
485
486        results
487    }
488
489    fn get_executed_effects(&self, digest: &TransactionDigest) -> Option<TransactionEffects> {
490        self.multi_get_executed_effects(&[*digest])
491            .pop()
492            .expect("multi-get must return correct number of items")
493    }
494
495    fn transaction_executed_in_last_epoch(
496        &self,
497        digest: &TransactionDigest,
498        current_epoch: EpochId,
499    ) -> bool;
500
501    fn multi_get_effects(
502        &self,
503        digests: &[TransactionEffectsDigest],
504    ) -> Vec<Option<TransactionEffects>>;
505
506    fn get_effects(&self, digest: &TransactionEffectsDigest) -> Option<TransactionEffects> {
507        self.multi_get_effects(&[*digest])
508            .pop()
509            .expect("multi-get must return correct number of items")
510    }
511
512    fn multi_get_events(&self, digests: &[TransactionDigest]) -> Vec<Option<TransactionEvents>>;
513
514    fn get_events(&self, digest: &TransactionDigest) -> Option<TransactionEvents> {
515        self.multi_get_events(&[*digest])
516            .pop()
517            .expect("multi-get must return correct number of items")
518    }
519
520    fn get_unchanged_loaded_runtime_objects(
521        &self,
522        digest: &TransactionDigest,
523    ) -> Option<Vec<ObjectKey>>;
524
525    fn take_accumulator_events(&self, digest: &TransactionDigest) -> Option<Vec<AccumulatorEvent>>;
526
527    fn notify_read_executed_effects_digests<'a>(
528        &'a self,
529        task_name: &'static str,
530        digests: &'a [TransactionDigest],
531    ) -> BoxFuture<'a, Vec<TransactionEffectsDigest>>;
532
533    /// Wait until the effects of the given transactions are available and return them.
534    /// WARNING: If calling this on a transaction that could be reverted, you must be
535    /// sure that this function cannot be called during reconfiguration. The best way to
536    /// do this is to wrap your future in EpochStore::within_alive_epoch. Holding an
537    /// ExecutionLockReadGuard would also prevent reconfig from happening while waiting,
538    /// but this is very dangerous, as it could prevent reconfiguration from ever
539    /// occurring!
540    ///
541    /// This function panics if any of the requested effects are not found. Use this in
542    /// critical paths where effects are expected to exist (e.g., checkpoint building,
543    /// consensus commit processing). For non-critical paths where effects may have been
544    /// pruned (e.g., serving historical data to clients), use `notify_read_executed_effects_may_fail`.
545    fn notify_read_executed_effects<'a>(
546        &'a self,
547        task_name: &'static str,
548        digests: &'a [TransactionDigest],
549    ) -> BoxFuture<'a, Vec<TransactionEffects>> {
550        async move {
551            self.notify_read_executed_effects_may_fail(task_name, digests)
552                .await
553                .unwrap_or_else(|e| panic!("effects must exist: {e}"))
554        }
555        .boxed()
556    }
557
558    /// Returns an error if any of the requested effects have been pruned from the database.
559    /// Use this in non-critical paths where effects may not exist (e.g., serving historical
560    /// data that may have been pruned). For critical paths where effects must exist,
561    /// use `notify_read_executed_effects`.
562    fn notify_read_executed_effects_may_fail<'a>(
563        &'a self,
564        task_name: &'static str,
565        digests: &'a [TransactionDigest],
566    ) -> BoxFuture<'a, SuiResult<Vec<TransactionEffects>>> {
567        async move {
568            let effects_digests = self
569                .notify_read_executed_effects_digests(task_name, digests)
570                .await;
571            self.multi_get_effects(&effects_digests)
572                .into_iter()
573                .zip_debug_eq(digests)
574                .map(|(e, digest)| {
575                    e.ok_or_else(|| {
576                        SuiError::from(SuiErrorKind::TransactionEffectsNotFound { digest: *digest })
577                    })
578                })
579                .collect()
580        }
581        .boxed()
582    }
583}
584
585pub trait ExecutionCacheWrite: Send + Sync {
586    /// Write the output of a transaction.
587    ///
588    /// Because of the child object consistency rule (readers that observe parents must observe all
589    /// children of that parent, up to the parent's version bound), implementations of this method
590    /// must not write any top-level (address-owned or shared) objects before they have written all
591    /// of the object-owned objects (i.e. child objects) in the `objects` list.
592    ///
593    /// In the future, we may modify this method to expose finer-grained information about
594    /// parent/child relationships. (This may be especially necessary for distributed object
595    /// storage, but is unlikely to be an issue before we tackle that problem).
596    ///
597    /// This function may evict the mutable input objects (and successfully received objects) of
598    /// transaction from the cache, since they cannot be read by any other transaction.
599    ///
600    /// Any write performed by this method immediately notifies any waiter that has previously
601    /// called notify_read_objects_for_execution or notify_read_objects_for_signing for the object
602    /// in question.
603    fn write_transaction_outputs(&self, epoch_id: EpochId, tx_outputs: Arc<TransactionOutputs>);
604
605    /// Validate owned object versions and digests without acquiring locks.
606    /// Used to validate transaction input before submitting or voting to accept the transaction.
607    fn validate_owned_object_versions(&self, owned_input_objects: &[ObjectRef]) -> SuiResult;
608
609    /// Write an object entry directly to the cache for testing.
610    /// This allows us to write an object without constructing the entire
611    /// transaction outputs.
612    #[cfg(test)]
613    fn write_object_entry_for_test(&self, object: Object);
614}
615
616pub trait CheckpointCache: Send + Sync {
617    // TODO: In addition to the deprecated methods below, this will eventually include access
618    // to the CheckpointStore
619
620    // DEPRECATED METHODS
621    fn deprecated_get_transaction_checkpoint(
622        &self,
623        digest: &TransactionDigest,
624    ) -> Option<(EpochId, CheckpointSequenceNumber)>;
625
626    fn deprecated_multi_get_transaction_checkpoint(
627        &self,
628        digests: &[TransactionDigest],
629    ) -> Vec<Option<(EpochId, CheckpointSequenceNumber)>>;
630
631    fn deprecated_insert_finalized_transactions(
632        &self,
633        digests: &[TransactionDigest],
634        epoch: EpochId,
635        sequence: CheckpointSequenceNumber,
636    );
637}
638
639pub trait ExecutionCacheReconfigAPI: Send + Sync {
640    fn insert_genesis_object(&self, object: Object);
641    fn bulk_insert_genesis_objects(&self, objects: &[Object]);
642
643    fn set_epoch_start_configuration(&self, epoch_start_config: &EpochStartConfiguration);
644
645    fn update_epoch_flags_metrics(&self, old: &[EpochFlag], new: &[EpochFlag]);
646
647    fn clear_state_end_of_epoch(&self, execution_guard: &ExecutionLockWriteGuard<'_>);
648
649    fn expensive_check_sui_conservation(
650        &self,
651        old_epoch_store: &AuthorityPerEpochStore,
652    ) -> SuiResult;
653
654    fn checkpoint_db(&self, path: &Path) -> SuiResult;
655
656    /// This is a temporary method to be used when we enable simplified_unwrap_then_delete.
657    /// It re-accumulates state hash for the new epoch if simplified_unwrap_then_delete is enabled.
658    fn maybe_reaccumulate_state_hash(
659        &self,
660        cur_epoch_store: &AuthorityPerEpochStore,
661        new_protocol_version: ProtocolVersion,
662    );
663
664    /// Reconfigure the cache itself.
665    /// TODO: this is only needed for ProxyCache to switch between cache impls. It can be removed
666    /// once WritebackCache is the sole cache impl.
667    fn reconfigure_cache<'a>(
668        &'a self,
669        epoch_start_config: &'a EpochStartConfiguration,
670    ) -> BoxFuture<'a, ()>;
671}
672
673// StateSyncAPI is for writing any data that was not the result of transaction execution,
674// but that arrived via state sync. The fact that it came via state sync implies that it
675// is certified output, and can be immediately persisted to the store.
676pub trait StateSyncAPI: Send + Sync {
677    fn insert_transaction_and_effects(
678        &self,
679        transaction: &VerifiedTransaction,
680        transaction_effects: &TransactionEffects,
681    );
682
683    fn multi_insert_transaction_and_effects(
684        &self,
685        transactions_and_effects: &[VerifiedExecutionData],
686    );
687}
688
689pub trait TestingAPI: Send + Sync {
690    fn database_for_testing(&self) -> Arc<AuthorityStore>;
691
692    fn cache_for_testing(&self) -> &WritebackCache;
693}
694
695macro_rules! implement_storage_traits {
696    ($implementor: ident) => {
697        impl ObjectStore for $implementor {
698            fn get_object(&self, object_id: &ObjectID) -> Option<Object> {
699                ObjectCacheRead::get_object(self, object_id)
700            }
701
702            fn get_object_by_key(
703                &self,
704                object_id: &ObjectID,
705                version: sui_types::base_types::VersionNumber,
706            ) -> Option<Object> {
707                ObjectCacheRead::get_object_by_key(self, object_id, version)
708            }
709        }
710
711        impl ChildObjectResolver for $implementor {
712            fn read_child_object(
713                &self,
714                parent: &ObjectID,
715                child: &ObjectID,
716                child_version_upper_bound: SequenceNumber,
717            ) -> SuiResult<Option<Object>> {
718                let Some(child_object) =
719                    self.find_object_lt_or_eq_version(*child, child_version_upper_bound)
720                else {
721                    return Ok(None);
722                };
723
724                let parent = *parent;
725                if child_object.owner != Owner::ObjectOwner(parent.into()) {
726                    return Err(SuiErrorKind::InvalidChildObjectAccess {
727                        object: *child,
728                        given_parent: parent,
729                        actual_owner: child_object.owner.clone(),
730                    }
731                    .into());
732                }
733                Ok(Some(child_object))
734            }
735
736            fn get_object_received_at_version(
737                &self,
738                owner: &ObjectID,
739                receiving_object_id: &ObjectID,
740                receive_object_at_version: SequenceNumber,
741                epoch_id: EpochId,
742            ) -> SuiResult<Option<Object>> {
743                let Some(recv_object) = ObjectCacheRead::get_object_by_key(
744                    self,
745                    receiving_object_id,
746                    receive_object_at_version,
747                ) else {
748                    return Ok(None);
749                };
750
751                // Check for:
752                // * Invalid access -- treat as the object does not exist. Or;
753                // * If we've already received the object at the version -- then treat it as though it doesn't exist.
754                // These two cases must remain indisguishable to the caller otherwise we risk forks in
755                // transaction replay due to possible reordering of transactions during replay.
756                if recv_object.owner != Owner::AddressOwner((*owner).into())
757                    || self.have_received_object_at_version(
758                        // TODO: Add support for receiving consensus objects. For now this assumes fastpath.
759                        FullObjectKey::new(
760                            FullObjectID::new(*receiving_object_id, None),
761                            receive_object_at_version,
762                        ),
763                        epoch_id,
764                    )
765                {
766                    return Ok(None);
767                }
768
769                Ok(Some(recv_object))
770            }
771        }
772
773        impl BackingPackageStore for $implementor {
774            fn get_package_object(
775                &self,
776                package_id: &ObjectID,
777            ) -> SuiResult<Option<PackageObject>> {
778                ObjectCacheRead::get_package_object(self, package_id)
779            }
780        }
781
782        impl ParentSync for $implementor {
783            fn get_latest_parent_entry_ref_deprecated(
784                &self,
785                object_id: ObjectID,
786            ) -> Option<ObjectRef> {
787                ObjectCacheRead::get_latest_object_ref_or_tombstone(self, object_id)
788            }
789        }
790    };
791}
792
793// Implement traits for a cache implementation that always go directly to the store.
794macro_rules! implement_passthrough_traits {
795    ($implementor: ident) => {
796        impl CheckpointCache for $implementor {
797            fn deprecated_get_transaction_checkpoint(
798                &self,
799                digest: &TransactionDigest,
800            ) -> Option<(EpochId, CheckpointSequenceNumber)> {
801                self.store
802                    .deprecated_get_transaction_checkpoint(digest)
803                    .expect("db error")
804            }
805
806            fn deprecated_multi_get_transaction_checkpoint(
807                &self,
808                digests: &[TransactionDigest],
809            ) -> Vec<Option<(EpochId, CheckpointSequenceNumber)>> {
810                self.store
811                    .deprecated_multi_get_transaction_checkpoint(digests)
812                    .expect("db error")
813            }
814
815            fn deprecated_insert_finalized_transactions(
816                &self,
817                digests: &[TransactionDigest],
818                epoch: EpochId,
819                sequence: CheckpointSequenceNumber,
820            ) {
821                self.store
822                    .deprecated_insert_finalized_transactions(digests, epoch, sequence)
823                    .expect("db error");
824            }
825        }
826
827        impl ExecutionCacheReconfigAPI for $implementor {
828            fn insert_genesis_object(&self, object: Object) {
829                self.insert_genesis_object_impl(object)
830            }
831
832            fn bulk_insert_genesis_objects(&self, objects: &[Object]) {
833                self.bulk_insert_genesis_objects_impl(objects)
834            }
835
836            fn set_epoch_start_configuration(&self, epoch_start_config: &EpochStartConfiguration) {
837                self.store
838                    .set_epoch_start_configuration(epoch_start_config)
839                    .expect("db error");
840            }
841
842            fn update_epoch_flags_metrics(&self, old: &[EpochFlag], new: &[EpochFlag]) {
843                self.store.update_epoch_flags_metrics(old, new)
844            }
845
846            fn clear_state_end_of_epoch(&self, execution_guard: &ExecutionLockWriteGuard<'_>) {
847                self.clear_state_end_of_epoch_impl(execution_guard)
848            }
849
850            fn expensive_check_sui_conservation(
851                &self,
852                old_epoch_store: &AuthorityPerEpochStore,
853            ) -> SuiResult {
854                self.store
855                    .expensive_check_sui_conservation(self, old_epoch_store)
856            }
857
858            fn checkpoint_db(&self, path: &std::path::Path) -> SuiResult {
859                self.store.perpetual_tables.checkpoint_db(path)
860            }
861
862            fn maybe_reaccumulate_state_hash(
863                &self,
864                cur_epoch_store: &AuthorityPerEpochStore,
865                new_protocol_version: ProtocolVersion,
866            ) {
867                self.store
868                    .maybe_reaccumulate_state_hash(cur_epoch_store, new_protocol_version)
869            }
870
871            fn reconfigure_cache<'a>(
872                &'a self,
873                _: &'a EpochStartConfiguration,
874            ) -> BoxFuture<'a, ()> {
875                // Since we now use WritebackCache directly at startup (if the epoch flag is set),
876                // this can be called at reconfiguration time. It is a no-op.
877                // TODO: remove this once we completely remove ProxyCache.
878                std::future::ready(()).boxed()
879            }
880        }
881
882        impl TestingAPI for $implementor {
883            fn database_for_testing(&self) -> Arc<AuthorityStore> {
884                self.store.clone()
885            }
886
887            fn cache_for_testing(&self) -> &WritebackCache {
888                self
889            }
890        }
891    };
892}
893
894use implement_passthrough_traits;
895
896implement_storage_traits!(WritebackCache);
897
898pub trait ExecutionCacheAPI:
899    ObjectCacheRead
900    + ExecutionCacheWrite
901    + ExecutionCacheCommit
902    + ExecutionCacheReconfigAPI
903    + CheckpointCache
904    + StateSyncAPI
905{
906}