walletkit_core/storage/cache/
mod.rs1use std::path::Path;
4
5use crate::storage::error::StorageResult;
6use secrecy::SecretBox;
7use walletkit_db::Vault;
8
9mod maintenance;
10mod merkle;
11mod nullifiers;
12mod schema;
13mod session;
14mod util;
15
16#[derive(Debug)]
25pub struct CacheDb {
26 vault: Vault,
27}
28
29impl CacheDb {
30 pub fn new(
39 path: &Path,
40 k_intermediate: &SecretBox<[u8; 32]>,
41 ) -> StorageResult<Self> {
42 let vault = maintenance::open_or_rebuild(path, k_intermediate)?;
43 Ok(Self { vault })
44 }
45
46 pub fn merkle_cache_get(&self, valid_until: u64) -> StorageResult<Option<Vec<u8>>> {
55 merkle::get(self.vault.connection(), valid_until)
56 }
57
58 pub fn merkle_cache_put(
65 &self,
66 proof_bytes: &[u8],
67 now: u64,
68 ttl_seconds: u64,
69 ) -> StorageResult<()> {
70 merkle::put(self.vault.connection(), proof_bytes, now, ttl_seconds)
71 }
72
73 pub fn session_seed_get(
81 &self,
82 oprf_seed: [u8; 32],
83 now: u64,
84 ) -> StorageResult<Option<[u8; 32]>> {
85 session::get(self.vault.connection(), oprf_seed, now)
86 }
87
88 pub fn session_seed_put(
94 &self,
95 oprf_seed: [u8; 32],
96 session_id_r_seed: [u8; 32],
97 now: u64,
98 ttl_seconds: u64,
99 ) -> StorageResult<()> {
100 session::put(
101 self.vault.connection(),
102 oprf_seed,
103 session_id_r_seed,
104 now,
105 ttl_seconds,
106 )
107 }
108
109 pub fn is_nullifier_replay(
120 &self,
121 nullifier: [u8; 32],
122 now: u64,
123 ) -> StorageResult<bool> {
124 nullifiers::is_nullifier_replay(self.vault.connection(), nullifier, now)
125 }
126
127 pub fn replay_guard_set(&self, nullifier: [u8; 32], now: u64) -> StorageResult<()> {
134 nullifiers::replay_guard_set(self.vault.connection(), nullifier, now)
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use secrecy::SecretBox;
142 use std::fs;
143 use std::path::PathBuf;
144 use uuid::Uuid;
145
146 fn temp_cache_path() -> PathBuf {
147 let mut path = std::env::temp_dir();
148 path.push(format!("walletkit-cache-{}.sqlite", Uuid::new_v4()));
149 path
150 }
151
152 fn cleanup_cache_files(path: &Path) {
153 let _ = fs::remove_file(path);
154 let _ = fs::remove_file(path.with_extension("sqlite-wal"));
155 let _ = fs::remove_file(path.with_extension("sqlite-shm"));
156 }
157
158 fn temp_lock_path() -> PathBuf {
159 let mut path = std::env::temp_dir();
160 path.push(format!("walletkit-cache-lock-{}.lock", Uuid::new_v4()));
161 path
162 }
163
164 fn cleanup_lock_file(path: &Path) {
165 let _ = fs::remove_file(path);
166 }
167
168 #[test]
169 fn test_cache_create_and_open() {
170 let path = temp_cache_path();
171 let key = SecretBox::init_with(|| [0x11u8; 32]);
172 let lock_path = temp_lock_path();
173 let db = CacheDb::new(&path, &key).expect("create cache");
174 drop(db);
175 CacheDb::new(&path, &key).expect("open cache");
176 cleanup_cache_files(&path);
177 cleanup_lock_file(&lock_path);
178 }
179
180 #[test]
181 fn test_cache_rebuild_on_corruption() {
182 let path = temp_cache_path();
183 let key = SecretBox::init_with(|| [0x22u8; 32]);
184 let lock_path = temp_lock_path();
185 let db = CacheDb::new(&path, &key).expect("create cache");
186 let oprf_seed = [0x01u8; 32];
187 let r_seed = [0x02u8; 32];
188 let now = 1_000;
189 db.session_seed_put(oprf_seed, r_seed, now, 1000)
190 .expect("put session seed");
191 drop(db);
192
193 fs::write(&path, b"corrupt").expect("corrupt cache file");
194
195 let db = CacheDb::new(&path, &key).expect("rebuild cache");
196 let value = db
197 .session_seed_get(oprf_seed, now)
198 .expect("get session seed");
199 assert!(value.is_none());
200 cleanup_cache_files(&path);
201 cleanup_lock_file(&lock_path);
202 }
203
204 #[test]
205 fn test_merkle_cache_ttl() {
206 let path = temp_cache_path();
207 let key = SecretBox::init_with(|| [0x33u8; 32]);
208 let lock_path = temp_lock_path();
209 let db = CacheDb::new(&path, &key).expect("create cache");
210 db.merkle_cache_put(&[1, 2, 3], 100, 10)
211 .expect("put merkle proof");
212 let hit = db.merkle_cache_get(105).expect("get merkle proof");
213 assert!(hit.is_some());
214 let miss = db.merkle_cache_get(111).expect("get merkle proof");
215 assert!(miss.is_none());
216 cleanup_cache_files(&path);
217 cleanup_lock_file(&lock_path);
218 }
219
220 #[test]
221 fn test_session_seed_cache_ttl() {
222 let path = temp_cache_path();
223 let key = SecretBox::init_with(|| [0x44u8; 32]);
224 let lock_path = temp_lock_path();
225 let db = CacheDb::new(&path, &key).expect("create cache");
226 let oprf_seed = [0x55u8; 32];
227 let r_seed = [0x66u8; 32];
228 let now = 100;
229 db.session_seed_put(oprf_seed, r_seed, now, 10)
230 .expect("put session seed");
231 let hit = db.session_seed_get(oprf_seed, now).expect("get");
232 assert_eq!(hit, Some(r_seed));
233 let miss = db.session_seed_get(oprf_seed, now + 11).expect("get");
234 assert!(miss.is_none());
235 cleanup_cache_files(&path);
236 cleanup_lock_file(&lock_path);
237 }
238}