use std::path::Path;
use crate::storage::error::StorageResult;
use crate::storage::lock::StorageLockGuard;
use walletkit_db::Connection;
use zeroize::Zeroizing;
mod maintenance;
mod merkle;
mod nullifiers;
mod schema;
mod session;
mod util;
#[derive(Debug)]
pub struct CacheDb {
conn: Connection,
}
impl CacheDb {
pub fn new(
path: &Path,
k_intermediate: &Zeroizing<[u8; 32]>,
_lock: &StorageLockGuard,
) -> StorageResult<Self> {
let conn = maintenance::open_or_rebuild(path, k_intermediate)?;
Ok(Self { conn })
}
pub fn merkle_cache_get(&self, valid_until: u64) -> StorageResult<Option<Vec<u8>>> {
merkle::get(&self.conn, valid_until)
}
#[allow(clippy::needless_pass_by_value)]
pub fn merkle_cache_put(
&mut self,
_lock: &StorageLockGuard,
proof_bytes: Vec<u8>,
now: u64,
ttl_seconds: u64,
) -> StorageResult<()> {
merkle::put(&self.conn, proof_bytes.as_ref(), now, ttl_seconds)
}
pub fn session_key_get(&self, rp_id: [u8; 32]) -> StorageResult<Option<[u8; 32]>> {
session::get(&self.conn, rp_id)
}
pub fn session_key_put(
&mut self,
_lock: &StorageLockGuard,
rp_id: [u8; 32],
k_session: [u8; 32],
ttl_seconds: u64,
) -> StorageResult<()> {
session::put(&self.conn, rp_id, k_session, ttl_seconds)
}
pub fn is_nullifier_replay(
&self,
nullifier: [u8; 32],
now: u64,
) -> StorageResult<bool> {
nullifiers::is_nullifier_replay(&self.conn, nullifier, now)
}
pub fn replay_guard_set(
&mut self,
_lock: &StorageLockGuard,
nullifier: [u8; 32],
now: u64,
) -> StorageResult<()> {
nullifiers::replay_guard_set(&self.conn, nullifier, now)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::lock::StorageLock;
use std::fs;
use std::path::PathBuf;
use std::time::Duration;
use uuid::Uuid;
fn temp_cache_path() -> PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("walletkit-cache-{}.sqlite", Uuid::new_v4()));
path
}
fn cleanup_cache_files(path: &Path) {
let _ = fs::remove_file(path);
let wal_path = path.with_extension("sqlite-wal");
let shm_path = path.with_extension("sqlite-shm");
let _ = fs::remove_file(wal_path);
let _ = fs::remove_file(shm_path);
}
fn temp_lock_path() -> PathBuf {
let mut path = std::env::temp_dir();
path.push(format!("walletkit-cache-lock-{}.lock", Uuid::new_v4()));
path
}
fn cleanup_lock_file(path: &Path) {
let _ = fs::remove_file(path);
}
#[test]
fn test_cache_create_and_open() {
let path = temp_cache_path();
let key = Zeroizing::new([0x11u8; 32]);
let lock_path = temp_lock_path();
let lock = StorageLock::open(&lock_path).expect("open lock");
let guard = lock.lock().expect("lock");
let db = CacheDb::new(&path, &key, &guard).expect("create cache");
drop(db);
CacheDb::new(&path, &key, &guard).expect("open cache");
cleanup_cache_files(&path);
cleanup_lock_file(&lock_path);
}
#[test]
fn test_cache_rebuild_on_corruption() {
let path = temp_cache_path();
let key = Zeroizing::new([0x22u8; 32]);
let lock_path = temp_lock_path();
let lock = StorageLock::open(&lock_path).expect("open lock");
let guard = lock.lock().expect("lock");
let mut db = CacheDb::new(&path, &key, &guard).expect("create cache");
let rp_id = [0x01u8; 32];
let k_session = [0x02u8; 32];
db.session_key_put(&guard, rp_id, k_session, 1000)
.expect("put session key");
drop(db);
fs::write(&path, b"corrupt").expect("corrupt cache file");
let db = CacheDb::new(&path, &key, &guard).expect("rebuild cache");
let value = db.session_key_get(rp_id).expect("get session key");
assert!(value.is_none());
cleanup_cache_files(&path);
cleanup_lock_file(&lock_path);
}
#[test]
fn test_merkle_cache_ttl() {
let path = temp_cache_path();
let key = Zeroizing::new([0x33u8; 32]);
let lock_path = temp_lock_path();
let lock = StorageLock::open(&lock_path).expect("open lock");
let guard = lock.lock().expect("lock");
let mut db = CacheDb::new(&path, &key, &guard).expect("create cache");
db.merkle_cache_put(&guard, vec![1, 2, 3], 100, 10)
.expect("put merkle proof");
let valid_until = 105;
let hit = db.merkle_cache_get(valid_until).expect("get merkle proof");
assert!(hit.is_some());
let miss = db.merkle_cache_get(111).expect("get merkle proof");
assert!(miss.is_none());
cleanup_cache_files(&path);
cleanup_lock_file(&lock_path);
}
#[test]
fn test_session_cache_ttl() {
let path = temp_cache_path();
let key = Zeroizing::new([0x44u8; 32]);
let lock_path = temp_lock_path();
let lock = StorageLock::open(&lock_path).expect("open lock");
let guard = lock.lock().expect("lock");
let mut db = CacheDb::new(&path, &key, &guard).expect("create cache");
let rp_id = [0x55u8; 32];
let k_session = [0x66u8; 32];
db.session_key_put(&guard, rp_id, k_session, 3)
.expect("put session key");
let hit = db.session_key_get(rp_id).expect("get session key");
assert!(hit.is_some());
std::thread::sleep(Duration::from_secs(4));
let miss = db.session_key_get(rp_id).expect("get session key");
assert!(miss.is_none());
cleanup_cache_files(&path);
cleanup_lock_file(&lock_path);
}
}