walletkit_core/storage/cache/
mod.rs1use std::path::Path;
4
5use rusqlite::Connection;
6
7use crate::storage::error::StorageResult;
8use crate::storage::lock::StorageLockGuard;
9
10mod maintenance;
11mod merkle;
12mod nullifiers;
13mod schema;
14mod session;
15mod util;
16
17#[derive(Debug)]
22pub struct CacheDb {
23 conn: Connection,
24}
25
26impl CacheDb {
27 pub fn new(
36 path: &Path,
37 k_intermediate: [u8; 32],
38 _lock: &StorageLockGuard,
39 ) -> StorageResult<Self> {
40 let conn = maintenance::open_or_rebuild(path, k_intermediate)?;
41 Ok(Self { conn })
42 }
43
44 pub fn merkle_cache_get(&self, valid_until: u64) -> StorageResult<Option<Vec<u8>>> {
53 merkle::get(&self.conn, valid_until)
54 }
55
56 #[allow(clippy::needless_pass_by_value)]
65 pub fn merkle_cache_put(
66 &mut self,
67 _lock: &StorageLockGuard,
68 proof_bytes: Vec<u8>,
69 now: u64,
70 ttl_seconds: u64,
71 ) -> StorageResult<()> {
72 merkle::put(&self.conn, proof_bytes.as_ref(), now, ttl_seconds)
73 }
74
75 pub fn session_key_get(&self, rp_id: [u8; 32]) -> StorageResult<Option<[u8; 32]>> {
86 session::get(&self.conn, rp_id)
87 }
88
89 pub fn session_key_put(
97 &mut self,
98 _lock: &StorageLockGuard,
99 rp_id: [u8; 32],
100 k_session: [u8; 32],
101 ttl_seconds: u64,
102 ) -> StorageResult<()> {
103 session::put(&self.conn, rp_id, k_session, ttl_seconds)
104 }
105
106 pub fn is_nullifier_replay(
115 &self,
116 nullifier: [u8; 32],
117 now: u64,
118 ) -> StorageResult<bool> {
119 nullifiers::is_nullifier_replay(&self.conn, nullifier, now)
120 }
121
122 pub fn replay_guard_set(
129 &mut self,
130 _lock: &StorageLockGuard,
131 nullifier: [u8; 32],
132 now: u64,
133 ) -> StorageResult<()> {
134 nullifiers::replay_guard_set(&mut self.conn, nullifier, now)
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::storage::lock::StorageLock;
142 use std::fs;
143 use std::path::PathBuf;
144 use std::time::Duration;
145 use uuid::Uuid;
146
147 fn temp_cache_path() -> PathBuf {
148 let mut path = std::env::temp_dir();
149 path.push(format!("walletkit-cache-{}.sqlite", Uuid::new_v4()));
150 path
151 }
152
153 fn cleanup_cache_files(path: &Path) {
154 let _ = fs::remove_file(path);
155 let wal_path = path.with_extension("sqlite-wal");
156 let shm_path = path.with_extension("sqlite-shm");
157 let _ = fs::remove_file(wal_path);
158 let _ = fs::remove_file(shm_path);
159 }
160
161 fn temp_lock_path() -> PathBuf {
162 let mut path = std::env::temp_dir();
163 path.push(format!("walletkit-cache-lock-{}.lock", Uuid::new_v4()));
164 path
165 }
166
167 fn cleanup_lock_file(path: &Path) {
168 let _ = fs::remove_file(path);
169 }
170
171 #[test]
172 fn test_cache_create_and_open() {
173 let path = temp_cache_path();
174 let key = [0x11u8; 32];
175 let lock_path = temp_lock_path();
176 let lock = StorageLock::open(&lock_path).expect("open lock");
177 let guard = lock.lock().expect("lock");
178 let db = CacheDb::new(&path, key, &guard).expect("create cache");
179 drop(db);
180 CacheDb::new(&path, key, &guard).expect("open cache");
181 cleanup_cache_files(&path);
182 cleanup_lock_file(&lock_path);
183 }
184
185 #[test]
186 fn test_cache_rebuild_on_corruption() {
187 let path = temp_cache_path();
188 let key = [0x22u8; 32];
189 let lock_path = temp_lock_path();
190 let lock = StorageLock::open(&lock_path).expect("open lock");
191 let guard = lock.lock().expect("lock");
192 let mut db = CacheDb::new(&path, key, &guard).expect("create cache");
193 let rp_id = [0x01u8; 32];
194 let k_session = [0x02u8; 32];
195 db.session_key_put(&guard, rp_id, k_session, 1000)
196 .expect("put session key");
197 drop(db);
198
199 fs::write(&path, b"corrupt").expect("corrupt cache file");
200
201 let db = CacheDb::new(&path, key, &guard).expect("rebuild cache");
202 let value = db.session_key_get(rp_id).expect("get session key");
203 assert!(value.is_none());
204 cleanup_cache_files(&path);
205 cleanup_lock_file(&lock_path);
206 }
207
208 #[test]
209 fn test_merkle_cache_ttl() {
210 let path = temp_cache_path();
211 let key = [0x33u8; 32];
212 let lock_path = temp_lock_path();
213 let lock = StorageLock::open(&lock_path).expect("open lock");
214 let guard = lock.lock().expect("lock");
215 let mut db = CacheDb::new(&path, key, &guard).expect("create cache");
216 db.merkle_cache_put(&guard, vec![1, 2, 3], 100, 10)
217 .expect("put merkle proof");
218 let valid_until = 105;
219 let hit = db.merkle_cache_get(valid_until).expect("get merkle proof");
220 assert!(hit.is_some());
221 let miss = db.merkle_cache_get(111).expect("get merkle proof");
222 assert!(miss.is_none());
223 cleanup_cache_files(&path);
224 cleanup_lock_file(&lock_path);
225 }
226
227 #[test]
228 fn test_session_cache_ttl() {
229 let path = temp_cache_path();
230 let key = [0x44u8; 32];
231 let lock_path = temp_lock_path();
232 let lock = StorageLock::open(&lock_path).expect("open lock");
233 let guard = lock.lock().expect("lock");
234 let mut db = CacheDb::new(&path, key, &guard).expect("create cache");
235 let rp_id = [0x55u8; 32];
236 let k_session = [0x66u8; 32];
237 db.session_key_put(&guard, rp_id, k_session, 1)
238 .expect("put session key");
239 let hit = db.session_key_get(rp_id).expect("get session key");
240 assert!(hit.is_some());
241 std::thread::sleep(Duration::from_secs(2));
242 let miss = db.session_key_get(rp_id).expect("get session key");
243 assert!(miss.is_none());
244 cleanup_cache_files(&path);
245 cleanup_lock_file(&lock_path);
246 }
247}