use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use crate::hash::Hash;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SealRecord {
pub chain: String,
pub seal_id: Vec<u8>,
pub consumed_at_height: u64,
pub commitment_hash: Hash,
pub recorded_at: u64,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnchorRecord {
pub chain: String,
pub anchor_id: Vec<u8>,
pub block_height: u64,
pub commitment_hash: Hash,
pub is_finalized: bool,
pub confirmations: u64,
pub recorded_at: u64,
}
pub trait SealStore: Send + Sync {
fn save_seal(&mut self, record: &SealRecord) -> Result<(), StoreError>;
fn is_seal_consumed(&self, chain: &str, seal_id: &[u8]) -> Result<bool, StoreError>;
fn get_seals(&self, chain: &str) -> Result<Vec<SealRecord>, StoreError>;
fn remove_seal(&mut self, chain: &str, seal_id: &[u8]) -> Result<(), StoreError>;
fn remove_seals_after(&mut self, chain: &str, height: u64) -> Result<usize, StoreError>;
fn save_anchor(&mut self, record: &AnchorRecord) -> Result<(), StoreError>;
fn has_anchor(&self, chain: &str, anchor_id: &[u8]) -> Result<bool, StoreError>;
fn finalize_anchor(
&mut self,
chain: &str,
anchor_id: &[u8],
confirmations: u64,
) -> Result<(), StoreError>;
fn pending_anchors(&self, chain: &str) -> Result<Vec<AnchorRecord>, StoreError>;
fn remove_anchors_after(&mut self, chain: &str, height: u64) -> Result<usize, StoreError>;
fn highest_block(&self, chain: &str) -> Result<u64, StoreError>;
}
pub struct InMemorySealStore {
seals: Vec<SealRecord>,
anchors: Vec<AnchorRecord>,
}
impl InMemorySealStore {
pub fn new() -> Self {
Self {
seals: Vec::new(),
anchors: Vec::new(),
}
}
}
impl Default for InMemorySealStore {
fn default() -> Self {
Self::new()
}
}
impl SealStore for InMemorySealStore {
fn save_seal(&mut self, record: &SealRecord) -> Result<(), StoreError> {
self.seals.push(record.clone());
Ok(())
}
fn is_seal_consumed(&self, chain: &str, seal_id: &[u8]) -> Result<bool, StoreError> {
Ok(self
.seals
.iter()
.any(|s| s.chain == chain && s.seal_id == seal_id))
}
fn get_seals(&self, chain: &str) -> Result<Vec<SealRecord>, StoreError> {
Ok(self
.seals
.iter()
.filter(|s| s.chain == chain)
.cloned()
.collect())
}
fn remove_seal(&mut self, chain: &str, seal_id: &[u8]) -> Result<(), StoreError> {
self.seals
.retain(|s| !(s.chain == chain && s.seal_id == seal_id));
Ok(())
}
fn remove_seals_after(&mut self, chain: &str, height: u64) -> Result<usize, StoreError> {
let before = self.seals.len();
self.seals
.retain(|s| !(s.chain == chain && s.consumed_at_height > height));
Ok(before - self.seals.len())
}
fn save_anchor(&mut self, record: &AnchorRecord) -> Result<(), StoreError> {
self.anchors.push(record.clone());
Ok(())
}
fn has_anchor(&self, chain: &str, anchor_id: &[u8]) -> Result<bool, StoreError> {
Ok(self
.anchors
.iter()
.any(|a| a.chain == chain && a.anchor_id == anchor_id))
}
fn finalize_anchor(
&mut self,
chain: &str,
anchor_id: &[u8],
confirmations: u64,
) -> Result<(), StoreError> {
if let Some(a) = self
.anchors
.iter_mut()
.find(|a| a.chain == chain && a.anchor_id == anchor_id)
{
a.is_finalized = true;
a.confirmations = confirmations;
}
Ok(())
}
fn pending_anchors(&self, chain: &str) -> Result<Vec<AnchorRecord>, StoreError> {
Ok(self
.anchors
.iter()
.filter(|a| a.chain == chain && !a.is_finalized)
.cloned()
.collect())
}
fn remove_anchors_after(&mut self, chain: &str, height: u64) -> Result<usize, StoreError> {
let before = self.anchors.len();
self.anchors
.retain(|a| !(a.chain == chain && a.block_height > height));
Ok(before - self.anchors.len())
}
fn highest_block(&self, chain: &str) -> Result<u64, StoreError> {
Ok(self
.anchors
.iter()
.filter(|a| a.chain == chain)
.map(|a| a.block_height)
.max()
.unwrap_or(0))
}
}
#[derive(Debug)]
#[allow(missing_docs)]
pub enum StoreError {
IoError(String),
SerializationError(String),
DuplicateRecord(String),
NotFound(String),
}
impl core::fmt::Display for StoreError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
StoreError::IoError(msg) => write!(f, "I/O error: {}", msg),
StoreError::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
StoreError::DuplicateRecord(msg) => write!(f, "Duplicate record: {}", msg),
StoreError::NotFound(msg) => write!(f, "Not found: {}", msg),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_seal_record(chain: &str, height: u64) -> SealRecord {
SealRecord {
chain: chain.to_string(),
seal_id: vec![1, 2, 3],
consumed_at_height: height,
commitment_hash: Hash::new([0xAA; 32]),
recorded_at: 1700000000,
}
}
fn test_anchor_record(chain: &str, height: u64) -> AnchorRecord {
AnchorRecord {
chain: chain.to_string(),
anchor_id: vec![4, 5, 6],
block_height: height,
commitment_hash: Hash::new([0xBB; 32]),
is_finalized: false,
confirmations: 0,
recorded_at: 1700000000,
}
}
#[test]
fn test_store_seal_and_check() {
let mut store = InMemorySealStore::new();
let record = test_seal_record("bitcoin", 100);
store.save_seal(&record).unwrap();
assert!(store.is_seal_consumed("bitcoin", &[1, 2, 3]).unwrap());
assert!(!store.is_seal_consumed("ethereum", &[1, 2, 3]).unwrap());
}
#[test]
fn test_remove_seal() {
let mut store = InMemorySealStore::new();
store.save_seal(&test_seal_record("bitcoin", 100)).unwrap();
store.remove_seal("bitcoin", &[1, 2, 3]).unwrap();
assert!(!store.is_seal_consumed("bitcoin", &[1, 2, 3]).unwrap());
}
#[test]
fn test_remove_seals_after_height() {
let mut store = InMemorySealStore::new();
store.save_seal(&test_seal_record("bitcoin", 100)).unwrap();
store.save_seal(&test_seal_record("bitcoin", 150)).unwrap();
store.save_seal(&test_seal_record("bitcoin", 200)).unwrap();
let removed = store.remove_seals_after("bitcoin", 150).unwrap();
assert_eq!(removed, 1);
assert!(store.is_seal_consumed("bitcoin", &[1, 2, 3]).unwrap());
}
#[test]
fn test_anchor_lifecycle() {
let mut store = InMemorySealStore::new();
let anchor = test_anchor_record("bitcoin", 100);
store.save_anchor(&anchor).unwrap();
assert!(store.has_anchor("bitcoin", &[4, 5, 6]).unwrap());
let pending = store.pending_anchors("bitcoin").unwrap();
assert_eq!(pending.len(), 1);
store.finalize_anchor("bitcoin", &[4, 5, 6], 6).unwrap();
let pending = store.pending_anchors("bitcoin").unwrap();
assert!(pending.is_empty());
}
#[test]
fn test_remove_anchors_after_height() {
let mut store = InMemorySealStore::new();
store
.save_anchor(&test_anchor_record("bitcoin", 100))
.unwrap();
store
.save_anchor(&test_anchor_record("bitcoin", 200))
.unwrap();
store
.save_anchor(&test_anchor_record("bitcoin", 300))
.unwrap();
let removed = store.remove_anchors_after("bitcoin", 200).unwrap();
assert_eq!(removed, 1);
assert!(store.has_anchor("bitcoin", &[4, 5, 6]).unwrap());
}
#[test]
fn test_highest_block() {
let mut store = InMemorySealStore::new();
store
.save_anchor(&test_anchor_record("bitcoin", 100))
.unwrap();
store
.save_anchor(&test_anchor_record("bitcoin", 300))
.unwrap();
store
.save_anchor(&test_anchor_record("bitcoin", 200))
.unwrap();
assert_eq!(store.highest_block("bitcoin").unwrap(), 300);
assert_eq!(store.highest_block("ethereum").unwrap(), 0);
}
#[test]
fn test_multi_chain_isolation() {
let mut store = InMemorySealStore::new();
store.save_seal(&test_seal_record("bitcoin", 100)).unwrap();
store.save_seal(&test_seal_record("ethereum", 200)).unwrap();
assert_eq!(store.get_seals("bitcoin").unwrap().len(), 1);
assert_eq!(store.get_seals("ethereum").unwrap().len(), 1);
}
#[test]
fn test_store_error_display() {
let err = StoreError::IoError("disk full".to_string());
assert!(err.to_string().contains("disk full"));
}
}