1use crate::convergence::VectorClock;
10use crate::operation_log::{MemoryOperationLog, OperationLogEntry};
11use crate::state_transition_store::{MemoryStateTransitionStore, StateTransitionRecord};
12use crate::transactions::*;
13use std::sync::{Arc, Mutex};
14
15pub type TxResult<T> = Result<T, TxError>;
17
18#[derive(Clone, Debug)]
20pub enum TxError {
21 PreconditionFailed(String),
22 ConflictDetected(String),
23 OperationDeduplication(String),
24 InvalidState(String),
25 PersistenceFailure(String),
26}
27
28impl std::fmt::Display for TxError {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 TxError::PreconditionFailed(msg) => write!(f, "Precondition failed: {}", msg),
32 TxError::ConflictDetected(msg) => write!(f, "Conflict detected: {}", msg),
33 TxError::OperationDeduplication(msg) => {
34 write!(f, "Operation already executed: {}", msg)
35 }
36 TxError::InvalidState(msg) => write!(f, "Invalid state: {}", msg),
37 TxError::PersistenceFailure(msg) => write!(f, "Persistence failure: {}", msg),
38 }
39 }
40}
41
42impl std::error::Error for TxError {}
43
44pub struct TransactionManager {
46 node_id: String,
47 executor: TransactionExecutor,
48 operation_log: MemoryOperationLog,
49 transition_store: MemoryStateTransitionStore,
50 current_version: Arc<Mutex<StateVersion>>,
51}
52
53impl TransactionManager {
54 pub fn new(node_id: String, initial_version: StateVersion) -> Self {
56 Self {
57 node_id: node_id.clone(),
58 executor: TransactionExecutor::new(node_id),
59 operation_log: MemoryOperationLog::new(),
60 transition_store: MemoryStateTransitionStore::new(),
61 current_version: Arc::new(Mutex::new(initial_version)),
62 }
63 }
64
65 pub fn current_state_version(&self) -> StateVersion {
67 self.current_version.lock().unwrap().clone()
68 }
69
70 pub fn execute_and_commit(
72 &mut self,
73 transaction_id: String,
74 mut operations: Vec<Operation>,
75 consistency_contract: ConsistencyContract,
76 ) -> TxResult<StateTransitionRecord> {
77 for op in &operations {
79 if self.operation_log.contains_operation_id(&op.operation_id) {
80 return Err(TxError::OperationDeduplication(format!(
81 "Operation {} already executed",
82 format!("{}:{}", op.operation_id.node_id, op.operation_id.sequence)
83 )));
84 }
85 }
86
87 for op in &mut operations {
89 op.transaction_id = Some(transaction_id.clone());
90 }
91
92 let current_version = self.current_version.lock().unwrap().clone();
94 let transition = self.executor.execute_transaction(
95 transaction_id.clone(),
96 current_version,
97 operations.clone(),
98 consistency_contract,
99 );
100
101 if !transition.is_successful() {
103 return Err(TxError::InvalidState(
104 "Transaction execution failed".to_string(),
105 ));
106 }
107
108 for op in &transition.operations {
110 self.operation_log
111 .append(op.clone())
112 .map_err(|e| TxError::PersistenceFailure(e))?;
113 }
114
115 let store_index = self
117 .transition_store
118 .record_transition(transition.clone())
119 .map_err(|e| TxError::PersistenceFailure(e))?;
120
121 {
123 let mut current = self.current_version.lock().unwrap();
124 *current = transition.to_version.clone();
125 }
126
127 let record = self
129 .transition_store
130 .get_by_index(store_index)
131 .ok_or_else(|| {
132 TxError::PersistenceFailure("Failed to retrieve recorded transition".to_string())
133 })?;
134
135 Ok(record)
136 }
137
138 pub fn get_operation(&self, operation_id: &OperationId) -> Option<OperationLogEntry> {
140 self.operation_log.get_by_id(operation_id)
141 }
142
143 pub fn get_transition(&self, transaction_id: &str) -> Option<StateTransitionRecord> {
145 self.transition_store.get_by_transaction_id(transaction_id)
146 }
147
148 pub fn operation_log_size(&self) -> u64 {
150 self.operation_log.size()
151 }
152
153 pub fn transition_store_size(&self) -> u64 {
155 self.transition_store.size()
156 }
157
158 pub fn rebuild_from_operation_log(&mut self) -> TxResult<StateVersion> {
161 let all_ops = self.operation_log.iter();
162
163 if all_ops.is_empty() {
164 return Ok(self.current_state_version());
165 }
166
167 let mut current_version = StateVersion::new(VectorClock::new(), "genesis".to_string());
168
169 for entry in all_ops {
170 let transition = self.executor.execute_transaction(
171 entry.operation.transaction_id.clone().unwrap_or_default(),
172 current_version.clone(),
173 vec![entry.operation.clone()],
174 ConsistencyContract::local(),
175 );
176
177 if !transition.is_successful() {
178 return Err(TxError::InvalidState(
179 "Failed to rebuild state from operation log".to_string(),
180 ));
181 }
182
183 current_version = transition.to_version;
184 }
185
186 {
187 let mut current = self.current_version.lock().unwrap();
188 *current = current_version.clone();
189 }
190
191 Ok(current_version)
192 }
193
194 pub fn get_transition_chain_from(&self, from_state_hash: &str) -> Vec<StateTransitionRecord> {
196 self.transition_store.get_transition_chain(from_state_hash)
197 }
198
199 pub fn validate_preconditions(&self, operation: &Operation) -> TxResult<()> {
202 if operation.preconditions.is_empty() {
203 return Ok(());
204 }
205
206 for (condition_name, condition) in &operation.preconditions {
209 if condition.is_empty() {
210 return Err(TxError::PreconditionFailed(format!(
211 "Empty condition: {}",
212 condition_name
213 )));
214 }
215 }
216
217 Ok(())
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::transactions::OperationCommand;
225 use std::collections::HashMap;
226
227 #[test]
228 fn test_create_transaction_manager() {
229 let mut vc = VectorClock::new();
230 vc.increment("node1");
231 let version = StateVersion::new(vc, "hash0".to_string());
232
233 let manager = TransactionManager::new("node1".to_string(), version);
234
235 assert_eq!(manager.operation_log_size(), 0);
236 assert_eq!(manager.transition_store_size(), 0);
237 }
238
239 #[test]
240 fn test_execute_and_commit_single_operation() {
241 let mut vc = VectorClock::new();
242 vc.increment("node1");
243 let version = StateVersion::new(vc, "hash0".to_string());
244
245 let mut manager = TransactionManager::new("node1".to_string(), version);
246
247 let op = Operation::new(
248 OperationId::new("node1".to_string(), 1),
249 manager.current_state_version(),
250 "Update".to_string(),
251 OperationCommand {
252 op_type: "UpdateField".to_string(),
253 collection: "test".to_string(),
254 record_id: "rec1".to_string(),
255 fields: {
256 let mut m = HashMap::new();
257 m.insert("field".to_string(), serde_json::json!("value"));
258 m
259 },
260 },
261 "user".to_string(),
262 );
263
264 let result =
265 manager.execute_and_commit("txn1".to_string(), vec![op], ConsistencyContract::local());
266
267 assert!(result.is_ok());
268 assert_eq!(manager.operation_log_size(), 1);
269 assert_eq!(manager.transition_store_size(), 1);
270 }
271
272 #[test]
273 fn test_duplicate_operation_rejection() {
274 let mut vc = VectorClock::new();
275 vc.increment("node1");
276 let version = StateVersion::new(vc, "hash0".to_string());
277
278 let mut manager = TransactionManager::new("node1".to_string(), version);
279
280 let op = Operation::new(
281 OperationId::new("node1".to_string(), 1),
282 manager.current_state_version(),
283 "Update".to_string(),
284 OperationCommand {
285 op_type: "UpdateField".to_string(),
286 collection: "test".to_string(),
287 record_id: "rec1".to_string(),
288 fields: {
289 let mut m = HashMap::new();
290 m.insert("field".to_string(), serde_json::json!("value"));
291 m
292 },
293 },
294 "user".to_string(),
295 );
296
297 let _ = manager.execute_and_commit(
298 "txn1".to_string(),
299 vec![op.clone()],
300 ConsistencyContract::local(),
301 );
302
303 let result =
304 manager.execute_and_commit("txn2".to_string(), vec![op], ConsistencyContract::local());
305
306 assert!(result.is_err());
307 assert!(result.unwrap_err().to_string().contains("already executed"));
308 }
309
310 #[test]
311 fn test_get_operation_after_commit() {
312 let mut vc = VectorClock::new();
313 vc.increment("node1");
314 let version = StateVersion::new(vc, "hash0".to_string());
315
316 let mut manager = TransactionManager::new("node1".to_string(), version);
317
318 let op_id = OperationId::new("node1".to_string(), 1);
319 let op = Operation::new(
320 op_id.clone(),
321 manager.current_state_version(),
322 "Update".to_string(),
323 OperationCommand {
324 op_type: "UpdateField".to_string(),
325 collection: "test".to_string(),
326 record_id: "rec1".to_string(),
327 fields: {
328 let mut m = HashMap::new();
329 m.insert("field".to_string(), serde_json::json!("value"));
330 m
331 },
332 },
333 "user".to_string(),
334 );
335
336 let _ =
337 manager.execute_and_commit("txn1".to_string(), vec![op], ConsistencyContract::local());
338
339 let retrieved = manager.get_operation(&op_id);
340 assert!(retrieved.is_some());
341 }
342
343 #[test]
344 fn test_get_transition_after_commit() {
345 let mut vc = VectorClock::new();
346 vc.increment("node1");
347 let version = StateVersion::new(vc, "hash0".to_string());
348
349 let mut manager = TransactionManager::new("node1".to_string(), version);
350
351 let op = Operation::new(
352 OperationId::new("node1".to_string(), 1),
353 manager.current_state_version(),
354 "Update".to_string(),
355 OperationCommand {
356 op_type: "UpdateField".to_string(),
357 collection: "test".to_string(),
358 record_id: "rec1".to_string(),
359 fields: {
360 let mut m = HashMap::new();
361 m.insert("field".to_string(), serde_json::json!("value"));
362 m
363 },
364 },
365 "user".to_string(),
366 );
367
368 let _ =
369 manager.execute_and_commit("txn1".to_string(), vec![op], ConsistencyContract::local());
370
371 let transition = manager.get_transition("txn1");
372 assert!(transition.is_some());
373 }
374
375 #[test]
376 fn test_precondition_validation() {
377 let mut vc = VectorClock::new();
378 vc.increment("node1");
379 let version = StateVersion::new(vc, "hash0".to_string());
380
381 let manager = TransactionManager::new("node1".to_string(), version);
382
383 let mut op = Operation::new(
384 OperationId::new("node1".to_string(), 1),
385 manager.current_state_version(),
386 "Update".to_string(),
387 OperationCommand {
388 op_type: "UpdateField".to_string(),
389 collection: "test".to_string(),
390 record_id: "rec1".to_string(),
391 fields: {
392 let mut m = HashMap::new();
393 m.insert("field".to_string(), serde_json::json!("value"));
394 m
395 },
396 },
397 "user".to_string(),
398 );
399
400 op = op.with_preconditions(vec![("count".to_string(), "count >= 2".to_string())]);
401
402 let result = manager.validate_preconditions(&op);
403 assert!(result.is_ok());
404
405 let mut bad_op = Operation::new(
406 OperationId::new("node1".to_string(), 2),
407 manager.current_state_version(),
408 "Update".to_string(),
409 OperationCommand {
410 op_type: "UpdateField".to_string(),
411 collection: "test".to_string(),
412 record_id: "rec2".to_string(),
413 fields: {
414 let mut m = HashMap::new();
415 m.insert("field".to_string(), serde_json::json!("value"));
416 m
417 },
418 },
419 "user".to_string(),
420 );
421
422 bad_op = bad_op.with_preconditions(vec![("invalid".to_string(), "".to_string())]);
423
424 let bad_result = manager.validate_preconditions(&bad_op);
425 assert!(bad_result.is_err());
426 }
427
428 #[test]
429 fn test_current_state_version_tracking() {
430 let mut vc = VectorClock::new();
431 vc.increment("node1");
432 let initial_version = StateVersion::new(vc, "hash0".to_string());
433 let initial_hash = initial_version.state_hash.clone();
434
435 let mut manager = TransactionManager::new("node1".to_string(), initial_version);
436
437 assert_eq!(manager.current_state_version().state_hash, initial_hash);
438
439 let op = Operation::new(
440 OperationId::new("node1".to_string(), 1),
441 manager.current_state_version(),
442 "Update".to_string(),
443 OperationCommand {
444 op_type: "UpdateField".to_string(),
445 collection: "test".to_string(),
446 record_id: "rec1".to_string(),
447 fields: {
448 let mut m = HashMap::new();
449 m.insert("field".to_string(), serde_json::json!("value"));
450 m
451 },
452 },
453 "user".to_string(),
454 );
455
456 let _ =
457 manager.execute_and_commit("txn1".to_string(), vec![op], ConsistencyContract::local());
458
459 let new_version = manager.current_state_version();
460 assert_ne!(new_version.state_hash, initial_hash);
461 }
462}