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