File size: 2,467 Bytes
1295969
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//! LMDB-backed ZK proof cache (heed 0.20).
//!
//! Key: hex(band_byte ‖ SHA-256(n_artists ‖ total_btt ‖ splits_bps)) = 66 hex chars
//! Value: hex-encoded ZK proof bytes (stored as JSON string in LMDB)
//!
//! Eviction policy: none (proofs are deterministic — same inputs always produce
//! the same proof, so stale entries are never harmful, only wasteful).
use sha2::{Digest, Sha256};

pub struct ZkProofCache {
    db: crate::persist::LmdbStore,
}

impl ZkProofCache {
    pub fn open(path: &str) -> anyhow::Result<Self> {
        Ok(Self {
            db: crate::persist::LmdbStore::open(path, "zk_proofs")?,
        })
    }

    /// Build the 33-byte cache key (band byte ‖ SHA-256 of inputs).
    pub fn cache_key(band: u8, n_artists: u32, total_btt: u64, splits_bps: &[u16]) -> [u8; 33] {
        let mut h = Sha256::new();
        h.update(n_artists.to_le_bytes());
        h.update(total_btt.to_le_bytes());
        for bps in splits_bps {
            h.update(bps.to_le_bytes());
        }
        let hash: [u8; 32] = h.finalize().into();
        let mut key = [0u8; 33];
        key[0] = band;
        key[1..].copy_from_slice(&hash);
        key
    }

    fn key_str(band: u8, n_artists: u32, total_btt: u64, splits_bps: &[u16]) -> String {
        hex::encode(Self::cache_key(band, n_artists, total_btt, splits_bps))
    }

    /// Retrieve a cached proof. Returns `None` on miss.
    pub fn get(
        &self,
        band: u8,
        n_artists: u32,
        total_btt: u64,
        splits_bps: &[u16],
    ) -> Option<Vec<u8>> {
        let key = Self::key_str(band, n_artists, total_btt, splits_bps);
        let hex_str: String = self.db.get(&key).ok().flatten()?;
        hex::decode(hex_str).ok()
    }

    /// Store a proof. The proof bytes are hex-encoded to stay JSON-compatible.
    pub fn put(
        &self,
        band: u8,
        n_artists: u32,
        total_btt: u64,
        splits_bps: &[u16],
        proof: Vec<u8>,
    ) {
        let key = Self::key_str(band, n_artists, total_btt, splits_bps);
        let hex_proof = hex::encode(&proof);
        if let Err(e) = self.db.put(&key, &hex_proof) {
            tracing::error!(err=%e, "ZK proof cache write error");
        }
    }

    /// Prometheus-compatible metrics line.
    pub fn metrics_text(&self) -> String {
        let count = self.db.all_values::<String>().map(|v| v.len()).unwrap_or(0);
        format!("retrosync_zk_cache_entries {count}\n")
    }
}