use std::cell::Cell;
use eyre::Result;
use mbx_cache_core::CacheDigest;
pub(super) const SAMPLE_RATE_ENV: &str = "MBX_VERIFY_SAMPLE_RATE";
thread_local! {
static SELECTED: Cell<Option<bool>> = const { Cell::new(None) };
}
pub(crate) struct Selection(Option<bool>);
fn full_verification() -> bool {
std::env::var_os(super::VERIFY_ENV).is_some_and(|value| !value.is_empty() && value != "0")
}
pub(super) fn requested() -> bool {
full_verification() || SELECTED.with(|selected| selected.get().unwrap_or(false))
}
pub(crate) fn select(identity: impl FnOnce() -> Result<CacheDigest>) -> Result<Selection> {
let rate = std::env::var(SAMPLE_RATE_ENV)
.ok()
.and_then(|rate| rate.parse::<u8>().ok())
.filter(|rate| *rate <= 100)
.unwrap_or(0);
let selected = if full_verification() || rate == 100 {
true
} else if rate == 0 {
false
} else {
sampled(&identity()?.hash, rate)
};
Ok(Selection(
SELECTED.with(|slot| slot.replace(Some(selected))),
))
}
fn sampled(hash: &str, rate: u8) -> bool {
hash.get(..16)
.and_then(|prefix| u64::from_str_radix(prefix, 16).ok())
.is_some_and(|bucket| bucket % 100 < u64::from(rate))
}
impl Drop for Selection {
fn drop(&mut self) {
SELECTED.with(|slot| slot.set(self.0));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn samples_are_stable_and_rates_are_nested() {
let hashes: Vec<_> = (0..1000)
.map(|n| CacheDigest::blake3(n.to_string().as_bytes()).hash)
.collect();
let first: Vec<_> = hashes.iter().map(|hash| sampled(hash, 10)).collect();
assert!(first.iter().any(|selected| *selected));
assert!(first.iter().any(|selected| !selected));
for (hash, selected) in hashes.iter().zip(&first).rev() {
assert_eq!(sampled(hash, 10), *selected);
assert!(!sampled(hash, 0));
assert!(sampled(hash, 100));
assert!(!selected || sampled(hash, 50));
}
}
}