1pub mod chain;
11pub mod error;
12pub mod log_entry;
13pub mod merkle_service;
14pub mod publication;
15
16use serde::{Deserialize, Serialize};
17use std::sync::Arc;
18use tokio::sync::RwLock;
19
20pub struct ImmutableLog {
22 chain: Arc<RwLock<chain::LogChain>>,
23 merkle: Arc<RwLock<merkle_service::MerkleService>>,
24}
25
26impl ImmutableLog {
27 pub fn new() -> Self {
29 ImmutableLog {
30 chain: Arc::new(RwLock::new(chain::LogChain::new())),
31 merkle: Arc::new(RwLock::new(merkle_service::MerkleService::new())),
32 }
33 }
34
35 pub async fn append(
37 &self,
38 entry: log_entry::LogEntry,
39 ) -> Result<log_entry::LogEntry, error::LogError> {
40 let mut chain = self.chain.write().await;
42 let entry = chain.append(entry).await?;
43
44 let mut merkle = self.merkle.write().await;
46 merkle.add_entry(entry.clone()).await?;
47
48 Ok(entry)
49 }
50
51 pub async fn verify(&self) -> Result<bool, error::LogError> {
53 let chain = self.chain.read().await;
54 Ok(chain.verify())
55 }
56
57 pub async fn entry_count(&self) -> usize {
59 let chain = self.chain.read().await;
60 chain.len()
61 }
62
63 pub async fn current_hash(&self) -> String {
65 let chain = self.chain.read().await;
66 chain.current_hash().to_string()
67 }
68
69 pub async fn get_hourly_root(&self) -> Option<merkle_service::HourlyRoot> {
71 let merkle = self.merkle.read().await;
72 merkle.get_current_root()
73 }
74
75 pub async fn hourly_roots_snapshot(&self) -> Vec<merkle_service::HourlyRoot> {
77 let merkle = self.merkle.read().await;
78 let mut roots = merkle.get_published_roots().to_vec();
79 if let Some(current) = merkle.get_current_root() {
80 let exists = roots
81 .iter()
82 .any(|r| r.hour == current.hour && r.root_hash == current.root_hash);
83 if !exists {
84 roots.push(current);
85 }
86 }
87 roots
88 }
89
90 pub async fn get_chain_proof(&self, entry_id: &str) -> Option<chain::ChainProof> {
92 let chain = self.chain.read().await;
93 let entry = chain.get_entry(entry_id)?.clone();
94 let mut proof = chain.generate_proof(entry_id)?;
95 drop(chain);
96
97 let merkle = self.merkle.read().await;
98 let merkle_proof = merkle.generate_proof(entry_id, &entry);
99 proof.attach_merkle_proof(merkle_proof);
100 Some(proof)
101 }
102}
103
104impl Default for ImmutableLog {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct LogConfig {
113 pub hash_algorithm: String,
115 pub hourly_publication: bool,
117 pub daily_publication: bool,
119 pub tsa_url: Option<String>,
121 pub blockchain_enabled: bool,
123}
124
125impl Default for LogConfig {
126 fn default() -> Self {
127 LogConfig {
128 hash_algorithm: "SHA256".to_string(),
129 hourly_publication: true,
130 daily_publication: true,
131 tsa_url: None,
132 blockchain_enabled: true,
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_default_config() {
143 let config = LogConfig::default();
144 assert_eq!(config.hash_algorithm, "SHA256");
145 assert!(config.hourly_publication);
146 }
147
148 #[tokio::test]
149 async fn test_append_entry() {
150 let log = ImmutableLog::new();
151
152 let entry = log_entry::LogEntry::new(
153 log_entry::EventType::AccountQuery,
154 "agent-001".to_string(),
155 "org-001".to_string(),
156 )
157 .unwrap();
158
159 let result = log.append(entry).await;
160 assert!(result.is_ok());
161 }
162
163 #[tokio::test]
164 async fn test_chain_stats() {
165 let log = ImmutableLog::new();
166 assert_eq!(log.entry_count().await, 0);
167 assert_eq!(log.current_hash().await.len(), 64);
168 }
169
170 #[tokio::test]
171 async fn test_hourly_roots_snapshot_includes_current_root() {
172 let log = ImmutableLog::new();
173 let entry = log_entry::LogEntry::new(
174 log_entry::EventType::AccountQuery,
175 "agent-001".to_string(),
176 "org-001".to_string(),
177 )
178 .unwrap();
179 log.append(entry).await.expect("append");
180
181 let roots = log.hourly_roots_snapshot().await;
182 assert_eq!(roots.len(), 1);
183 assert_eq!(roots[0].entry_count, 1);
184 }
185}