use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use crate::commitment::Commitment;
use crate::hash::Hash;
use crate::right::Right;
use crate::seal::SealRef;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StateTransitionRecord {
pub commitment: Commitment,
pub seal_ref: SealRef,
pub rights: Vec<Right>,
pub block_height: u64,
pub verified: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ContractHistory {
pub contract_id: Hash,
pub transitions: Vec<StateTransitionRecord>,
pub active_rights: BTreeMap<Hash, Right>,
pub consumed_seals: BTreeMap<Vec<u8>, SealRef>,
pub latest_commitment_hash: Hash,
}
impl ContractHistory {
pub fn from_genesis(genesis_commitment: Commitment) -> Self {
let contract_id = genesis_commitment.contract_id;
let latest_hash = genesis_commitment.hash();
Self {
contract_id,
transitions: Vec::new(),
active_rights: BTreeMap::new(),
consumed_seals: BTreeMap::new(),
latest_commitment_hash: latest_hash,
}
}
pub fn add_transition(&mut self, transition: StateTransitionRecord) -> Result<(), StoreError> {
let expected_previous = self.latest_commitment_hash;
if transition.commitment.previous_commitment != expected_previous {
return Err(StoreError::InvalidHistory(format!(
"Transition commitment does not chain from latest: expected {:?}, got {:?}",
expected_previous, transition.commitment.previous_commitment
)));
}
self.latest_commitment_hash = transition.commitment.hash();
self.transitions.push(transition);
Ok(())
}
pub fn add_right(&mut self, right: Right) {
self.active_rights.insert(right.id.0, right);
}
pub fn consume_right(&mut self, right_id: &Hash) -> Option<Right> {
self.active_rights.remove(right_id)
}
pub fn is_seal_consumed(&self, seal_ref: &SealRef) -> bool {
self.consumed_seals.contains_key(&seal_ref.to_vec())
}
pub fn mark_seal_consumed(&mut self, seal_ref: SealRef) {
self.consumed_seals.insert(seal_ref.to_vec(), seal_ref);
}
pub fn transition_count(&self) -> usize {
self.transitions.len()
}
pub fn get_active_rights(&self) -> Vec<&Right> {
self.active_rights.values().collect()
}
}
pub trait StateHistoryStore: Send + Sync {
fn save_contract_history(
&mut self,
contract_id: Hash,
history: &ContractHistory,
) -> Result<(), StoreError>;
fn load_contract_history(
&self,
contract_id: Hash,
) -> Result<Option<ContractHistory>, StoreError>;
fn list_contracts(&self) -> Result<Vec<Hash>, StoreError>;
fn delete_contract(&mut self, contract_id: Hash) -> Result<(), StoreError>;
}
#[derive(Default)]
pub struct InMemoryStateStore {
contracts: BTreeMap<Hash, ContractHistory>,
}
impl InMemoryStateStore {
pub fn new() -> Self {
Self {
contracts: BTreeMap::new(),
}
}
}
impl StateHistoryStore for InMemoryStateStore {
fn save_contract_history(
&mut self,
contract_id: Hash,
history: &ContractHistory,
) -> Result<(), StoreError> {
self.contracts.insert(contract_id, history.clone());
Ok(())
}
fn load_contract_history(
&self,
contract_id: Hash,
) -> Result<Option<ContractHistory>, StoreError> {
Ok(self.contracts.get(&contract_id).cloned())
}
fn list_contracts(&self) -> Result<Vec<Hash>, StoreError> {
Ok(self.contracts.keys().cloned().collect())
}
fn delete_contract(&mut self, contract_id: Hash) -> Result<(), StoreError> {
self.contracts.remove(&contract_id);
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum StoreError {
#[error("Contract not found: {0}")]
ContractNotFound(Hash),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Invalid contract history: {0}")]
InvalidHistory(String),
#[error("IO error: {0}")]
IoError(String),
}
use serde::{Deserialize, Serialize};
#[cfg(test)]
mod tests {
use super::*;
fn make_test_commitment(previous: Hash, seal_id: u8) -> Commitment {
let domain = [0u8; 32];
let seal = SealRef::new(vec![seal_id], None).unwrap();
Commitment::simple(
Hash::new([0xAB; 32]),
previous,
Hash::new([0u8; 32]),
&seal,
domain,
)
}
#[test]
fn test_contract_history_creation() {
let genesis = make_test_commitment(Hash::new([0u8; 32]), 0x01);
let history = ContractHistory::from_genesis(genesis.clone());
assert_eq!(history.contract_id, genesis.contract_id);
assert_eq!(history.transition_count(), 0);
assert_eq!(history.latest_commitment_hash, genesis.hash());
}
#[test]
fn test_add_transition() {
let genesis = make_test_commitment(Hash::new([0u8; 32]), 0x01);
let mut history = ContractHistory::from_genesis(genesis.clone());
let transition = StateTransitionRecord {
commitment: make_test_commitment(genesis.hash(), 0x02),
seal_ref: SealRef::new(vec![0x02], None).unwrap(),
rights: Vec::new(),
block_height: 100,
verified: true,
};
history.add_transition(transition).unwrap();
assert_eq!(history.transition_count(), 1);
}
#[test]
fn test_right_lifecycle() {
let genesis = make_test_commitment(Hash::new([0u8; 32]), 0x01);
let mut history = ContractHistory::from_genesis(genesis);
let right = Right::new(
Hash::new([0xCD; 32]),
crate::right::OwnershipProof {
proof: vec![0x01],
owner: vec![0xFF; 32],
scheme: None,
},
&[0x42],
);
history.add_right(right.clone());
assert_eq!(history.get_active_rights().len(), 1);
let consumed = history.consume_right(&right.id.0);
assert!(consumed.is_some());
assert_eq!(history.get_active_rights().len(), 0);
}
#[test]
fn test_seal_consumption_tracking() {
let genesis = make_test_commitment(Hash::new([0u8; 32]), 0x01);
let mut history = ContractHistory::from_genesis(genesis);
let seal = SealRef::new(vec![0xAB], None).unwrap();
assert!(!history.is_seal_consumed(&seal));
history.mark_seal_consumed(seal.clone());
assert!(history.is_seal_consumed(&seal));
}
#[test]
fn test_in_memory_store() {
let mut store = InMemoryStateStore::new();
let genesis = make_test_commitment(Hash::new([0u8; 32]), 0x01);
let history = ContractHistory::from_genesis(genesis.clone());
store
.save_contract_history(genesis.contract_id, &history)
.unwrap();
let loaded = store.load_contract_history(genesis.contract_id).unwrap();
assert!(loaded.is_some());
assert_eq!(loaded.unwrap().contract_id, genesis.contract_id);
let contracts = store.list_contracts().unwrap();
assert_eq!(contracts.len(), 1);
}
}