1use alloc::collections::BTreeMap;
14use alloc::vec::Vec;
15
16use crate::commitment::Commitment;
17use crate::hash::Hash;
18use crate::right::Right;
19use crate::seal::SealRef;
20
21#[derive(Clone, Debug, Serialize, Deserialize)]
23pub struct StateTransitionRecord {
24 pub commitment: Commitment,
26 pub seal_ref: SealRef,
28 pub rights: Vec<Right>,
30 pub block_height: u64,
32 pub verified: bool,
34}
35
36#[derive(Clone, Debug, Serialize, Deserialize)]
38pub struct ContractHistory {
39 pub contract_id: Hash,
41 pub transitions: Vec<StateTransitionRecord>,
43 pub active_rights: BTreeMap<Hash, Right>,
45 pub consumed_seals: BTreeMap<Vec<u8>, SealRef>,
47 pub latest_commitment_hash: Hash,
49}
50
51impl ContractHistory {
52 pub fn from_genesis(genesis_commitment: Commitment) -> Self {
54 let contract_id = genesis_commitment.contract_id;
55 let latest_hash = genesis_commitment.hash();
56
57 Self {
58 contract_id,
59 transitions: Vec::new(),
60 active_rights: BTreeMap::new(),
61 consumed_seals: BTreeMap::new(),
62 latest_commitment_hash: latest_hash,
63 }
64 }
65
66 pub fn add_transition(&mut self, transition: StateTransitionRecord) -> Result<(), StoreError> {
68 let expected_previous = self.latest_commitment_hash;
70 if transition.commitment.previous_commitment != expected_previous {
71 return Err(StoreError::InvalidHistory(format!(
72 "Transition commitment does not chain from latest: expected {:?}, got {:?}",
73 expected_previous, transition.commitment.previous_commitment
74 )));
75 }
76
77 self.latest_commitment_hash = transition.commitment.hash();
79
80 self.transitions.push(transition);
82
83 Ok(())
84 }
85
86 pub fn add_right(&mut self, right: Right) {
88 self.active_rights.insert(right.id.0, right);
89 }
90
91 pub fn consume_right(&mut self, right_id: &Hash) -> Option<Right> {
93 self.active_rights.remove(right_id)
94 }
95
96 pub fn is_seal_consumed(&self, seal_ref: &SealRef) -> bool {
98 self.consumed_seals.contains_key(&seal_ref.to_vec())
99 }
100
101 pub fn mark_seal_consumed(&mut self, seal_ref: SealRef) {
103 self.consumed_seals.insert(seal_ref.to_vec(), seal_ref);
104 }
105
106 pub fn transition_count(&self) -> usize {
108 self.transitions.len()
109 }
110
111 pub fn get_active_rights(&self) -> Vec<&Right> {
113 self.active_rights.values().collect()
114 }
115}
116
117pub trait StateHistoryStore: Send + Sync {
119 fn save_contract_history(
121 &mut self,
122 contract_id: Hash,
123 history: &ContractHistory,
124 ) -> Result<(), StoreError>;
125
126 fn load_contract_history(
128 &self,
129 contract_id: Hash,
130 ) -> Result<Option<ContractHistory>, StoreError>;
131
132 fn list_contracts(&self) -> Result<Vec<Hash>, StoreError>;
134
135 fn delete_contract(&mut self, contract_id: Hash) -> Result<(), StoreError>;
137}
138
139#[derive(Default)]
141pub struct InMemoryStateStore {
142 contracts: BTreeMap<Hash, ContractHistory>,
143}
144
145impl InMemoryStateStore {
146 pub fn new() -> Self {
148 Self {
149 contracts: BTreeMap::new(),
150 }
151 }
152}
153
154impl StateHistoryStore for InMemoryStateStore {
155 fn save_contract_history(
156 &mut self,
157 contract_id: Hash,
158 history: &ContractHistory,
159 ) -> Result<(), StoreError> {
160 self.contracts.insert(contract_id, history.clone());
161 Ok(())
162 }
163
164 fn load_contract_history(
165 &self,
166 contract_id: Hash,
167 ) -> Result<Option<ContractHistory>, StoreError> {
168 Ok(self.contracts.get(&contract_id).cloned())
169 }
170
171 fn list_contracts(&self) -> Result<Vec<Hash>, StoreError> {
172 Ok(self.contracts.keys().cloned().collect())
173 }
174
175 fn delete_contract(&mut self, contract_id: Hash) -> Result<(), StoreError> {
176 self.contracts.remove(&contract_id);
177 Ok(())
178 }
179}
180
181#[derive(Debug, thiserror::Error)]
183#[allow(missing_docs)]
184pub enum StoreError {
185 #[error("Contract not found: {0}")]
186 ContractNotFound(Hash),
187 #[error("Serialization error: {0}")]
188 SerializationError(String),
189 #[error("Invalid contract history: {0}")]
190 InvalidHistory(String),
191 #[error("IO error: {0}")]
192 IoError(String),
193}
194
195use serde::{Deserialize, Serialize};
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 fn make_test_commitment(previous: Hash, seal_id: u8) -> Commitment {
202 let domain = [0u8; 32];
203 let seal = SealRef::new(vec![seal_id], None).unwrap();
204 Commitment::simple(
205 Hash::new([0xAB; 32]),
206 previous,
207 Hash::new([0u8; 32]),
208 &seal,
209 domain,
210 )
211 }
212
213 #[test]
214 fn test_contract_history_creation() {
215 let genesis = make_test_commitment(Hash::new([0u8; 32]), 0x01);
216 let history = ContractHistory::from_genesis(genesis.clone());
217
218 assert_eq!(history.contract_id, genesis.contract_id);
219 assert_eq!(history.transition_count(), 0);
220 assert_eq!(history.latest_commitment_hash, genesis.hash());
221 }
222
223 #[test]
224 fn test_add_transition() {
225 let genesis = make_test_commitment(Hash::new([0u8; 32]), 0x01);
226 let mut history = ContractHistory::from_genesis(genesis.clone());
227
228 let transition = StateTransitionRecord {
229 commitment: make_test_commitment(genesis.hash(), 0x02),
230 seal_ref: SealRef::new(vec![0x02], None).unwrap(),
231 rights: Vec::new(),
232 block_height: 100,
233 verified: true,
234 };
235
236 history.add_transition(transition).unwrap();
237 assert_eq!(history.transition_count(), 1);
238 }
239
240 #[test]
241 fn test_right_lifecycle() {
242 let genesis = make_test_commitment(Hash::new([0u8; 32]), 0x01);
243 let mut history = ContractHistory::from_genesis(genesis);
244
245 let right = Right::new(
246 Hash::new([0xCD; 32]),
247 crate::right::OwnershipProof {
248 proof: vec![0x01],
249 owner: vec![0xFF; 32],
250 scheme: None,
251 },
252 &[0x42],
253 );
254
255 history.add_right(right.clone());
256 assert_eq!(history.get_active_rights().len(), 1);
257
258 let consumed = history.consume_right(&right.id.0);
259 assert!(consumed.is_some());
260 assert_eq!(history.get_active_rights().len(), 0);
261 }
262
263 #[test]
264 fn test_seal_consumption_tracking() {
265 let genesis = make_test_commitment(Hash::new([0u8; 32]), 0x01);
266 let mut history = ContractHistory::from_genesis(genesis);
267
268 let seal = SealRef::new(vec![0xAB], None).unwrap();
269 assert!(!history.is_seal_consumed(&seal));
270
271 history.mark_seal_consumed(seal.clone());
272 assert!(history.is_seal_consumed(&seal));
273 }
274
275 #[test]
276 fn test_in_memory_store() {
277 let mut store = InMemoryStateStore::new();
278
279 let genesis = make_test_commitment(Hash::new([0u8; 32]), 0x01);
280 let history = ContractHistory::from_genesis(genesis.clone());
281
282 store
283 .save_contract_history(genesis.contract_id, &history)
284 .unwrap();
285
286 let loaded = store.load_contract_history(genesis.contract_id).unwrap();
287 assert!(loaded.is_some());
288 assert_eq!(loaded.unwrap().contract_id, genesis.contract_id);
289
290 let contracts = store.list_contracts().unwrap();
291 assert_eq!(contracts.len(), 1);
292 }
293}