1use crate::accumulators::funds_read::AccountFundsRead;
41use crate::authority::AuthorityStore;
42use crate::authority::authority_per_epoch_store::AuthorityPerEpochStore;
43use crate::authority::authority_store::ExecutionLockWriteGuard;
44#[cfg(test)]
45use crate::authority::authority_store::{LockDetailsDeprecated, ObjectLockStatus, SuiLockResult};
46use crate::authority::authority_store_tables::LiveObject;
47use crate::authority::backpressure::BackpressureManager;
48use crate::authority::epoch_start_configuration::{EpochFlag, EpochStartConfiguration};
49use crate::fallback_fetch::do_fallback_lookup;
50use crate::global_state_hasher::GlobalStateHashStore;
51use crate::transaction_outputs::TransactionOutputs;
52
53use dashmap::DashMap;
54use dashmap::mapref::entry::Entry as DashMapEntry;
55use futures::{FutureExt, future::BoxFuture};
56use moka::sync::SegmentedCache as MokaCache;
57use mysten_common::ZipDebugEqIteratorExt;
58use mysten_common::debug_fatal;
59use mysten_common::random_util::randomize_cache_capacity_in_tests;
60use mysten_common::sync::notify_read::NotifyRead;
61use parking_lot::Mutex;
62use rayon::prelude::*;
63use std::collections::{BTreeMap, HashSet};
64use std::hash::Hash;
65use std::sync::Arc;
66use std::sync::atomic::AtomicU64;
67use std::time::Instant;
68use sui_config::ExecutionCacheConfig;
69use sui_macros::fail_point;
70use sui_protocol_config::ProtocolVersion;
71use sui_types::SUI_ACCUMULATOR_ROOT_OBJECT_ID;
72use sui_types::accumulator_event::AccumulatorEvent;
73use sui_types::accumulator_root::{AccumulatorObjId, AccumulatorValue};
74use sui_types::base_types::{
75 ConsensusObjectVersion, EpochId, FullObjectID, ObjectID, ObjectRef, SequenceNumber,
76 VerifiedExecutionData,
77};
78use sui_types::bridge::{Bridge, get_bridge};
79use sui_types::digests::{ObjectDigest, TransactionDigest, TransactionEffectsDigest};
80use sui_types::effects::{TransactionEffects, TransactionEvents};
81#[cfg(test)]
82use sui_types::error::SuiError;
83use sui_types::error::{SuiErrorKind, SuiResult, UserInputError};
84use sui_types::executable_transaction::VerifiedExecutableTransaction;
85use sui_types::global_state_hash::GlobalStateHash;
86use sui_types::message_envelope::Message;
87use sui_types::messages_checkpoint::CheckpointSequenceNumber;
88use sui_types::object::Object;
89use sui_types::storage::{
90 FullObjectKey, InputKey, MarkerValue, ObjectKey, ObjectOrTombstone, ObjectStore, PackageObject,
91};
92use sui_types::sui_system_state::{SuiSystemState, get_sui_system_state};
93use sui_types::transaction::{TransactionDataAPI, VerifiedTransaction};
94use tap::TapOptional;
95use tracing::{debug, info, instrument, trace, warn};
96
97use super::ExecutionCacheAPI;
98use super::cache_types::Ticket;
99use super::{
100 Batch, CheckpointCache, ExecutionCacheCommit, ExecutionCacheMetrics, ExecutionCacheReconfigAPI,
101 ExecutionCacheWrite, ObjectCacheRead, StateSyncAPI, TestingAPI, TransactionCacheRead,
102 cache_types::{CacheResult, CachedVersionMap, IsNewer, MonotonicCache},
103 implement_passthrough_traits,
104 object_locks::ObjectLocks,
105};
106
107#[cfg(test)]
108#[path = "unit_tests/writeback_cache_tests.rs"]
109pub mod writeback_cache_tests;
110
111#[cfg(test)]
112#[path = "unit_tests/notify_read_input_objects_tests.rs"]
113mod notify_read_input_objects_tests;
114
115#[derive(Clone, PartialEq, Eq)]
116enum ObjectEntry {
117 Object(Object),
118 Deleted,
119 Wrapped,
120}
121
122impl ObjectEntry {
123 #[cfg(test)]
124 fn unwrap_object(&self) -> &Object {
125 match self {
126 ObjectEntry::Object(o) => o,
127 _ => panic!("unwrap_object called on non-Object"),
128 }
129 }
130
131 fn is_tombstone(&self) -> bool {
132 match self {
133 ObjectEntry::Deleted | ObjectEntry::Wrapped => true,
134 ObjectEntry::Object(_) => false,
135 }
136 }
137}
138
139impl std::fmt::Debug for ObjectEntry {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 match self {
142 ObjectEntry::Object(o) => {
143 write!(f, "ObjectEntry::Object({:?})", o.compute_object_reference())
144 }
145 ObjectEntry::Deleted => write!(f, "ObjectEntry::Deleted"),
146 ObjectEntry::Wrapped => write!(f, "ObjectEntry::Wrapped"),
147 }
148 }
149}
150
151impl From<Object> for ObjectEntry {
152 fn from(object: Object) -> Self {
153 ObjectEntry::Object(object)
154 }
155}
156
157impl From<ObjectOrTombstone> for ObjectEntry {
158 fn from(object: ObjectOrTombstone) -> Self {
159 match object {
160 ObjectOrTombstone::Object(o) => o.into(),
161 ObjectOrTombstone::Tombstone(obj_ref) => {
162 if obj_ref.2.is_deleted() {
163 ObjectEntry::Deleted
164 } else if obj_ref.2.is_wrapped() {
165 ObjectEntry::Wrapped
166 } else {
167 panic!("tombstone digest must either be deleted or wrapped");
168 }
169 }
170 }
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
175enum LatestObjectCacheEntry {
176 Object(SequenceNumber, ObjectEntry),
177 NonExistent,
178}
179
180impl LatestObjectCacheEntry {
181 #[cfg(test)]
182 fn version(&self) -> Option<SequenceNumber> {
183 match self {
184 LatestObjectCacheEntry::Object(version, _) => Some(*version),
185 LatestObjectCacheEntry::NonExistent => None,
186 }
187 }
188
189 fn is_alive(&self) -> bool {
190 match self {
191 LatestObjectCacheEntry::Object(_, entry) => !entry.is_tombstone(),
192 LatestObjectCacheEntry::NonExistent => false,
193 }
194 }
195}
196
197impl IsNewer for LatestObjectCacheEntry {
198 fn is_newer_than(&self, other: &LatestObjectCacheEntry) -> bool {
199 match (self, other) {
200 (LatestObjectCacheEntry::Object(v1, _), LatestObjectCacheEntry::Object(v2, _)) => {
201 v1 > v2
202 }
203 (LatestObjectCacheEntry::Object(_, _), LatestObjectCacheEntry::NonExistent) => true,
204 _ => false,
205 }
206 }
207}
208
209type MarkerKey = (EpochId, FullObjectID);
210
211struct UncommittedData {
214 objects: DashMap<ObjectID, CachedVersionMap<ObjectEntry>>,
227
228 markers: DashMap<MarkerKey, CachedVersionMap<MarkerValue>>,
233
234 transaction_effects: DashMap<TransactionEffectsDigest, TransactionEffects>,
235
236 transaction_events: DashMap<TransactionDigest, TransactionEvents>,
237
238 unchanged_loaded_runtime_objects: DashMap<TransactionDigest, Vec<ObjectKey>>,
239
240 executed_effects_digests: DashMap<TransactionDigest, TransactionEffectsDigest>,
241
242 pending_transaction_writes: DashMap<TransactionDigest, Arc<TransactionOutputs>>,
245
246 total_transaction_inserts: AtomicU64,
247 total_transaction_commits: AtomicU64,
248}
249
250impl UncommittedData {
251 fn new() -> Self {
252 Self {
253 objects: DashMap::with_shard_amount(2048),
254 markers: DashMap::with_shard_amount(2048),
255 transaction_effects: DashMap::with_shard_amount(2048),
256 executed_effects_digests: DashMap::with_shard_amount(2048),
257 pending_transaction_writes: DashMap::with_shard_amount(2048),
258 transaction_events: DashMap::with_shard_amount(2048),
259 unchanged_loaded_runtime_objects: DashMap::with_shard_amount(2048),
260 total_transaction_inserts: AtomicU64::new(0),
261 total_transaction_commits: AtomicU64::new(0),
262 }
263 }
264
265 fn clear(&self) {
266 self.objects.clear();
267 self.markers.clear();
268 self.transaction_effects.clear();
269 self.executed_effects_digests.clear();
270 self.pending_transaction_writes.clear();
271 self.transaction_events.clear();
272 self.unchanged_loaded_runtime_objects.clear();
273 self.total_transaction_inserts
274 .store(0, std::sync::atomic::Ordering::Relaxed);
275 self.total_transaction_commits
276 .store(0, std::sync::atomic::Ordering::Relaxed);
277 }
278
279 fn is_empty(&self) -> bool {
280 let empty = self.pending_transaction_writes.is_empty();
281 if empty && cfg!(debug_assertions) {
282 assert!(
283 self.objects.is_empty()
284 && self.markers.is_empty()
285 && self.transaction_effects.is_empty()
286 && self.executed_effects_digests.is_empty()
287 && self.transaction_events.is_empty()
288 && self.unchanged_loaded_runtime_objects.is_empty()
289 && self
290 .total_transaction_inserts
291 .load(std::sync::atomic::Ordering::Relaxed)
292 == self
293 .total_transaction_commits
294 .load(std::sync::atomic::Ordering::Relaxed),
295 );
296 }
297 empty
298 }
299}
300
301type PointCacheItem<T> = Option<T>;
303
304impl<T: Eq + std::fmt::Debug> IsNewer for PointCacheItem<T> {
307 fn is_newer_than(&self, other: &PointCacheItem<T>) -> bool {
308 match (self, other) {
309 (Some(_), None) => true,
310
311 (Some(a), Some(b)) => {
312 debug_assert_eq!(a, b);
314 false
315 }
316
317 _ => false,
318 }
319 }
320}
321
322struct CachedCommittedData {
324 object_cache: MokaCache<ObjectID, Arc<Mutex<CachedVersionMap<ObjectEntry>>>>,
326
327 marker_cache: MokaCache<MarkerKey, Arc<Mutex<CachedVersionMap<MarkerValue>>>>,
329
330 transactions: MonotonicCache<TransactionDigest, PointCacheItem<Arc<VerifiedTransaction>>>,
331
332 transaction_effects:
333 MonotonicCache<TransactionEffectsDigest, PointCacheItem<Arc<TransactionEffects>>>,
334
335 transaction_events: MonotonicCache<TransactionDigest, PointCacheItem<Arc<TransactionEvents>>>,
336
337 executed_effects_digests:
338 MonotonicCache<TransactionDigest, PointCacheItem<TransactionEffectsDigest>>,
339
340 transaction_executed_in_last_epoch:
341 MonotonicCache<(EpochId, TransactionDigest), PointCacheItem<()>>,
342
343 _transaction_objects: MokaCache<TransactionDigest, Vec<Object>>,
346}
347
348impl CachedCommittedData {
349 fn new(config: &ExecutionCacheConfig) -> Self {
350 let object_cache = MokaCache::builder(8)
351 .max_capacity(randomize_cache_capacity_in_tests(
352 config.object_cache_size(),
353 ))
354 .build();
355 let marker_cache = MokaCache::builder(8)
356 .max_capacity(randomize_cache_capacity_in_tests(
357 config.marker_cache_size(),
358 ))
359 .build();
360
361 let transactions = MonotonicCache::new(randomize_cache_capacity_in_tests(
362 config.transaction_cache_size(),
363 ));
364 let transaction_effects = MonotonicCache::new(randomize_cache_capacity_in_tests(
365 config.effect_cache_size(),
366 ));
367 let transaction_events = MonotonicCache::new(randomize_cache_capacity_in_tests(
368 config.events_cache_size(),
369 ));
370 let executed_effects_digests = MonotonicCache::new(randomize_cache_capacity_in_tests(
371 config.executed_effect_cache_size(),
372 ));
373
374 let transaction_objects = MokaCache::builder(8)
375 .max_capacity(randomize_cache_capacity_in_tests(
376 config.transaction_objects_cache_size(),
377 ))
378 .build();
379
380 let transaction_executed_in_last_epoch = MonotonicCache::new(
381 randomize_cache_capacity_in_tests(config.executed_effect_cache_size()),
382 );
383
384 Self {
385 object_cache,
386 marker_cache,
387 transactions,
388 transaction_effects,
389 transaction_events,
390 executed_effects_digests,
391 transaction_executed_in_last_epoch,
392 _transaction_objects: transaction_objects,
393 }
394 }
395
396 fn clear_and_assert_empty(&self) {
397 self.object_cache.invalidate_all();
398 self.marker_cache.invalidate_all();
399 self.transactions.invalidate_all();
400 self.transaction_effects.invalidate_all();
401 self.transaction_events.invalidate_all();
402 self.executed_effects_digests.invalidate_all();
403 self.transaction_executed_in_last_epoch.invalidate_all();
404 self._transaction_objects.invalidate_all();
405
406 assert_empty(&self.object_cache);
407 assert_empty(&self.marker_cache);
408 assert!(self.transactions.is_empty());
409 assert!(self.transaction_effects.is_empty());
410 assert!(self.transaction_events.is_empty());
411 assert!(self.executed_effects_digests.is_empty());
412 assert!(self.transaction_executed_in_last_epoch.is_empty());
413 assert_empty(&self._transaction_objects);
414 }
415}
416
417fn assert_empty<K, V>(cache: &MokaCache<K, V>)
418where
419 K: std::hash::Hash + std::cmp::Eq + std::cmp::PartialEq + Send + Sync + 'static,
420 V: std::clone::Clone + std::marker::Send + std::marker::Sync + 'static,
421{
422 if cache.iter().next().is_some() {
423 panic!("cache should be empty");
424 }
425}
426
427pub struct WritebackCache {
428 dirty: UncommittedData,
429 cached: CachedCommittedData,
430
431 object_by_id_cache: MonotonicCache<ObjectID, LatestObjectCacheEntry>,
438
439 packages: MokaCache<ObjectID, PackageObject>,
450
451 object_locks: ObjectLocks,
452
453 executed_effects_digests_notify_read: NotifyRead<TransactionDigest, TransactionEffectsDigest>,
454 object_notify_read: NotifyRead<InputKey, ()>,
455
456 store: Arc<AuthorityStore>,
457 backpressure_threshold: u64,
458 backpressure_manager: Arc<BackpressureManager>,
459 metrics: Arc<ExecutionCacheMetrics>,
460}
461
462macro_rules! check_cache_entry_by_version {
463 ($self: ident, $table: expr, $level: expr, $cache: expr, $version: expr) => {
464 $self.metrics.record_cache_request($table, $level);
465 if let Some(cache) = $cache {
466 if let Some(entry) = cache.get(&$version) {
467 $self.metrics.record_cache_hit($table, $level);
468 return CacheResult::Hit(entry.clone());
469 }
470
471 if let Some(least_version) = cache.get_least() {
472 if least_version.0 < $version {
473 $self.metrics.record_cache_negative_hit($table, $level);
476 return CacheResult::NegativeHit;
477 }
478 }
479 }
480 $self.metrics.record_cache_miss($table, $level);
481 };
482}
483
484macro_rules! check_cache_entry_by_latest {
485 ($self: ident, $table: expr, $level: expr, $cache: expr) => {
486 $self.metrics.record_cache_request($table, $level);
487 if let Some(cache) = $cache {
488 if let Some((version, entry)) = cache.get_highest() {
489 $self.metrics.record_cache_hit($table, $level);
490 return CacheResult::Hit((*version, entry.clone()));
491 } else {
492 panic!("empty CachedVersionMap should have been removed");
493 }
494 }
495 $self.metrics.record_cache_miss($table, $level);
496 };
497}
498
499impl WritebackCache {
500 pub(crate) fn load_implicitly_read_system_object(
505 &self,
506 object_id: &ObjectID,
507 version: ConsensusObjectVersion,
508 ) -> Option<Object> {
509 assert!(
510 sui_types::IMPLICITLY_READ_SYSTEM_OBJECTS.contains(object_id),
511 "{object_id} is not an implicitly read system object"
512 );
513 let ConsensusObjectVersion {
514 initial_shared_version,
515 version,
516 } = version;
517 if let Some(object) = ObjectCacheRead::get_object_by_key(self, object_id, version) {
518 return Some(object);
519 }
520 self.metrics
521 .implicit_system_object_read_waits
522 .with_label_values(&[object_id.to_string().as_str()])
523 .inc();
524 let wait_start = Instant::now();
525 let key = InputKey::VersionedObject {
526 id: FullObjectID::Consensus((*object_id, initial_shared_version)),
527 version,
528 };
529 self.object_notify_read.read_one_blocking(
536 "load_implicitly_read_system_object",
537 &key,
538 |_key| {
539 ObjectCacheRead::get_object(self, object_id)
540 .is_some_and(|latest| latest.version() >= version)
541 .then_some(())
542 },
543 );
544 self.metrics
545 .implicit_system_object_read_wait_latency
546 .with_label_values(&[object_id.to_string().as_str()])
547 .observe(wait_start.elapsed().as_secs_f64());
548 ObjectCacheRead::get_object_by_key(self, object_id, version)
549 }
550
551 pub fn new(
552 config: &ExecutionCacheConfig,
553 store: Arc<AuthorityStore>,
554 metrics: Arc<ExecutionCacheMetrics>,
555 backpressure_manager: Arc<BackpressureManager>,
556 ) -> Self {
557 let packages = MokaCache::builder(8)
558 .max_capacity(randomize_cache_capacity_in_tests(
559 config.package_cache_size(),
560 ))
561 .build();
562 Self {
563 dirty: UncommittedData::new(),
564 cached: CachedCommittedData::new(config),
565 object_by_id_cache: MonotonicCache::new(randomize_cache_capacity_in_tests(
566 config.object_by_id_cache_size(),
567 )),
568 packages,
569 object_locks: ObjectLocks::new(),
570 executed_effects_digests_notify_read: NotifyRead::new(),
571 object_notify_read: NotifyRead::new(),
572 store,
573 backpressure_manager,
574 backpressure_threshold: config.backpressure_threshold(),
575 metrics,
576 }
577 }
578
579 pub fn new_for_tests(store: Arc<AuthorityStore>) -> Self {
580 Self::new(
581 &Default::default(),
582 store,
583 ExecutionCacheMetrics::new(&prometheus::Registry::new()).into(),
584 BackpressureManager::new_for_tests(),
585 )
586 }
587
588 #[cfg(test)]
589 pub fn reset_for_test(&mut self) {
590 let mut new = Self::new(
591 &Default::default(),
592 self.store.clone(),
593 self.metrics.clone(),
594 self.backpressure_manager.clone(),
595 );
596 std::mem::swap(self, &mut new);
597 }
598
599 pub fn evict_executed_effects_from_cache_for_testing(&self, tx_digest: &TransactionDigest) {
600 self.cached.executed_effects_digests.invalidate(tx_digest);
601 self.cached.transaction_events.invalidate(tx_digest);
602 self.cached.transactions.invalidate(tx_digest);
603 }
604
605 fn write_object_entry(
606 &self,
607 object_id: &ObjectID,
608 version: SequenceNumber,
609 object: ObjectEntry,
610 ) {
611 trace!(?object_id, ?version, ?object, "inserting object entry");
612 self.metrics.record_cache_write("object");
613
614 let mut entry = self.dirty.objects.entry(*object_id).or_default();
634
635 self.object_by_id_cache
636 .insert(
637 object_id,
638 LatestObjectCacheEntry::Object(version, object.clone()),
639 Ticket::Write,
640 )
641 .ok();
644
645 entry.insert(version, object.clone());
646
647 if let ObjectEntry::Object(object) = &object {
648 if object.is_package() {
649 self.object_notify_read
650 .notify(&InputKey::Package { id: *object_id }, &());
651 } else if !object.is_child_object() {
652 self.object_notify_read.notify(
653 &InputKey::VersionedObject {
654 id: object.full_id(),
655 version: object.version(),
656 },
657 &(),
658 );
659 }
660 }
661 }
662
663 fn write_marker_value(
664 &self,
665 epoch_id: EpochId,
666 object_key: FullObjectKey,
667 marker_value: MarkerValue,
668 ) {
669 tracing::trace!("inserting marker value {object_key:?}: {marker_value:?}",);
670 self.metrics.record_cache_write("marker");
671 self.dirty
672 .markers
673 .entry((epoch_id, object_key.id()))
674 .or_default()
675 .value_mut()
676 .insert(object_key.version(), marker_value);
677 if matches!(marker_value, MarkerValue::ConsensusStreamEnded(_)) {
682 self.object_notify_read.notify(
683 &InputKey::VersionedObject {
684 id: object_key.id(),
685 version: object_key.version(),
686 },
687 &(),
688 );
689 }
690 }
691
692 fn with_locked_cache_entries<K, V, R>(
696 dirty_map: &DashMap<K, CachedVersionMap<V>>,
697 cached_map: &MokaCache<K, Arc<Mutex<CachedVersionMap<V>>>>,
698 key: &K,
699 cb: impl FnOnce(Option<&CachedVersionMap<V>>, Option<&CachedVersionMap<V>>) -> R,
700 ) -> R
701 where
702 K: Copy + Eq + Hash + Send + Sync + 'static,
703 V: Send + Sync + 'static,
704 {
705 let dirty_entry = dirty_map.entry(*key);
706 let dirty_entry = match &dirty_entry {
707 DashMapEntry::Occupied(occupied) => Some(occupied.get()),
708 DashMapEntry::Vacant(_) => None,
709 };
710
711 let cached_entry = cached_map.get(key);
712 let cached_lock = cached_entry.as_ref().map(|entry| entry.lock());
713 let cached_entry = cached_lock.as_deref();
714
715 cb(dirty_entry, cached_entry)
716 }
717
718 fn get_object_entry_by_key_cache_only(
721 &self,
722 object_id: &ObjectID,
723 version: SequenceNumber,
724 ) -> CacheResult<ObjectEntry> {
725 Self::with_locked_cache_entries(
726 &self.dirty.objects,
727 &self.cached.object_cache,
728 object_id,
729 |dirty_entry, cached_entry| {
730 check_cache_entry_by_version!(
731 self,
732 "object_by_version",
733 "uncommitted",
734 dirty_entry,
735 version
736 );
737 check_cache_entry_by_version!(
738 self,
739 "object_by_version",
740 "committed",
741 cached_entry,
742 version
743 );
744 CacheResult::Miss
745 },
746 )
747 }
748
749 fn get_object_by_key_cache_only(
750 &self,
751 object_id: &ObjectID,
752 version: SequenceNumber,
753 ) -> CacheResult<Object> {
754 match self.get_object_entry_by_key_cache_only(object_id, version) {
755 CacheResult::Hit(entry) => match entry {
756 ObjectEntry::Object(object) => CacheResult::Hit(object),
757 ObjectEntry::Deleted | ObjectEntry::Wrapped => CacheResult::NegativeHit,
758 },
759 CacheResult::Miss => CacheResult::Miss,
760 CacheResult::NegativeHit => CacheResult::NegativeHit,
761 }
762 }
763
764 fn get_object_entry_by_id_cache_only(
765 &self,
766 request_type: &'static str,
767 object_id: &ObjectID,
768 ) -> CacheResult<(SequenceNumber, ObjectEntry)> {
769 self.metrics
770 .record_cache_request(request_type, "object_by_id");
771 let entry = self.object_by_id_cache.get(object_id);
772
773 if cfg!(debug_assertions)
774 && let Some(entry) = &entry
775 {
776 let highest: Option<ObjectEntry> = self
778 .dirty
779 .objects
780 .get(object_id)
781 .and_then(|entry| entry.get_highest().map(|(_, o)| o.clone()))
782 .or_else(|| {
783 let obj: Option<ObjectEntry> = self
784 .store
785 .get_latest_object_or_tombstone(*object_id)
786 .unwrap()
787 .map(|(_, o)| o.into());
788 obj
789 });
790
791 let cache_entry = match &*entry.lock() {
792 LatestObjectCacheEntry::Object(_, entry) => Some(entry.clone()),
793 LatestObjectCacheEntry::NonExistent => None,
794 };
795
796 let tombstone_possibly_pruned = highest.is_none()
798 && cache_entry
799 .as_ref()
800 .map(|e| e.is_tombstone())
801 .unwrap_or(false);
802
803 if highest != cache_entry && !tombstone_possibly_pruned {
804 tracing::error!(
805 ?highest,
806 ?cache_entry,
807 ?tombstone_possibly_pruned,
808 "object_by_id cache is incoherent for {:?}",
809 object_id
810 );
811 panic!("object_by_id cache is incoherent for {:?}", object_id);
812 }
813 }
814
815 if let Some(entry) = entry {
816 let entry = entry.lock();
817 match &*entry {
818 LatestObjectCacheEntry::Object(latest_version, latest_object) => {
819 self.metrics.record_cache_hit(request_type, "object_by_id");
820 return CacheResult::Hit((*latest_version, latest_object.clone()));
821 }
822 LatestObjectCacheEntry::NonExistent => {
823 self.metrics
824 .record_cache_negative_hit(request_type, "object_by_id");
825 return CacheResult::NegativeHit;
826 }
827 }
828 } else {
829 self.metrics.record_cache_miss(request_type, "object_by_id");
830 }
831
832 Self::with_locked_cache_entries(
833 &self.dirty.objects,
834 &self.cached.object_cache,
835 object_id,
836 |dirty_entry, cached_entry| {
837 check_cache_entry_by_latest!(self, request_type, "uncommitted", dirty_entry);
838 check_cache_entry_by_latest!(self, request_type, "committed", cached_entry);
839 CacheResult::Miss
840 },
841 )
842 }
843
844 fn get_marker_value_cache_only(
845 &self,
846 object_key: FullObjectKey,
847 epoch_id: EpochId,
848 ) -> CacheResult<MarkerValue> {
849 Self::with_locked_cache_entries(
850 &self.dirty.markers,
851 &self.cached.marker_cache,
852 &(epoch_id, object_key.id()),
853 |dirty_entry, cached_entry| {
854 check_cache_entry_by_version!(
855 self,
856 "marker_by_version",
857 "uncommitted",
858 dirty_entry,
859 object_key.version()
860 );
861 check_cache_entry_by_version!(
862 self,
863 "marker_by_version",
864 "committed",
865 cached_entry,
866 object_key.version()
867 );
868 CacheResult::Miss
869 },
870 )
871 }
872
873 fn get_latest_marker_value_cache_only(
874 &self,
875 object_id: FullObjectID,
876 epoch_id: EpochId,
877 ) -> CacheResult<(SequenceNumber, MarkerValue)> {
878 Self::with_locked_cache_entries(
879 &self.dirty.markers,
880 &self.cached.marker_cache,
881 &(epoch_id, object_id),
882 |dirty_entry, cached_entry| {
883 check_cache_entry_by_latest!(self, "marker_latest", "uncommitted", dirty_entry);
884 check_cache_entry_by_latest!(self, "marker_latest", "committed", cached_entry);
885 CacheResult::Miss
886 },
887 )
888 }
889
890 fn get_object_impl(&self, request_type: &'static str, id: &ObjectID) -> Option<Object> {
891 let ticket = self.object_by_id_cache.get_ticket_for_read(id);
892 match self.get_object_entry_by_id_cache_only(request_type, id) {
893 CacheResult::Hit((_, entry)) => match entry {
894 ObjectEntry::Object(object) => Some(object),
895 ObjectEntry::Deleted | ObjectEntry::Wrapped => None,
896 },
897 CacheResult::NegativeHit => None,
898 CacheResult::Miss => {
899 let obj = self
900 .store
901 .get_latest_object_or_tombstone(*id)
902 .expect("db error");
903 match obj {
904 Some((key, obj)) => {
905 self.cache_latest_object_by_id(
906 id,
907 LatestObjectCacheEntry::Object(key.1, obj.clone().into()),
908 ticket,
909 );
910 match obj {
911 ObjectOrTombstone::Object(object) => Some(object),
912 ObjectOrTombstone::Tombstone(_) => None,
913 }
914 }
915 None => {
916 self.cache_object_not_found(id, ticket);
917 None
918 }
919 }
920 }
921 }
922 }
923
924 fn record_db_get(&self, request_type: &'static str) -> &AuthorityStore {
925 self.metrics.record_cache_request(request_type, "db");
926 &self.store
927 }
928
929 fn record_db_multi_get(&self, request_type: &'static str, count: usize) -> &AuthorityStore {
930 self.metrics
931 .record_cache_multi_request(request_type, "db", count);
932 &self.store
933 }
934
935 #[instrument(level = "debug", skip_all)]
936 fn write_transaction_outputs(&self, epoch_id: EpochId, tx_outputs: Arc<TransactionOutputs>) {
937 let tx_digest = *tx_outputs.transaction.digest();
938 trace!(?tx_digest, "writing transaction outputs to cache");
939
940 assert!(
941 !self.transaction_executed_in_last_epoch(&tx_digest, epoch_id),
942 "Transaction {:?} was already executed in epoch {}",
943 tx_digest,
944 epoch_id.saturating_sub(1)
945 );
946
947 let TransactionOutputs {
948 transaction,
949 effects,
950 markers,
951 written,
952 deleted,
953 wrapped,
954 events,
955 unchanged_loaded_runtime_objects,
956 ..
957 } = &*tx_outputs;
958
959 for ObjectKey(id, version) in deleted.iter() {
964 self.write_object_entry(id, *version, ObjectEntry::Deleted);
965 }
966
967 for ObjectKey(id, version) in wrapped.iter() {
968 self.write_object_entry(id, *version, ObjectEntry::Wrapped);
969 }
970
971 for (object_key, marker_value) in markers.iter() {
973 self.write_marker_value(epoch_id, *object_key, *marker_value);
974 }
975
976 for (object_id, object) in written.iter() {
979 if object.is_child_object() {
980 self.write_object_entry(object_id, object.version(), object.clone().into());
981 }
982 }
983 for (object_id, object) in written.iter() {
984 if !object.is_child_object() {
985 self.write_object_entry(object_id, object.version(), object.clone().into());
986 if object.is_package() {
987 debug!("caching package: {:?}", object.compute_object_reference());
988 self.packages
989 .insert(*object_id, PackageObject::new(object.clone()));
990 }
991 }
992 }
993
994 let tx_digest = *transaction.digest();
995 debug!(
996 ?tx_digest,
997 "Writing transaction output objects to cache: {:?}",
998 written
999 .values()
1000 .map(|o| (o.id(), o.version()))
1001 .collect::<Vec<_>>(),
1002 );
1003 let effects_digest = effects.digest();
1004
1005 self.metrics.record_cache_write("transaction_block");
1006 self.dirty
1007 .pending_transaction_writes
1008 .insert(tx_digest, tx_outputs.clone());
1009
1010 self.metrics.record_cache_write("transaction_effects");
1013 self.dirty
1014 .transaction_effects
1015 .insert(effects_digest, effects.clone());
1016
1017 self.metrics.record_cache_write("transaction_events");
1021 self.dirty
1022 .transaction_events
1023 .insert(tx_digest, events.clone());
1024
1025 self.metrics
1026 .record_cache_write("unchanged_loaded_runtime_objects");
1027 self.dirty
1028 .unchanged_loaded_runtime_objects
1029 .insert(tx_digest, unchanged_loaded_runtime_objects.clone());
1030
1031 self.metrics.record_cache_write("executed_effects_digests");
1032 self.dirty
1033 .executed_effects_digests
1034 .insert(tx_digest, effects_digest);
1035
1036 self.executed_effects_digests_notify_read
1037 .notify(&tx_digest, &effects_digest);
1038
1039 self.metrics
1040 .pending_notify_read
1041 .set(self.executed_effects_digests_notify_read.num_pending() as i64);
1042
1043 let prev = self
1044 .dirty
1045 .total_transaction_inserts
1046 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1047
1048 let pending_count = (prev + 1).saturating_sub(
1049 self.dirty
1050 .total_transaction_commits
1051 .load(std::sync::atomic::Ordering::Relaxed),
1052 );
1053
1054 self.set_backpressure(pending_count);
1055 }
1056
1057 fn build_db_batch(&self, epoch: EpochId, digests: &[TransactionDigest]) -> Batch {
1058 let _metrics_guard = mysten_metrics::monitored_scope("WritebackCache::build_db_batch");
1059 let mut all_outputs = Vec::with_capacity(digests.len());
1060 for tx in digests {
1061 let Some(outputs) = self
1062 .dirty
1063 .pending_transaction_writes
1064 .get(tx)
1065 .map(|o| o.clone())
1066 else {
1067 warn!("Attempt to commit unknown transaction {:?}", tx);
1073 continue;
1074 };
1075 all_outputs.push(outputs);
1076 }
1077
1078 let batch = self
1079 .store
1080 .build_db_batch(epoch, &all_outputs)
1081 .expect("db error");
1082 (all_outputs, batch)
1083 }
1084
1085 #[instrument(level = "debug", skip_all)]
1087 fn commit_transaction_outputs(
1088 &self,
1089 epoch: EpochId,
1090 (all_outputs, db_batch): Batch,
1091 digests: &[TransactionDigest],
1092 ) {
1093 let _metrics_guard =
1094 mysten_metrics::monitored_scope("WritebackCache::commit_transaction_outputs");
1095 fail_point!("writeback-cache-commit");
1096 trace!(?digests);
1097
1098 db_batch.write().expect("db error");
1102
1103 let _metrics_guard =
1104 mysten_metrics::monitored_scope("WritebackCache::commit_transaction_outputs::flush");
1105 all_outputs.par_iter().with_min_len(16).for_each(|outputs| {
1108 let tx_digest = outputs.transaction.digest();
1109 assert!(
1110 self.dirty
1111 .pending_transaction_writes
1112 .remove(tx_digest)
1113 .is_some()
1114 );
1115 self.flush_tx_metadata_from_dirty_to_cached(*tx_digest, outputs);
1116 });
1117
1118 for outputs in all_outputs.iter() {
1123 self.flush_objects_from_dirty_to_cached(epoch, outputs);
1124 }
1125
1126 let num_outputs = all_outputs.len() as u64;
1127 let num_commits = self
1128 .dirty
1129 .total_transaction_commits
1130 .fetch_add(num_outputs, std::sync::atomic::Ordering::Relaxed)
1131 + num_outputs;
1132
1133 let pending_count = self
1134 .dirty
1135 .total_transaction_inserts
1136 .load(std::sync::atomic::Ordering::Relaxed)
1137 .saturating_sub(num_commits);
1138
1139 self.set_backpressure(pending_count);
1140 }
1141
1142 fn approximate_pending_transaction_count(&self) -> u64 {
1143 let num_commits = self
1144 .dirty
1145 .total_transaction_commits
1146 .load(std::sync::atomic::Ordering::Relaxed);
1147
1148 self.dirty
1149 .total_transaction_inserts
1150 .load(std::sync::atomic::Ordering::Relaxed)
1151 .saturating_sub(num_commits)
1152 }
1153
1154 fn set_backpressure(&self, pending_count: u64) {
1155 let backpressure = pending_count > self.backpressure_threshold;
1156 let backpressure_changed = self.backpressure_manager.set_backpressure(backpressure);
1157 if backpressure_changed {
1158 self.metrics.backpressure_toggles.inc();
1159 }
1160 self.metrics
1161 .backpressure_status
1162 .set(if backpressure { 1 } else { 0 });
1163 }
1164
1165 fn flush_tx_metadata_from_dirty_to_cached(
1169 &self,
1170 tx_digest: TransactionDigest,
1171 outputs: &TransactionOutputs,
1172 ) {
1173 let TransactionOutputs {
1175 transaction,
1176 effects,
1177 events,
1178 ..
1179 } = outputs;
1180
1181 let effects_digest = effects.digest();
1182
1183 self.cached
1186 .transactions
1187 .insert(
1188 &tx_digest,
1189 PointCacheItem::Some(transaction.clone()),
1190 Ticket::Write,
1191 )
1192 .ok();
1193 self.cached
1194 .transaction_effects
1195 .insert(
1196 &effects_digest,
1197 PointCacheItem::Some(effects.clone().into()),
1198 Ticket::Write,
1199 )
1200 .ok();
1201 self.cached
1202 .executed_effects_digests
1203 .insert(
1204 &tx_digest,
1205 PointCacheItem::Some(effects_digest),
1206 Ticket::Write,
1207 )
1208 .ok();
1209 self.cached
1210 .transaction_events
1211 .insert(
1212 &tx_digest,
1213 PointCacheItem::Some(events.clone().into()),
1214 Ticket::Write,
1215 )
1216 .ok();
1217
1218 self.dirty
1219 .transaction_effects
1220 .remove(&effects_digest)
1221 .expect("effects must exist");
1222
1223 self.dirty
1224 .transaction_events
1225 .remove(&tx_digest)
1226 .expect("events must exist");
1227
1228 self.dirty
1229 .unchanged_loaded_runtime_objects
1230 .remove(&tx_digest)
1231 .expect("unchanged_loaded_runtime_objects must exist");
1232
1233 self.dirty
1234 .executed_effects_digests
1235 .remove(&tx_digest)
1236 .expect("executed effects must exist");
1237 }
1238
1239 fn flush_objects_from_dirty_to_cached(&self, epoch: EpochId, outputs: &TransactionOutputs) {
1243 let TransactionOutputs {
1244 markers,
1245 written,
1246 deleted,
1247 wrapped,
1248 ..
1249 } = outputs;
1250
1251 for (object_key, marker_value) in markers.iter() {
1252 Self::move_version_from_dirty_to_cache(
1253 &self.dirty.markers,
1254 &self.cached.marker_cache,
1255 (epoch, object_key.id()),
1256 object_key.version(),
1257 marker_value,
1258 );
1259 }
1260
1261 for (object_id, object) in written.iter() {
1262 Self::move_version_from_dirty_to_cache(
1263 &self.dirty.objects,
1264 &self.cached.object_cache,
1265 *object_id,
1266 object.version(),
1267 &ObjectEntry::Object(object.clone()),
1268 );
1269 }
1270
1271 for ObjectKey(object_id, version) in deleted.iter() {
1272 Self::move_version_from_dirty_to_cache(
1273 &self.dirty.objects,
1274 &self.cached.object_cache,
1275 *object_id,
1276 *version,
1277 &ObjectEntry::Deleted,
1278 );
1279 }
1280
1281 for ObjectKey(object_id, version) in wrapped.iter() {
1282 Self::move_version_from_dirty_to_cache(
1283 &self.dirty.objects,
1284 &self.cached.object_cache,
1285 *object_id,
1286 *version,
1287 &ObjectEntry::Wrapped,
1288 );
1289 }
1290 }
1291
1292 fn move_version_from_dirty_to_cache<K, V>(
1295 dirty: &DashMap<K, CachedVersionMap<V>>,
1296 cache: &MokaCache<K, Arc<Mutex<CachedVersionMap<V>>>>,
1297 key: K,
1298 version: SequenceNumber,
1299 value: &V,
1300 ) where
1301 K: Eq + std::hash::Hash + Clone + Send + Sync + Copy + 'static,
1302 V: Send + Sync + Clone + Eq + std::fmt::Debug + 'static,
1303 {
1304 static MAX_VERSIONS: usize = 3;
1305
1306 let dirty_entry = dirty.entry(key);
1309 let cache_entry = cache.entry(key).or_default();
1310 let mut cache_map = cache_entry.value().lock();
1311
1312 cache_map.insert(version, value.clone());
1314 cache_map.truncate_to(MAX_VERSIONS);
1316
1317 let DashMapEntry::Occupied(mut occupied_dirty_entry) = dirty_entry else {
1318 panic!("dirty map must exist");
1319 };
1320
1321 let removed = occupied_dirty_entry.get_mut().pop_oldest(&version);
1322
1323 assert_eq!(removed.as_ref(), Some(value), "dirty version must exist");
1324
1325 if occupied_dirty_entry.get().is_empty() {
1327 occupied_dirty_entry.remove();
1328 }
1329 }
1330
1331 fn cache_latest_object_by_id(
1333 &self,
1334 object_id: &ObjectID,
1335 object: LatestObjectCacheEntry,
1336 ticket: Ticket,
1337 ) {
1338 trace!("caching object by id: {:?} {:?}", object_id, object);
1339 if self
1340 .object_by_id_cache
1341 .insert(object_id, object, ticket)
1342 .is_ok()
1343 {
1344 self.metrics.record_cache_write("object_by_id");
1345 } else {
1346 trace!("discarded cache write due to expired ticket");
1347 self.metrics.record_ticket_expiry();
1348 }
1349 }
1350
1351 fn cache_object_not_found(&self, object_id: &ObjectID, ticket: Ticket) {
1352 self.cache_latest_object_by_id(object_id, LatestObjectCacheEntry::NonExistent, ticket);
1353 }
1354
1355 fn clear_state_end_of_epoch_impl(&self, execution_guard: &ExecutionLockWriteGuard<'_>) {
1356 info!("clearing state at end of epoch");
1357
1358 for r in self.dirty.pending_transaction_writes.iter() {
1361 let outputs = r.value();
1362 if !outputs
1363 .transaction
1364 .transaction_data()
1365 .shared_input_objects()
1366 .is_empty()
1367 {
1368 debug_fatal!("transaction must be single writer");
1369 }
1370 info!(
1371 "clearing state for transaction {:?}",
1372 outputs.transaction.digest()
1373 );
1374 for (object_id, object) in outputs.written.iter() {
1375 if object.is_package() {
1376 info!("removing non-finalized package from cache: {:?}", object_id);
1377 self.packages.invalidate(object_id);
1378 }
1379 self.object_by_id_cache.invalidate(object_id);
1380 self.cached.object_cache.invalidate(object_id);
1381 }
1382
1383 for ObjectKey(object_id, _) in outputs.deleted.iter().chain(outputs.wrapped.iter()) {
1384 self.object_by_id_cache.invalidate(object_id);
1385 self.cached.object_cache.invalidate(object_id);
1386 }
1387 }
1388
1389 self.dirty.clear();
1390
1391 info!("clearing old transaction locks");
1392 self.object_locks.clear();
1393 info!("clearing object per epoch marker table");
1394 self.store
1395 .clear_object_per_epoch_marker_table(execution_guard)
1396 .expect("db error");
1397 }
1398
1399 fn bulk_insert_genesis_objects_impl(&self, objects: &[Object]) {
1400 self.store
1401 .bulk_insert_genesis_objects(objects)
1402 .expect("db error");
1403 for obj in objects {
1404 self.cached.object_cache.invalidate(&obj.id());
1405 self.object_by_id_cache.invalidate(&obj.id());
1406 }
1407 }
1408
1409 fn insert_genesis_object_impl(&self, object: Object) {
1410 self.object_by_id_cache.invalidate(&object.id());
1411 self.cached.object_cache.invalidate(&object.id());
1412 self.store.insert_genesis_object(object).expect("db error");
1413 }
1414
1415 pub fn clear_caches_and_assert_empty(&self) {
1416 info!("clearing caches");
1417 self.cached.clear_and_assert_empty();
1418 self.object_by_id_cache.invalidate_all();
1419 assert!(&self.object_by_id_cache.is_empty());
1420 self.packages.invalidate_all();
1421 assert_empty(&self.packages);
1422 }
1423}
1424
1425fn account_amount_from_object(account_obj: &Object) -> u128 {
1426 let (_, AccumulatorValue::U128(value)) =
1427 account_obj.data.try_as_move().unwrap().try_into().unwrap();
1428 value.value
1429}
1430
1431impl AccountFundsRead for WritebackCache {
1432 fn get_latest_account_amount(&self, account_id: &AccumulatorObjId) -> u128 {
1433 ObjectCacheRead::get_object(self, account_id.inner())
1434 .map(|account_obj| account_amount_from_object(&account_obj))
1435 .unwrap_or(0)
1436 }
1437
1438 fn get_consistent_latest_account_amount_and_version(
1439 &self,
1440 account_id: &AccumulatorObjId,
1441 ) -> (u128, SequenceNumber) {
1442 let mut pre_root_version =
1467 ObjectCacheRead::get_object(self, &SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1468 .unwrap()
1469 .version();
1470 let starting_root_version = pre_root_version;
1471 let mut loop_iter = 0;
1472 loop {
1473 loop_iter += 1;
1474 let value = self.get_account_amount_at_version(account_id, pre_root_version);
1477 let post_root_version =
1478 ObjectCacheRead::get_object(self, &SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1479 .unwrap()
1480 .version();
1481 if pre_root_version == post_root_version {
1482 if loop_iter > 10 {
1483 debug!(
1484 iterations = loop_iter,
1485 starting_root_version = %starting_root_version,
1486 ending_root_version = %post_root_version,
1487 "Root version stabilized after multiple iterations during MVCC read"
1488 );
1489 }
1490 return (value, pre_root_version);
1491 }
1492 debug!(
1493 "Root version changed from {} to {} during MVCC read, retrying",
1494 pre_root_version, post_root_version
1495 );
1496 pre_root_version = post_root_version;
1497 }
1498 }
1499
1500 fn get_account_amount_at_version(
1501 &self,
1502 account_id: &AccumulatorObjId,
1503 version: SequenceNumber,
1504 ) -> u128 {
1505 let account_obj = self.find_object_lt_or_eq_version(*account_id.inner(), version);
1506 account_obj
1507 .map(|account_obj| account_amount_from_object(&account_obj))
1508 .unwrap_or(0)
1509 }
1510}
1511
1512impl ExecutionCacheAPI for WritebackCache {}
1513
1514impl ExecutionCacheCommit for WritebackCache {
1515 fn build_db_batch(&self, epoch: EpochId, digests: &[TransactionDigest]) -> Batch {
1516 self.build_db_batch(epoch, digests)
1517 }
1518
1519 fn set_highest_committed_checkpoint_in_batch(
1520 &self,
1521 batch: &mut Batch,
1522 checkpoint: CheckpointSequenceNumber,
1523 ) {
1524 self.store
1525 .perpetual_tables
1526 .set_highest_committed_checkpoint(&mut batch.1, checkpoint)
1527 .expect("db error");
1528 }
1529
1530 fn commit_transaction_outputs(
1531 &self,
1532 epoch: EpochId,
1533 batch: Batch,
1534 digests: &[TransactionDigest],
1535 ) {
1536 WritebackCache::commit_transaction_outputs(self, epoch, batch, digests)
1537 }
1538
1539 fn persist_transaction(&self, tx: &VerifiedExecutableTransaction) {
1540 self.store.persist_transaction(tx).expect("db error");
1541 }
1542
1543 fn approximate_pending_transaction_count(&self) -> u64 {
1544 WritebackCache::approximate_pending_transaction_count(self)
1545 }
1546}
1547
1548impl ObjectCacheRead for WritebackCache {
1549 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1550 self.metrics
1551 .record_cache_request("package", "package_cache");
1552 if let Some(p) = self.packages.get(package_id) {
1553 if cfg!(debug_assertions) {
1554 let canonical_package = self
1555 .dirty
1556 .objects
1557 .get(package_id)
1558 .and_then(|v| match v.get_highest().map(|v| v.1.clone()) {
1559 Some(ObjectEntry::Object(object)) => Some(object),
1560 _ => None,
1561 })
1562 .or_else(|| self.store.get_object(package_id));
1563
1564 if let Some(canonical_package) = canonical_package {
1565 assert_eq!(
1566 canonical_package.digest(),
1567 p.object().digest(),
1568 "Package object cache is inconsistent for package {:?}",
1569 package_id
1570 );
1571 }
1572 }
1573 self.metrics.record_cache_hit("package", "package_cache");
1574 return Ok(Some(p));
1575 } else {
1576 self.metrics.record_cache_miss("package", "package_cache");
1577 }
1578
1579 if let Some(p) = self.get_object_impl("package", package_id) {
1583 if p.is_package() {
1584 let p = PackageObject::new(p);
1585 tracing::trace!(
1586 "caching package: {:?}",
1587 p.object().compute_object_reference()
1588 );
1589 self.metrics.record_cache_write("package");
1590 self.packages.insert(*package_id, p.clone());
1591 Ok(Some(p))
1592 } else {
1593 Err(SuiErrorKind::UserInputError {
1594 error: UserInputError::MoveObjectAsPackage {
1595 object_id: *package_id,
1596 },
1597 }
1598 .into())
1599 }
1600 } else {
1601 Ok(None)
1602 }
1603 }
1604
1605 fn force_reload_system_packages(&self, _system_package_ids: &[ObjectID]) {
1606 }
1609
1610 fn get_object(&self, id: &ObjectID) -> Option<Object> {
1613 self.get_object_impl("object_latest", id)
1614 }
1615
1616 fn get_object_by_key(&self, object_id: &ObjectID, version: SequenceNumber) -> Option<Object> {
1617 match self.get_object_by_key_cache_only(object_id, version) {
1618 CacheResult::Hit(object) => Some(object),
1619 CacheResult::NegativeHit => None,
1620 CacheResult::Miss => self
1621 .record_db_get("object_by_version")
1622 .get_object_by_key(object_id, version),
1623 }
1624 }
1625
1626 fn multi_get_objects_by_key(&self, object_keys: &[ObjectKey]) -> Vec<Option<Object>> {
1627 do_fallback_lookup(
1628 object_keys,
1629 |key| match self.get_object_by_key_cache_only(&key.0, key.1) {
1630 CacheResult::Hit(maybe_object) => CacheResult::Hit(Some(maybe_object)),
1631 CacheResult::NegativeHit => CacheResult::NegativeHit,
1632 CacheResult::Miss => CacheResult::Miss,
1633 },
1634 |remaining| {
1635 self.record_db_multi_get("object_by_version", remaining.len())
1636 .multi_get_objects_by_key(remaining)
1637 .expect("db error")
1638 },
1639 )
1640 }
1641
1642 fn object_exists_by_key(&self, object_id: &ObjectID, version: SequenceNumber) -> bool {
1643 match self.get_object_by_key_cache_only(object_id, version) {
1644 CacheResult::Hit(_) => true,
1645 CacheResult::NegativeHit => false,
1646 CacheResult::Miss => self
1647 .record_db_get("object_by_version")
1648 .object_exists_by_key(object_id, version)
1649 .expect("db error"),
1650 }
1651 }
1652
1653 fn multi_object_exists_by_key(&self, object_keys: &[ObjectKey]) -> Vec<bool> {
1654 do_fallback_lookup(
1655 object_keys,
1656 |key| match self.get_object_by_key_cache_only(&key.0, key.1) {
1657 CacheResult::Hit(_) => CacheResult::Hit(true),
1658 CacheResult::NegativeHit => CacheResult::Hit(false),
1659 CacheResult::Miss => CacheResult::Miss,
1660 },
1661 |remaining| {
1662 self.record_db_multi_get("object_by_version", remaining.len())
1663 .multi_object_exists_by_key(remaining)
1664 .expect("db error")
1665 },
1666 )
1667 }
1668
1669 fn get_latest_object_ref_or_tombstone(&self, object_id: ObjectID) -> Option<ObjectRef> {
1670 match self.get_object_entry_by_id_cache_only("latest_objref_or_tombstone", &object_id) {
1671 CacheResult::Hit((version, entry)) => Some(match entry {
1672 ObjectEntry::Object(object) => object.compute_object_reference(),
1673 ObjectEntry::Deleted => (object_id, version, ObjectDigest::OBJECT_DIGEST_DELETED),
1674 ObjectEntry::Wrapped => (object_id, version, ObjectDigest::OBJECT_DIGEST_WRAPPED),
1675 }),
1676 CacheResult::NegativeHit => None,
1677 CacheResult::Miss => self
1678 .record_db_get("latest_objref_or_tombstone")
1679 .get_latest_object_ref_or_tombstone(object_id)
1680 .expect("db error"),
1681 }
1682 }
1683
1684 fn get_latest_object_or_tombstone(
1685 &self,
1686 object_id: ObjectID,
1687 ) -> Option<(ObjectKey, ObjectOrTombstone)> {
1688 match self.get_object_entry_by_id_cache_only("latest_object_or_tombstone", &object_id) {
1689 CacheResult::Hit((version, entry)) => {
1690 let key = ObjectKey(object_id, version);
1691 Some(match entry {
1692 ObjectEntry::Object(object) => (key, object.into()),
1693 ObjectEntry::Deleted => (
1694 key,
1695 ObjectOrTombstone::Tombstone((
1696 object_id,
1697 version,
1698 ObjectDigest::OBJECT_DIGEST_DELETED,
1699 )),
1700 ),
1701 ObjectEntry::Wrapped => (
1702 key,
1703 ObjectOrTombstone::Tombstone((
1704 object_id,
1705 version,
1706 ObjectDigest::OBJECT_DIGEST_WRAPPED,
1707 )),
1708 ),
1709 })
1710 }
1711 CacheResult::NegativeHit => None,
1712 CacheResult::Miss => self
1713 .record_db_get("latest_object_or_tombstone")
1714 .get_latest_object_or_tombstone(object_id)
1715 .expect("db error"),
1716 }
1717 }
1718
1719 fn multi_input_objects_available_cache_only(&self, keys: &[InputKey]) -> Vec<bool> {
1720 keys.iter()
1721 .map(|key| {
1722 if key.is_cancelled() {
1723 true
1724 } else {
1725 match key {
1726 InputKey::VersionedObject { id, version } => {
1727 matches!(
1728 self.get_object_by_key_cache_only(&id.id(), *version),
1729 CacheResult::Hit(_)
1730 )
1731 }
1732 InputKey::Package { id } => self.packages.contains_key(id),
1733 }
1734 }
1735 })
1736 .collect()
1737 }
1738
1739 #[instrument(level = "trace", skip_all, fields(object_id, version_bound))]
1740 fn find_object_lt_or_eq_version(
1741 &self,
1742 object_id: ObjectID,
1743 version_bound: SequenceNumber,
1744 ) -> Option<Object> {
1745 macro_rules! check_cache_entry {
1746 ($level: expr, $objects: expr) => {
1747 self.metrics
1748 .record_cache_request("object_lt_or_eq_version", $level);
1749 if let Some(objects) = $objects {
1750 if let Some((_, object)) = objects
1751 .all_versions_lt_or_eq_descending(&version_bound)
1752 .next()
1753 {
1754 if let ObjectEntry::Object(object) = object {
1755 self.metrics
1756 .record_cache_hit("object_lt_or_eq_version", $level);
1757 return Some(object.clone());
1758 } else {
1759 self.metrics
1761 .record_cache_negative_hit("object_lt_or_eq_version", $level);
1762 return None;
1763 }
1764 } else {
1765 self.metrics
1766 .record_cache_miss("object_lt_or_eq_version", $level);
1767 }
1768 }
1769 };
1770 }
1771
1772 self.metrics
1774 .record_cache_request("object_lt_or_eq_version", "object_by_id");
1775 let latest_cache_entry = self.object_by_id_cache.get(&object_id);
1776 if let Some(latest) = &latest_cache_entry {
1777 let latest = latest.lock();
1778 match &*latest {
1779 LatestObjectCacheEntry::Object(latest_version, object) => {
1780 if *latest_version <= version_bound {
1781 if let ObjectEntry::Object(object) = object {
1782 self.metrics
1783 .record_cache_hit("object_lt_or_eq_version", "object_by_id");
1784 return Some(object.clone());
1785 } else {
1786 self.metrics.record_cache_negative_hit(
1788 "object_lt_or_eq_version",
1789 "object_by_id",
1790 );
1791 return None;
1792 }
1793 }
1794 }
1796 LatestObjectCacheEntry::NonExistent => {
1798 self.metrics
1799 .record_cache_negative_hit("object_lt_or_eq_version", "object_by_id");
1800 return None;
1801 }
1802 }
1803 }
1804 self.metrics
1805 .record_cache_miss("object_lt_or_eq_version", "object_by_id");
1806
1807 Self::with_locked_cache_entries(
1808 &self.dirty.objects,
1809 &self.cached.object_cache,
1810 &object_id,
1811 |dirty_entry, cached_entry| {
1812 check_cache_entry!("committed", dirty_entry);
1813 check_cache_entry!("uncommitted", cached_entry);
1814
1815 let latest: Option<(SequenceNumber, ObjectEntry)> =
1836 if let Some(dirty_set) = dirty_entry {
1837 dirty_set
1838 .get_highest()
1839 .cloned()
1840 .tap_none(|| panic!("dirty set cannot be empty"))
1841 } else {
1842 self.record_db_get("object_lt_or_eq_version_latest")
1844 .get_latest_object_or_tombstone(object_id)
1845 .expect("db error")
1846 .map(|(ObjectKey(_, version), obj_or_tombstone)| {
1847 (version, ObjectEntry::from(obj_or_tombstone))
1848 })
1849 };
1850
1851 if let Some((obj_version, obj_entry)) = latest {
1852 self.cache_latest_object_by_id(
1860 &object_id,
1861 LatestObjectCacheEntry::Object(obj_version, obj_entry.clone()),
1862 self.object_by_id_cache.get_ticket_for_read(&object_id),
1865 );
1866
1867 if obj_version <= version_bound {
1868 match obj_entry {
1869 ObjectEntry::Object(object) => Some(object),
1870 ObjectEntry::Deleted | ObjectEntry::Wrapped => None,
1871 }
1872 } else {
1873 self.record_db_get("object_lt_or_eq_version_scan")
1877 .find_object_lt_or_eq_version(object_id, version_bound)
1878 .expect("db error")
1879 }
1880
1881 } else if let Some(latest_cache_entry) = latest_cache_entry {
1887 assert!(!latest_cache_entry.lock().is_alive());
1889 None
1890 } else {
1891 let highest = cached_entry.and_then(|c| c.get_highest());
1893 assert!(highest.is_none() || highest.unwrap().1.is_tombstone());
1894 self.cache_object_not_found(
1895 &object_id,
1896 self.object_by_id_cache.get_ticket_for_read(&object_id),
1898 );
1899 None
1900 }
1901 },
1902 )
1903 }
1904
1905 fn get_sui_system_state_object_unsafe(&self) -> SuiResult<SuiSystemState> {
1906 get_sui_system_state(self)
1907 }
1908
1909 fn get_bridge_object_unsafe(&self) -> SuiResult<Bridge> {
1910 get_bridge(self)
1911 }
1912
1913 fn get_marker_value(
1914 &self,
1915 object_key: FullObjectKey,
1916 epoch_id: EpochId,
1917 ) -> Option<MarkerValue> {
1918 match self.get_marker_value_cache_only(object_key, epoch_id) {
1919 CacheResult::Hit(marker) => Some(marker),
1920 CacheResult::NegativeHit => None,
1921 CacheResult::Miss => self
1922 .record_db_get("marker_by_version")
1923 .get_marker_value(object_key, epoch_id)
1924 .expect("db error"),
1925 }
1926 }
1927
1928 fn get_latest_marker(
1929 &self,
1930 object_id: FullObjectID,
1931 epoch_id: EpochId,
1932 ) -> Option<(SequenceNumber, MarkerValue)> {
1933 match self.get_latest_marker_value_cache_only(object_id, epoch_id) {
1934 CacheResult::Hit((v, marker)) => Some((v, marker)),
1935 CacheResult::NegativeHit => {
1936 panic!("cannot have negative hit when getting latest marker")
1937 }
1938 CacheResult::Miss => self
1939 .record_db_get("marker_latest")
1940 .get_latest_marker(object_id, epoch_id)
1941 .expect("db error"),
1942 }
1943 }
1944
1945 #[cfg(test)]
1946 fn get_lock(&self, obj_ref: ObjectRef, epoch_store: &AuthorityPerEpochStore) -> SuiLockResult {
1947 let cur_epoch = epoch_store.epoch();
1948 let Some(obj) = self.get_object_impl("lock", &obj_ref.0) else {
1949 return Err(SuiError::from(UserInputError::ObjectNotFound {
1950 object_id: obj_ref.0,
1951 version: None,
1954 }));
1955 };
1956 let actual_objref = obj.compute_object_reference();
1957 if obj_ref != actual_objref {
1958 Ok(ObjectLockStatus::LockedAtDifferentVersion {
1959 locked_ref: actual_objref,
1960 })
1961 } else {
1962 Ok(
1964 match self
1965 .object_locks
1966 .get_transaction_lock(&obj_ref, epoch_store)?
1967 {
1968 Some(tx_digest) => ObjectLockStatus::LockedToTx {
1969 locked_by_tx: LockDetailsDeprecated {
1970 epoch: cur_epoch,
1971 tx_digest,
1972 },
1973 },
1974 None => ObjectLockStatus::Initialized,
1975 },
1976 )
1977 }
1978 }
1979
1980 fn _get_live_objref(&self, object_id: ObjectID) -> SuiResult<ObjectRef> {
1981 let obj = self.get_object_impl("live_objref", &object_id).ok_or(
1982 UserInputError::ObjectNotFound {
1983 object_id,
1984 version: None,
1985 },
1986 )?;
1987 Ok(obj.compute_object_reference())
1988 }
1989
1990 fn get_highest_pruned_checkpoint(&self) -> Option<CheckpointSequenceNumber> {
1991 self.store
1992 .perpetual_tables
1993 .get_highest_pruned_checkpoint()
1994 .expect("db error")
1995 }
1996
1997 fn notify_read_input_objects<'a>(
1998 &'a self,
1999 input_and_receiving_keys: &'a [InputKey],
2000 receiving_keys: &'a HashSet<InputKey>,
2001 epoch: EpochId,
2002 ) -> BoxFuture<'a, ()> {
2003 self.object_notify_read
2004 .read(
2005 "notify_read_input_objects",
2006 input_and_receiving_keys,
2007 move |keys| {
2008 self.multi_input_objects_available(keys, receiving_keys, epoch)
2009 .into_iter()
2010 .map(|available| if available { Some(()) } else { None })
2011 .collect::<Vec<_>>()
2012 },
2013 )
2014 .map(|_| ())
2015 .boxed()
2016 }
2017}
2018
2019impl TransactionCacheRead for WritebackCache {
2020 fn multi_get_transaction_blocks(
2021 &self,
2022 digests: &[TransactionDigest],
2023 ) -> Vec<Option<Arc<VerifiedTransaction>>> {
2024 let digests_and_tickets: Vec<_> = digests
2025 .iter()
2026 .map(|d| (*d, self.cached.transactions.get_ticket_for_read(d)))
2027 .collect();
2028 do_fallback_lookup(
2029 &digests_and_tickets,
2030 |(digest, _)| {
2031 self.metrics
2032 .record_cache_request("transaction_block", "uncommitted");
2033 if let Some(tx) = self.dirty.pending_transaction_writes.get(digest) {
2034 self.metrics
2035 .record_cache_hit("transaction_block", "uncommitted");
2036 return CacheResult::Hit(Some(tx.transaction.clone()));
2037 }
2038 self.metrics
2039 .record_cache_miss("transaction_block", "uncommitted");
2040
2041 self.metrics
2042 .record_cache_request("transaction_block", "committed");
2043
2044 match self
2045 .cached
2046 .transactions
2047 .get(digest)
2048 .map(|l| l.lock().clone())
2049 {
2050 Some(PointCacheItem::Some(tx)) => {
2051 self.metrics
2052 .record_cache_hit("transaction_block", "committed");
2053 CacheResult::Hit(Some(tx))
2054 }
2055 Some(PointCacheItem::None) => CacheResult::NegativeHit,
2056 None => {
2057 self.metrics
2058 .record_cache_miss("transaction_block", "committed");
2059
2060 CacheResult::Miss
2061 }
2062 }
2063 },
2064 |remaining| {
2065 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
2066 let results: Vec<_> = self
2067 .record_db_multi_get("transaction_block", remaining.len())
2068 .multi_get_transaction_blocks(&remaining_digests)
2069 .expect("db error")
2070 .into_iter()
2071 .map(|o| o.map(Arc::new))
2072 .collect();
2073 for ((digest, ticket), result) in remaining.iter().zip_debug_eq(results.iter()) {
2074 if result.is_none() {
2075 self.cached.transactions.insert(digest, None, *ticket).ok();
2076 }
2077 }
2078 results
2079 },
2080 )
2081 }
2082
2083 fn multi_get_executed_effects_digests(
2084 &self,
2085 digests: &[TransactionDigest],
2086 ) -> Vec<Option<TransactionEffectsDigest>> {
2087 let digests_and_tickets: Vec<_> = digests
2088 .iter()
2089 .map(|d| {
2090 (
2091 *d,
2092 self.cached.executed_effects_digests.get_ticket_for_read(d),
2093 )
2094 })
2095 .collect();
2096 do_fallback_lookup(
2097 &digests_and_tickets,
2098 |(digest, _)| {
2099 self.metrics
2100 .record_cache_request("executed_effects_digests", "uncommitted");
2101 if let Some(digest) = self.dirty.executed_effects_digests.get(digest) {
2102 self.metrics
2103 .record_cache_hit("executed_effects_digests", "uncommitted");
2104 return CacheResult::Hit(Some(*digest));
2105 }
2106 self.metrics
2107 .record_cache_miss("executed_effects_digests", "uncommitted");
2108
2109 self.metrics
2110 .record_cache_request("executed_effects_digests", "committed");
2111 match self
2112 .cached
2113 .executed_effects_digests
2114 .get(digest)
2115 .map(|l| *l.lock())
2116 {
2117 Some(PointCacheItem::Some(digest)) => {
2118 self.metrics
2119 .record_cache_hit("executed_effects_digests", "committed");
2120 CacheResult::Hit(Some(digest))
2121 }
2122 Some(PointCacheItem::None) => CacheResult::NegativeHit,
2123 None => {
2124 self.metrics
2125 .record_cache_miss("executed_effects_digests", "committed");
2126 CacheResult::Miss
2127 }
2128 }
2129 },
2130 |remaining| {
2131 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
2132 let results = self
2133 .record_db_multi_get("executed_effects_digests", remaining.len())
2134 .multi_get_executed_effects_digests(&remaining_digests)
2135 .expect("db error");
2136 for ((digest, ticket), result) in remaining.iter().zip_debug_eq(results.iter()) {
2137 if result.is_none() {
2138 self.cached
2139 .executed_effects_digests
2140 .insert(digest, None, *ticket)
2141 .ok();
2142 }
2143 }
2144 results
2145 },
2146 )
2147 }
2148
2149 fn multi_get_effects(
2150 &self,
2151 digests: &[TransactionEffectsDigest],
2152 ) -> Vec<Option<TransactionEffects>> {
2153 let digests_and_tickets: Vec<_> = digests
2154 .iter()
2155 .map(|d| (*d, self.cached.transaction_effects.get_ticket_for_read(d)))
2156 .collect();
2157 do_fallback_lookup(
2158 &digests_and_tickets,
2159 |(digest, _)| {
2160 self.metrics
2161 .record_cache_request("transaction_effects", "uncommitted");
2162 if let Some(effects) = self.dirty.transaction_effects.get(digest) {
2163 self.metrics
2164 .record_cache_hit("transaction_effects", "uncommitted");
2165 return CacheResult::Hit(Some(effects.clone()));
2166 }
2167 self.metrics
2168 .record_cache_miss("transaction_effects", "uncommitted");
2169
2170 self.metrics
2171 .record_cache_request("transaction_effects", "committed");
2172 match self
2173 .cached
2174 .transaction_effects
2175 .get(digest)
2176 .map(|l| l.lock().clone())
2177 {
2178 Some(PointCacheItem::Some(effects)) => {
2179 self.metrics
2180 .record_cache_hit("transaction_effects", "committed");
2181 CacheResult::Hit(Some((*effects).clone()))
2182 }
2183 Some(PointCacheItem::None) => CacheResult::NegativeHit,
2184 None => {
2185 self.metrics
2186 .record_cache_miss("transaction_effects", "committed");
2187 CacheResult::Miss
2188 }
2189 }
2190 },
2191 |remaining| {
2192 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
2193 let results = self
2194 .record_db_multi_get("transaction_effects", remaining.len())
2195 .multi_get_effects(remaining_digests.iter())
2196 .expect("db error");
2197 for ((digest, ticket), result) in remaining.iter().zip_debug_eq(results.iter()) {
2198 if result.is_none() {
2199 self.cached
2200 .transaction_effects
2201 .insert(digest, None, *ticket)
2202 .ok();
2203 }
2204 }
2205 results
2206 },
2207 )
2208 }
2209
2210 fn transaction_executed_in_last_epoch(
2211 &self,
2212 digest: &TransactionDigest,
2213 current_epoch: EpochId,
2214 ) -> bool {
2215 if current_epoch == 0 {
2216 return false;
2217 }
2218 let last_epoch = current_epoch - 1;
2219 let cache_key = (last_epoch, *digest);
2220
2221 let ticket = self
2222 .cached
2223 .transaction_executed_in_last_epoch
2224 .get_ticket_for_read(&cache_key);
2225
2226 if let Some(cached) = self
2227 .cached
2228 .transaction_executed_in_last_epoch
2229 .get(&cache_key)
2230 {
2231 return cached.lock().is_some();
2232 }
2233
2234 let was_executed = self
2235 .store
2236 .perpetual_tables
2237 .was_transaction_executed_in_last_epoch(digest, current_epoch);
2238
2239 let value = if was_executed { Some(()) } else { None };
2240 self.cached
2241 .transaction_executed_in_last_epoch
2242 .insert(&cache_key, value, ticket)
2243 .ok();
2244
2245 was_executed
2246 }
2247
2248 fn notify_read_executed_effects_digests<'a>(
2249 &'a self,
2250 task_name: &'static str,
2251 digests: &'a [TransactionDigest],
2252 ) -> BoxFuture<'a, Vec<TransactionEffectsDigest>> {
2253 self.executed_effects_digests_notify_read
2254 .read(task_name, digests, |digests| {
2255 self.multi_get_executed_effects_digests(digests)
2256 })
2257 .boxed()
2258 }
2259
2260 fn multi_get_events(
2261 &self,
2262 event_digests: &[TransactionDigest],
2263 ) -> Vec<Option<TransactionEvents>> {
2264 fn map_events(events: TransactionEvents) -> Option<TransactionEvents> {
2265 if events.data.is_empty() {
2266 None
2267 } else {
2268 Some(events)
2269 }
2270 }
2271
2272 let digests_and_tickets: Vec<_> = event_digests
2273 .iter()
2274 .map(|d| (*d, self.cached.transaction_events.get_ticket_for_read(d)))
2275 .collect();
2276 do_fallback_lookup(
2277 &digests_and_tickets,
2278 |(digest, _)| {
2279 self.metrics
2280 .record_cache_request("transaction_events", "uncommitted");
2281 if let Some(events) = self.dirty.transaction_events.get(digest).map(|e| e.clone()) {
2282 self.metrics
2283 .record_cache_hit("transaction_events", "uncommitted");
2284
2285 return CacheResult::Hit(map_events(events));
2286 }
2287 self.metrics
2288 .record_cache_miss("transaction_events", "uncommitted");
2289
2290 self.metrics
2291 .record_cache_request("transaction_events", "committed");
2292 match self
2293 .cached
2294 .transaction_events
2295 .get(digest)
2296 .map(|l| l.lock().clone())
2297 {
2298 Some(PointCacheItem::Some(events)) => {
2299 self.metrics
2300 .record_cache_hit("transaction_events", "committed");
2301 CacheResult::Hit(map_events((*events).clone()))
2302 }
2303 Some(PointCacheItem::None) => CacheResult::NegativeHit,
2304 None => {
2305 self.metrics
2306 .record_cache_miss("transaction_events", "committed");
2307
2308 CacheResult::Miss
2309 }
2310 }
2311 },
2312 |remaining| {
2313 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
2314 let results = self
2315 .store
2316 .multi_get_events(&remaining_digests)
2317 .expect("db error");
2318 for ((digest, ticket), result) in remaining.iter().zip_debug_eq(results.iter()) {
2319 if result.is_none() {
2320 self.cached
2321 .transaction_events
2322 .insert(digest, None, *ticket)
2323 .ok();
2324 }
2325 }
2326 results
2327 },
2328 )
2329 }
2330
2331 fn get_unchanged_loaded_runtime_objects(
2332 &self,
2333 digest: &TransactionDigest,
2334 ) -> Option<Vec<ObjectKey>> {
2335 self.dirty
2336 .unchanged_loaded_runtime_objects
2337 .get(digest)
2338 .map(|b| b.clone())
2339 .or_else(|| {
2340 self.store
2341 .get_unchanged_loaded_runtime_objects(digest)
2342 .expect("db error")
2343 })
2344 }
2345
2346 fn multi_get_unchanged_loaded_runtime_objects(
2347 &self,
2348 digests: &[TransactionDigest],
2349 ) -> Vec<Option<Vec<ObjectKey>>> {
2350 do_fallback_lookup(
2351 digests,
2352 |digest| match self.dirty.unchanged_loaded_runtime_objects.get(digest) {
2353 Some(objects) => CacheResult::Hit(Some(objects.clone())),
2354 None => CacheResult::Miss,
2355 },
2356 |digests| {
2357 self.store
2358 .multi_get_unchanged_loaded_runtime_objects(digests)
2359 .expect("db error")
2360 },
2361 )
2362 }
2363
2364 fn take_accumulator_events(&self, digest: &TransactionDigest) -> Option<Vec<AccumulatorEvent>> {
2365 self.dirty
2366 .pending_transaction_writes
2367 .get(digest)
2368 .map(|transaction_output| transaction_output.take_accumulator_events())
2369 }
2370}
2371
2372impl ExecutionCacheWrite for WritebackCache {
2373 fn validate_owned_object_versions(&self, owned_input_objects: &[ObjectRef]) -> SuiResult {
2374 ObjectLocks::validate_owned_object_versions(self, owned_input_objects)
2375 }
2376
2377 fn write_transaction_outputs(&self, epoch_id: EpochId, tx_outputs: Arc<TransactionOutputs>) {
2378 WritebackCache::write_transaction_outputs(self, epoch_id, tx_outputs);
2379 }
2380
2381 #[cfg(test)]
2382 fn write_object_entry_for_test(&self, object: Object) {
2383 self.write_object_entry(&object.id(), object.version(), object.into());
2384 }
2385}
2386
2387implement_passthrough_traits!(WritebackCache);
2388
2389impl GlobalStateHashStore for WritebackCache {
2390 fn get_object_ref_prior_to_key_deprecated(
2391 &self,
2392 object_id: &ObjectID,
2393 version: SequenceNumber,
2394 ) -> SuiResult<Option<ObjectRef>> {
2395 let mut candidates = Vec::new();
2399
2400 let check_versions =
2401 |versions: &CachedVersionMap<ObjectEntry>| match versions.get_prior_to(&version) {
2402 Some((version, object_entry)) => match object_entry {
2403 ObjectEntry::Object(object) => {
2404 assert_eq!(object.version(), version);
2405 Some(object.compute_object_reference())
2406 }
2407 ObjectEntry::Deleted => {
2408 Some((*object_id, version, ObjectDigest::OBJECT_DIGEST_DELETED))
2409 }
2410 ObjectEntry::Wrapped => {
2411 Some((*object_id, version, ObjectDigest::OBJECT_DIGEST_WRAPPED))
2412 }
2413 },
2414 None => None,
2415 };
2416
2417 if let Some(objects) = self.dirty.objects.get(object_id)
2419 && let Some(prior) = check_versions(&objects)
2420 {
2421 candidates.push(prior);
2422 }
2423
2424 if let Some(objects) = self.cached.object_cache.get(object_id)
2425 && let Some(prior) = check_versions(&objects.lock())
2426 {
2427 candidates.push(prior);
2428 }
2429
2430 if let Some(prior) = self
2431 .store
2432 .get_object_ref_prior_to_key_deprecated(object_id, version)?
2433 {
2434 candidates.push(prior);
2435 }
2436
2437 candidates.sort_by_key(|(_, version, _)| *version);
2439 Ok(candidates.pop())
2440 }
2441
2442 fn get_root_state_hash_for_epoch(
2443 &self,
2444 epoch: EpochId,
2445 ) -> SuiResult<Option<(CheckpointSequenceNumber, GlobalStateHash)>> {
2446 self.store.get_root_state_hash_for_epoch(epoch)
2447 }
2448
2449 fn get_root_state_hash_for_highest_epoch(
2450 &self,
2451 ) -> SuiResult<Option<(EpochId, (CheckpointSequenceNumber, GlobalStateHash))>> {
2452 self.store.get_root_state_hash_for_highest_epoch()
2453 }
2454
2455 fn insert_state_hash_for_epoch(
2456 &self,
2457 epoch: EpochId,
2458 checkpoint_seq_num: &CheckpointSequenceNumber,
2459 acc: &GlobalStateHash,
2460 ) -> SuiResult {
2461 self.store
2462 .insert_state_hash_for_epoch(epoch, checkpoint_seq_num, acc)
2463 }
2464
2465 fn iter_live_object_set(
2466 &self,
2467 include_wrapped_tombstone: bool,
2468 ) -> Box<dyn Iterator<Item = LiveObject> + '_> {
2469 assert!(
2473 self.dirty.is_empty(),
2474 "cannot iterate live object set with dirty data"
2475 );
2476 self.store.iter_live_object_set(include_wrapped_tombstone)
2477 }
2478
2479 fn iter_cached_live_object_set_for_testing(
2483 &self,
2484 include_wrapped_tombstone: bool,
2485 ) -> Box<dyn Iterator<Item = LiveObject> + '_> {
2486 let iter = self.dirty.objects.iter();
2488 let mut dirty_objects = BTreeMap::new();
2489
2490 for obj in self.store.iter_live_object_set(include_wrapped_tombstone) {
2492 dirty_objects.insert(obj.object_id(), obj);
2493 }
2494
2495 for entry in iter {
2497 let id = *entry.key();
2498 let value = entry.value();
2499 match value.get_highest().unwrap() {
2500 (_, ObjectEntry::Object(object)) => {
2501 dirty_objects.insert(id, LiveObject::Normal(object.clone()));
2502 }
2503 (version, ObjectEntry::Wrapped) => {
2504 if include_wrapped_tombstone {
2505 dirty_objects.insert(id, LiveObject::Wrapped(ObjectKey(id, *version)));
2506 } else {
2507 dirty_objects.remove(&id);
2508 }
2509 }
2510 (_, ObjectEntry::Deleted) => {
2511 dirty_objects.remove(&id);
2512 }
2513 }
2514 }
2515
2516 Box::new(dirty_objects.into_values())
2517 }
2518}
2519
2520impl StateSyncAPI for WritebackCache {
2526 fn insert_transaction_and_effects(
2527 &self,
2528 transaction: &VerifiedTransaction,
2529 transaction_effects: &TransactionEffects,
2530 ) {
2531 self.store
2532 .insert_transaction_and_effects(transaction, transaction_effects)
2533 .expect("db error");
2534 self.cached
2535 .transactions
2536 .insert(
2537 transaction.digest(),
2538 PointCacheItem::Some(Arc::new(transaction.clone())),
2539 Ticket::Write,
2540 )
2541 .ok();
2542 self.cached
2543 .transaction_effects
2544 .insert(
2545 &transaction_effects.digest(),
2546 PointCacheItem::Some(Arc::new(transaction_effects.clone())),
2547 Ticket::Write,
2548 )
2549 .ok();
2550 }
2551
2552 fn multi_insert_transaction_and_effects(
2553 &self,
2554 transactions_and_effects: &[VerifiedExecutionData],
2555 ) {
2556 self.store
2557 .multi_insert_transaction_and_effects(transactions_and_effects.iter())
2558 .expect("db error");
2559 for VerifiedExecutionData {
2560 transaction,
2561 effects,
2562 } in transactions_and_effects
2563 {
2564 self.cached
2565 .transactions
2566 .insert(
2567 transaction.digest(),
2568 PointCacheItem::Some(Arc::new(transaction.clone())),
2569 Ticket::Write,
2570 )
2571 .ok();
2572 self.cached
2573 .transaction_effects
2574 .insert(
2575 &effects.digest(),
2576 PointCacheItem::Some(Arc::new(effects.clone())),
2577 Ticket::Write,
2578 )
2579 .ok();
2580 }
2581 }
2582}