drive/cache/
data_contract.rs1use crate::drive::contract::DataContractFetchInfo;
2use dpp::data_contract::accessors::v0::DataContractV0Getters;
3use moka::ops::compute::Op;
4use moka::sync::Cache;
5use std::sync::Arc;
6
7pub struct DataContractCache {
9 global_cache: Cache<[u8; 32], Arc<DataContractFetchInfo>>,
10 block_cache: Cache<[u8; 32], Arc<DataContractFetchInfo>>,
11}
12
13impl DataContractCache {
14 pub fn new(global_cache_max_capacity: u64, block_cache_max_capacity: u64) -> Self {
16 Self {
17 global_cache: Cache::new(global_cache_max_capacity),
18 block_cache: Cache::new(block_cache_max_capacity),
19 }
20 }
21
22 pub fn insert(&self, fetch_info: Arc<DataContractFetchInfo>, is_block_cache: bool) {
43 let data_contract_id_bytes = fetch_info.contract.id().to_buffer();
44
45 let cache = if is_block_cache {
46 &self.block_cache
47 } else {
48 &self.global_cache
49 };
50
51 cache
52 .entry(data_contract_id_bytes)
53 .and_compute_with(|existing| match existing {
54 Some(entry) if entry.value().contract.version() > fetch_info.contract.version() => {
55 Op::Nop
56 }
57 _ => Op::Put(Arc::clone(&fetch_info)),
58 });
59 }
60
61 pub fn get(
65 &self,
66 contract_id: [u8; 32],
67 is_block_cache: bool,
68 ) -> Option<Arc<DataContractFetchInfo>> {
69 let maybe_fetch_info = if is_block_cache {
70 self.block_cache.get(&contract_id)
71 } else {
72 None
73 };
74
75 maybe_fetch_info.or_else(|| self.global_cache.get(&contract_id))
76 }
77
78 pub fn remove(&self, contract_id: [u8; 32]) {
80 self.block_cache.remove(&contract_id);
81 self.global_cache.remove(&contract_id);
82 }
83
84 pub fn merge_and_clear_block_cache(&self) {
86 for (contract_id, fetch_info) in self.block_cache.into_iter() {
87 self.global_cache
88 .insert(Arc::unwrap_or_clone(contract_id), fetch_info);
89 }
90 self.clear_block_cache();
91 }
92
93 pub fn clear_block_cache(&self) {
95 self.block_cache.invalidate_all();
96 }
97
98 pub fn clear(&self) {
100 self.block_cache.invalidate_all();
101 self.global_cache.invalidate_all();
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use dpp::version::PlatformVersion;
109
110 mod get {
111 use super::*;
112 use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters};
113
114 #[test]
115 fn test_get_from_global_cache_when_block_cache_is_not_requested() {
116 let data_contract_cache = DataContractCache::new(10, 10);
117
118 let protocol_version = PlatformVersion::latest().protocol_version;
119
120 let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
122 protocol_version,
123 ));
124
125 let contract_id = fetch_info_global.contract.id().to_buffer();
126
127 data_contract_cache
128 .global_cache
129 .insert(contract_id, Arc::clone(&fetch_info_global));
130
131 let mut fetch_info_block =
133 DataContractFetchInfo::dpns_contract_fixture(protocol_version);
134
135 fetch_info_block.contract.increment_version();
136
137 let fetch_info_block_boxed = Arc::new(fetch_info_block);
138
139 data_contract_cache
140 .block_cache
141 .insert(contract_id, Arc::clone(&fetch_info_block_boxed));
142
143 let fetch_info_from_cache = data_contract_cache
144 .get(contract_id, false)
145 .expect("should be present");
146
147 assert_eq!(fetch_info_from_cache, fetch_info_global)
148 }
149
150 #[test]
151 fn test_get_from_global_cache_when_block_cache_does_not_have_contract() {
152 let data_contract_cache = DataContractCache::new(10, 10);
153
154 let protocol_version = PlatformVersion::latest().protocol_version;
155
156 let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
157 protocol_version,
158 ));
159
160 let contract_id = fetch_info_global.contract.id().to_buffer();
161
162 data_contract_cache
163 .global_cache
164 .insert(contract_id, Arc::clone(&fetch_info_global));
165
166 let fetch_info_from_cache = data_contract_cache
167 .get(contract_id, true)
168 .expect("should be present");
169
170 assert_eq!(fetch_info_from_cache, fetch_info_global)
171 }
172
173 #[test]
174 fn test_get_from_block_cache() {
175 let data_contract_cache = DataContractCache::new(10, 10);
176
177 let protocol_version = PlatformVersion::latest().protocol_version;
178
179 let fetch_info_block = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
180 protocol_version,
181 ));
182
183 let contract_id = fetch_info_block.contract.id().to_buffer();
184
185 data_contract_cache
186 .block_cache
187 .insert(contract_id, Arc::clone(&fetch_info_block));
188
189 let fetch_info_from_cache = data_contract_cache
190 .get(contract_id, true)
191 .expect("should be present");
192
193 assert_eq!(fetch_info_from_cache, fetch_info_block)
194 }
195 }
196
197 mod insert {
198 use super::*;
199 use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters};
200
201 fn same_contract_at_versions(
205 first: u32,
206 second: u32,
207 ) -> (Arc<DataContractFetchInfo>, Arc<DataContractFetchInfo>) {
208 let fetch_info = DataContractFetchInfo::dpns_contract_fixture(
209 PlatformVersion::latest().protocol_version,
210 );
211 let mut first_info = fetch_info.clone();
212 first_info.contract.set_version(first);
213 let mut second_info = fetch_info;
214 second_info.contract.set_version(second);
215 (Arc::new(first_info), Arc::new(second_info))
216 }
217
218 #[test]
224 fn test_insert_does_not_overwrite_newer_version_with_older() {
225 let data_contract_cache = DataContractCache::new(10, 10);
226
227 let (stale, newer) = same_contract_at_versions(1, 2);
228 let contract_id = newer.contract.id().to_buffer();
229 data_contract_cache.insert(newer, false);
230
231 data_contract_cache.insert(stale, false);
233
234 let cached = data_contract_cache
235 .get(contract_id, false)
236 .expect("should be present");
237 assert_eq!(cached.contract.version(), 2);
238 }
239
240 #[test]
243 fn test_insert_overwrites_same_version() {
244 let data_contract_cache = DataContractCache::new(10, 10);
245
246 let (mut original, mut with_fee) = same_contract_at_versions(1, 1);
247 let contract_id = original.contract.id().to_buffer();
248 Arc::make_mut(&mut original).fee = None;
249 data_contract_cache.insert(original, false);
250
251 Arc::make_mut(&mut with_fee).fee =
252 Some(dpp::fee::fee_result::FeeResult::new_from_processing_fee(1));
253 data_contract_cache.insert(with_fee, false);
254
255 let cached = data_contract_cache
256 .get(contract_id, false)
257 .expect("should be present");
258 assert!(cached.fee.is_some(), "same-version insert must overwrite");
259 }
260
261 #[test]
267 fn test_delayed_stale_insert_after_promotion_does_not_stick() {
268 let data_contract_cache = DataContractCache::new(10, 10);
269
270 let (stale, migrated) = same_contract_at_versions(1, 2);
273 let contract_id = stale.contract.id().to_buffer();
274
275 data_contract_cache.insert(migrated, true);
277
278 data_contract_cache.merge_and_clear_block_cache();
280
281 data_contract_cache.insert(stale, false);
283
284 let cached = data_contract_cache
285 .get(contract_id, false)
286 .expect("should be present");
287 assert_eq!(
288 cached.contract.version(),
289 2,
290 "the promoted migrated contract must survive a delayed stale insert"
291 );
292 }
293 }
294
295 mod remove {
296 use super::*;
297
298 #[test]
299 fn test_remove_clears_global_cache_entry() {
300 let data_contract_cache = DataContractCache::new(10, 10);
301
302 let protocol_version = PlatformVersion::latest().protocol_version;
303 let fetch_info = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
304 protocol_version,
305 ));
306 let contract_id = fetch_info.contract.id().to_buffer();
307
308 data_contract_cache.insert(fetch_info, false);
309 data_contract_cache.remove(contract_id);
310
311 assert!(data_contract_cache.get(contract_id, false).is_none());
312 }
313
314 #[test]
315 fn test_remove_clears_entry_from_both_caches() {
316 let data_contract_cache = DataContractCache::new(10, 10);
317
318 let protocol_version = PlatformVersion::latest().protocol_version;
319 let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
320 protocol_version,
321 ));
322 let contract_id = fetch_info_global.contract.id().to_buffer();
323 let fetch_info_block = Arc::clone(&fetch_info_global);
324
325 data_contract_cache.insert(fetch_info_global, false);
326 data_contract_cache.insert(fetch_info_block, true);
327 data_contract_cache.remove(contract_id);
328
329 assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
330 assert!(data_contract_cache.global_cache.get(&contract_id).is_none());
331 }
332 }
333
334 mod merge_and_clear_block_cache {
335 use super::*;
336
337 #[test]
338 fn test_merge_moves_block_items_to_global_cache() {
339 let data_contract_cache = DataContractCache::new(10, 10);
340
341 let protocol_version = PlatformVersion::latest().protocol_version;
342 let fetch_info = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
343 protocol_version,
344 ));
345 let contract_id = fetch_info.contract.id().to_buffer();
346
347 data_contract_cache.insert(fetch_info, true);
348 data_contract_cache.merge_and_clear_block_cache();
349
350 assert!(data_contract_cache.global_cache.get(&contract_id).is_some());
351 }
352
353 #[test]
354 fn test_merge_clears_block_cache() {
355 let data_contract_cache = DataContractCache::new(10, 10);
356
357 let protocol_version = PlatformVersion::latest().protocol_version;
358 let fetch_info = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
359 protocol_version,
360 ));
361 let contract_id = fetch_info.contract.id().to_buffer();
362
363 data_contract_cache.insert(fetch_info, true);
364 data_contract_cache.merge_and_clear_block_cache();
365
366 assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
367 }
368 }
369
370 mod clear {
371 use super::*;
372
373 #[test]
374 fn test_clear_empties_global_and_block_caches() {
375 let data_contract_cache = DataContractCache::new(10, 10);
376
377 let protocol_version = PlatformVersion::latest().protocol_version;
378 let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
379 protocol_version,
380 ));
381 let contract_id = fetch_info_global.contract.id().to_buffer();
382 let fetch_info_block = Arc::clone(&fetch_info_global);
383
384 data_contract_cache.insert(fetch_info_global, false);
385 data_contract_cache.insert(fetch_info_block, true);
386 data_contract_cache.clear();
387
388 assert!(data_contract_cache.get(contract_id, false).is_none());
389 assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
390 }
391 }
392}