saorsa_node/payment/
cache.rs1use lru::LruCache;
7use parking_lot::Mutex;
8use std::num::NonZeroUsize;
9use std::sync::Arc;
10
11pub type XorName = [u8; 32];
14
15const DEFAULT_CACHE_CAPACITY: usize = 100_000;
17
18#[derive(Clone)]
23pub struct VerifiedCache {
24 inner: Arc<Mutex<LruCache<XorName, ()>>>,
25 stats: Arc<Mutex<CacheStats>>,
26}
27
28#[derive(Debug, Default, Clone)]
30pub struct CacheStats {
31 pub hits: u64,
33 pub misses: u64,
35 pub additions: u64,
37}
38
39impl CacheStats {
40 #[must_use]
42 #[allow(clippy::cast_precision_loss)]
43 pub fn hit_rate(&self) -> f64 {
44 let total = self.hits + self.misses;
45 if total == 0 {
46 0.0
47 } else {
48 (self.hits as f64 / total as f64) * 100.0
49 }
50 }
51}
52
53impl VerifiedCache {
54 #[must_use]
56 pub fn new() -> Self {
57 Self::with_capacity(DEFAULT_CACHE_CAPACITY)
58 }
59
60 #[must_use]
64 pub fn with_capacity(capacity: usize) -> Self {
65 let effective_capacity = capacity.max(1);
67 let cap = NonZeroUsize::new(effective_capacity).unwrap_or(NonZeroUsize::MIN);
70 Self {
71 inner: Arc::new(Mutex::new(LruCache::new(cap))),
72 stats: Arc::new(Mutex::new(CacheStats::default())),
73 }
74 }
75
76 #[must_use]
80 pub fn contains(&self, xorname: &XorName) -> bool {
81 let found = self.inner.lock().get(xorname).is_some();
82
83 let mut stats = self.stats.lock();
84 if found {
85 stats.hits += 1;
86 } else {
87 stats.misses += 1;
88 }
89 drop(stats);
90
91 found
92 }
93
94 pub fn insert(&self, xorname: XorName) {
98 self.inner.lock().put(xorname, ());
99 self.stats.lock().additions += 1;
100 }
101
102 #[must_use]
104 pub fn stats(&self) -> CacheStats {
105 self.stats.lock().clone()
106 }
107
108 #[must_use]
110 pub fn len(&self) -> usize {
111 self.inner.lock().len()
112 }
113
114 #[must_use]
116 pub fn is_empty(&self) -> bool {
117 self.inner.lock().is_empty()
118 }
119
120 pub fn clear(&self) {
122 self.inner.lock().clear();
123 }
124}
125
126impl Default for VerifiedCache {
127 fn default() -> Self {
128 Self::new()
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_cache_basic_operations() {
138 let cache = VerifiedCache::new();
139
140 let xorname1 = [1u8; 32];
141 let xorname2 = [2u8; 32];
142
143 assert!(cache.is_empty());
145 assert!(!cache.contains(&xorname1));
146
147 cache.insert(xorname1);
149 assert!(cache.contains(&xorname1));
150 assert!(!cache.contains(&xorname2));
151 assert_eq!(cache.len(), 1);
152
153 cache.insert(xorname2);
155 assert!(cache.contains(&xorname1));
156 assert!(cache.contains(&xorname2));
157 assert_eq!(cache.len(), 2);
158 }
159
160 #[test]
161 fn test_cache_stats() {
162 let cache = VerifiedCache::new();
163 let xorname = [1u8; 32];
164
165 assert!(!cache.contains(&xorname));
167 let stats = cache.stats();
168 assert_eq!(stats.misses, 1);
169 assert_eq!(stats.hits, 0);
170
171 cache.insert(xorname);
173 let stats = cache.stats();
174 assert_eq!(stats.additions, 1);
175
176 assert!(cache.contains(&xorname));
178 let stats = cache.stats();
179 assert_eq!(stats.hits, 1);
180 assert_eq!(stats.misses, 1);
181
182 assert!((stats.hit_rate() - 50.0).abs() < 0.01);
184 }
185
186 #[test]
187 fn test_cache_lru_eviction() {
188 let cache = VerifiedCache::with_capacity(2);
190
191 let xorname1 = [1u8; 32];
192 let xorname2 = [2u8; 32];
193 let xorname3 = [3u8; 32];
194
195 cache.insert(xorname1);
196 cache.insert(xorname2);
197 assert_eq!(cache.len(), 2);
198
199 cache.insert(xorname3);
201 assert_eq!(cache.len(), 2);
202 assert!(!cache.contains(&xorname1)); }
205
206 #[test]
207 fn test_cache_clear() {
208 let cache = VerifiedCache::new();
209
210 cache.insert([1u8; 32]);
211 cache.insert([2u8; 32]);
212 assert_eq!(cache.len(), 2);
213
214 cache.clear();
215 assert!(cache.is_empty());
216 }
217}