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::random_util::randomize_cache_capacity_in_tests;
59use mysten_common::sync::notify_read::NotifyRead;
60use mysten_common::{debug_fatal, debug_fatal_no_invariant};
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 mut loop_iter = 0;
1471 loop {
1472 loop_iter += 1;
1473 let value = self.get_account_amount_at_version(account_id, pre_root_version);
1476 let post_root_version =
1477 ObjectCacheRead::get_object(self, &SUI_ACCUMULATOR_ROOT_OBJECT_ID)
1478 .unwrap()
1479 .version();
1480 if pre_root_version == post_root_version {
1481 if loop_iter > 3 {
1482 debug_fatal_no_invariant!(
1483 "Root version stabilized after {} iterations during MVCC read",
1484 loop_iter
1485 );
1486 }
1487 return (value, pre_root_version);
1488 }
1489 debug!(
1490 "Root version changed from {} to {} during MVCC read, retrying",
1491 pre_root_version, post_root_version
1492 );
1493 pre_root_version = post_root_version;
1494 }
1495 }
1496
1497 fn get_account_amount_at_version(
1498 &self,
1499 account_id: &AccumulatorObjId,
1500 version: SequenceNumber,
1501 ) -> u128 {
1502 let account_obj = self.find_object_lt_or_eq_version(*account_id.inner(), version);
1503 account_obj
1504 .map(|account_obj| account_amount_from_object(&account_obj))
1505 .unwrap_or(0)
1506 }
1507}
1508
1509impl ExecutionCacheAPI for WritebackCache {}
1510
1511impl ExecutionCacheCommit for WritebackCache {
1512 fn build_db_batch(&self, epoch: EpochId, digests: &[TransactionDigest]) -> Batch {
1513 self.build_db_batch(epoch, digests)
1514 }
1515
1516 fn set_highest_committed_checkpoint_in_batch(
1517 &self,
1518 batch: &mut Batch,
1519 checkpoint: CheckpointSequenceNumber,
1520 ) {
1521 self.store
1522 .perpetual_tables
1523 .set_highest_committed_checkpoint(&mut batch.1, checkpoint)
1524 .expect("db error");
1525 }
1526
1527 fn commit_transaction_outputs(
1528 &self,
1529 epoch: EpochId,
1530 batch: Batch,
1531 digests: &[TransactionDigest],
1532 ) {
1533 WritebackCache::commit_transaction_outputs(self, epoch, batch, digests)
1534 }
1535
1536 fn persist_transaction(&self, tx: &VerifiedExecutableTransaction) {
1537 self.store.persist_transaction(tx).expect("db error");
1538 }
1539
1540 fn approximate_pending_transaction_count(&self) -> u64 {
1541 WritebackCache::approximate_pending_transaction_count(self)
1542 }
1543}
1544
1545impl ObjectCacheRead for WritebackCache {
1546 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
1547 self.metrics
1548 .record_cache_request("package", "package_cache");
1549 if let Some(p) = self.packages.get(package_id) {
1550 if cfg!(debug_assertions) {
1551 let canonical_package = self
1552 .dirty
1553 .objects
1554 .get(package_id)
1555 .and_then(|v| match v.get_highest().map(|v| v.1.clone()) {
1556 Some(ObjectEntry::Object(object)) => Some(object),
1557 _ => None,
1558 })
1559 .or_else(|| self.store.get_object(package_id));
1560
1561 if let Some(canonical_package) = canonical_package {
1562 assert_eq!(
1563 canonical_package.digest(),
1564 p.object().digest(),
1565 "Package object cache is inconsistent for package {:?}",
1566 package_id
1567 );
1568 }
1569 }
1570 self.metrics.record_cache_hit("package", "package_cache");
1571 return Ok(Some(p));
1572 } else {
1573 self.metrics.record_cache_miss("package", "package_cache");
1574 }
1575
1576 if let Some(p) = self.get_object_impl("package", package_id) {
1580 if p.is_package() {
1581 let p = PackageObject::new(p);
1582 tracing::trace!(
1583 "caching package: {:?}",
1584 p.object().compute_object_reference()
1585 );
1586 self.metrics.record_cache_write("package");
1587 self.packages.insert(*package_id, p.clone());
1588 Ok(Some(p))
1589 } else {
1590 Err(SuiErrorKind::UserInputError {
1591 error: UserInputError::MoveObjectAsPackage {
1592 object_id: *package_id,
1593 },
1594 }
1595 .into())
1596 }
1597 } else {
1598 Ok(None)
1599 }
1600 }
1601
1602 fn force_reload_system_packages(&self, _system_package_ids: &[ObjectID]) {
1603 }
1606
1607 fn get_object(&self, id: &ObjectID) -> Option<Object> {
1610 self.get_object_impl("object_latest", id)
1611 }
1612
1613 fn get_object_by_key(&self, object_id: &ObjectID, version: SequenceNumber) -> Option<Object> {
1614 match self.get_object_by_key_cache_only(object_id, version) {
1615 CacheResult::Hit(object) => Some(object),
1616 CacheResult::NegativeHit => None,
1617 CacheResult::Miss => self
1618 .record_db_get("object_by_version")
1619 .get_object_by_key(object_id, version),
1620 }
1621 }
1622
1623 fn multi_get_objects_by_key(&self, object_keys: &[ObjectKey]) -> Vec<Option<Object>> {
1624 do_fallback_lookup(
1625 object_keys,
1626 |key| match self.get_object_by_key_cache_only(&key.0, key.1) {
1627 CacheResult::Hit(maybe_object) => CacheResult::Hit(Some(maybe_object)),
1628 CacheResult::NegativeHit => CacheResult::NegativeHit,
1629 CacheResult::Miss => CacheResult::Miss,
1630 },
1631 |remaining| {
1632 self.record_db_multi_get("object_by_version", remaining.len())
1633 .multi_get_objects_by_key(remaining)
1634 .expect("db error")
1635 },
1636 )
1637 }
1638
1639 fn object_exists_by_key(&self, object_id: &ObjectID, version: SequenceNumber) -> bool {
1640 match self.get_object_by_key_cache_only(object_id, version) {
1641 CacheResult::Hit(_) => true,
1642 CacheResult::NegativeHit => false,
1643 CacheResult::Miss => self
1644 .record_db_get("object_by_version")
1645 .object_exists_by_key(object_id, version)
1646 .expect("db error"),
1647 }
1648 }
1649
1650 fn multi_object_exists_by_key(&self, object_keys: &[ObjectKey]) -> Vec<bool> {
1651 do_fallback_lookup(
1652 object_keys,
1653 |key| match self.get_object_by_key_cache_only(&key.0, key.1) {
1654 CacheResult::Hit(_) => CacheResult::Hit(true),
1655 CacheResult::NegativeHit => CacheResult::Hit(false),
1656 CacheResult::Miss => CacheResult::Miss,
1657 },
1658 |remaining| {
1659 self.record_db_multi_get("object_by_version", remaining.len())
1660 .multi_object_exists_by_key(remaining)
1661 .expect("db error")
1662 },
1663 )
1664 }
1665
1666 fn get_latest_object_ref_or_tombstone(&self, object_id: ObjectID) -> Option<ObjectRef> {
1667 match self.get_object_entry_by_id_cache_only("latest_objref_or_tombstone", &object_id) {
1668 CacheResult::Hit((version, entry)) => Some(match entry {
1669 ObjectEntry::Object(object) => object.compute_object_reference(),
1670 ObjectEntry::Deleted => (object_id, version, ObjectDigest::OBJECT_DIGEST_DELETED),
1671 ObjectEntry::Wrapped => (object_id, version, ObjectDigest::OBJECT_DIGEST_WRAPPED),
1672 }),
1673 CacheResult::NegativeHit => None,
1674 CacheResult::Miss => self
1675 .record_db_get("latest_objref_or_tombstone")
1676 .get_latest_object_ref_or_tombstone(object_id)
1677 .expect("db error"),
1678 }
1679 }
1680
1681 fn get_latest_object_or_tombstone(
1682 &self,
1683 object_id: ObjectID,
1684 ) -> Option<(ObjectKey, ObjectOrTombstone)> {
1685 match self.get_object_entry_by_id_cache_only("latest_object_or_tombstone", &object_id) {
1686 CacheResult::Hit((version, entry)) => {
1687 let key = ObjectKey(object_id, version);
1688 Some(match entry {
1689 ObjectEntry::Object(object) => (key, object.into()),
1690 ObjectEntry::Deleted => (
1691 key,
1692 ObjectOrTombstone::Tombstone((
1693 object_id,
1694 version,
1695 ObjectDigest::OBJECT_DIGEST_DELETED,
1696 )),
1697 ),
1698 ObjectEntry::Wrapped => (
1699 key,
1700 ObjectOrTombstone::Tombstone((
1701 object_id,
1702 version,
1703 ObjectDigest::OBJECT_DIGEST_WRAPPED,
1704 )),
1705 ),
1706 })
1707 }
1708 CacheResult::NegativeHit => None,
1709 CacheResult::Miss => self
1710 .record_db_get("latest_object_or_tombstone")
1711 .get_latest_object_or_tombstone(object_id)
1712 .expect("db error"),
1713 }
1714 }
1715
1716 fn multi_input_objects_available_cache_only(&self, keys: &[InputKey]) -> Vec<bool> {
1717 keys.iter()
1718 .map(|key| {
1719 if key.is_cancelled() {
1720 true
1721 } else {
1722 match key {
1723 InputKey::VersionedObject { id, version } => {
1724 matches!(
1725 self.get_object_by_key_cache_only(&id.id(), *version),
1726 CacheResult::Hit(_)
1727 )
1728 }
1729 InputKey::Package { id } => self.packages.contains_key(id),
1730 }
1731 }
1732 })
1733 .collect()
1734 }
1735
1736 #[instrument(level = "trace", skip_all, fields(object_id, version_bound))]
1737 fn find_object_lt_or_eq_version(
1738 &self,
1739 object_id: ObjectID,
1740 version_bound: SequenceNumber,
1741 ) -> Option<Object> {
1742 macro_rules! check_cache_entry {
1743 ($level: expr, $objects: expr) => {
1744 self.metrics
1745 .record_cache_request("object_lt_or_eq_version", $level);
1746 if let Some(objects) = $objects {
1747 if let Some((_, object)) = objects
1748 .all_versions_lt_or_eq_descending(&version_bound)
1749 .next()
1750 {
1751 if let ObjectEntry::Object(object) = object {
1752 self.metrics
1753 .record_cache_hit("object_lt_or_eq_version", $level);
1754 return Some(object.clone());
1755 } else {
1756 self.metrics
1758 .record_cache_negative_hit("object_lt_or_eq_version", $level);
1759 return None;
1760 }
1761 } else {
1762 self.metrics
1763 .record_cache_miss("object_lt_or_eq_version", $level);
1764 }
1765 }
1766 };
1767 }
1768
1769 self.metrics
1771 .record_cache_request("object_lt_or_eq_version", "object_by_id");
1772 let latest_cache_entry = self.object_by_id_cache.get(&object_id);
1773 if let Some(latest) = &latest_cache_entry {
1774 let latest = latest.lock();
1775 match &*latest {
1776 LatestObjectCacheEntry::Object(latest_version, object) => {
1777 if *latest_version <= version_bound {
1778 if let ObjectEntry::Object(object) = object {
1779 self.metrics
1780 .record_cache_hit("object_lt_or_eq_version", "object_by_id");
1781 return Some(object.clone());
1782 } else {
1783 self.metrics.record_cache_negative_hit(
1785 "object_lt_or_eq_version",
1786 "object_by_id",
1787 );
1788 return None;
1789 }
1790 }
1791 }
1793 LatestObjectCacheEntry::NonExistent => {
1795 self.metrics
1796 .record_cache_negative_hit("object_lt_or_eq_version", "object_by_id");
1797 return None;
1798 }
1799 }
1800 }
1801 self.metrics
1802 .record_cache_miss("object_lt_or_eq_version", "object_by_id");
1803
1804 Self::with_locked_cache_entries(
1805 &self.dirty.objects,
1806 &self.cached.object_cache,
1807 &object_id,
1808 |dirty_entry, cached_entry| {
1809 check_cache_entry!("committed", dirty_entry);
1810 check_cache_entry!("uncommitted", cached_entry);
1811
1812 let latest: Option<(SequenceNumber, ObjectEntry)> =
1833 if let Some(dirty_set) = dirty_entry {
1834 dirty_set
1835 .get_highest()
1836 .cloned()
1837 .tap_none(|| panic!("dirty set cannot be empty"))
1838 } else {
1839 self.record_db_get("object_lt_or_eq_version_latest")
1841 .get_latest_object_or_tombstone(object_id)
1842 .expect("db error")
1843 .map(|(ObjectKey(_, version), obj_or_tombstone)| {
1844 (version, ObjectEntry::from(obj_or_tombstone))
1845 })
1846 };
1847
1848 if let Some((obj_version, obj_entry)) = latest {
1849 self.cache_latest_object_by_id(
1857 &object_id,
1858 LatestObjectCacheEntry::Object(obj_version, obj_entry.clone()),
1859 self.object_by_id_cache.get_ticket_for_read(&object_id),
1862 );
1863
1864 if obj_version <= version_bound {
1865 match obj_entry {
1866 ObjectEntry::Object(object) => Some(object),
1867 ObjectEntry::Deleted | ObjectEntry::Wrapped => None,
1868 }
1869 } else {
1870 self.record_db_get("object_lt_or_eq_version_scan")
1874 .find_object_lt_or_eq_version(object_id, version_bound)
1875 .expect("db error")
1876 }
1877
1878 } else if let Some(latest_cache_entry) = latest_cache_entry {
1884 assert!(!latest_cache_entry.lock().is_alive());
1886 None
1887 } else {
1888 let highest = cached_entry.and_then(|c| c.get_highest());
1890 assert!(highest.is_none() || highest.unwrap().1.is_tombstone());
1891 self.cache_object_not_found(
1892 &object_id,
1893 self.object_by_id_cache.get_ticket_for_read(&object_id),
1895 );
1896 None
1897 }
1898 },
1899 )
1900 }
1901
1902 fn get_sui_system_state_object_unsafe(&self) -> SuiResult<SuiSystemState> {
1903 get_sui_system_state(self)
1904 }
1905
1906 fn get_bridge_object_unsafe(&self) -> SuiResult<Bridge> {
1907 get_bridge(self)
1908 }
1909
1910 fn get_marker_value(
1911 &self,
1912 object_key: FullObjectKey,
1913 epoch_id: EpochId,
1914 ) -> Option<MarkerValue> {
1915 match self.get_marker_value_cache_only(object_key, epoch_id) {
1916 CacheResult::Hit(marker) => Some(marker),
1917 CacheResult::NegativeHit => None,
1918 CacheResult::Miss => self
1919 .record_db_get("marker_by_version")
1920 .get_marker_value(object_key, epoch_id)
1921 .expect("db error"),
1922 }
1923 }
1924
1925 fn get_latest_marker(
1926 &self,
1927 object_id: FullObjectID,
1928 epoch_id: EpochId,
1929 ) -> Option<(SequenceNumber, MarkerValue)> {
1930 match self.get_latest_marker_value_cache_only(object_id, epoch_id) {
1931 CacheResult::Hit((v, marker)) => Some((v, marker)),
1932 CacheResult::NegativeHit => {
1933 panic!("cannot have negative hit when getting latest marker")
1934 }
1935 CacheResult::Miss => self
1936 .record_db_get("marker_latest")
1937 .get_latest_marker(object_id, epoch_id)
1938 .expect("db error"),
1939 }
1940 }
1941
1942 #[cfg(test)]
1943 fn get_lock(&self, obj_ref: ObjectRef, epoch_store: &AuthorityPerEpochStore) -> SuiLockResult {
1944 let cur_epoch = epoch_store.epoch();
1945 let Some(obj) = self.get_object_impl("lock", &obj_ref.0) else {
1946 return Err(SuiError::from(UserInputError::ObjectNotFound {
1947 object_id: obj_ref.0,
1948 version: None,
1951 }));
1952 };
1953 let actual_objref = obj.compute_object_reference();
1954 if obj_ref != actual_objref {
1955 Ok(ObjectLockStatus::LockedAtDifferentVersion {
1956 locked_ref: actual_objref,
1957 })
1958 } else {
1959 Ok(
1961 match self
1962 .object_locks
1963 .get_transaction_lock(&obj_ref, epoch_store)?
1964 {
1965 Some(tx_digest) => ObjectLockStatus::LockedToTx {
1966 locked_by_tx: LockDetailsDeprecated {
1967 epoch: cur_epoch,
1968 tx_digest,
1969 },
1970 },
1971 None => ObjectLockStatus::Initialized,
1972 },
1973 )
1974 }
1975 }
1976
1977 fn _get_live_objref(&self, object_id: ObjectID) -> SuiResult<ObjectRef> {
1978 let obj = self.get_object_impl("live_objref", &object_id).ok_or(
1979 UserInputError::ObjectNotFound {
1980 object_id,
1981 version: None,
1982 },
1983 )?;
1984 Ok(obj.compute_object_reference())
1985 }
1986
1987 fn get_highest_pruned_checkpoint(&self) -> Option<CheckpointSequenceNumber> {
1988 self.store
1989 .perpetual_tables
1990 .get_highest_pruned_checkpoint()
1991 .expect("db error")
1992 }
1993
1994 fn notify_read_input_objects<'a>(
1995 &'a self,
1996 input_and_receiving_keys: &'a [InputKey],
1997 receiving_keys: &'a HashSet<InputKey>,
1998 epoch: EpochId,
1999 ) -> BoxFuture<'a, ()> {
2000 self.object_notify_read
2001 .read(
2002 "notify_read_input_objects",
2003 input_and_receiving_keys,
2004 move |keys| {
2005 self.multi_input_objects_available(keys, receiving_keys, epoch)
2006 .into_iter()
2007 .map(|available| if available { Some(()) } else { None })
2008 .collect::<Vec<_>>()
2009 },
2010 )
2011 .map(|_| ())
2012 .boxed()
2013 }
2014}
2015
2016impl TransactionCacheRead for WritebackCache {
2017 fn multi_get_transaction_blocks(
2018 &self,
2019 digests: &[TransactionDigest],
2020 ) -> Vec<Option<Arc<VerifiedTransaction>>> {
2021 let digests_and_tickets: Vec<_> = digests
2022 .iter()
2023 .map(|d| (*d, self.cached.transactions.get_ticket_for_read(d)))
2024 .collect();
2025 do_fallback_lookup(
2026 &digests_and_tickets,
2027 |(digest, _)| {
2028 self.metrics
2029 .record_cache_request("transaction_block", "uncommitted");
2030 if let Some(tx) = self.dirty.pending_transaction_writes.get(digest) {
2031 self.metrics
2032 .record_cache_hit("transaction_block", "uncommitted");
2033 return CacheResult::Hit(Some(tx.transaction.clone()));
2034 }
2035 self.metrics
2036 .record_cache_miss("transaction_block", "uncommitted");
2037
2038 self.metrics
2039 .record_cache_request("transaction_block", "committed");
2040
2041 match self
2042 .cached
2043 .transactions
2044 .get(digest)
2045 .map(|l| l.lock().clone())
2046 {
2047 Some(PointCacheItem::Some(tx)) => {
2048 self.metrics
2049 .record_cache_hit("transaction_block", "committed");
2050 CacheResult::Hit(Some(tx))
2051 }
2052 Some(PointCacheItem::None) => CacheResult::NegativeHit,
2053 None => {
2054 self.metrics
2055 .record_cache_miss("transaction_block", "committed");
2056
2057 CacheResult::Miss
2058 }
2059 }
2060 },
2061 |remaining| {
2062 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
2063 let results: Vec<_> = self
2064 .record_db_multi_get("transaction_block", remaining.len())
2065 .multi_get_transaction_blocks(&remaining_digests)
2066 .expect("db error")
2067 .into_iter()
2068 .map(|o| o.map(Arc::new))
2069 .collect();
2070 for ((digest, ticket), result) in remaining.iter().zip_debug_eq(results.iter()) {
2071 if result.is_none() {
2072 self.cached.transactions.insert(digest, None, *ticket).ok();
2073 }
2074 }
2075 results
2076 },
2077 )
2078 }
2079
2080 fn multi_get_executed_effects_digests(
2081 &self,
2082 digests: &[TransactionDigest],
2083 ) -> Vec<Option<TransactionEffectsDigest>> {
2084 let digests_and_tickets: Vec<_> = digests
2085 .iter()
2086 .map(|d| {
2087 (
2088 *d,
2089 self.cached.executed_effects_digests.get_ticket_for_read(d),
2090 )
2091 })
2092 .collect();
2093 do_fallback_lookup(
2094 &digests_and_tickets,
2095 |(digest, _)| {
2096 self.metrics
2097 .record_cache_request("executed_effects_digests", "uncommitted");
2098 if let Some(digest) = self.dirty.executed_effects_digests.get(digest) {
2099 self.metrics
2100 .record_cache_hit("executed_effects_digests", "uncommitted");
2101 return CacheResult::Hit(Some(*digest));
2102 }
2103 self.metrics
2104 .record_cache_miss("executed_effects_digests", "uncommitted");
2105
2106 self.metrics
2107 .record_cache_request("executed_effects_digests", "committed");
2108 match self
2109 .cached
2110 .executed_effects_digests
2111 .get(digest)
2112 .map(|l| *l.lock())
2113 {
2114 Some(PointCacheItem::Some(digest)) => {
2115 self.metrics
2116 .record_cache_hit("executed_effects_digests", "committed");
2117 CacheResult::Hit(Some(digest))
2118 }
2119 Some(PointCacheItem::None) => CacheResult::NegativeHit,
2120 None => {
2121 self.metrics
2122 .record_cache_miss("executed_effects_digests", "committed");
2123 CacheResult::Miss
2124 }
2125 }
2126 },
2127 |remaining| {
2128 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
2129 let results = self
2130 .record_db_multi_get("executed_effects_digests", remaining.len())
2131 .multi_get_executed_effects_digests(&remaining_digests)
2132 .expect("db error");
2133 for ((digest, ticket), result) in remaining.iter().zip_debug_eq(results.iter()) {
2134 if result.is_none() {
2135 self.cached
2136 .executed_effects_digests
2137 .insert(digest, None, *ticket)
2138 .ok();
2139 }
2140 }
2141 results
2142 },
2143 )
2144 }
2145
2146 fn multi_get_effects(
2147 &self,
2148 digests: &[TransactionEffectsDigest],
2149 ) -> Vec<Option<TransactionEffects>> {
2150 let digests_and_tickets: Vec<_> = digests
2151 .iter()
2152 .map(|d| (*d, self.cached.transaction_effects.get_ticket_for_read(d)))
2153 .collect();
2154 do_fallback_lookup(
2155 &digests_and_tickets,
2156 |(digest, _)| {
2157 self.metrics
2158 .record_cache_request("transaction_effects", "uncommitted");
2159 if let Some(effects) = self.dirty.transaction_effects.get(digest) {
2160 self.metrics
2161 .record_cache_hit("transaction_effects", "uncommitted");
2162 return CacheResult::Hit(Some(effects.clone()));
2163 }
2164 self.metrics
2165 .record_cache_miss("transaction_effects", "uncommitted");
2166
2167 self.metrics
2168 .record_cache_request("transaction_effects", "committed");
2169 match self
2170 .cached
2171 .transaction_effects
2172 .get(digest)
2173 .map(|l| l.lock().clone())
2174 {
2175 Some(PointCacheItem::Some(effects)) => {
2176 self.metrics
2177 .record_cache_hit("transaction_effects", "committed");
2178 CacheResult::Hit(Some((*effects).clone()))
2179 }
2180 Some(PointCacheItem::None) => CacheResult::NegativeHit,
2181 None => {
2182 self.metrics
2183 .record_cache_miss("transaction_effects", "committed");
2184 CacheResult::Miss
2185 }
2186 }
2187 },
2188 |remaining| {
2189 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
2190 let results = self
2191 .record_db_multi_get("transaction_effects", remaining.len())
2192 .multi_get_effects(remaining_digests.iter())
2193 .expect("db error");
2194 for ((digest, ticket), result) in remaining.iter().zip_debug_eq(results.iter()) {
2195 if result.is_none() {
2196 self.cached
2197 .transaction_effects
2198 .insert(digest, None, *ticket)
2199 .ok();
2200 }
2201 }
2202 results
2203 },
2204 )
2205 }
2206
2207 fn transaction_executed_in_last_epoch(
2208 &self,
2209 digest: &TransactionDigest,
2210 current_epoch: EpochId,
2211 ) -> bool {
2212 if current_epoch == 0 {
2213 return false;
2214 }
2215 let last_epoch = current_epoch - 1;
2216 let cache_key = (last_epoch, *digest);
2217
2218 let ticket = self
2219 .cached
2220 .transaction_executed_in_last_epoch
2221 .get_ticket_for_read(&cache_key);
2222
2223 if let Some(cached) = self
2224 .cached
2225 .transaction_executed_in_last_epoch
2226 .get(&cache_key)
2227 {
2228 return cached.lock().is_some();
2229 }
2230
2231 let was_executed = self
2232 .store
2233 .perpetual_tables
2234 .was_transaction_executed_in_last_epoch(digest, current_epoch);
2235
2236 let value = if was_executed { Some(()) } else { None };
2237 self.cached
2238 .transaction_executed_in_last_epoch
2239 .insert(&cache_key, value, ticket)
2240 .ok();
2241
2242 was_executed
2243 }
2244
2245 fn notify_read_executed_effects_digests<'a>(
2246 &'a self,
2247 task_name: &'static str,
2248 digests: &'a [TransactionDigest],
2249 ) -> BoxFuture<'a, Vec<TransactionEffectsDigest>> {
2250 self.executed_effects_digests_notify_read
2251 .read(task_name, digests, |digests| {
2252 self.multi_get_executed_effects_digests(digests)
2253 })
2254 .boxed()
2255 }
2256
2257 fn multi_get_events(
2258 &self,
2259 event_digests: &[TransactionDigest],
2260 ) -> Vec<Option<TransactionEvents>> {
2261 fn map_events(events: TransactionEvents) -> Option<TransactionEvents> {
2262 if events.data.is_empty() {
2263 None
2264 } else {
2265 Some(events)
2266 }
2267 }
2268
2269 let digests_and_tickets: Vec<_> = event_digests
2270 .iter()
2271 .map(|d| (*d, self.cached.transaction_events.get_ticket_for_read(d)))
2272 .collect();
2273 do_fallback_lookup(
2274 &digests_and_tickets,
2275 |(digest, _)| {
2276 self.metrics
2277 .record_cache_request("transaction_events", "uncommitted");
2278 if let Some(events) = self.dirty.transaction_events.get(digest).map(|e| e.clone()) {
2279 self.metrics
2280 .record_cache_hit("transaction_events", "uncommitted");
2281
2282 return CacheResult::Hit(map_events(events));
2283 }
2284 self.metrics
2285 .record_cache_miss("transaction_events", "uncommitted");
2286
2287 self.metrics
2288 .record_cache_request("transaction_events", "committed");
2289 match self
2290 .cached
2291 .transaction_events
2292 .get(digest)
2293 .map(|l| l.lock().clone())
2294 {
2295 Some(PointCacheItem::Some(events)) => {
2296 self.metrics
2297 .record_cache_hit("transaction_events", "committed");
2298 CacheResult::Hit(map_events((*events).clone()))
2299 }
2300 Some(PointCacheItem::None) => CacheResult::NegativeHit,
2301 None => {
2302 self.metrics
2303 .record_cache_miss("transaction_events", "committed");
2304
2305 CacheResult::Miss
2306 }
2307 }
2308 },
2309 |remaining| {
2310 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
2311 let results = self
2312 .store
2313 .multi_get_events(&remaining_digests)
2314 .expect("db error");
2315 for ((digest, ticket), result) in remaining.iter().zip_debug_eq(results.iter()) {
2316 if result.is_none() {
2317 self.cached
2318 .transaction_events
2319 .insert(digest, None, *ticket)
2320 .ok();
2321 }
2322 }
2323 results
2324 },
2325 )
2326 }
2327
2328 fn get_unchanged_loaded_runtime_objects(
2329 &self,
2330 digest: &TransactionDigest,
2331 ) -> Option<Vec<ObjectKey>> {
2332 self.dirty
2333 .unchanged_loaded_runtime_objects
2334 .get(digest)
2335 .map(|b| b.clone())
2336 .or_else(|| {
2337 self.store
2338 .get_unchanged_loaded_runtime_objects(digest)
2339 .expect("db error")
2340 })
2341 }
2342
2343 fn multi_get_unchanged_loaded_runtime_objects(
2344 &self,
2345 digests: &[TransactionDigest],
2346 ) -> Vec<Option<Vec<ObjectKey>>> {
2347 do_fallback_lookup(
2348 digests,
2349 |digest| match self.dirty.unchanged_loaded_runtime_objects.get(digest) {
2350 Some(objects) => CacheResult::Hit(Some(objects.clone())),
2351 None => CacheResult::Miss,
2352 },
2353 |digests| {
2354 self.store
2355 .multi_get_unchanged_loaded_runtime_objects(digests)
2356 .expect("db error")
2357 },
2358 )
2359 }
2360
2361 fn take_accumulator_events(&self, digest: &TransactionDigest) -> Option<Vec<AccumulatorEvent>> {
2362 self.dirty
2363 .pending_transaction_writes
2364 .get(digest)
2365 .map(|transaction_output| transaction_output.take_accumulator_events())
2366 }
2367}
2368
2369impl ExecutionCacheWrite for WritebackCache {
2370 fn validate_owned_object_versions(&self, owned_input_objects: &[ObjectRef]) -> SuiResult {
2371 ObjectLocks::validate_owned_object_versions(self, owned_input_objects)
2372 }
2373
2374 fn write_transaction_outputs(&self, epoch_id: EpochId, tx_outputs: Arc<TransactionOutputs>) {
2375 WritebackCache::write_transaction_outputs(self, epoch_id, tx_outputs);
2376 }
2377
2378 #[cfg(test)]
2379 fn write_object_entry_for_test(&self, object: Object) {
2380 self.write_object_entry(&object.id(), object.version(), object.into());
2381 }
2382}
2383
2384implement_passthrough_traits!(WritebackCache);
2385
2386impl GlobalStateHashStore for WritebackCache {
2387 fn get_object_ref_prior_to_key_deprecated(
2388 &self,
2389 object_id: &ObjectID,
2390 version: SequenceNumber,
2391 ) -> SuiResult<Option<ObjectRef>> {
2392 let mut candidates = Vec::new();
2396
2397 let check_versions =
2398 |versions: &CachedVersionMap<ObjectEntry>| match versions.get_prior_to(&version) {
2399 Some((version, object_entry)) => match object_entry {
2400 ObjectEntry::Object(object) => {
2401 assert_eq!(object.version(), version);
2402 Some(object.compute_object_reference())
2403 }
2404 ObjectEntry::Deleted => {
2405 Some((*object_id, version, ObjectDigest::OBJECT_DIGEST_DELETED))
2406 }
2407 ObjectEntry::Wrapped => {
2408 Some((*object_id, version, ObjectDigest::OBJECT_DIGEST_WRAPPED))
2409 }
2410 },
2411 None => None,
2412 };
2413
2414 if let Some(objects) = self.dirty.objects.get(object_id)
2416 && let Some(prior) = check_versions(&objects)
2417 {
2418 candidates.push(prior);
2419 }
2420
2421 if let Some(objects) = self.cached.object_cache.get(object_id)
2422 && let Some(prior) = check_versions(&objects.lock())
2423 {
2424 candidates.push(prior);
2425 }
2426
2427 if let Some(prior) = self
2428 .store
2429 .get_object_ref_prior_to_key_deprecated(object_id, version)?
2430 {
2431 candidates.push(prior);
2432 }
2433
2434 candidates.sort_by_key(|(_, version, _)| *version);
2436 Ok(candidates.pop())
2437 }
2438
2439 fn get_root_state_hash_for_epoch(
2440 &self,
2441 epoch: EpochId,
2442 ) -> SuiResult<Option<(CheckpointSequenceNumber, GlobalStateHash)>> {
2443 self.store.get_root_state_hash_for_epoch(epoch)
2444 }
2445
2446 fn get_root_state_hash_for_highest_epoch(
2447 &self,
2448 ) -> SuiResult<Option<(EpochId, (CheckpointSequenceNumber, GlobalStateHash))>> {
2449 self.store.get_root_state_hash_for_highest_epoch()
2450 }
2451
2452 fn insert_state_hash_for_epoch(
2453 &self,
2454 epoch: EpochId,
2455 checkpoint_seq_num: &CheckpointSequenceNumber,
2456 acc: &GlobalStateHash,
2457 ) -> SuiResult {
2458 self.store
2459 .insert_state_hash_for_epoch(epoch, checkpoint_seq_num, acc)
2460 }
2461
2462 fn iter_live_object_set(
2463 &self,
2464 include_wrapped_tombstone: bool,
2465 ) -> Box<dyn Iterator<Item = LiveObject> + '_> {
2466 assert!(
2470 self.dirty.is_empty(),
2471 "cannot iterate live object set with dirty data"
2472 );
2473 self.store.iter_live_object_set(include_wrapped_tombstone)
2474 }
2475
2476 fn iter_cached_live_object_set_for_testing(
2480 &self,
2481 include_wrapped_tombstone: bool,
2482 ) -> Box<dyn Iterator<Item = LiveObject> + '_> {
2483 let iter = self.dirty.objects.iter();
2485 let mut dirty_objects = BTreeMap::new();
2486
2487 for obj in self.store.iter_live_object_set(include_wrapped_tombstone) {
2489 dirty_objects.insert(obj.object_id(), obj);
2490 }
2491
2492 for entry in iter {
2494 let id = *entry.key();
2495 let value = entry.value();
2496 match value.get_highest().unwrap() {
2497 (_, ObjectEntry::Object(object)) => {
2498 dirty_objects.insert(id, LiveObject::Normal(object.clone()));
2499 }
2500 (version, ObjectEntry::Wrapped) => {
2501 if include_wrapped_tombstone {
2502 dirty_objects.insert(id, LiveObject::Wrapped(ObjectKey(id, *version)));
2503 } else {
2504 dirty_objects.remove(&id);
2505 }
2506 }
2507 (_, ObjectEntry::Deleted) => {
2508 dirty_objects.remove(&id);
2509 }
2510 }
2511 }
2512
2513 Box::new(dirty_objects.into_values())
2514 }
2515}
2516
2517impl StateSyncAPI for WritebackCache {
2523 fn insert_transaction_and_effects(
2524 &self,
2525 transaction: &VerifiedTransaction,
2526 transaction_effects: &TransactionEffects,
2527 ) {
2528 self.store
2529 .insert_transaction_and_effects(transaction, transaction_effects)
2530 .expect("db error");
2531 self.cached
2532 .transactions
2533 .insert(
2534 transaction.digest(),
2535 PointCacheItem::Some(Arc::new(transaction.clone())),
2536 Ticket::Write,
2537 )
2538 .ok();
2539 self.cached
2540 .transaction_effects
2541 .insert(
2542 &transaction_effects.digest(),
2543 PointCacheItem::Some(Arc::new(transaction_effects.clone())),
2544 Ticket::Write,
2545 )
2546 .ok();
2547 }
2548
2549 fn multi_insert_transaction_and_effects(
2550 &self,
2551 transactions_and_effects: &[VerifiedExecutionData],
2552 ) {
2553 self.store
2554 .multi_insert_transaction_and_effects(transactions_and_effects.iter())
2555 .expect("db error");
2556 for VerifiedExecutionData {
2557 transaction,
2558 effects,
2559 } in transactions_and_effects
2560 {
2561 self.cached
2562 .transactions
2563 .insert(
2564 transaction.digest(),
2565 PointCacheItem::Some(Arc::new(transaction.clone())),
2566 Ticket::Write,
2567 )
2568 .ok();
2569 self.cached
2570 .transaction_effects
2571 .insert(
2572 &effects.digest(),
2573 PointCacheItem::Some(Arc::new(effects.clone())),
2574 Ticket::Write,
2575 )
2576 .ok();
2577 }
2578 }
2579}