1use lru::LruCache;
10use parking_lot::RwLock;
11use std::num::NonZeroUsize;
12use std::sync::Arc;
13use std::time::{Duration, Instant};
14
15#[derive(Clone)]
17struct CacheEntry<V> {
18 value: V,
19 inserted_at: Instant,
20 ttl: Duration,
21}
22
23impl<V> CacheEntry<V> {
24 fn new(value: V, ttl: Duration) -> Self {
25 Self {
26 value,
27 inserted_at: Instant::now(),
28 ttl,
29 }
30 }
31
32 fn is_expired(&self) -> bool {
33 self.inserted_at.elapsed() > self.ttl
34 }
35
36 fn get(&self) -> Option<&V> {
37 if self.is_expired() {
38 None
39 } else {
40 Some(&self.value)
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct CacheConfig {
48 pub max_entries: usize,
50 pub storage_ttl: Duration,
52 pub balance_ttl: Duration,
54 pub metadata_ttl: Duration,
56 pub rpc_ttl: Duration,
58 pub enable_stats: bool,
60}
61
62impl Default for CacheConfig {
63 fn default() -> Self {
64 Self {
65 max_entries: 1000,
66 storage_ttl: Duration::from_secs(30),
67 balance_ttl: Duration::from_secs(10),
68 metadata_ttl: Duration::from_secs(300),
69 rpc_ttl: Duration::from_secs(60),
70 enable_stats: true,
71 }
72 }
73}
74
75impl CacheConfig {
76 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn with_max_entries(mut self, max_entries: usize) -> Self {
83 self.max_entries = max_entries;
84 self
85 }
86
87 pub fn with_storage_ttl(mut self, ttl: Duration) -> Self {
89 self.storage_ttl = ttl;
90 self
91 }
92
93 pub fn with_balance_ttl(mut self, ttl: Duration) -> Self {
95 self.balance_ttl = ttl;
96 self
97 }
98
99 pub fn with_metadata_ttl(mut self, ttl: Duration) -> Self {
101 self.metadata_ttl = ttl;
102 self
103 }
104
105 pub fn with_rpc_ttl(mut self, ttl: Duration) -> Self {
107 self.rpc_ttl = ttl;
108 self
109 }
110}
111
112pub struct Cache {
114 config: CacheConfig,
115 storage_cache: Arc<RwLock<LruCache<String, CacheEntry<Vec<u8>>>>>,
116 balance_cache: Arc<RwLock<LruCache<String, CacheEntry<u128>>>>,
117 metadata_cache: Arc<RwLock<LruCache<String, CacheEntry<String>>>>,
118 rpc_cache: Arc<RwLock<LruCache<String, CacheEntry<String>>>>,
119 stats: Arc<RwLock<CacheStats>>,
120}
121
122impl Cache {
123 pub fn new() -> Self {
125 Self::with_config(CacheConfig::default())
126 }
127
128 pub fn with_config(config: CacheConfig) -> Self {
130 let capacity = NonZeroUsize::new(config.max_entries)
131 .expect("CacheConfig.max_entries must be greater than 0");
132
133 Self {
134 storage_cache: Arc::new(RwLock::new(LruCache::new(capacity))),
135 balance_cache: Arc::new(RwLock::new(LruCache::new(capacity))),
136 metadata_cache: Arc::new(RwLock::new(LruCache::new(capacity))),
137 rpc_cache: Arc::new(RwLock::new(LruCache::new(capacity))),
138 stats: Arc::new(RwLock::new(CacheStats::default())),
139 config,
140 }
141 }
142
143 pub fn get_storage(&self, key: &str) -> Option<Vec<u8>> {
145 let mut cache = self.storage_cache.write();
146 if let Some(entry) = cache.get(key) {
147 if let Some(value) = entry.get() {
148 self.record_hit();
149 return Some(value.clone());
150 } else {
151 cache.pop(key);
153 }
154 }
155 self.record_miss();
156 None
157 }
158
159 pub fn put_storage(&self, key: String, value: Vec<u8>) {
161 let entry = CacheEntry::new(value, self.config.storage_ttl);
162 self.storage_cache.write().put(key, entry);
163 }
164
165 pub fn get_balance(&self, address: &str) -> Option<u128> {
167 let mut cache = self.balance_cache.write();
168 if let Some(entry) = cache.get(address) {
169 if let Some(value) = entry.get() {
170 self.record_hit();
171 return Some(*value);
172 } else {
173 cache.pop(address);
174 }
175 }
176 self.record_miss();
177 None
178 }
179
180 pub fn put_balance(&self, address: String, balance: u128) {
182 let entry = CacheEntry::new(balance, self.config.balance_ttl);
183 self.balance_cache.write().put(address, entry);
184 }
185
186 pub fn get_metadata(&self, key: &str) -> Option<String> {
188 let mut cache = self.metadata_cache.write();
189 if let Some(entry) = cache.get(key) {
190 if let Some(value) = entry.get() {
191 self.record_hit();
192 return Some(value.clone());
193 } else {
194 cache.pop(key);
195 }
196 }
197 self.record_miss();
198 None
199 }
200
201 pub fn put_metadata(&self, key: String, metadata: String) {
203 let entry = CacheEntry::new(metadata, self.config.metadata_ttl);
204 self.metadata_cache.write().put(key, entry);
205 }
206
207 pub fn get_rpc(&self, key: &str) -> Option<String> {
209 let mut cache = self.rpc_cache.write();
210 if let Some(entry) = cache.get(key) {
211 if let Some(value) = entry.get() {
212 self.record_hit();
213 return Some(value.clone());
214 } else {
215 cache.pop(key);
216 }
217 }
218 self.record_miss();
219 None
220 }
221
222 pub fn put_rpc(&self, key: String, response: String) {
224 let entry = CacheEntry::new(response, self.config.rpc_ttl);
225 self.rpc_cache.write().put(key, entry);
226 }
227
228 pub fn clear(&self) {
230 self.storage_cache.write().clear();
231 self.balance_cache.write().clear();
232 self.metadata_cache.write().clear();
233 self.rpc_cache.write().clear();
234 self.stats.write().reset();
235 }
236
237 pub fn clear_expired(&self) {
239 {
241 let mut cache = self.storage_cache.write();
242 let keys: Vec<String> = cache
243 .iter()
244 .filter_map(|(k, v)| {
245 if v.is_expired() {
246 Some(k.clone())
247 } else {
248 None
249 }
250 })
251 .collect();
252 for key in keys {
253 cache.pop(&key);
254 }
255 }
256
257 {
259 let mut cache = self.balance_cache.write();
260 let keys: Vec<String> = cache
261 .iter()
262 .filter_map(|(k, v)| {
263 if v.is_expired() {
264 Some(k.clone())
265 } else {
266 None
267 }
268 })
269 .collect();
270 for key in keys {
271 cache.pop(&key);
272 }
273 }
274
275 {
277 let mut cache = self.metadata_cache.write();
278 let keys: Vec<String> = cache
279 .iter()
280 .filter_map(|(k, v)| {
281 if v.is_expired() {
282 Some(k.clone())
283 } else {
284 None
285 }
286 })
287 .collect();
288 for key in keys {
289 cache.pop(&key);
290 }
291 }
292
293 {
295 let mut cache = self.rpc_cache.write();
296 let keys: Vec<String> = cache
297 .iter()
298 .filter_map(|(k, v)| {
299 if v.is_expired() {
300 Some(k.clone())
301 } else {
302 None
303 }
304 })
305 .collect();
306 for key in keys {
307 cache.pop(&key);
308 }
309 }
310 }
311
312 pub fn stats(&self) -> CacheStats {
314 let mut stats = self.stats.read().clone();
315
316 stats.storage_size = self.storage_cache.read().len();
318 stats.balance_size = self.balance_cache.read().len();
319 stats.metadata_size = self.metadata_cache.read().len();
320 stats.rpc_size = self.rpc_cache.read().len();
321
322 stats
323 }
324
325 fn record_hit(&self) {
327 if self.config.enable_stats {
328 self.stats.write().hits += 1;
329 }
330 }
331
332 fn record_miss(&self) {
334 if self.config.enable_stats {
335 self.stats.write().misses += 1;
336 }
337 }
338
339 pub fn total_size(&self) -> usize {
341 self.storage_cache.read().len()
342 + self.balance_cache.read().len()
343 + self.metadata_cache.read().len()
344 + self.rpc_cache.read().len()
345 }
346}
347
348impl Default for Cache {
349 fn default() -> Self {
350 Self::new()
351 }
352}
353
354#[derive(Debug, Clone, Default)]
356pub struct CacheStats {
357 pub hits: u64,
359 pub misses: u64,
361 pub storage_size: usize,
363 pub balance_size: usize,
365 pub metadata_size: usize,
367 pub rpc_size: usize,
369}
370
371impl CacheStats {
372 pub fn hit_rate(&self) -> f64 {
374 let total = self.hits + self.misses;
375 if total == 0 {
376 0.0
377 } else {
378 (self.hits as f64 / total as f64) * 100.0
379 }
380 }
381
382 pub fn total_entries(&self) -> usize {
384 self.storage_size + self.balance_size + self.metadata_size + self.rpc_size
385 }
386
387 fn reset(&mut self) {
389 self.hits = 0;
390 self.misses = 0;
391 self.storage_size = 0;
392 self.balance_size = 0;
393 self.metadata_size = 0;
394 self.rpc_size = 0;
395 }
396}
397
398impl std::fmt::Display for CacheStats {
399 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
400 write!(
401 f,
402 "Cache Stats: {} hits, {} misses ({:.2}% hit rate), {} total entries",
403 self.hits,
404 self.misses,
405 self.hit_rate(),
406 self.total_entries()
407 )
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn test_cache_creation() {
417 let cache = Cache::new();
418 let stats = cache.stats();
419
420 assert_eq!(stats.hits, 0);
421 assert_eq!(stats.misses, 0);
422 assert_eq!(stats.total_entries(), 0);
423 }
424
425 #[test]
426 fn test_storage_cache() {
427 let cache = Cache::new();
428
429 assert_eq!(cache.get_storage("key1"), None);
431
432 cache.put_storage("key1".to_string(), vec![1, 2, 3]);
434
435 assert_eq!(cache.get_storage("key1"), Some(vec![1, 2, 3]));
437
438 let stats = cache.stats();
439 assert_eq!(stats.hits, 1);
440 assert_eq!(stats.misses, 1);
441 assert_eq!(stats.hit_rate(), 50.0);
442 }
443
444 #[test]
445 fn test_balance_cache() {
446 let cache = Cache::new();
447
448 cache.put_balance("addr1".to_string(), 1_000_000);
449 assert_eq!(cache.get_balance("addr1"), Some(1_000_000));
450
451 cache.put_balance("addr2".to_string(), 2_000_000);
452 assert_eq!(cache.get_balance("addr2"), Some(2_000_000));
453
454 let stats = cache.stats();
455 assert_eq!(stats.balance_size, 2);
456 }
457
458 #[test]
459 fn test_metadata_cache() {
460 let cache = Cache::new();
461
462 cache.put_metadata("pallet1".to_string(), "metadata".to_string());
463 assert_eq!(cache.get_metadata("pallet1"), Some("metadata".to_string()));
464 }
465
466 #[test]
467 fn test_rpc_cache() {
468 let cache = Cache::new();
469
470 cache.put_rpc("method1".to_string(), "response".to_string());
471 assert_eq!(cache.get_rpc("method1"), Some("response".to_string()));
472 }
473
474 #[test]
475 fn test_cache_clear() {
476 let cache = Cache::new();
477
478 cache.put_storage("key1".to_string(), vec![1, 2, 3]);
479 cache.put_balance("addr1".to_string(), 1_000_000);
480
481 assert!(cache.total_size() > 0);
482
483 cache.clear();
484
485 assert_eq!(cache.total_size(), 0);
486 let stats = cache.stats();
487 assert_eq!(stats.hits, 0);
488 assert_eq!(stats.misses, 0);
489 }
490
491 #[test]
492 fn test_cache_expiration() {
493 let config = CacheConfig::new().with_storage_ttl(Duration::from_millis(10));
494
495 let cache = Cache::with_config(config);
496
497 cache.put_storage("key1".to_string(), vec![1, 2, 3]);
498
499 assert_eq!(cache.get_storage("key1"), Some(vec![1, 2, 3]));
501
502 std::thread::sleep(Duration::from_millis(20));
504
505 assert_eq!(cache.get_storage("key1"), None);
507 }
508
509 #[test]
510 fn test_lru_eviction() {
511 let config = CacheConfig::new().with_max_entries(2);
512 let cache = Cache::with_config(config);
513
514 cache.put_storage("key1".to_string(), vec![1]);
515 cache.put_storage("key2".to_string(), vec![2]);
516 cache.put_storage("key3".to_string(), vec![3]); let stats = cache.stats();
520 assert_eq!(stats.storage_size, 2);
521 }
522}