drive/cache/
data_contract.rs

1use crate::drive::contract::DataContractFetchInfo;
2use dpp::data_contract::accessors::v0::DataContractV0Getters;
3use moka::sync::Cache;
4use std::sync::Arc;
5
6/// DataContract cache that handles both global and block data
7pub struct DataContractCache {
8    global_cache: Cache<[u8; 32], Arc<DataContractFetchInfo>>,
9    block_cache: Cache<[u8; 32], Arc<DataContractFetchInfo>>,
10}
11
12impl DataContractCache {
13    /// Create a new DataContract cache instance
14    pub fn new(global_cache_max_capacity: u64, block_cache_max_capacity: u64) -> Self {
15        Self {
16            global_cache: Cache::new(global_cache_max_capacity),
17            block_cache: Cache::new(block_cache_max_capacity),
18        }
19    }
20
21    /// Inserts DataContract to block cache
22    /// otherwise to goes to global cache
23    pub fn insert(&self, fetch_info: Arc<DataContractFetchInfo>, is_block_cache: bool) {
24        let data_contract_id_bytes = fetch_info.contract.id().to_buffer();
25
26        if is_block_cache {
27            self.block_cache.insert(data_contract_id_bytes, fetch_info);
28        } else {
29            self.global_cache.insert(data_contract_id_bytes, fetch_info);
30        }
31    }
32
33    /// Tries to get a data contract from block cache if present
34    /// if block cache doesn't have the contract
35    /// then it tries get the contract from global cache
36    pub fn get(
37        &self,
38        contract_id: [u8; 32],
39        is_block_cache: bool,
40    ) -> Option<Arc<DataContractFetchInfo>> {
41        let maybe_fetch_info = if is_block_cache {
42            self.block_cache.get(&contract_id)
43        } else {
44            None
45        };
46
47        maybe_fetch_info.or_else(|| self.global_cache.get(&contract_id))
48    }
49
50    /// Remove contract from both block and global cache
51    pub fn remove(&self, contract_id: [u8; 32]) {
52        self.block_cache.remove(&contract_id);
53        self.global_cache.remove(&contract_id);
54    }
55
56    /// Move block cache to global cache
57    pub fn merge_and_clear_block_cache(&self) {
58        for (contract_id, fetch_info) in self.block_cache.into_iter() {
59            self.global_cache
60                .insert(Arc::unwrap_or_clone(contract_id), fetch_info);
61        }
62        self.clear_block_cache();
63    }
64
65    /// Clear block cache
66    pub fn clear_block_cache(&self) {
67        self.block_cache.invalidate_all();
68    }
69
70    /// Clear cache
71    pub fn clear(&self) {
72        self.block_cache.invalidate_all();
73        self.global_cache.invalidate_all();
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use dpp::version::PlatformVersion;
81
82    mod get {
83        use super::*;
84        use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters};
85
86        #[test]
87        fn test_get_from_global_cache_when_block_cache_is_not_requested() {
88            let data_contract_cache = DataContractCache::new(10, 10);
89
90            let protocol_version = PlatformVersion::latest().protocol_version;
91
92            // Create global contract
93            let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
94                protocol_version,
95            ));
96
97            let contract_id = fetch_info_global.contract.id().to_buffer();
98
99            data_contract_cache
100                .global_cache
101                .insert(contract_id, Arc::clone(&fetch_info_global));
102
103            // Create transactional contract with a new version
104            let mut fetch_info_block =
105                DataContractFetchInfo::dpns_contract_fixture(protocol_version);
106
107            fetch_info_block.contract.increment_version();
108
109            let fetch_info_block_boxed = Arc::new(fetch_info_block);
110
111            data_contract_cache
112                .block_cache
113                .insert(contract_id, Arc::clone(&fetch_info_block_boxed));
114
115            let fetch_info_from_cache = data_contract_cache
116                .get(contract_id, false)
117                .expect("should be present");
118
119            assert_eq!(fetch_info_from_cache, fetch_info_global)
120        }
121
122        #[test]
123        fn test_get_from_global_cache_when_block_cache_does_not_have_contract() {
124            let data_contract_cache = DataContractCache::new(10, 10);
125
126            let protocol_version = PlatformVersion::latest().protocol_version;
127
128            let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
129                protocol_version,
130            ));
131
132            let contract_id = fetch_info_global.contract.id().to_buffer();
133
134            data_contract_cache
135                .global_cache
136                .insert(contract_id, Arc::clone(&fetch_info_global));
137
138            let fetch_info_from_cache = data_contract_cache
139                .get(contract_id, true)
140                .expect("should be present");
141
142            assert_eq!(fetch_info_from_cache, fetch_info_global)
143        }
144
145        #[test]
146        fn test_get_from_block_cache() {
147            let data_contract_cache = DataContractCache::new(10, 10);
148
149            let protocol_version = PlatformVersion::latest().protocol_version;
150
151            let fetch_info_block = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
152                protocol_version,
153            ));
154
155            let contract_id = fetch_info_block.contract.id().to_buffer();
156
157            data_contract_cache
158                .block_cache
159                .insert(contract_id, Arc::clone(&fetch_info_block));
160
161            let fetch_info_from_cache = data_contract_cache
162                .get(contract_id, true)
163                .expect("should be present");
164
165            assert_eq!(fetch_info_from_cache, fetch_info_block)
166        }
167    }
168
169    mod remove {
170        use super::*;
171
172        #[test]
173        fn test_remove_clears_global_cache_entry() {
174            let data_contract_cache = DataContractCache::new(10, 10);
175
176            let protocol_version = PlatformVersion::latest().protocol_version;
177            let fetch_info = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
178                protocol_version,
179            ));
180            let contract_id = fetch_info.contract.id().to_buffer();
181
182            data_contract_cache.insert(fetch_info, false);
183            data_contract_cache.remove(contract_id);
184
185            assert!(data_contract_cache.get(contract_id, false).is_none());
186        }
187
188        #[test]
189        fn test_remove_clears_entry_from_both_caches() {
190            let data_contract_cache = DataContractCache::new(10, 10);
191
192            let protocol_version = PlatformVersion::latest().protocol_version;
193            let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
194                protocol_version,
195            ));
196            let contract_id = fetch_info_global.contract.id().to_buffer();
197            let fetch_info_block = Arc::clone(&fetch_info_global);
198
199            data_contract_cache.insert(fetch_info_global, false);
200            data_contract_cache.insert(fetch_info_block, true);
201            data_contract_cache.remove(contract_id);
202
203            assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
204            assert!(data_contract_cache.global_cache.get(&contract_id).is_none());
205        }
206    }
207
208    mod merge_and_clear_block_cache {
209        use super::*;
210
211        #[test]
212        fn test_merge_moves_block_items_to_global_cache() {
213            let data_contract_cache = DataContractCache::new(10, 10);
214
215            let protocol_version = PlatformVersion::latest().protocol_version;
216            let fetch_info = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
217                protocol_version,
218            ));
219            let contract_id = fetch_info.contract.id().to_buffer();
220
221            data_contract_cache.insert(fetch_info, true);
222            data_contract_cache.merge_and_clear_block_cache();
223
224            assert!(data_contract_cache.global_cache.get(&contract_id).is_some());
225        }
226
227        #[test]
228        fn test_merge_clears_block_cache() {
229            let data_contract_cache = DataContractCache::new(10, 10);
230
231            let protocol_version = PlatformVersion::latest().protocol_version;
232            let fetch_info = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
233                protocol_version,
234            ));
235            let contract_id = fetch_info.contract.id().to_buffer();
236
237            data_contract_cache.insert(fetch_info, true);
238            data_contract_cache.merge_and_clear_block_cache();
239
240            assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
241        }
242    }
243
244    mod clear {
245        use super::*;
246
247        #[test]
248        fn test_clear_empties_global_and_block_caches() {
249            let data_contract_cache = DataContractCache::new(10, 10);
250
251            let protocol_version = PlatformVersion::latest().protocol_version;
252            let fetch_info_global = Arc::new(DataContractFetchInfo::dpns_contract_fixture(
253                protocol_version,
254            ));
255            let contract_id = fetch_info_global.contract.id().to_buffer();
256            let fetch_info_block = Arc::clone(&fetch_info_global);
257
258            data_contract_cache.insert(fetch_info_global, false);
259            data_contract_cache.insert(fetch_info_block, true);
260            data_contract_cache.clear();
261
262            assert!(data_contract_cache.get(contract_id, false).is_none());
263            assert!(data_contract_cache.block_cache.get(&contract_id).is_none());
264        }
265    }
266}