1use crate::drive::contract::DataContractFetchInfo;
2use dpp::data_contract::accessors::v0::DataContractV0Getters;
3use moka::ops::compute::Op;
4use moka::sync::Cache;
5use parking_lot::RwLock;
6use std::collections::HashSet;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::Arc;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub struct CommittedGeneration(u64);
17
18pub struct DataContractCache {
47 global_cache: Cache<[u8; 32], Arc<DataContractFetchInfo>>,
48 block_cache: Cache<[u8; 32], Arc<DataContractFetchInfo>>,
49 block_modified: RwLock<HashSet<[u8; 32]>>,
54 committed_generation: AtomicU64,
56}
57
58impl DataContractCache {
59 pub fn new(global_cache_max_capacity: u64, block_cache_max_capacity: u64) -> Self {
61 Self {
62 global_cache: Cache::new(global_cache_max_capacity),
63 block_cache: Cache::new(block_cache_max_capacity),
64 block_modified: RwLock::new(HashSet::new()),
65 committed_generation: AtomicU64::new(0),
66 }
67 }
68
69 pub fn committed_generation(&self) -> CommittedGeneration {
72 CommittedGeneration(self.committed_generation.load(Ordering::SeqCst))
73 }
74
75 pub fn insert_block(&self, fetch_info: Arc<DataContractFetchInfo>) {
86 let data_contract_id_bytes = fetch_info.contract.id().to_buffer();
87
88 self.block_cache
89 .entry(data_contract_id_bytes)
90 .and_compute_with(|existing| match existing {
91 Some(entry) if entry.value().contract.version() > fetch_info.contract.version() => {
92 Op::Nop
93 }
94 _ => Op::Put(Arc::clone(&fetch_info)),
95 });
96 }
97
98 pub fn insert_committed(
111 &self,
112 fetch_info: Arc<DataContractFetchInfo>,
113 observed: CommittedGeneration,
114 ) {
115 let data_contract_id_bytes = fetch_info.contract.id().to_buffer();
116
117 self.global_cache
118 .entry(data_contract_id_bytes)
119 .and_compute_with(|existing| {
120 if self.committed_generation.load(Ordering::SeqCst) != observed.0 {
121 return Op::Nop;
122 }
123 match existing {
124 Some(entry)
125 if entry.value().contract.version() > fetch_info.contract.version() =>
126 {
127 Op::Nop
128 }
129 _ => Op::Put(Arc::clone(&fetch_info)),
130 }
131 });
132 }
133
134 pub fn insert_rewritten(
141 &self,
142 fetch_info: Arc<DataContractFetchInfo>,
143 in_block_transaction: bool,
144 ) {
145 let contract_id = fetch_info.contract.id().to_buffer();
146 if in_block_transaction {
147 self.mark_modified_in_block(contract_id);
148 self.insert_block(fetch_info);
149 } else {
150 self.replace_committed(contract_id, Some(fetch_info));
151 }
152 }
153
154 pub fn replace_committed(
166 &self,
167 contract_id: [u8; 32],
168 fetch_info: Option<Arc<DataContractFetchInfo>>,
169 ) {
170 self.committed_generation.fetch_add(1, Ordering::SeqCst);
171 self.block_cache.remove(&contract_id);
172 self.global_cache
173 .entry(contract_id)
174 .and_compute_with(|_| match fetch_info {
175 Some(fetch_info) => Op::Put(fetch_info),
176 None => Op::Remove,
177 });
178 }
179
180 pub fn get(
188 &self,
189 contract_id: [u8; 32],
190 is_block_cache: bool,
191 ) -> Option<Arc<DataContractFetchInfo>> {
192 if is_block_cache {
193 if let Some(fetch_info) = self.block_cache.get(&contract_id) {
194 return Some(fetch_info);
195 }
196 if self.block_modified.read().contains(&contract_id) {
197 return None;
198 }
199 }
200
201 self.global_cache.get(&contract_id)
202 }
203
204 pub fn remove(&self, contract_id: [u8; 32]) {
206 self.block_cache.remove(&contract_id);
207 self.global_cache.remove(&contract_id);
208 }
209
210 pub fn mark_modified_in_block(&self, contract_id: [u8; 32]) {
217 self.block_modified.write().insert(contract_id);
218 }
219
220 pub fn is_modified_in_block(&self, contract_id: [u8; 32]) -> bool {
222 self.block_modified.read().contains(&contract_id)
223 }
224
225 pub fn drop_block_modified_entries(&self) {
233 for contract_id in self.block_modified.read().iter() {
234 self.block_cache.remove(contract_id);
235 }
236 }
237
238 pub fn merge_and_clear_block_cache(&self) {
260 self.committed_generation.fetch_add(1, Ordering::SeqCst);
264
265 let modified = std::mem::take(&mut *self.block_modified.write());
266 for contract_id in modified {
267 if !self.block_cache.contains_key(&contract_id) {
268 self.global_cache
269 .entry(contract_id)
270 .and_compute_with(|_| Op::Remove);
271 }
272 }
273
274 for (contract_id, fetch_info) in self.block_cache.iter() {
275 self.global_cache
276 .entry(*contract_id)
277 .and_compute_with(|_| Op::Put(fetch_info));
278 }
279 self.block_cache.invalidate_all();
280 }
281
282 pub fn clear_block_cache(&self) {
287 self.block_cache.invalidate_all();
288 self.block_modified.write().clear();
289 }
290
291 pub fn clear(&self) {
298 self.block_cache.invalidate_all();
299 self.global_cache.invalidate_all();
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306 use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters};
307 use dpp::fee::fee_result::FeeResult;
308 use dpp::version::PlatformVersion;
309 use std::sync::Barrier;
310 use std::thread;
311 use std::time::Duration;
312
313 fn same_contract_at_versions(
316 first: u32,
317 second: u32,
318 ) -> (Arc<DataContractFetchInfo>, Arc<DataContractFetchInfo>) {
319 let fetch_info = DataContractFetchInfo::dpns_contract_fixture(
320 PlatformVersion::latest().protocol_version,
321 );
322 let mut first_info = fetch_info.clone();
323 first_info.contract.set_version(first);
324 let mut second_info = fetch_info;
325 second_info.contract.set_version(second);
326 (Arc::new(first_info), Arc::new(second_info))
327 }
328
329 mod get {
330 use super::*;
331
332 #[test]
333 fn test_get_from_global_cache_when_block_cache_is_not_requested() {
334 let data_contract_cache = DataContractCache::new(10, 10);
335
336 let protocol_version = PlatformVersion::latest().protocol_version;
337
338 let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
340 protocol_version,
341 ));
342
343 let contract_id = fetch_info_global.contract.id().to_buffer();
344
345 data_contract_cache
346 .global_cache
347 .insert(contract_id, Arc::clone(&fetch_info_global));
348
349 let mut fetch_info_block =
351 DataContractFetchInfo::dpns_contract_fixture(protocol_version);
352
353 fetch_info_block.contract.increment_version();
354
355 let fetch_info_block_boxed = Arc::new(fetch_info_block);
356
357 data_contract_cache
358 .block_cache
359 .insert(contract_id, Arc::clone(&fetch_info_block_boxed));
360
361 let fetch_info_from_cache = data_contract_cache
362 .get(contract_id, false)
363 .expect("should be present");
364
365 assert_eq!(fetch_info_from_cache, fetch_info_global)
366 }
367
368 #[test]
369 fn test_get_from_global_cache_when_block_cache_does_not_have_contract() {
370 let data_contract_cache = DataContractCache::new(10, 10);
371
372 let protocol_version = PlatformVersion::latest().protocol_version;
373
374 let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
375 protocol_version,
376 ));
377
378 let contract_id = fetch_info_global.contract.id().to_buffer();
379
380 data_contract_cache
381 .global_cache
382 .insert(contract_id, Arc::clone(&fetch_info_global));
383
384 let fetch_info_from_cache = data_contract_cache
385 .get(contract_id, true)
386 .expect("should be present");
387
388 assert_eq!(fetch_info_from_cache, fetch_info_global)
389 }
390
391 #[test]
392 fn test_get_from_block_cache() {
393 let data_contract_cache = DataContractCache::new(10, 10);
394
395 let protocol_version = PlatformVersion::latest().protocol_version;
396
397 let fetch_info_block = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
398 protocol_version,
399 ));
400
401 let contract_id = fetch_info_block.contract.id().to_buffer();
402
403 data_contract_cache
404 .block_cache
405 .insert(contract_id, Arc::clone(&fetch_info_block));
406
407 let fetch_info_from_cache = data_contract_cache
408 .get(contract_id, true)
409 .expect("should be present");
410
411 assert_eq!(fetch_info_from_cache, fetch_info_block)
412 }
413
414 #[test]
420 fn test_get_in_block_does_not_fall_back_to_global_for_a_modified_contract() {
421 let data_contract_cache = DataContractCache::new(10, 10);
422
423 let (committed, _) = same_contract_at_versions(1, 2);
424 let contract_id = committed.contract.id().to_buffer();
425
426 data_contract_cache.mark_modified_in_block(contract_id);
427 data_contract_cache
428 .insert_committed(committed, data_contract_cache.committed_generation());
429
430 assert!(
431 data_contract_cache.get(contract_id, true).is_none(),
432 "a transactional read of a rewritten contract must not be served the committed copy"
433 );
434 }
435
436 #[test]
437 fn test_get_in_block_reads_block_cache_for_a_modified_contract() {
438 let data_contract_cache = DataContractCache::new(10, 10);
439
440 let (committed, rewritten) = same_contract_at_versions(1, 2);
441 let contract_id = committed.contract.id().to_buffer();
442
443 data_contract_cache.mark_modified_in_block(contract_id);
444 data_contract_cache.insert_block(rewritten);
445 data_contract_cache
446 .insert_committed(committed, data_contract_cache.committed_generation());
447
448 let cached = data_contract_cache
449 .get(contract_id, true)
450 .expect("should be present");
451 assert_eq!(cached.contract.version(), 2);
452 }
453
454 #[test]
456 fn test_get_committed_still_reads_global_for_a_modified_contract() {
457 let data_contract_cache = DataContractCache::new(10, 10);
458
459 let (committed, rewritten) = same_contract_at_versions(1, 2);
460 let contract_id = committed.contract.id().to_buffer();
461
462 data_contract_cache.mark_modified_in_block(contract_id);
463 data_contract_cache.insert_block(rewritten);
464 data_contract_cache
465 .insert_committed(committed, data_contract_cache.committed_generation());
466
467 let cached = data_contract_cache
468 .get(contract_id, false)
469 .expect("should be present");
470 assert_eq!(cached.contract.version(), 1);
471 }
472 }
473
474 mod insert_block {
475 use super::*;
476
477 #[test]
479 fn test_insert_does_not_overwrite_newer_version_with_older() {
480 let data_contract_cache = DataContractCache::new(10, 10);
481
482 let (stale, newer) = same_contract_at_versions(1, 2);
483 let contract_id = newer.contract.id().to_buffer();
484 data_contract_cache.insert_block(newer);
485
486 data_contract_cache.insert_block(stale);
487
488 let cached = data_contract_cache
489 .get(contract_id, true)
490 .expect("should be present");
491 assert_eq!(cached.contract.version(), 2);
492 }
493
494 #[test]
497 fn test_insert_overwrites_same_version() {
498 let data_contract_cache = DataContractCache::new(10, 10);
499
500 let (mut original, mut with_fee) = same_contract_at_versions(1, 1);
501 let contract_id = original.contract.id().to_buffer();
502 Arc::make_mut(&mut original).fee = None;
503 data_contract_cache.insert_block(original);
504
505 Arc::make_mut(&mut with_fee).fee = Some(FeeResult::new_from_processing_fee(1));
506 data_contract_cache.insert_block(with_fee);
507
508 let cached = data_contract_cache
509 .get(contract_id, true)
510 .expect("should be present");
511 assert!(cached.fee.is_some(), "same-version insert must overwrite");
512 }
513 }
514
515 mod insert_committed {
516 use super::*;
517
518 #[test]
519 fn test_insert_with_a_current_snapshot_is_stored() {
520 let data_contract_cache = DataContractCache::new(10, 10);
521
522 let (fetch_info, _) = same_contract_at_versions(1, 1);
523 let contract_id = fetch_info.contract.id().to_buffer();
524
525 let observed = data_contract_cache.committed_generation();
526 data_contract_cache.insert_committed(fetch_info, observed);
527
528 assert!(data_contract_cache.get(contract_id, false).is_some());
529 }
530
531 #[test]
535 fn test_insert_does_not_overwrite_newer_version_with_older() {
536 let data_contract_cache = DataContractCache::new(10, 10);
537
538 let (stale, newer) = same_contract_at_versions(1, 2);
539 let contract_id = newer.contract.id().to_buffer();
540 let observed = data_contract_cache.committed_generation();
541 data_contract_cache.insert_committed(newer, observed);
542
543 data_contract_cache.insert_committed(stale, observed);
544
545 let cached = data_contract_cache
546 .get(contract_id, false)
547 .expect("should be present");
548 assert_eq!(cached.contract.version(), 2);
549 }
550
551 #[test]
556 fn test_insert_with_a_snapshot_taken_before_a_promotion_is_dropped() {
557 let data_contract_cache = DataContractCache::new(10, 10);
558
559 let (stale, rewritten) = same_contract_at_versions(1, 2);
560 let contract_id = stale.contract.id().to_buffer();
561
562 let observed_before_the_block = data_contract_cache.committed_generation();
564
565 data_contract_cache.mark_modified_in_block(contract_id);
567 data_contract_cache.insert_block(rewritten);
568 data_contract_cache.merge_and_clear_block_cache();
569
570 data_contract_cache.insert_committed(stale, observed_before_the_block);
572
573 let cached = data_contract_cache
574 .get(contract_id, false)
575 .expect("should be present");
576 assert_eq!(
577 cached.contract.version(),
578 2,
579 "the promoted contract must survive a delayed stale insert"
580 );
581 }
582
583 #[test]
588 fn test_same_version_insert_with_a_snapshot_taken_before_a_promotion_is_dropped() {
589 let data_contract_cache = DataContractCache::new(10, 10);
590
591 let (mut stale, mut rewritten) = same_contract_at_versions(1, 1);
592 let contract_id = stale.contract.id().to_buffer();
593 Arc::make_mut(&mut stale).fee = None;
594 Arc::make_mut(&mut rewritten).fee = Some(FeeResult::new_from_processing_fee(1));
595
596 let observed_before_the_block = data_contract_cache.committed_generation();
597
598 data_contract_cache.mark_modified_in_block(contract_id);
601 data_contract_cache.merge_and_clear_block_cache();
602
603 data_contract_cache.insert_committed(stale, observed_before_the_block);
604
605 assert!(
606 data_contract_cache.get(contract_id, false).is_none(),
607 "a committed-state copy read before the commit must not enter the global cache after it"
608 );
609 }
610 }
611
612 mod replace_committed {
613 use super::*;
614
615 #[test]
619 fn test_a_reader_that_read_before_the_rewrite_cannot_insert_after_it() {
620 let data_contract_cache = DataContractCache::new(10, 10);
621 let (pre_write, rewritten) = same_contract_at_versions(1, 2);
622 let contract_id = pre_write.contract.id().to_buffer();
623
624 let observed_before_the_rewrite = data_contract_cache.committed_generation();
625 data_contract_cache.replace_committed(contract_id, Some(rewritten));
626 data_contract_cache
627 .insert_committed(Arc::clone(&pre_write), observed_before_the_rewrite);
628 assert_eq!(
629 data_contract_cache
630 .get(contract_id, false)
631 .expect("should be present")
632 .contract
633 .version(),
634 2
635 );
636
637 let observed_before_the_eviction = data_contract_cache.committed_generation();
638 data_contract_cache.replace_committed(contract_id, None);
639 data_contract_cache.insert_committed(pre_write, observed_before_the_eviction);
640 assert!(
641 data_contract_cache.get(contract_id, false).is_none(),
642 "an evicted contract must not be re-admitted by a reader that read before the rewrite"
643 );
644 }
645
646 #[test]
647 fn test_replaces_the_block_cache_copy_as_well() {
648 let data_contract_cache = DataContractCache::new(10, 10);
649 let (stale, rewritten) = same_contract_at_versions(1, 2);
650 let contract_id = stale.contract.id().to_buffer();
651
652 data_contract_cache.insert_block(stale);
653 data_contract_cache.replace_committed(contract_id, Some(rewritten));
654
655 assert_eq!(
656 data_contract_cache
657 .get(contract_id, true)
658 .expect("should be present")
659 .contract
660 .version(),
661 2
662 );
663 }
664 }
665
666 mod remove {
667 use super::*;
668
669 #[test]
670 fn test_remove_clears_global_cache_entry() {
671 let data_contract_cache = DataContractCache::new(10, 10);
672
673 let protocol_version = PlatformVersion::latest().protocol_version;
674 let fetch_info = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
675 protocol_version,
676 ));
677 let contract_id = fetch_info.contract.id().to_buffer();
678
679 data_contract_cache
680 .insert_committed(fetch_info, data_contract_cache.committed_generation());
681 data_contract_cache.remove(contract_id);
682
683 assert!(data_contract_cache.get(contract_id, false).is_none());
684 }
685
686 #[test]
687 fn test_remove_clears_entry_from_both_caches() {
688 let data_contract_cache = DataContractCache::new(10, 10);
689
690 let protocol_version = PlatformVersion::latest().protocol_version;
691 let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
692 protocol_version,
693 ));
694 let contract_id = fetch_info_global.contract.id().to_buffer();
695 let fetch_info_block = Arc::clone(&fetch_info_global);
696
697 data_contract_cache.insert_committed(
698 fetch_info_global,
699 data_contract_cache.committed_generation(),
700 );
701 data_contract_cache.insert_block(fetch_info_block);
702 data_contract_cache.remove(contract_id);
703
704 assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
705 assert!(data_contract_cache.global_cache.get(&contract_id).is_none());
706 }
707 }
708
709 mod drop_block_modified_entries {
710 use super::*;
711
712 #[test]
715 fn test_drops_modified_entries_from_block_cache_and_keeps_them_marked() {
716 let data_contract_cache = DataContractCache::new(10, 10);
717
718 let (committed, rewritten) = same_contract_at_versions(1, 2);
719 let contract_id = committed.contract.id().to_buffer();
720
721 data_contract_cache
722 .insert_committed(committed, data_contract_cache.committed_generation());
723 data_contract_cache.mark_modified_in_block(contract_id);
724 data_contract_cache.insert_block(rewritten);
725
726 data_contract_cache.drop_block_modified_entries();
727
728 assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
729 assert!(data_contract_cache.is_modified_in_block(contract_id));
730 assert!(
731 data_contract_cache.get(contract_id, true).is_none(),
732 "the transactional read must go to state, not to the committed copy"
733 );
734 }
735
736 #[test]
737 fn test_leaves_unmodified_block_entries_in_place() {
738 let data_contract_cache = DataContractCache::new(10, 10);
739
740 let (only_read, _) = same_contract_at_versions(1, 1);
741 let contract_id = only_read.contract.id().to_buffer();
742 data_contract_cache.insert_block(only_read);
743
744 data_contract_cache.drop_block_modified_entries();
745
746 assert!(data_contract_cache.get(contract_id, true).is_some());
747 }
748 }
749
750 mod merge_and_clear_block_cache {
751 use super::*;
752
753 #[test]
754 fn test_merge_moves_block_items_to_global_cache() {
755 let data_contract_cache = DataContractCache::new(10, 10);
756
757 let protocol_version = PlatformVersion::latest().protocol_version;
758 let fetch_info = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
759 protocol_version,
760 ));
761 let contract_id = fetch_info.contract.id().to_buffer();
762
763 data_contract_cache.insert_block(fetch_info);
764 data_contract_cache.merge_and_clear_block_cache();
765
766 assert!(data_contract_cache.global_cache.get(&contract_id).is_some());
767 }
768
769 #[test]
770 fn test_merge_clears_block_cache() {
771 let data_contract_cache = DataContractCache::new(10, 10);
772
773 let protocol_version = PlatformVersion::latest().protocol_version;
774 let fetch_info = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
775 protocol_version,
776 ));
777 let contract_id = fetch_info.contract.id().to_buffer();
778
779 data_contract_cache.insert_block(fetch_info);
780 data_contract_cache.merge_and_clear_block_cache();
781
782 assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
783 }
784
785 #[test]
788 fn test_merge_promotes_a_rewritten_contract_over_the_committed_copy() {
789 let data_contract_cache = DataContractCache::new(10, 10);
790
791 let (committed, rewritten) = same_contract_at_versions(1, 2);
792 let contract_id = committed.contract.id().to_buffer();
793
794 data_contract_cache.mark_modified_in_block(contract_id);
795 data_contract_cache.insert_block(rewritten);
796 data_contract_cache
797 .insert_committed(committed, data_contract_cache.committed_generation());
798
799 data_contract_cache.merge_and_clear_block_cache();
800
801 let cached = data_contract_cache
802 .get(contract_id, false)
803 .expect("should be present");
804 assert_eq!(cached.contract.version(), 2);
805 }
806
807 #[test]
811 fn test_merge_removes_the_committed_copy_of_a_rewritten_contract_missing_from_the_block_cache(
812 ) {
813 let data_contract_cache = DataContractCache::new(10, 10);
814
815 let (committed, _) = same_contract_at_versions(1, 2);
816 let contract_id = committed.contract.id().to_buffer();
817
818 data_contract_cache.mark_modified_in_block(contract_id);
819 data_contract_cache
820 .insert_committed(committed, data_contract_cache.committed_generation());
821
822 data_contract_cache.merge_and_clear_block_cache();
823
824 assert!(data_contract_cache.get(contract_id, false).is_none());
825 }
826
827 #[test]
828 fn test_merge_clears_modified_marks() {
829 let data_contract_cache = DataContractCache::new(10, 10);
830
831 let (_, rewritten) = same_contract_at_versions(1, 2);
832 let contract_id = rewritten.contract.id().to_buffer();
833
834 data_contract_cache.mark_modified_in_block(contract_id);
835 data_contract_cache.insert_block(rewritten);
836
837 data_contract_cache.merge_and_clear_block_cache();
838
839 assert!(!data_contract_cache.is_modified_in_block(contract_id));
840 }
841
842 #[test]
850 fn test_merge_overwrites_a_committed_insert_approved_before_the_promotion() {
851 let data_contract_cache = Arc::new(DataContractCache::new(10, 10));
852 let (committed, rewritten) = same_contract_at_versions(1, 2);
853 let contract_id = committed.contract.id().to_buffer();
854
855 data_contract_cache.mark_modified_in_block(contract_id);
856 data_contract_cache.insert_block(rewritten);
857
858 let final_version =
859 promote_while_a_committed_insert_is_paused(&data_contract_cache, committed);
860
861 assert_eq!(
862 final_version,
863 Some(2),
864 "the promoted contract must survive a committed insert approved before the promotion"
865 );
866 }
867
868 #[test]
872 fn test_merge_removal_wins_over_a_committed_insert_approved_before_the_promotion() {
873 let data_contract_cache = Arc::new(DataContractCache::new(10, 10));
874 let (committed, _) = same_contract_at_versions(1, 2);
875 let contract_id = committed.contract.id().to_buffer();
876
877 data_contract_cache.mark_modified_in_block(contract_id);
878
879 let final_version =
880 promote_while_a_committed_insert_is_paused(&data_contract_cache, committed);
881
882 assert_eq!(
883 final_version, None,
884 "a rewritten contract absent from the block cache must not be resurrected by a committed insert approved before the promotion"
885 );
886 }
887
888 fn promote_while_a_committed_insert_is_paused(
893 data_contract_cache: &Arc<DataContractCache>,
894 committed: Arc<DataContractFetchInfo>,
895 ) -> Option<u32> {
896 let contract_id = committed.contract.id().to_buffer();
897 let reader_is_inside = Arc::new(Barrier::new(2));
898 let release_reader = Arc::new(Barrier::new(2));
899
900 let reader = {
901 let data_contract_cache = Arc::clone(data_contract_cache);
902 let reader_is_inside = Arc::clone(&reader_is_inside);
903 let release_reader = Arc::clone(&release_reader);
904 thread::spawn(move || {
905 data_contract_cache
906 .global_cache
907 .entry(contract_id)
908 .and_compute_with(|_| {
909 reader_is_inside.wait();
910 release_reader.wait();
911 Op::Put(committed)
912 });
913 })
914 };
915 reader_is_inside.wait();
916
917 let promotion = {
918 let data_contract_cache = Arc::clone(data_contract_cache);
919 thread::spawn(move || data_contract_cache.merge_and_clear_block_cache())
920 };
921 thread::sleep(Duration::from_millis(100));
924 release_reader.wait();
925
926 reader.join().expect("the reader must finish");
927 promotion.join().expect("the promotion must finish");
928
929 data_contract_cache
930 .get(contract_id, false)
931 .map(|fetch_info| fetch_info.contract.version())
932 }
933
934 #[test]
935 fn test_merge_advances_the_committed_generation() {
936 let data_contract_cache = DataContractCache::new(10, 10);
937
938 let before = data_contract_cache.committed_generation();
939 data_contract_cache.merge_and_clear_block_cache();
940
941 assert_ne!(before, data_contract_cache.committed_generation());
942 }
943 }
944
945 mod clear {
946 use super::*;
947
948 #[test]
949 fn test_clear_empties_global_and_block_caches() {
950 let data_contract_cache = DataContractCache::new(10, 10);
951
952 let protocol_version = PlatformVersion::latest().protocol_version;
953 let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
954 protocol_version,
955 ));
956 let contract_id = fetch_info_global.contract.id().to_buffer();
957 let fetch_info_block = Arc::clone(&fetch_info_global);
958
959 data_contract_cache.insert_committed(
960 fetch_info_global,
961 data_contract_cache.committed_generation(),
962 );
963 data_contract_cache.insert_block(fetch_info_block);
964 data_contract_cache.clear();
965
966 assert!(data_contract_cache.get(contract_id, false).is_none());
967 assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
968 }
969
970 #[test]
973 fn test_clear_keeps_modified_marks() {
974 let data_contract_cache = DataContractCache::new(10, 10);
975
976 let (_, rewritten) = same_contract_at_versions(1, 2);
977 let contract_id = rewritten.contract.id().to_buffer();
978
979 data_contract_cache.mark_modified_in_block(contract_id);
980 data_contract_cache.insert_block(rewritten);
981 data_contract_cache.clear();
982
983 assert!(data_contract_cache.is_modified_in_block(contract_id));
984 }
985
986 #[test]
987 fn test_clear_block_cache_clears_modified_marks() {
988 let data_contract_cache = DataContractCache::new(10, 10);
989
990 let (_, rewritten) = same_contract_at_versions(1, 2);
991 let contract_id = rewritten.contract.id().to_buffer();
992
993 data_contract_cache.mark_modified_in_block(contract_id);
994 data_contract_cache.insert_block(rewritten);
995 data_contract_cache.clear_block_cache();
996
997 assert!(!data_contract_cache.is_modified_in_block(contract_id));
998 assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
999 }
1000 }
1001}