use std::collections::BTreeMap;
use alloy::primitives::{hex, keccak256};
use serde::{Deserialize, Deserializer};
use crate::error::Error;
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize)]
pub struct MetaCache {
inner: BTreeMap<Vec<u8>, Vec<u8>>,
}
impl<'de> Deserialize<'de> for MetaCache {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
struct Wire {
inner: BTreeMap<Vec<u8>, Vec<u8>>,
}
let wire = Wire::deserialize(deserializer)?;
let mut cache = MetaCache::default();
for (hash, bytes) in wire.inner {
cache
.insert_verified(&hash, bytes)
.map_err(serde::de::Error::custom)?;
}
Ok(cache)
}
}
impl MetaCache {
pub fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
if keccak256(&bytes).0 != hash {
return Err(Error::CorruptRecord(format!(
"bytes do not hash to the requested {}",
hex::encode_prefixed(hash)
)));
}
self.inner.insert(hash.to_vec(), bytes);
self.inner.get(hash).ok_or(Error::NoRecordFound)
}
pub fn get(&self, hash: &[u8]) -> Option<&Vec<u8>> {
self.inner.get(hash)
}
pub fn contains_key(&self, hash: &[u8]) -> bool {
self.inner.contains_key(hash)
}
pub fn remove(&mut self, hash: &[u8]) {
self.inner.remove(hash);
}
pub fn iter(&self) -> impl Iterator<Item = (&Vec<u8>, &Vec<u8>)> {
self.inner.iter()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn len(&self) -> usize {
self.inner.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hashed(bytes: &[u8]) -> Vec<u8> {
keccak256(bytes).0.to_vec()
}
#[test]
fn test_insert_verified_accepts_matching_bytes() {
let bytes = b"content".to_vec();
let hash = hashed(&bytes);
let mut cache = MetaCache::default();
assert_eq!(cache.insert_verified(&hash, bytes.clone()).unwrap(), &bytes);
assert_eq!(cache.get(&hash), Some(&bytes));
assert!(cache.contains_key(&hash));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_insert_verified_rejects_mismatched_bytes_as_corrupt() {
let wrong_hash = vec![0x99u8; 32];
let mut cache = MetaCache::default();
match cache
.insert_verified(&wrong_hash, b"content".to_vec())
.unwrap_err()
{
Error::CorruptRecord(message) => assert!(
message.contains(&hex::encode_prefixed(&wrong_hash)),
"{}",
message
),
other => panic!("expected CorruptRecord, got {:?}", other),
}
assert!(cache.is_empty());
assert!(!cache.contains_key(&wrong_hash));
}
#[test]
fn test_deserialize_rejects_an_unverified_entry() {
#[derive(serde::Serialize)]
struct Wire {
inner: std::collections::BTreeMap<Vec<u8>, Vec<u8>>,
}
let planted = Wire {
inner: std::collections::BTreeMap::from([(
vec![0x99u8; 32],
b"not the preimage".to_vec(),
)]),
};
let wire = serde_cbor::to_vec(&planted).unwrap();
let round: Result<MetaCache, _> = serde_cbor::from_slice(&wire);
assert!(round.is_err(), "an unverified entry round tripped in");
}
#[test]
fn test_deserialize_keeps_a_verified_entry() {
let bytes = b"content".to_vec();
let hash = hashed(&bytes);
let mut cache = MetaCache::default();
cache.insert_verified(&hash, bytes.clone()).unwrap();
let wire = serde_cbor::to_vec(&cache).unwrap();
let round: MetaCache = serde_cbor::from_slice(&wire).unwrap();
assert_eq!(round.get(&hash), Some(&bytes));
}
#[test]
fn test_serialization_is_deterministic() {
let mut cache = MetaCache::default();
for content in [b"one".to_vec(), b"two".to_vec(), b"three".to_vec()] {
let hash = hashed(&content);
cache.insert_verified(&hash, content).unwrap();
}
let a = serde_cbor::to_vec(&cache).unwrap();
let b = serde_cbor::to_vec(&cache.clone()).unwrap();
assert_eq!(a, b);
}
}