1use anyhow::Result;
6use std::path::Path;
7
8#[derive(Debug, Clone)]
10pub struct MerkleCommit {
11 pub commit_id: String,
12 pub session_id: String,
13 pub node_id: String,
14 pub merkle_root: [u8; 32],
15 pub parent_hash: Option<[u8; 32]>,
16 pub timestamp: i64,
17 pub energy: f32,
18 pub stable: bool,
19}
20
21#[derive(Debug, Clone)]
23pub struct SessionRecord {
24 pub session_id: String,
25 pub task: String,
26 pub started_at: i64,
27 pub ended_at: Option<i64>,
28 pub status: String,
29 pub total_nodes: usize,
30 pub completed_nodes: usize,
31}
32
33pub struct MerkleLedger {
35 db_path: String,
37 current_session: Option<SessionRecord>,
39}
40
41impl MerkleLedger {
42 pub fn new(db_path: &str) -> Result<Self> {
44 let ledger = Self {
45 db_path: db_path.to_string(),
46 current_session: None,
47 };
48
49 ledger.init_schema()?;
51
52 Ok(ledger)
53 }
54
55 pub fn in_memory() -> Result<Self> {
57 Self::new(":memory:")
58 }
59
60 fn init_schema(&self) -> Result<()> {
62 log::info!("Initializing Merkle Ledger at: {}", self.db_path);
65
66 if self.db_path != ":memory:" {
68 if let Some(parent) = Path::new(&self.db_path).parent() {
69 std::fs::create_dir_all(parent)?;
70 }
71 }
72
73 Ok(())
74 }
75
76 pub fn start_session(&mut self, session_id: &str, task: &str) -> Result<()> {
78 let record = SessionRecord {
79 session_id: session_id.to_string(),
80 task: task.to_string(),
81 started_at: chrono_timestamp(),
82 ended_at: None,
83 status: "RUNNING".to_string(),
84 total_nodes: 0,
85 completed_nodes: 0,
86 };
87
88 self.current_session = Some(record);
89 log::info!("Started session: {}", session_id);
90
91 Ok(())
92 }
93
94 pub fn commit_node(
96 &mut self,
97 node_id: &str,
98 merkle_root: [u8; 32],
99 parent_hash: Option<[u8; 32]>,
100 energy: f32,
101 ) -> Result<String> {
102 let session_id = self
103 .current_session
104 .as_ref()
105 .map(|s| s.session_id.clone())
106 .unwrap_or_else(|| "unknown".to_string());
107
108 let commit = MerkleCommit {
109 commit_id: generate_commit_id(),
110 session_id,
111 node_id: node_id.to_string(),
112 merkle_root,
113 parent_hash,
114 timestamp: chrono_timestamp(),
115 energy,
116 stable: energy < 0.1,
117 };
118
119 log::info!("Committed node {} with energy {:.4}", node_id, energy);
120
121 if let Some(ref mut session) = self.current_session {
123 session.completed_nodes += 1;
124 }
125
126 Ok(commit.commit_id)
127 }
128
129 pub fn end_session(&mut self, status: &str) -> Result<()> {
131 if let Some(ref mut session) = self.current_session {
132 session.ended_at = Some(chrono_timestamp());
133 session.status = status.to_string();
134 log::info!(
135 "Ended session {} with status: {}",
136 session.session_id,
137 status
138 );
139 }
140 Ok(())
141 }
142
143 pub fn get_recent_commits(&self, session_id: &str, limit: usize) -> Vec<MerkleCommit> {
145 log::debug!(
147 "Getting recent {} commits for session {}",
148 limit,
149 session_id
150 );
151 Vec::new()
152 }
153
154 pub fn rollback_to(&mut self, commit_id: &str) -> Result<()> {
156 log::info!("Rolling back to commit: {}", commit_id);
157 Ok(())
159 }
160
161 pub fn get_stats(&self) -> LedgerStats {
163 LedgerStats {
164 total_sessions: 0,
165 total_commits: 0,
166 db_size_bytes: 0,
167 }
168 }
169
170 pub fn current_merkle_root(&self) -> [u8; 32] {
172 [0u8; 32] }
174}
175
176#[derive(Debug, Clone)]
178pub struct LedgerStats {
179 pub total_sessions: usize,
180 pub total_commits: usize,
181 pub db_size_bytes: u64,
182}
183
184fn generate_commit_id() -> String {
186 use std::time::{SystemTime, UNIX_EPOCH};
187 let now = SystemTime::now()
188 .duration_since(UNIX_EPOCH)
189 .unwrap()
190 .as_nanos();
191 format!("{:x}", now)
192}
193
194fn chrono_timestamp() -> i64 {
196 use std::time::{SystemTime, UNIX_EPOCH};
197 SystemTime::now()
198 .duration_since(UNIX_EPOCH)
199 .unwrap()
200 .as_secs() as i64
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn test_ledger_creation() {
209 let ledger = MerkleLedger::in_memory().unwrap();
210 assert!(ledger.current_session.is_none());
211 }
212
213 #[test]
214 fn test_session_lifecycle() {
215 let mut ledger = MerkleLedger::in_memory().unwrap();
216
217 ledger.start_session("test-123", "Test task").unwrap();
218 assert!(ledger.current_session.is_some());
219
220 ledger.commit_node("node-1", [0u8; 32], None, 0.05).unwrap();
221 assert_eq!(ledger.current_session.as_ref().unwrap().completed_nodes, 1);
222
223 ledger.end_session("COMPLETED").unwrap();
224 assert_eq!(ledger.current_session.as_ref().unwrap().status, "COMPLETED");
225 }
226}