1use std::{
49 collections::{BTreeMap, HashSet},
50 hash::Hash,
51 sync::{Arc, atomic::AtomicU64},
52};
53
54use dashmap::{DashMap, mapref::entry::Entry as DashMapEntry};
55use futures::{FutureExt, future::BoxFuture};
56use iota_common::{random_util::randomize_cache_capacity_in_tests, sync::notify_read::NotifyRead};
57use iota_config::WritebackCacheConfig;
58use iota_macros::fail_point;
59use iota_sdk_types::{
60 ObjectDigest, ObjectId, ObjectReference, Owner, TransactionDigest, TransactionEffects,
61 TransactionEffectsDigest, TransactionEvents, Version,
62};
63use iota_types::{
64 base_types::{EpochId, VerifiedExecutionData},
65 error::{IotaError, IotaResult, UserInputError},
66 executable_transaction::VerifiedExecutableTransaction,
67 global_state_hash::GlobalStateHash,
68 iota_system_state::{IotaSystemState, get_iota_system_state},
69 messages_checkpoint::CheckpointSequenceNumber,
70 object::Object,
71 storage::{
72 BackingPackageStore, ChildObjectResolver, InputKey, MarkerValue, ObjectKey,
73 ObjectOrTombstone, ObjectStore, PackageObject,
74 error::{Error as StorageError, Result as StorageResult},
75 },
76 transaction::{VerifiedSignedTransaction, VerifiedTransaction},
77};
78use moka::sync::SegmentedCache as MokaCache;
79use parking_lot::Mutex;
80use tap::TapOptional;
81use tracing::{debug, info, instrument, trace, warn};
82
83use super::{
84 Batch, CheckpointCache, ExecutionCacheAPI, ExecutionCacheCommit, ExecutionCacheMetrics,
85 ExecutionCacheReconfigAPI, ExecutionCacheWrite, ObjectCacheRead, StateSyncAPI, TestingAPI,
86 TransactionCacheRead,
87 cache_types::{CacheResult, CachedVersionMap, IsNewer, MonotonicCache, Ticket},
88 object_locks::ObjectLocks,
89};
90use crate::{
91 authority::{
92 AuthorityStore,
93 authority_per_epoch_store::AuthorityPerEpochStore,
94 authority_store::{ExecutionLockWriteGuard, IotaLockResult, ObjectLockStatus},
95 authority_store_tables::LiveObject,
96 backpressure::BackpressureManager,
97 epoch_start_configuration::{EpochFlag, EpochStartConfiguration},
98 },
99 fallback_fetch::try_do_fallback_lookup,
100 global_state_hasher::GlobalStateHashStore,
101 transaction_outputs::TransactionOutputs,
102};
103
104#[cfg(test)]
105#[path = "unit_tests/writeback_cache_tests.rs"]
106pub mod writeback_cache_tests;
107
108#[derive(Clone, PartialEq, Eq)]
109enum ObjectEntry {
110 Object(Object),
111 Deleted,
112 Wrapped,
113}
114
115impl ObjectEntry {
116 #[cfg(test)]
117 fn unwrap_object(&self) -> &Object {
118 match self {
119 ObjectEntry::Object(o) => o,
120 _ => panic!("unwrap_object called on non-Object"),
121 }
122 }
123
124 fn is_tombstone(&self) -> bool {
125 match self {
126 ObjectEntry::Deleted | ObjectEntry::Wrapped => true,
127 ObjectEntry::Object(_) => false,
128 }
129 }
130}
131
132impl std::fmt::Debug for ObjectEntry {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 match self {
135 ObjectEntry::Object(o) => {
136 write!(f, "ObjectEntry::Object({:?})", o.object_ref())
137 }
138 ObjectEntry::Deleted => write!(f, "ObjectEntry::Deleted"),
139 ObjectEntry::Wrapped => write!(f, "ObjectEntry::Wrapped"),
140 }
141 }
142}
143
144impl From<Object> for ObjectEntry {
145 fn from(object: Object) -> Self {
146 ObjectEntry::Object(object)
147 }
148}
149
150impl From<ObjectOrTombstone> for ObjectEntry {
151 fn from(object: ObjectOrTombstone) -> Self {
152 match object {
153 ObjectOrTombstone::Object(o) => o.into(),
154 ObjectOrTombstone::Tombstone(obj_ref) => {
155 if obj_ref.digest.is_deleted() {
156 ObjectEntry::Deleted
157 } else if obj_ref.digest.is_wrapped() {
158 ObjectEntry::Wrapped
159 } else {
160 panic!("tombstone digest must either be deleted or wrapped");
161 }
162 }
163 }
164 }
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
168enum LatestObjectCacheEntry {
169 Object(Version, ObjectEntry),
170 NonExistent,
171}
172
173impl LatestObjectCacheEntry {
174 #[cfg(test)]
175 fn version(&self) -> Option<Version> {
176 match self {
177 LatestObjectCacheEntry::Object(version, _) => Some(*version),
178 LatestObjectCacheEntry::NonExistent => None,
179 }
180 }
181
182 fn is_alive(&self) -> bool {
183 match self {
184 LatestObjectCacheEntry::Object(_, entry) => !entry.is_tombstone(),
185 LatestObjectCacheEntry::NonExistent => false,
186 }
187 }
188}
189
190impl IsNewer for LatestObjectCacheEntry {
191 fn is_newer_than(&self, other: &LatestObjectCacheEntry) -> bool {
192 match (self, other) {
193 (LatestObjectCacheEntry::Object(v1, _), LatestObjectCacheEntry::Object(v2, _)) => {
194 v1 > v2
195 }
196 (LatestObjectCacheEntry::Object(_, _), LatestObjectCacheEntry::NonExistent) => true,
197 _ => false,
198 }
199 }
200}
201
202type MarkerKey = (EpochId, ObjectId);
203
204struct UncommittedData {
207 objects: DashMap<ObjectId, CachedVersionMap<ObjectEntry>>,
224
225 markers: DashMap<MarkerKey, CachedVersionMap<MarkerValue>>,
230
231 transaction_effects: DashMap<TransactionEffectsDigest, TransactionEffects>,
232
233 transaction_events: DashMap<TransactionDigest, TransactionEvents>,
234
235 executed_effects_digests: DashMap<TransactionDigest, TransactionEffectsDigest>,
236
237 pending_transaction_writes: DashMap<TransactionDigest, Arc<TransactionOutputs>>,
240
241 total_transaction_inserts: AtomicU64,
242 total_transaction_commits: AtomicU64,
243}
244
245impl UncommittedData {
246 fn new() -> Self {
247 Self {
248 objects: DashMap::with_shard_amount(2048),
249 markers: DashMap::with_shard_amount(2048),
250 transaction_effects: DashMap::with_shard_amount(2048),
251 executed_effects_digests: DashMap::with_shard_amount(2048),
252 pending_transaction_writes: DashMap::with_shard_amount(2048),
253 transaction_events: DashMap::with_shard_amount(2048),
254 total_transaction_inserts: AtomicU64::new(0),
255 total_transaction_commits: AtomicU64::new(0),
256 }
257 }
258
259 fn clear(&self) {
260 self.objects.clear();
261 self.markers.clear();
262 self.transaction_effects.clear();
263 self.executed_effects_digests.clear();
264 self.pending_transaction_writes.clear();
265 self.transaction_events.clear();
266 self.total_transaction_inserts
267 .store(0, std::sync::atomic::Ordering::Relaxed);
268 self.total_transaction_commits
269 .store(0, std::sync::atomic::Ordering::Relaxed);
270 }
271
272 fn is_empty(&self) -> bool {
273 let empty = self.pending_transaction_writes.is_empty();
274 if empty && cfg!(debug_assertions) {
275 assert!(
276 self.objects.is_empty()
277 && self.markers.is_empty()
278 && self.transaction_effects.is_empty()
279 && self.executed_effects_digests.is_empty()
280 && self.transaction_events.is_empty()
281 && self
282 .total_transaction_inserts
283 .load(std::sync::atomic::Ordering::Relaxed)
284 == self
285 .total_transaction_commits
286 .load(std::sync::atomic::Ordering::Relaxed),
287 );
288 }
289 empty
290 }
291}
292
293type PointCacheItem<T> = Option<T>;
296
297impl<T: Eq + std::fmt::Debug> IsNewer for PointCacheItem<T> {
300 fn is_newer_than(&self, other: &PointCacheItem<T>) -> bool {
301 match (self, other) {
302 (Some(_), None) => true,
303
304 (Some(a), Some(b)) => {
305 debug_assert_eq!(a, b);
307 false
308 }
309
310 _ => false,
311 }
312 }
313}
314
315struct CachedCommittedData {
318 object_cache: MokaCache<ObjectId, Arc<Mutex<CachedVersionMap<ObjectEntry>>>>,
320
321 marker_cache: MokaCache<MarkerKey, Arc<Mutex<CachedVersionMap<MarkerValue>>>>,
323
324 transactions: MonotonicCache<TransactionDigest, PointCacheItem<Arc<VerifiedTransaction>>>,
325
326 transaction_effects:
327 MonotonicCache<TransactionEffectsDigest, PointCacheItem<Arc<TransactionEffects>>>,
328
329 transaction_events: MonotonicCache<TransactionDigest, PointCacheItem<Arc<TransactionEvents>>>,
330
331 executed_effects_digests:
332 MonotonicCache<TransactionDigest, PointCacheItem<TransactionEffectsDigest>>,
333
334 _transaction_objects: MokaCache<TransactionDigest, Vec<Object>>,
337}
338
339impl CachedCommittedData {
340 fn new(config: &WritebackCacheConfig) -> Self {
341 let object_cache = MokaCache::builder(8)
342 .max_capacity(randomize_cache_capacity_in_tests(
343 config.object_cache_size(),
344 ))
345 .build();
346 let marker_cache = MokaCache::builder(8)
347 .max_capacity(randomize_cache_capacity_in_tests(
348 config.marker_cache_size(),
349 ))
350 .build();
351
352 let transactions = MonotonicCache::new(randomize_cache_capacity_in_tests(
353 config.transaction_cache_size(),
354 ));
355 let transaction_effects = MonotonicCache::new(randomize_cache_capacity_in_tests(
356 config.effect_cache_size(),
357 ));
358 let transaction_events = MonotonicCache::new(randomize_cache_capacity_in_tests(
359 config.events_cache_size(),
360 ));
361 let executed_effects_digests = MonotonicCache::new(randomize_cache_capacity_in_tests(
362 config.executed_effect_cache_size(),
363 ));
364
365 let transaction_objects = MokaCache::builder(8)
366 .max_capacity(randomize_cache_capacity_in_tests(
367 config.transaction_objects_cache_size(),
368 ))
369 .build();
370
371 Self {
372 object_cache,
373 marker_cache,
374 transactions,
375 transaction_effects,
376 transaction_events,
377 executed_effects_digests,
378 _transaction_objects: transaction_objects,
379 }
380 }
381
382 fn clear_and_assert_empty(&self) {
383 self.object_cache.invalidate_all();
384 self.marker_cache.invalidate_all();
385 self.transactions.invalidate_all();
386 self.transaction_effects.invalidate_all();
387 self.transaction_events.invalidate_all();
388 self.executed_effects_digests.invalidate_all();
389 self._transaction_objects.invalidate_all();
390
391 assert_empty(&self.object_cache);
392 assert_empty(&self.marker_cache);
393 assert!(self.transactions.is_empty());
394 assert!(self.transaction_effects.is_empty());
395 assert!(self.transaction_events.is_empty());
396 assert!(self.executed_effects_digests.is_empty());
397 assert_empty(&self._transaction_objects);
398 }
399}
400
401fn assert_empty<K, V>(cache: &MokaCache<K, V>)
402where
403 K: std::hash::Hash + std::cmp::Eq + std::cmp::PartialEq + Send + Sync + 'static,
404 V: std::clone::Clone + std::marker::Send + std::marker::Sync + 'static,
405{
406 if cache.iter().next().is_some() {
407 panic!("cache should be empty");
408 }
409}
410
411pub struct WritebackCache {
412 dirty: UncommittedData,
413 cached: CachedCommittedData,
414
415 object_by_id_cache: MonotonicCache<ObjectId, LatestObjectCacheEntry>,
422
423 packages: MokaCache<ObjectId, PackageObject>,
434
435 object_locks: ObjectLocks,
436
437 executed_effects_digests_notify_read: NotifyRead<TransactionDigest, TransactionEffectsDigest>,
438 object_notify_read: NotifyRead<InputKey, ()>,
439 store: Arc<AuthorityStore>,
440 backpressure_threshold: u64,
441 backpressure_manager: Arc<BackpressureManager>,
442 metrics: Arc<ExecutionCacheMetrics>,
443}
444
445macro_rules! check_cache_entry_by_version {
446 ($self: ident, $table: expr, $level: expr, $cache: expr, $version: expr) => {
447 $self.metrics.record_cache_request($table, $level);
448 if let Some(cache) = $cache {
449 if let Some(entry) = cache.get(&$version) {
450 $self.metrics.record_cache_hit($table, $level);
451 return CacheResult::Hit(entry.clone());
452 }
453
454 if let Some(least_version) = cache.get_least() {
455 if least_version.0 < $version {
456 $self.metrics.record_cache_negative_hit($table, $level);
459 return CacheResult::NegativeHit;
460 }
461 }
462 }
463 $self.metrics.record_cache_miss($table, $level);
464 };
465}
466
467macro_rules! check_cache_entry_by_latest {
468 ($self: ident, $table: expr, $level: expr, $cache: expr) => {
469 $self.metrics.record_cache_request($table, $level);
470 if let Some(cache) = $cache {
471 if let Some((version, entry)) = cache.get_highest() {
472 $self.metrics.record_cache_hit($table, $level);
473 return CacheResult::Hit((*version, entry.clone()));
474 } else {
475 panic!("empty CachedVersionMap should have been removed");
476 }
477 }
478 $self.metrics.record_cache_miss($table, $level);
479 };
480}
481
482impl WritebackCache {
483 pub fn new(
484 config: &WritebackCacheConfig,
485 store: Arc<AuthorityStore>,
486 metrics: Arc<ExecutionCacheMetrics>,
487 backpressure_manager: Arc<BackpressureManager>,
488 ) -> Self {
489 let packages = MokaCache::builder(8)
490 .max_capacity(randomize_cache_capacity_in_tests(
491 config.package_cache_size(),
492 ))
493 .build();
494 Self {
495 dirty: UncommittedData::new(),
496 cached: CachedCommittedData::new(config),
497 object_by_id_cache: MonotonicCache::new(randomize_cache_capacity_in_tests(
498 config.object_by_id_cache_size(),
499 )),
500 packages,
501 object_locks: ObjectLocks::new(),
502 executed_effects_digests_notify_read: NotifyRead::new(),
503 object_notify_read: NotifyRead::new(),
504 store,
505 backpressure_manager,
506 backpressure_threshold: config.backpressure_threshold(),
507 metrics,
508 }
509 }
510
511 pub fn new_for_tests(store: Arc<AuthorityStore>) -> Self {
512 Self::new(
513 &Default::default(),
514 store,
515 ExecutionCacheMetrics::new(&prometheus_filtered::Registry::new()).into(),
516 BackpressureManager::new_for_tests(),
517 )
518 }
519
520 #[cfg(test)]
521 pub fn reset_for_test(&mut self) {
522 let mut new = Self::new(
523 &Default::default(),
524 self.store.clone(),
525 self.metrics.clone(),
526 self.backpressure_manager.clone(),
527 );
528 std::mem::swap(self, &mut new);
529 }
530
531 fn write_object_entry(&self, object_id: &ObjectId, version: Version, object: ObjectEntry) {
532 trace!(?object_id, ?version, ?object, "inserting object entry");
533 self.metrics.record_cache_write("object");
534
535 let mut entry = self.dirty.objects.entry(*object_id).or_default();
556
557 self.object_by_id_cache
558 .insert(
559 object_id,
560 LatestObjectCacheEntry::Object(version, object.clone()),
561 Ticket::Write,
562 )
563 .ok();
566
567 entry.insert(version, object.clone());
568
569 if let ObjectEntry::Object(object) = &object {
570 super::notify_object_written(&self.object_notify_read, object);
571 }
572 }
573
574 fn write_marker_value(
575 &self,
576 epoch_id: EpochId,
577 object_key: &ObjectKey,
578 marker_value: MarkerValue,
579 ) {
580 tracing::trace!(
581 "inserting marker value {:?}: {:?}",
582 object_key,
583 marker_value
584 );
585 fail_point!("write_marker_entry");
586 self.metrics.record_cache_write("marker");
587 self.dirty
588 .markers
589 .entry((epoch_id, object_key.0))
590 .or_default()
591 .value_mut()
592 .insert(object_key.1, marker_value);
593
594 super::notify_marker_written(&self.object_notify_read, object_key, &marker_value);
599 }
600
601 fn with_locked_cache_entries<K, V, R>(
605 dirty_map: &DashMap<K, CachedVersionMap<V>>,
606 cached_map: &MokaCache<K, Arc<Mutex<CachedVersionMap<V>>>>,
607 key: &K,
608 cb: impl FnOnce(Option<&CachedVersionMap<V>>, Option<&CachedVersionMap<V>>) -> R,
609 ) -> R
610 where
611 K: Copy + Eq + Hash + Send + Sync + 'static,
612 V: Send + Sync + 'static,
613 {
614 let dirty_entry = dirty_map.entry(*key);
615 let dirty_entry = match &dirty_entry {
616 DashMapEntry::Occupied(occupied) => Some(occupied.get()),
617 DashMapEntry::Vacant(_) => None,
618 };
619
620 let cached_entry = cached_map.get(key);
621 let cached_lock = cached_entry.as_ref().map(|entry| entry.lock());
622 let cached_entry = cached_lock.as_deref();
623
624 cb(dirty_entry, cached_entry)
625 }
626
627 fn get_object_entry_by_key_cache_only(
630 &self,
631 object_id: &ObjectId,
632 version: Version,
633 ) -> CacheResult<ObjectEntry> {
634 Self::with_locked_cache_entries(
635 &self.dirty.objects,
636 &self.cached.object_cache,
637 object_id,
638 |dirty_entry, cached_entry| {
639 check_cache_entry_by_version!(
640 self,
641 "object_by_version",
642 "uncommitted",
643 dirty_entry,
644 version
645 );
646 check_cache_entry_by_version!(
647 self,
648 "object_by_version",
649 "committed",
650 cached_entry,
651 version
652 );
653 CacheResult::Miss
654 },
655 )
656 }
657
658 fn get_object_by_key_cache_only(
659 &self,
660 object_id: &ObjectId,
661 version: Version,
662 ) -> CacheResult<Object> {
663 match self.get_object_entry_by_key_cache_only(object_id, version) {
664 CacheResult::Hit(entry) => match entry {
665 ObjectEntry::Object(object) => CacheResult::Hit(object),
666 ObjectEntry::Deleted | ObjectEntry::Wrapped => CacheResult::NegativeHit,
667 },
668 CacheResult::Miss => CacheResult::Miss,
669 CacheResult::NegativeHit => CacheResult::NegativeHit,
670 }
671 }
672
673 fn get_object_entry_by_id_cache_only(
674 &self,
675 request_type: &'static str,
676 object_id: &ObjectId,
677 ) -> CacheResult<(Version, ObjectEntry)> {
678 self.metrics
679 .record_cache_request(request_type, "object_by_id");
680 let entry = self.object_by_id_cache.get(object_id);
681
682 if cfg!(debug_assertions) {
683 if let Some(entry) = &entry {
684 let highest: Option<ObjectEntry> = self
686 .dirty
687 .objects
688 .get(object_id)
689 .and_then(|entry| entry.get_highest().map(|(_, o)| o.clone()))
690 .or_else(|| {
691 let obj: Option<ObjectEntry> = self
692 .store
693 .get_latest_object_or_tombstone(*object_id)
694 .unwrap()
695 .map(|(_, o)| o.into());
696 obj
697 });
698
699 let cache_entry = match &*entry.lock() {
700 LatestObjectCacheEntry::Object(_, entry) => Some(entry.clone()),
701 LatestObjectCacheEntry::NonExistent => None,
702 };
703
704 let tombstone_possibly_pruned = highest.is_none()
707 && cache_entry
708 .as_ref()
709 .map(|e| e.is_tombstone())
710 .unwrap_or(false);
711
712 if highest != cache_entry && !tombstone_possibly_pruned {
713 tracing::error!(
714 ?highest,
715 ?cache_entry,
716 ?tombstone_possibly_pruned,
717 "object_by_id cache is incoherent for {}",
718 object_id
719 );
720 panic!("object_by_id cache is incoherent for {object_id}");
721 }
722 }
723 }
724
725 if let Some(entry) = entry {
726 let entry = entry.lock();
727 match &*entry {
728 LatestObjectCacheEntry::Object(latest_version, latest_object) => {
729 self.metrics.record_cache_hit(request_type, "object_by_id");
730 return CacheResult::Hit((*latest_version, latest_object.clone()));
731 }
732 LatestObjectCacheEntry::NonExistent => {
733 self.metrics
734 .record_cache_negative_hit(request_type, "object_by_id");
735 return CacheResult::NegativeHit;
736 }
737 }
738 } else {
739 self.metrics.record_cache_miss(request_type, "object_by_id");
740 }
741
742 Self::with_locked_cache_entries(
743 &self.dirty.objects,
744 &self.cached.object_cache,
745 object_id,
746 |dirty_entry, cached_entry| {
747 check_cache_entry_by_latest!(self, request_type, "uncommitted", dirty_entry);
748 check_cache_entry_by_latest!(self, request_type, "committed", cached_entry);
749 CacheResult::Miss
750 },
751 )
752 }
753
754 fn get_object_by_id_cache_only(
755 &self,
756 request_type: &'static str,
757 object_id: &ObjectId,
758 ) -> CacheResult<(Version, Object)> {
759 match self.get_object_entry_by_id_cache_only(request_type, object_id) {
760 CacheResult::Hit((version, entry)) => match entry {
761 ObjectEntry::Object(object) => CacheResult::Hit((version, object)),
762 ObjectEntry::Deleted | ObjectEntry::Wrapped => CacheResult::NegativeHit,
763 },
764 CacheResult::NegativeHit => CacheResult::NegativeHit,
765 CacheResult::Miss => CacheResult::Miss,
766 }
767 }
768
769 fn get_marker_value_cache_only(
770 &self,
771 object_id: &ObjectId,
772 version: Version,
773 epoch_id: EpochId,
774 ) -> CacheResult<MarkerValue> {
775 Self::with_locked_cache_entries(
776 &self.dirty.markers,
777 &self.cached.marker_cache,
778 &(epoch_id, *object_id),
779 |dirty_entry, cached_entry| {
780 check_cache_entry_by_version!(
781 self,
782 "marker_by_version",
783 "uncommitted",
784 dirty_entry,
785 version
786 );
787 check_cache_entry_by_version!(
788 self,
789 "marker_by_version",
790 "committed",
791 cached_entry,
792 version
793 );
794 CacheResult::Miss
795 },
796 )
797 }
798
799 fn get_latest_marker_value_cache_only(
800 &self,
801 object_id: &ObjectId,
802 epoch_id: EpochId,
803 ) -> CacheResult<(Version, MarkerValue)> {
804 Self::with_locked_cache_entries(
805 &self.dirty.markers,
806 &self.cached.marker_cache,
807 &(epoch_id, *object_id),
808 |dirty_entry, cached_entry| {
809 check_cache_entry_by_latest!(self, "marker_latest", "uncommitted", dirty_entry);
810 check_cache_entry_by_latest!(self, "marker_latest", "committed", cached_entry);
811 CacheResult::Miss
812 },
813 )
814 }
815
816 fn get_object_impl(
817 &self,
818 request_type: &'static str,
819 id: &ObjectId,
820 ) -> IotaResult<Option<Object>> {
821 let ticket = self.object_by_id_cache.get_ticket_for_read(id);
822 match self.get_object_entry_by_id_cache_only(request_type, id) {
823 CacheResult::Hit((_, object)) => match object {
824 ObjectEntry::Object(object) => Ok(Some(object)),
825 ObjectEntry::Deleted | ObjectEntry::Wrapped => Ok(None),
826 },
827 CacheResult::NegativeHit => Ok(None),
828 CacheResult::Miss => {
829 let obj = self.store.get_latest_object_or_tombstone(*id);
830 match obj {
831 Ok(Some((key, obj))) => {
832 self.cache_latest_object_by_id(
833 id,
834 LatestObjectCacheEntry::Object(key.1, obj.clone().into()),
835 ticket,
836 );
837 match obj {
838 ObjectOrTombstone::Object(object) => Ok(Some(object)),
839 ObjectOrTombstone::Tombstone(_) => Ok(None),
840 }
841 }
842 Ok(None) => {
843 self.cache_object_not_found(id, ticket);
844 Ok(None)
845 }
846 Err(e) => Err(format!("failed to get latest object or tomebstone: {e}").into()),
847 }
848 }
849 }
850 }
851
852 fn record_db_get(&self, request_type: &'static str) -> &AuthorityStore {
853 self.metrics.record_cache_request(request_type, "db");
854 &self.store
855 }
856
857 fn record_db_multi_get(&self, request_type: &'static str, count: usize) -> &AuthorityStore {
858 self.metrics
859 .record_cache_multi_request(request_type, "db", count);
860 &self.store
861 }
862
863 #[instrument(level = "debug", skip_all)]
864 fn write_transaction_outputs(
865 &self,
866 epoch_id: EpochId,
867 tx_outputs: Arc<TransactionOutputs>,
868 ) -> IotaResult {
869 trace!(digest = ?tx_outputs.transaction.digest(), "writing transaction outputs to cache");
870
871 let TransactionOutputs {
872 transaction,
873 effects,
874 markers,
875 written,
876 deleted,
877 wrapped,
878 events,
879 ..
880 } = &*tx_outputs;
881
882 for ObjectKey(id, version) in deleted.iter() {
888 self.write_object_entry(id, *version, ObjectEntry::Deleted);
889 }
890
891 for ObjectKey(id, version) in wrapped.iter() {
892 self.write_object_entry(id, *version, ObjectEntry::Wrapped);
893 }
894
895 for (object_key, marker_value) in markers.iter() {
897 self.write_marker_value(epoch_id, object_key, *marker_value);
898 }
899
900 for (object_id, object) in written.iter() {
903 if object.is_child_object() {
904 self.write_object_entry(object_id, object.version(), object.clone().into());
905 }
906 }
907 for (object_id, object) in written.iter() {
908 if !object.is_child_object() {
909 self.write_object_entry(object_id, object.version(), object.clone().into());
910 if object.is_package() {
911 debug!("caching package: {:?}", object.object_ref());
912 self.packages
913 .insert(*object_id, PackageObject::new(object.clone()));
914 }
915 }
916 }
917
918 let tx_digest = *transaction.digest();
919 let effects_digest = effects.digest();
920
921 self.metrics.record_cache_write("transaction_block");
922 self.dirty
923 .pending_transaction_writes
924 .insert(tx_digest, tx_outputs.clone());
925
926 self.metrics.record_cache_write("transaction_effects");
929 self.dirty
930 .transaction_effects
931 .insert(effects_digest, effects.clone());
932
933 self.metrics.record_cache_write("transaction_events");
938 self.dirty
939 .transaction_events
940 .insert(tx_digest, events.clone());
941
942 self.metrics.record_cache_write("executed_effects_digests");
943 self.dirty
944 .executed_effects_digests
945 .insert(tx_digest, effects_digest);
946
947 self.executed_effects_digests_notify_read
948 .notify(&tx_digest, &effects_digest);
949
950 self.metrics
951 .pending_notify_read
952 .set(self.executed_effects_digests_notify_read.num_pending() as i64);
953
954 let prev = self
955 .dirty
956 .total_transaction_inserts
957 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
958
959 let pending_count = (prev + 1).saturating_sub(
960 self.dirty
961 .total_transaction_commits
962 .load(std::sync::atomic::Ordering::Relaxed),
963 );
964
965 self.set_backpressure(pending_count);
966
967 Ok(())
968 }
969
970 fn build_db_batch(
971 &self,
972 epoch: EpochId,
973 checkpoint_sequence_number: CheckpointSequenceNumber,
974 digests: &[TransactionDigest],
975 ) -> Batch {
976 let _metrics_guard = iota_metrics::monitored_scope("WritebackCache::build_db_batch");
977 let mut all_outputs = Vec::with_capacity(digests.len());
978 for tx in digests {
979 let Some(outputs) = self
980 .dirty
981 .pending_transaction_writes
982 .get(tx)
983 .map(|o| o.clone())
984 else {
985 warn!("Attempt to commit unknown transaction {:?}", tx);
993 continue;
994 };
995 all_outputs.push(outputs);
996 }
997
998 let batch = self
999 .store
1000 .build_db_batch(epoch, checkpoint_sequence_number, &all_outputs)
1001 .expect("db error");
1002 (all_outputs, batch)
1003 }
1004
1005 #[instrument(level = "debug", skip_all)]
1007 fn commit_transaction_outputs(
1008 &self,
1009 epoch: EpochId,
1010 (all_outputs, db_batch): Batch,
1011 digests: &[TransactionDigest],
1012 ) -> IotaResult {
1013 let _metrics_guard =
1014 iota_metrics::monitored_scope("WritebackCache::commit_transaction_outputs");
1015 fail_point!("writeback-cache-commit");
1016 trace!(?digests);
1017
1018 db_batch.write().expect("db error");
1022
1023 let _metrics_guard =
1024 iota_metrics::monitored_scope("WritebackCache::commit_transaction_outputs::flush");
1025 for outputs in all_outputs.iter() {
1026 let tx_digest = outputs.transaction.digest();
1027 assert!(
1028 self.dirty
1029 .pending_transaction_writes
1030 .remove(tx_digest)
1031 .is_some()
1032 );
1033 self.flush_transactions_from_dirty_to_cached(epoch, *tx_digest, outputs);
1034 }
1035
1036 let num_outputs = all_outputs.len() as u64;
1037 let num_commits = self
1038 .dirty
1039 .total_transaction_commits
1040 .fetch_add(num_outputs, std::sync::atomic::Ordering::Relaxed)
1041 + num_outputs;
1042
1043 let pending_count = self
1044 .dirty
1045 .total_transaction_inserts
1046 .load(std::sync::atomic::Ordering::Relaxed)
1047 .saturating_sub(num_commits);
1048
1049 self.set_backpressure(pending_count);
1050
1051 Ok(())
1052 }
1053
1054 fn approximate_pending_transaction_count(&self) -> u64 {
1055 let num_commits = self
1056 .dirty
1057 .total_transaction_commits
1058 .load(std::sync::atomic::Ordering::Relaxed);
1059
1060 self.dirty
1061 .total_transaction_inserts
1062 .load(std::sync::atomic::Ordering::Relaxed)
1063 .saturating_sub(num_commits)
1064 }
1065
1066 fn set_backpressure(&self, pending_count: u64) {
1067 let backpressure = pending_count > self.backpressure_threshold;
1068 let backpressure_changed = self.backpressure_manager.set_backpressure(backpressure);
1069 if backpressure_changed {
1070 self.metrics.backpressure_toggles.inc();
1071 }
1072 self.metrics
1073 .backpressure_status
1074 .set(if backpressure { 1 } else { 0 });
1075 }
1076
1077 fn flush_transactions_from_dirty_to_cached(
1078 &self,
1079 epoch: EpochId,
1080 tx_digest: TransactionDigest,
1081 outputs: &TransactionOutputs,
1082 ) {
1083 let TransactionOutputs {
1087 transaction,
1088 effects,
1089 markers,
1090 written,
1091 deleted,
1092 wrapped,
1093 events,
1094 ..
1095 } = outputs;
1096
1097 let effects_digest = effects.digest();
1098
1099 self.cached
1102 .transactions
1103 .insert(
1104 &tx_digest,
1105 PointCacheItem::Some(transaction.clone()),
1106 Ticket::Write,
1107 )
1108 .ok();
1109 self.cached
1110 .transaction_effects
1111 .insert(
1112 &effects_digest,
1113 PointCacheItem::Some(effects.clone().into()),
1114 Ticket::Write,
1115 )
1116 .ok();
1117 self.cached
1118 .executed_effects_digests
1119 .insert(
1120 &tx_digest,
1121 PointCacheItem::Some(effects_digest),
1122 Ticket::Write,
1123 )
1124 .ok();
1125 self.cached
1126 .transaction_events
1127 .insert(
1128 &tx_digest,
1129 PointCacheItem::Some(events.clone().into()),
1130 Ticket::Write,
1131 )
1132 .ok();
1133
1134 self.dirty
1135 .transaction_effects
1136 .remove(&effects_digest)
1137 .expect("effects must exist");
1138
1139 self.dirty
1140 .transaction_events
1141 .remove(&tx_digest)
1142 .expect("events must exist");
1143
1144 self.dirty
1145 .executed_effects_digests
1146 .remove(&tx_digest)
1147 .expect("executed effects must exist");
1148
1149 for (object_key, marker_value) in markers.iter() {
1151 Self::move_version_from_dirty_to_cache(
1152 &self.dirty.markers,
1153 &self.cached.marker_cache,
1154 (epoch, object_key.0),
1155 object_key.1,
1156 marker_value,
1157 );
1158 }
1159
1160 for (object_id, object) in written.iter() {
1161 Self::move_version_from_dirty_to_cache(
1162 &self.dirty.objects,
1163 &self.cached.object_cache,
1164 *object_id,
1165 object.version(),
1166 &ObjectEntry::Object(object.clone()),
1167 );
1168 }
1169
1170 for ObjectKey(object_id, version) in deleted.iter() {
1171 Self::move_version_from_dirty_to_cache(
1172 &self.dirty.objects,
1173 &self.cached.object_cache,
1174 *object_id,
1175 *version,
1176 &ObjectEntry::Deleted,
1177 );
1178 }
1179
1180 for ObjectKey(object_id, version) in wrapped.iter() {
1181 Self::move_version_from_dirty_to_cache(
1182 &self.dirty.objects,
1183 &self.cached.object_cache,
1184 *object_id,
1185 *version,
1186 &ObjectEntry::Wrapped,
1187 );
1188 }
1189 }
1190
1191 fn move_version_from_dirty_to_cache<K, V>(
1194 dirty: &DashMap<K, CachedVersionMap<V>>,
1195 cache: &MokaCache<K, Arc<Mutex<CachedVersionMap<V>>>>,
1196 key: K,
1197 version: Version,
1198 value: &V,
1199 ) where
1200 K: Eq + std::hash::Hash + Clone + Send + Sync + Copy + 'static,
1201 V: Send + Sync + Clone + Eq + std::fmt::Debug + 'static,
1202 {
1203 static MAX_VERSIONS: usize = 3;
1204
1205 let dirty_entry = dirty.entry(key);
1209 let cache_entry = cache.entry(key).or_default();
1210 let mut cache_map = cache_entry.value().lock();
1211
1212 cache_map.insert(version, value.clone());
1214 cache_map.truncate_to(MAX_VERSIONS);
1216
1217 let DashMapEntry::Occupied(mut occupied_dirty_entry) = dirty_entry else {
1218 panic!("dirty map must exist");
1219 };
1220
1221 let removed = occupied_dirty_entry.get_mut().pop_oldest(&version);
1222
1223 assert_eq!(removed.as_ref(), Some(value), "dirty version must exist");
1224
1225 if occupied_dirty_entry.get().is_empty() {
1227 occupied_dirty_entry.remove();
1228 }
1229 }
1230
1231 fn cache_latest_object_by_id(
1233 &self,
1234 object_id: &ObjectId,
1235 object: LatestObjectCacheEntry,
1236 ticket: Ticket,
1237 ) {
1238 trace!("caching object by id: {} {:?}", object_id, object);
1239 if self
1240 .object_by_id_cache
1241 .insert(object_id, object, ticket)
1242 .is_ok()
1243 {
1244 self.metrics.record_cache_write("object_by_id");
1245 } else {
1246 trace!("discarded cache write due to expired ticket");
1247 self.metrics.record_ticket_expiry();
1248 }
1249 }
1250
1251 fn cache_object_not_found(&self, object_id: &ObjectId, ticket: Ticket) {
1252 self.cache_latest_object_by_id(object_id, LatestObjectCacheEntry::NonExistent, ticket);
1253 }
1254
1255 fn clear_state_end_of_epoch_impl(&self, execution_guard: &ExecutionLockWriteGuard<'_>) {
1256 info!("clearing state at end of epoch");
1257 assert!(
1258 self.dirty.pending_transaction_writes.is_empty(),
1259 "should be empty due to revert_state_update"
1260 );
1261 self.dirty.clear();
1262 info!("clearing old transaction locks");
1263 self.object_locks.clear();
1264 info!("clearing object per epoch marker table");
1265 self.store
1266 .clear_object_per_epoch_marker_table(execution_guard)
1267 .expect("db error");
1268 }
1269
1270 fn revert_state_update_impl(&self, tx: &TransactionDigest) -> IotaResult {
1271 let Some((_, outputs)) = self.dirty.pending_transaction_writes.remove(tx) else {
1276 assert!(
1277 !self.try_is_tx_already_executed(tx)?,
1278 "attempt to revert committed transaction"
1279 );
1280
1281 info!("Not reverting {:?} as it was not executed", tx);
1284 return Ok(());
1285 };
1286
1287 for (object_id, object) in outputs.written.iter() {
1288 if object.is_package() {
1289 info!("removing non-finalized package from cache: {}", object_id);
1290 self.packages.invalidate(object_id);
1291 }
1292 self.object_by_id_cache.invalidate(object_id);
1293 self.cached.object_cache.invalidate(object_id);
1294 }
1295
1296 for ObjectKey(object_id, _) in outputs.deleted.iter().chain(outputs.wrapped.iter()) {
1297 self.object_by_id_cache.invalidate(object_id);
1298 self.cached.object_cache.invalidate(object_id);
1299 }
1300
1301 Ok(())
1304 }
1305
1306 fn bulk_insert_genesis_objects_impl(&self, objects: &[Object]) -> IotaResult {
1307 self.store.bulk_insert_genesis_objects(objects)?;
1308 for obj in objects {
1309 self.cached.object_cache.invalidate(&obj.id());
1310 self.object_by_id_cache.invalidate(&obj.id());
1311 }
1312 Ok(())
1313 }
1314
1315 fn insert_genesis_object_impl(&self, object: Object) -> IotaResult {
1316 self.object_by_id_cache.invalidate(&object.id());
1317 self.cached.object_cache.invalidate(&object.id());
1318 self.store.insert_genesis_object(object)
1319 }
1320
1321 pub fn clear_caches_and_assert_empty(&self) {
1322 info!("clearing caches");
1323 self.cached.clear_and_assert_empty();
1324 self.object_by_id_cache.invalidate_all();
1325 assert!(&self.object_by_id_cache.is_empty());
1326 self.packages.invalidate_all();
1327 assert_empty(&self.packages);
1328 }
1329}
1330
1331impl ExecutionCacheAPI for WritebackCache {}
1332
1333impl ExecutionCacheCommit for WritebackCache {
1334 fn build_db_batch(
1335 &self,
1336 epoch: EpochId,
1337 checkpoint_sequence_number: CheckpointSequenceNumber,
1338 digests: &[TransactionDigest],
1339 ) -> Batch {
1340 self.build_db_batch(epoch, checkpoint_sequence_number, digests)
1341 }
1342
1343 fn try_commit_transaction_outputs(
1344 &self,
1345 epoch: EpochId,
1346 batch: Batch,
1347 digests: &[TransactionDigest],
1348 ) -> IotaResult {
1349 WritebackCache::commit_transaction_outputs(self, epoch, batch, digests)
1350 }
1351
1352 fn try_persist_transaction(&self, tx: &VerifiedExecutableTransaction) -> IotaResult {
1353 self.store.persist_transaction(tx)
1354 }
1355
1356 fn approximate_pending_transaction_count(&self) -> u64 {
1357 WritebackCache::approximate_pending_transaction_count(self)
1358 }
1359}
1360
1361impl ObjectCacheRead for WritebackCache {
1362 #[instrument(level="trace", skip_all, fields(package_id=?package_id))]
1363 fn try_get_package_object(&self, package_id: &ObjectId) -> IotaResult<Option<PackageObject>> {
1364 self.metrics
1365 .record_cache_request("package", "package_cache");
1366 if let Some(p) = self.packages.get(package_id) {
1367 if cfg!(debug_assertions) {
1368 let canonical_package = self
1369 .dirty
1370 .objects
1371 .get(package_id)
1372 .and_then(|v| match v.get_highest().map(|v| v.1.clone()) {
1373 Some(ObjectEntry::Object(object)) => Some(object),
1374 _ => None,
1375 })
1376 .or_else(|| self.store.get_object(package_id));
1377
1378 if let Some(canonical_package) = canonical_package {
1379 assert_eq!(
1380 canonical_package.digest(),
1381 p.object().digest(),
1382 "Package object cache is inconsistent for package {package_id}"
1383 );
1384 }
1385 }
1386 self.metrics.record_cache_hit("package", "package_cache");
1387 return Ok(Some(p));
1388 } else {
1389 self.metrics.record_cache_miss("package", "package_cache");
1390 }
1391
1392 if let Some(p) = self.get_object_impl("package", package_id)? {
1396 if p.is_package() {
1397 let p = PackageObject::new(p);
1398 tracing::trace!("caching package: {:?}", p.object().object_ref());
1399 self.metrics.record_cache_write("package");
1400 self.packages.insert(*package_id, p.clone());
1401 Ok(Some(p))
1402 } else {
1403 Err(IotaError::UserInput {
1404 error: UserInputError::MoveObjectAsPackage {
1405 object_id: *package_id,
1406 },
1407 })
1408 }
1409 } else {
1410 Ok(None)
1411 }
1412 }
1413
1414 fn force_reload_system_packages(&self, _system_package_ids: &[ObjectId]) {
1415 }
1418
1419 #[instrument(level = "trace", skip_all, fields(object_id=?id))]
1422 fn try_get_object(&self, id: &ObjectId) -> IotaResult<Option<Object>> {
1423 self.get_object_impl("object_latest", id)
1424 }
1425
1426 #[instrument(level = "trace", skip_all, fields(object_id, version))]
1427 fn try_get_object_by_key(
1428 &self,
1429 object_id: &ObjectId,
1430 version: Version,
1431 ) -> IotaResult<Option<Object>> {
1432 match self.get_object_by_key_cache_only(object_id, version) {
1433 CacheResult::Hit(object) => Ok(Some(object)),
1434 CacheResult::NegativeHit => Ok(None),
1435 CacheResult::Miss => Ok(self
1436 .record_db_get("object_by_version")
1437 .try_get_object_by_key(object_id, version)?),
1438 }
1439 }
1440
1441 #[instrument(level = "trace", skip_all)]
1442 fn try_multi_get_objects_by_key(
1443 &self,
1444 object_keys: &[ObjectKey],
1445 ) -> Result<Vec<Option<Object>>, IotaError> {
1446 try_do_fallback_lookup(
1447 object_keys,
1448 |key| {
1449 Ok(match self.get_object_by_key_cache_only(&key.0, key.1) {
1450 CacheResult::Hit(maybe_object) => CacheResult::Hit(Some(maybe_object)),
1451 CacheResult::NegativeHit => CacheResult::NegativeHit,
1452 CacheResult::Miss => CacheResult::Miss,
1453 })
1454 },
1455 |remaining| {
1456 self.record_db_multi_get("object_by_version", remaining.len())
1457 .multi_get_objects_by_key(remaining)
1458 },
1459 )
1460 }
1461
1462 #[instrument(level = "trace", skip_all, fields(object_id, version))]
1463 fn try_object_exists_by_key(&self, object_id: &ObjectId, version: Version) -> IotaResult<bool> {
1464 match self.get_object_by_key_cache_only(object_id, version) {
1465 CacheResult::Hit(_) => Ok(true),
1466 CacheResult::NegativeHit => Ok(false),
1467 CacheResult::Miss => self
1468 .record_db_get("object_by_version")
1469 .object_exists_by_key(object_id, version),
1470 }
1471 }
1472
1473 #[instrument(level = "trace", skip_all)]
1474 fn try_multi_object_exists_by_key(&self, object_keys: &[ObjectKey]) -> IotaResult<Vec<bool>> {
1475 try_do_fallback_lookup(
1476 object_keys,
1477 |key| {
1478 Ok(match self.get_object_by_key_cache_only(&key.0, key.1) {
1479 CacheResult::Hit(_) => CacheResult::Hit(true),
1480 CacheResult::NegativeHit => CacheResult::Hit(false),
1481 CacheResult::Miss => CacheResult::Miss,
1482 })
1483 },
1484 |remaining| {
1485 self.record_db_multi_get("object_by_version", remaining.len())
1486 .multi_object_exists_by_key(remaining)
1487 },
1488 )
1489 }
1490
1491 #[instrument(level = "trace", skip_all, fields(object_id))]
1492 fn try_get_latest_object_ref_or_tombstone(
1493 &self,
1494 object_id: ObjectId,
1495 ) -> IotaResult<Option<ObjectReference>> {
1496 match self.get_object_entry_by_id_cache_only("latest_objref_or_tombstone", &object_id) {
1497 CacheResult::Hit((version, entry)) => Ok(Some(match entry {
1498 ObjectEntry::Object(object) => object.object_ref(),
1499 ObjectEntry::Deleted => {
1500 ObjectReference::new(object_id, version, ObjectDigest::OBJECT_DELETED)
1501 }
1502 ObjectEntry::Wrapped => {
1503 ObjectReference::new(object_id, version, ObjectDigest::OBJECT_WRAPPED)
1504 }
1505 })),
1506 CacheResult::NegativeHit => Ok(None),
1507 CacheResult::Miss => self
1508 .record_db_get("latest_objref_or_tombstone")
1509 .get_latest_object_ref_or_tombstone(object_id),
1510 }
1511 }
1512
1513 #[instrument(level = "trace", skip_all, fields(object_id))]
1514 fn try_get_latest_object_or_tombstone(
1515 &self,
1516 object_id: ObjectId,
1517 ) -> Result<Option<(ObjectKey, ObjectOrTombstone)>, IotaError> {
1518 match self.get_object_entry_by_id_cache_only("latest_object_or_tombstone", &object_id) {
1519 CacheResult::Hit((version, entry)) => {
1520 let key = ObjectKey(object_id, version);
1521 Ok(Some(match entry {
1522 ObjectEntry::Object(object) => (key, object.into()),
1523 ObjectEntry::Deleted => (
1524 key,
1525 ObjectOrTombstone::Tombstone(ObjectReference::new(
1526 object_id,
1527 version,
1528 ObjectDigest::OBJECT_DELETED,
1529 )),
1530 ),
1531 ObjectEntry::Wrapped => (
1532 key,
1533 ObjectOrTombstone::Tombstone(ObjectReference::new(
1534 object_id,
1535 version,
1536 ObjectDigest::OBJECT_WRAPPED,
1537 )),
1538 ),
1539 }))
1540 }
1541 CacheResult::NegativeHit => Ok(None),
1542 CacheResult::Miss => self
1543 .record_db_get("latest_object_or_tombstone")
1544 .get_latest_object_or_tombstone(object_id),
1545 }
1546 }
1547
1548 #[instrument(level = "trace", skip_all, fields(object_id, version_bound))]
1549 fn try_find_object_lt_or_eq_version(
1550 &self,
1551 object_id: ObjectId,
1552 version_bound: Version,
1553 ) -> IotaResult<Option<Object>> {
1554 macro_rules! check_cache_entry {
1555 ($level: expr, $objects: expr) => {
1556 self.metrics
1557 .record_cache_request("object_lt_or_eq_version", $level);
1558 if let Some(objects) = $objects {
1559 if let Some((_, object)) = objects
1560 .all_versions_lt_or_eq_descending(&version_bound)
1561 .next()
1562 {
1563 if let ObjectEntry::Object(object) = object {
1564 self.metrics
1565 .record_cache_hit("object_lt_or_eq_version", $level);
1566 return Ok(Some(object.clone()));
1567 } else {
1568 self.metrics
1570 .record_cache_negative_hit("object_lt_or_eq_version", $level);
1571 return Ok(None);
1572 }
1573 } else {
1574 self.metrics
1575 .record_cache_miss("object_lt_or_eq_version", $level);
1576 }
1577 }
1578 };
1579 }
1580
1581 self.metrics
1583 .record_cache_request("object_lt_or_eq_version", "object_by_id");
1584 let latest_cache_entry = self.object_by_id_cache.get(&object_id);
1585 if let Some(latest) = &latest_cache_entry {
1586 let latest = latest.lock();
1587 match &*latest {
1588 LatestObjectCacheEntry::Object(latest_version, object) => {
1589 if *latest_version <= version_bound {
1590 if let ObjectEntry::Object(object) = object {
1591 self.metrics
1592 .record_cache_hit("object_lt_or_eq_version", "object_by_id");
1593 return Ok(Some(object.clone()));
1594 } else {
1595 self.metrics.record_cache_negative_hit(
1597 "object_lt_or_eq_version",
1598 "object_by_id",
1599 );
1600 return Ok(None);
1601 }
1602 }
1603 }
1606 LatestObjectCacheEntry::NonExistent => {
1608 self.metrics
1609 .record_cache_negative_hit("object_lt_or_eq_version", "object_by_id");
1610 return Ok(None);
1611 }
1612 }
1613 }
1614 self.metrics
1615 .record_cache_miss("object_lt_or_eq_version", "object_by_id");
1616
1617 Self::with_locked_cache_entries(
1618 &self.dirty.objects,
1619 &self.cached.object_cache,
1620 &object_id,
1621 |dirty_entry, cached_entry| {
1622 check_cache_entry!("committed", dirty_entry);
1623 check_cache_entry!("uncommitted", cached_entry);
1624
1625 let latest: Option<(Version, ObjectEntry)> = if let Some(dirty_set) = dirty_entry {
1649 dirty_set
1650 .get_highest()
1651 .cloned()
1652 .tap_none(|| panic!("dirty set cannot be empty"))
1653 } else {
1654 self.record_db_get("object_lt_or_eq_version_latest")
1656 .get_latest_object_or_tombstone(object_id)?
1657 .map(|(ObjectKey(_, version), obj_or_tombstone)| {
1658 (version, ObjectEntry::from(obj_or_tombstone))
1659 })
1660 };
1661
1662 if let Some((obj_version, obj_entry)) = latest {
1663 self.cache_latest_object_by_id(
1672 &object_id,
1673 LatestObjectCacheEntry::Object(obj_version, obj_entry.clone()),
1674 self.object_by_id_cache.get_ticket_for_read(&object_id),
1677 );
1678
1679 if obj_version <= version_bound {
1680 match obj_entry {
1681 ObjectEntry::Object(object) => Ok(Some(object)),
1682 ObjectEntry::Deleted | ObjectEntry::Wrapped => Ok(None),
1683 }
1684 } else {
1685 self.record_db_get("object_lt_or_eq_version_scan")
1689 .find_object_lt_or_eq_version(object_id, version_bound)
1690 }
1691
1692 } else if let Some(latest_cache_entry) = latest_cache_entry {
1699 assert!(!latest_cache_entry.lock().is_alive());
1701 Ok(None)
1702 } else {
1703 let highest = cached_entry.and_then(|c| c.get_highest());
1705 assert!(highest.is_none() || highest.unwrap().1.is_tombstone());
1706 self.cache_object_not_found(
1707 &object_id,
1708 self.object_by_id_cache.get_ticket_for_read(&object_id),
1710 );
1711 Ok(None)
1712 }
1713 },
1714 )
1715 }
1716
1717 fn try_get_iota_system_state_object_unsafe(&self) -> IotaResult<IotaSystemState> {
1718 get_iota_system_state(self)
1719 }
1720
1721 fn try_get_marker_value(
1722 &self,
1723 object_id: &ObjectId,
1724 version: Version,
1725 epoch_id: EpochId,
1726 ) -> IotaResult<Option<MarkerValue>> {
1727 match self.get_marker_value_cache_only(object_id, version, epoch_id) {
1728 CacheResult::Hit(marker) => Ok(Some(marker)),
1729 CacheResult::NegativeHit => Ok(None),
1730 CacheResult::Miss => self
1731 .record_db_get("marker_by_version")
1732 .get_marker_value(object_id, &version, epoch_id),
1733 }
1734 }
1735
1736 fn try_get_latest_marker(
1737 &self,
1738 object_id: &ObjectId,
1739 epoch_id: EpochId,
1740 ) -> IotaResult<Option<(Version, MarkerValue)>> {
1741 match self.get_latest_marker_value_cache_only(object_id, epoch_id) {
1742 CacheResult::Hit((v, marker)) => Ok(Some((v, marker))),
1743 CacheResult::NegativeHit => {
1744 panic!("cannot have negative hit when getting latest marker")
1745 }
1746 CacheResult::Miss => self
1747 .record_db_get("marker_latest")
1748 .get_latest_marker(object_id, epoch_id),
1749 }
1750 }
1751
1752 fn try_get_lock(
1753 &self,
1754 obj_ref: ObjectReference,
1755 epoch_store: &AuthorityPerEpochStore,
1756 ) -> IotaLockResult {
1757 match self.get_object_by_id_cache_only("lock", &obj_ref.object_id) {
1758 CacheResult::Hit((_, obj)) => {
1759 let actual_objref = obj.object_ref();
1760 if obj_ref != actual_objref {
1761 Ok(ObjectLockStatus::LockedAtDifferentVersion {
1762 locked_ref: actual_objref,
1763 })
1764 } else {
1765 Ok(
1767 match self
1768 .object_locks
1769 .get_transaction_lock(&obj_ref, epoch_store)?
1770 {
1771 Some(tx_digest) => ObjectLockStatus::LockedToTx {
1772 locked_by_tx: tx_digest,
1773 },
1774 None => ObjectLockStatus::Initialized,
1775 },
1776 )
1777 }
1778 }
1779 CacheResult::NegativeHit => {
1780 Err(IotaError::from(UserInputError::ObjectNotFound {
1781 object_id: obj_ref.object_id,
1782 version: None,
1785 }))
1786 }
1787 CacheResult::Miss => self.record_db_get("lock").get_lock(obj_ref, epoch_store),
1788 }
1789 }
1790
1791 fn _try_get_live_objref(&self, object_id: ObjectId) -> IotaResult<ObjectReference> {
1792 let obj = self.get_object_impl("live_objref", &object_id)?.ok_or(
1793 UserInputError::ObjectNotFound {
1794 object_id,
1795 version: None,
1796 },
1797 )?;
1798 Ok(obj.object_ref())
1799 }
1800
1801 fn try_check_owned_objects_are_live(
1802 &self,
1803 owned_object_refs: &[ObjectReference],
1804 ) -> IotaResult {
1805 try_do_fallback_lookup(
1806 owned_object_refs,
1807 |obj_ref| match self.get_object_by_id_cache_only("object_is_live", &obj_ref.object_id) {
1808 CacheResult::Hit((version, obj)) => {
1809 if obj.object_ref() != *obj_ref {
1810 Err(UserInputError::ObjectVersionUnavailableForConsumption {
1811 provided_obj_ref: *obj_ref,
1812 current_version: version,
1813 }
1814 .into())
1815 } else {
1816 Ok(CacheResult::Hit(()))
1817 }
1818 }
1819 CacheResult::NegativeHit => Err(UserInputError::ObjectNotFound {
1820 object_id: obj_ref.object_id,
1821 version: None,
1822 }
1823 .into()),
1824 CacheResult::Miss => Ok(CacheResult::Miss),
1825 },
1826 |remaining| {
1827 self.record_db_multi_get("object_is_live", remaining.len())
1828 .check_owned_objects_are_live(remaining)?;
1829 Ok(vec![(); remaining.len()])
1830 },
1831 )?;
1832 Ok(())
1833 }
1834
1835 fn try_get_highest_pruned_checkpoint(&self) -> IotaResult<Option<CheckpointSequenceNumber>> {
1836 self.store
1837 .perpetual_tables
1838 .get_highest_pruned_checkpoint()
1839 .map_err(IotaError::from)
1840 }
1841
1842 fn notify_read_input_objects<'a>(
1843 &'a self,
1844 input_and_receiving_keys: &'a [InputKey],
1845 receiving_keys: &'a HashSet<InputKey>,
1846 epoch: EpochId,
1847 ) -> BoxFuture<'a, ()> {
1848 self.object_notify_read
1853 .read::<std::convert::Infallible>(
1854 "notify_read_input_objects",
1855 input_and_receiving_keys,
1856 move |keys| {
1857 Ok(self
1858 .multi_input_objects_available(keys, receiving_keys, epoch)
1859 .into_iter()
1860 .map(|available| if available { Some(()) } else { None })
1861 .collect::<Vec<_>>())
1862 },
1863 )
1864 .map(|_| ())
1865 .boxed()
1866 }
1867
1868 fn multi_input_objects_available_cache_only(&self, keys: &[InputKey]) -> Vec<bool> {
1869 keys.iter()
1870 .map(|key| {
1871 if key.is_cancelled() {
1872 true
1873 } else {
1874 match key {
1875 InputKey::VersionedObject { id, version } => matches!(
1876 self.get_object_by_key_cache_only(id, *version),
1877 CacheResult::Hit(_)
1878 ),
1879 InputKey::Package { id } => self.packages.contains_key(id),
1880 }
1881 }
1882 })
1883 .collect()
1884 }
1885}
1886
1887impl TransactionCacheRead for WritebackCache {
1888 #[instrument(level = "trace", skip_all)]
1889 fn try_multi_get_transaction_blocks(
1890 &self,
1891 digests: &[TransactionDigest],
1892 ) -> IotaResult<Vec<Option<Arc<VerifiedTransaction>>>> {
1893 let digests_and_tickets: Vec<_> = digests
1894 .iter()
1895 .map(|d| (*d, self.cached.transactions.get_ticket_for_read(d)))
1896 .collect();
1897 try_do_fallback_lookup(
1898 &digests_and_tickets,
1899 |(digest, _)| {
1900 self.metrics
1901 .record_cache_request("transaction_block", "uncommitted");
1902 if let Some(tx) = self.dirty.pending_transaction_writes.get(digest) {
1903 self.metrics
1904 .record_cache_hit("transaction_block", "uncommitted");
1905 return Ok(CacheResult::Hit(Some(tx.transaction.clone())));
1906 }
1907 self.metrics
1908 .record_cache_miss("transaction_block", "uncommitted");
1909
1910 self.metrics
1911 .record_cache_request("transaction_block", "committed");
1912 match self
1913 .cached
1914 .transactions
1915 .get(digest)
1916 .map(|l| l.lock().clone())
1917 {
1918 Some(PointCacheItem::Some(tx)) => {
1919 self.metrics
1920 .record_cache_hit("transaction_block", "committed");
1921 Ok(CacheResult::Hit(Some(tx)))
1922 }
1923 Some(PointCacheItem::None) => Ok(CacheResult::NegativeHit),
1924 None => {
1925 self.metrics
1926 .record_cache_miss("transaction_block", "committed");
1927
1928 Ok(CacheResult::Miss)
1929 }
1930 }
1931 },
1932 |remaining| {
1933 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
1934 let results: Vec<_> = self
1935 .record_db_multi_get("transaction_block", remaining.len())
1936 .multi_get_transaction_blocks(&remaining_digests)
1937 .map(|v| v.into_iter().map(|o| o.map(Arc::new)).collect())?;
1938 for ((digest, ticket), result) in remaining.iter().zip(results.iter()) {
1939 if result.is_none() {
1940 self.cached.transactions.insert(digest, None, *ticket).ok();
1941 }
1942 }
1943 Ok(results)
1944 },
1945 )
1946 }
1947
1948 #[instrument(level = "trace", skip_all)]
1949 fn try_multi_get_executed_effects_digests(
1950 &self,
1951 digests: &[TransactionDigest],
1952 ) -> IotaResult<Vec<Option<TransactionEffectsDigest>>> {
1953 let digests_and_tickets: Vec<_> = digests
1954 .iter()
1955 .map(|d| {
1956 (
1957 *d,
1958 self.cached.executed_effects_digests.get_ticket_for_read(d),
1959 )
1960 })
1961 .collect();
1962 try_do_fallback_lookup(
1963 &digests_and_tickets,
1964 |(digest, _)| {
1965 self.metrics
1966 .record_cache_request("executed_effects_digests", "uncommitted");
1967 if let Some(digest) = self.dirty.executed_effects_digests.get(digest) {
1968 self.metrics
1969 .record_cache_hit("executed_effects_digests", "uncommitted");
1970 return Ok(CacheResult::Hit(Some(*digest)));
1971 }
1972 self.metrics
1973 .record_cache_miss("executed_effects_digests", "uncommitted");
1974
1975 self.metrics
1976 .record_cache_request("executed_effects_digests", "committed");
1977 match self
1978 .cached
1979 .executed_effects_digests
1980 .get(digest)
1981 .map(|l| *l.lock())
1982 {
1983 Some(PointCacheItem::Some(digest)) => {
1984 self.metrics
1985 .record_cache_hit("executed_effects_digests", "committed");
1986 Ok(CacheResult::Hit(Some(digest)))
1987 }
1988 Some(PointCacheItem::None) => Ok(CacheResult::NegativeHit),
1989 None => {
1990 self.metrics
1991 .record_cache_miss("executed_effects_digests", "committed");
1992 Ok(CacheResult::Miss)
1993 }
1994 }
1995 },
1996 |remaining| {
1997 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
1998 let results = self
1999 .record_db_multi_get("executed_effects_digests", remaining.len())
2000 .multi_get_executed_effects_digests(&remaining_digests)?;
2001 for ((digest, ticket), result) in remaining.iter().zip(results.iter()) {
2002 if result.is_none() {
2003 self.cached
2004 .executed_effects_digests
2005 .insert(digest, None, *ticket)
2006 .ok();
2007 }
2008 }
2009 Ok(results)
2010 },
2011 )
2012 }
2013
2014 #[instrument(level = "trace", skip_all)]
2015 fn try_multi_get_effects(
2016 &self,
2017 digests: &[TransactionEffectsDigest],
2018 ) -> IotaResult<Vec<Option<TransactionEffects>>> {
2019 let digests_and_tickets: Vec<_> = digests
2020 .iter()
2021 .map(|d| (*d, self.cached.transaction_effects.get_ticket_for_read(d)))
2022 .collect();
2023 try_do_fallback_lookup(
2024 &digests_and_tickets,
2025 |(digest, _)| {
2026 self.metrics
2027 .record_cache_request("transaction_effects", "uncommitted");
2028 if let Some(effects) = self.dirty.transaction_effects.get(digest) {
2029 self.metrics
2030 .record_cache_hit("transaction_effects", "uncommitted");
2031 return Ok(CacheResult::Hit(Some(effects.clone())));
2032 }
2033 self.metrics
2034 .record_cache_miss("transaction_effects", "uncommitted");
2035
2036 self.metrics
2037 .record_cache_request("transaction_effects", "committed");
2038 match self
2039 .cached
2040 .transaction_effects
2041 .get(digest)
2042 .map(|l| l.lock().clone())
2043 {
2044 Some(PointCacheItem::Some(effects)) => {
2045 self.metrics
2046 .record_cache_hit("transaction_effects", "committed");
2047 Ok(CacheResult::Hit(Some((*effects).clone())))
2048 }
2049 Some(PointCacheItem::None) => Ok(CacheResult::NegativeHit),
2050 None => {
2051 self.metrics
2052 .record_cache_miss("transaction_effects", "committed");
2053 Ok(CacheResult::Miss)
2054 }
2055 }
2056 },
2057 |remaining| {
2058 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
2059 let results = self
2060 .record_db_multi_get("transaction_effects", remaining.len())
2061 .multi_get_effects(remaining_digests.iter())
2062 .expect("db error");
2063 for ((digest, ticket), result) in remaining.iter().zip(results.iter()) {
2064 if result.is_none() {
2065 self.cached
2066 .transaction_effects
2067 .insert(digest, None, *ticket)
2068 .ok();
2069 }
2070 }
2071 Ok(results)
2072 },
2073 )
2074 }
2075
2076 #[instrument(level = "trace", skip_all)]
2077 fn try_notify_read_executed_effects_digests<'a>(
2078 &'a self,
2079 task_name: &'static str,
2080 digests: &'a [TransactionDigest],
2081 ) -> BoxFuture<'a, IotaResult<Vec<TransactionEffectsDigest>>> {
2082 self.executed_effects_digests_notify_read
2083 .read(task_name, digests, |digests| {
2084 self.try_multi_get_executed_effects_digests(digests)
2085 })
2086 .boxed()
2087 }
2088
2089 #[instrument(level = "trace", skip_all)]
2090 fn try_multi_get_events(
2091 &self,
2092 tx_digests: &[TransactionDigest],
2093 ) -> IotaResult<Vec<Option<TransactionEvents>>> {
2094 fn map_events(events: TransactionEvents) -> Option<TransactionEvents> {
2095 if events.is_empty() {
2096 None
2097 } else {
2098 Some(events)
2099 }
2100 }
2101
2102 let digests_and_tickets: Vec<_> = tx_digests
2103 .iter()
2104 .map(|d| (*d, self.cached.transaction_events.get_ticket_for_read(d)))
2105 .collect();
2106 try_do_fallback_lookup(
2107 &digests_and_tickets,
2108 |(digest, _)| {
2109 self.metrics
2110 .record_cache_request("transaction_events", "uncommitted");
2111 if let Some(events) = self.dirty.transaction_events.get(digest).map(|e| e.clone()) {
2112 self.metrics
2113 .record_cache_hit("transaction_events", "uncommitted");
2114
2115 return Ok(CacheResult::Hit(map_events(events)));
2116 }
2117 self.metrics
2118 .record_cache_miss("transaction_events", "uncommitted");
2119
2120 self.metrics
2121 .record_cache_request("transaction_events", "committed");
2122 match self
2123 .cached
2124 .transaction_events
2125 .get(digest)
2126 .map(|l| l.lock().clone())
2127 {
2128 Some(PointCacheItem::Some(events)) => {
2129 self.metrics
2130 .record_cache_hit("transaction_events", "committed");
2131 Ok(CacheResult::Hit(map_events((*events).clone())))
2132 }
2133 Some(PointCacheItem::None) => Ok(CacheResult::NegativeHit),
2134 None => {
2135 self.metrics
2136 .record_cache_miss("transaction_events", "committed");
2137
2138 Ok(CacheResult::Miss)
2139 }
2140 }
2141 },
2142 |remaining| {
2143 let remaining_digests: Vec<_> = remaining.iter().map(|(d, _)| *d).collect();
2144 let results = self
2145 .store
2146 .multi_get_events(&remaining_digests)
2147 .expect("db error");
2148 for ((digest, ticket), result) in remaining.iter().zip(results.iter()) {
2149 if result.is_none() {
2150 self.cached
2151 .transaction_events
2152 .insert(digest, None, *ticket)
2153 .ok();
2154 }
2155 }
2156 Ok(results)
2157 },
2158 )
2159 }
2160}
2161
2162impl ExecutionCacheWrite for WritebackCache {
2163 #[cfg(test)]
2164 fn write_object_entry_for_test(&self, object: Object) {
2165 self.write_object_entry(&object.id(), object.version(), object.into());
2166 }
2167
2168 #[instrument(level = "debug", skip_all)]
2169 fn try_acquire_transaction_locks(
2170 &self,
2171 epoch_store: &AuthorityPerEpochStore,
2172 owned_input_objects: &[ObjectReference],
2173 transaction: VerifiedSignedTransaction,
2174 ) -> IotaResult {
2175 self.object_locks.acquire_transaction_locks(
2176 self,
2177 epoch_store,
2178 owned_input_objects,
2179 transaction,
2180 )
2181 }
2182
2183 fn validate_owned_object_versions(
2184 &self,
2185 owned_input_objects: &[ObjectReference],
2186 ) -> IotaResult {
2187 ObjectLocks::validate_owned_object_versions(self, owned_input_objects)
2188 }
2189
2190 fn try_write_transaction_outputs(
2191 &self,
2192 epoch_id: EpochId,
2193 tx_outputs: Arc<TransactionOutputs>,
2194 ) -> IotaResult {
2195 WritebackCache::write_transaction_outputs(self, epoch_id, tx_outputs)
2196 }
2197}
2198
2199impl CheckpointCache for WritebackCache {
2200 fn try_get_transaction_perpetual_checkpoint(
2201 &self,
2202 digest: &TransactionDigest,
2203 ) -> IotaResult<Option<(EpochId, CheckpointSequenceNumber)>> {
2204 self.store.get_transaction_perpetual_checkpoint(digest)
2205 }
2206
2207 fn try_multi_get_transactions_perpetual_checkpoints(
2208 &self,
2209 digests: &[TransactionDigest],
2210 ) -> IotaResult<Vec<Option<(EpochId, CheckpointSequenceNumber)>>> {
2211 self.store
2212 .multi_get_transactions_perpetual_checkpoints(digests)
2213 }
2214
2215 fn try_insert_finalized_transactions_perpetual_checkpoints(
2216 &self,
2217 digests: &[TransactionDigest],
2218 epoch: EpochId,
2219 sequence: CheckpointSequenceNumber,
2220 ) -> IotaResult {
2221 self.store
2222 .insert_finalized_transactions_perpetual_checkpoints(digests, epoch, sequence)
2223 }
2224}
2225
2226impl ExecutionCacheReconfigAPI for WritebackCache {
2227 fn try_insert_genesis_object(&self, object: Object) -> IotaResult {
2228 self.insert_genesis_object_impl(object)
2229 }
2230
2231 fn try_bulk_insert_genesis_objects(&self, objects: &[Object]) -> IotaResult {
2232 self.bulk_insert_genesis_objects_impl(objects)
2233 }
2234
2235 fn try_revert_state_update(&self, digest: &TransactionDigest) -> IotaResult {
2236 self.revert_state_update_impl(digest)
2237 }
2238
2239 fn try_set_epoch_start_configuration(
2240 &self,
2241 epoch_start_config: &EpochStartConfiguration,
2242 ) -> IotaResult {
2243 self.store.set_epoch_start_configuration(epoch_start_config)
2244 }
2245
2246 fn update_epoch_flags_metrics(&self, old: &[EpochFlag], new: &[EpochFlag]) {
2247 self.store.update_epoch_flags_metrics(old, new)
2248 }
2249
2250 fn clear_state_end_of_epoch(&self, execution_guard: &ExecutionLockWriteGuard<'_>) {
2251 self.clear_state_end_of_epoch_impl(execution_guard)
2252 }
2253
2254 fn try_expensive_check_iota_conservation(
2255 &self,
2256 old_epoch_store: &AuthorityPerEpochStore,
2257 epoch_supply_change: Option<i64>,
2258 ) -> IotaResult {
2259 self.store
2260 .expensive_check_iota_conservation(self, old_epoch_store, epoch_supply_change)
2261 }
2262}
2263
2264impl TestingAPI for WritebackCache {
2265 fn database_for_testing(&self) -> Arc<AuthorityStore> {
2266 self.store.clone()
2267 }
2268}
2269
2270impl ObjectStore for WritebackCache {
2271 fn try_get_object(&self, object_id: &ObjectId) -> StorageResult<Option<Object>> {
2272 ObjectCacheRead::try_get_object(self, object_id).map_err(StorageError::custom)
2273 }
2274
2275 fn try_get_object_by_key(
2276 &self,
2277 object_id: &ObjectId,
2278 version: iota_types::base_types::VersionNumber,
2279 ) -> StorageResult<Option<Object>> {
2280 ObjectCacheRead::try_get_object_by_key(self, object_id, version)
2281 .map_err(StorageError::custom)
2282 }
2283}
2284
2285impl ChildObjectResolver for WritebackCache {
2286 fn read_child_object(
2287 &self,
2288 parent: &ObjectId,
2289 child: &ObjectId,
2290 child_version_upper_bound: Version,
2291 ) -> IotaResult<Option<Object>> {
2292 let Some(child_object) =
2293 self.try_find_object_lt_or_eq_version(*child, child_version_upper_bound)?
2294 else {
2295 return Ok(None);
2296 };
2297
2298 let parent = *parent;
2299 if child_object.owner != Owner::Object(parent) {
2300 return Err(IotaError::InvalidChildObjectAccess {
2301 object: *child,
2302 given_parent: parent,
2303 actual_owner: child_object.owner,
2304 });
2305 }
2306 Ok(Some(child_object))
2307 }
2308
2309 fn get_object_received_at_version(
2310 &self,
2311 owner: &ObjectId,
2312 receiving_object_id: &ObjectId,
2313 receive_object_at_version: Version,
2314 epoch_id: EpochId,
2315 ) -> IotaResult<Option<Object>> {
2316 let Some(recv_object) = ObjectCacheRead::try_get_object_by_key(
2317 self,
2318 receiving_object_id,
2319 receive_object_at_version,
2320 )?
2321 else {
2322 return Ok(None);
2323 };
2324
2325 if recv_object.owner != Owner::Address((*owner).into())
2333 || self.try_have_received_object_at_version(
2334 receiving_object_id,
2335 receive_object_at_version,
2336 epoch_id,
2337 )?
2338 {
2339 return Ok(None);
2340 }
2341
2342 Ok(Some(recv_object))
2343 }
2344}
2345
2346impl BackingPackageStore for WritebackCache {
2347 fn get_package_object(&self, package_id: &ObjectId) -> IotaResult<Option<PackageObject>> {
2348 ObjectCacheRead::try_get_package_object(self, package_id)
2349 }
2350}
2351
2352impl GlobalStateHashStore for WritebackCache {
2353 fn get_root_state_hash_for_epoch(
2354 &self,
2355 epoch: EpochId,
2356 ) -> IotaResult<Option<(CheckpointSequenceNumber, GlobalStateHash)>> {
2357 self.store.get_root_state_hash_for_epoch(epoch)
2358 }
2359
2360 fn insert_state_hash_for_epoch(
2361 &self,
2362 epoch: EpochId,
2363 checkpoint_seq_num: &CheckpointSequenceNumber,
2364 acc: &GlobalStateHash,
2365 ) -> IotaResult {
2366 self.store
2367 .insert_state_hash_for_epoch(epoch, checkpoint_seq_num, acc)
2368 }
2369
2370 fn iter_live_object_set(&self) -> Box<dyn Iterator<Item = LiveObject> + '_> {
2371 assert!(
2375 self.dirty.is_empty(),
2376 "cannot iterate live object set with dirty data"
2377 );
2378 self.store.iter_live_object_set()
2379 }
2380
2381 fn iter_cached_live_object_set_for_testing(&self) -> Box<dyn Iterator<Item = LiveObject> + '_> {
2385 let iter = self.dirty.objects.iter();
2387 let mut dirty_objects = BTreeMap::new();
2388
2389 for obj in self.store.iter_live_object_set() {
2391 dirty_objects.insert(obj.object_id(), obj);
2392 }
2393
2394 for entry in iter {
2396 let id = *entry.key();
2397 let value = entry.value();
2398 match value.get_highest().unwrap() {
2399 (_, ObjectEntry::Object(object)) => {
2400 dirty_objects.insert(
2401 id,
2402 LiveObject {
2403 object: object.clone(),
2404 previous_transaction_checkpoint: None,
2408 },
2409 );
2410 }
2411 (_version, ObjectEntry::Wrapped) => {
2412 dirty_objects.remove(&id);
2413 }
2414 (_, ObjectEntry::Deleted) => {
2415 dirty_objects.remove(&id);
2416 }
2417 }
2418 }
2419
2420 Box::new(dirty_objects.into_values())
2421 }
2422}
2423
2424impl StateSyncAPI for WritebackCache {
2431 fn try_insert_transaction_and_effects(
2432 &self,
2433 transaction: &VerifiedTransaction,
2434 transaction_effects: &TransactionEffects,
2435 ) -> IotaResult {
2436 self.store
2437 .insert_transaction_and_effects(transaction, transaction_effects)?;
2438
2439 self.cached
2442 .transactions
2443 .insert(
2444 transaction.digest(),
2445 PointCacheItem::Some(Arc::new(transaction.clone())),
2446 Ticket::Write,
2447 )
2448 .ok();
2449 self.cached
2450 .transaction_effects
2451 .insert(
2452 &transaction_effects.digest(),
2453 PointCacheItem::Some(Arc::new(transaction_effects.clone())),
2454 Ticket::Write,
2455 )
2456 .ok();
2457
2458 Ok(())
2459 }
2460
2461 fn try_multi_insert_transaction_and_effects(
2462 &self,
2463 transactions_and_effects: &[VerifiedExecutionData],
2464 ) -> IotaResult {
2465 self.store
2466 .multi_insert_transaction_and_effects(transactions_and_effects.iter())?;
2467 for VerifiedExecutionData {
2468 transaction,
2469 effects,
2470 } in transactions_and_effects
2471 {
2472 self.cached
2473 .transactions
2474 .insert(
2475 transaction.digest(),
2476 PointCacheItem::Some(Arc::new(transaction.clone())),
2477 Ticket::Write,
2478 )
2479 .ok();
2480 self.cached
2481 .transaction_effects
2482 .insert(
2483 &effects.digest(),
2484 PointCacheItem::Some(Arc::new(effects.clone())),
2485 Ticket::Write,
2486 )
2487 .ok();
2488 }
2489
2490 Ok(())
2491 }
2492}
2493
2494#[cfg(test)]
2495impl WritebackCache {
2496 pub(super) fn object_locks_for_testing(&self) -> &ObjectLocks {
2497 &self.object_locks
2498 }
2499
2500 pub(super) fn write_object_for_testing(&self, object: Object) {
2501 let id = object.id();
2502 let version = object.version();
2503 self.write_object_entry(&id, version, ObjectEntry::Object(object));
2504 }
2505
2506 pub(super) fn write_marker_for_testing(
2507 &self,
2508 epoch_id: EpochId,
2509 object_key: &ObjectKey,
2510 marker_value: MarkerValue,
2511 ) {
2512 self.write_marker_value(epoch_id, object_key, marker_value);
2513 }
2514}