1use async_trait::async_trait;
8use guts_auth::AuthStore;
9use guts_collaboration::CollaborationStore;
10use guts_consensus::{ConsensusApplication, ConsensusError, FinalizedBlock, Result, Transaction};
11use guts_realtime::{EventHub, EventKind};
12use guts_storage::RepoStore;
13use parking_lot::RwLock;
14use std::sync::Arc;
15use tracing::{debug, error, info, warn};
16
17pub struct GutsApplication {
19 repos: Arc<RepoStore>,
21
22 #[allow(dead_code)]
25 collaboration: Arc<CollaborationStore>,
26
27 #[allow(dead_code)]
30 auth: Arc<AuthStore>,
31
32 realtime: Arc<EventHub>,
34
35 height: RwLock<u64>,
37
38 state_root: RwLock<[u8; 32]>,
40}
41
42impl GutsApplication {
43 pub fn new(
45 repos: Arc<RepoStore>,
46 collaboration: Arc<CollaborationStore>,
47 auth: Arc<AuthStore>,
48 realtime: Arc<EventHub>,
49 ) -> Self {
50 Self {
51 repos,
52 collaboration,
53 auth,
54 realtime,
55 height: RwLock::new(0),
56 state_root: RwLock::new([0u8; 32]),
57 }
58 }
59
60 fn apply_transaction(&self, tx: &Transaction) -> Result<()> {
62 match tx {
63 Transaction::CreateRepository {
64 owner,
65 name,
66 description: _,
67 default_branch: _,
68 visibility: _,
69 creator: _,
70 signature: _,
71 } => {
72 match self.repos.create(name, owner) {
74 Ok(_repo) => {
75 info!(owner = %owner, name = %name, "Created repository via consensus");
76
77 let repo_key = format!("{}/{}", owner, name);
79 self.realtime.emit_event(
80 format!("repo:{}", repo_key),
81 EventKind::RepoCreated,
82 serde_json::json!({
83 "owner": owner,
84 "name": name,
85 "repository": repo_key
86 }),
87 );
88
89 Ok(())
90 }
91 Err(e) => {
92 warn!(owner = %owner, name = %name, error = %e, "Failed to create repository");
93 Err(ConsensusError::TransactionFailed(format!(
94 "create repository failed: {}",
95 e
96 )))
97 }
98 }
99 }
100
101 Transaction::DeleteRepository {
102 repo_key,
103 deleter: _,
104 signature: _,
105 } => {
106 info!(repo_key = %repo_key, "Repository deletion requested via consensus");
109
110 self.realtime.emit_event(
112 format!("repo:{}", repo_key),
113 EventKind::Push, serde_json::json!({
115 "type": "repository_deleted",
116 "repository": repo_key
117 }),
118 );
119
120 Ok(())
121 }
122
123 Transaction::CreateIssue {
124 repo_key,
125 title,
126 description,
127 author,
128 signer: _,
129 signature: _,
130 } => {
131 info!(
134 repo_key = %repo_key,
135 title = %title,
136 author = %author,
137 "Issue creation requested via consensus"
138 );
139
140 self.realtime.emit_event(
142 format!("repo:{}", repo_key),
143 EventKind::IssueOpened,
144 serde_json::json!({
145 "repository": repo_key,
146 "title": title,
147 "author": author,
148 "description": description
149 }),
150 );
151
152 Ok(())
153 }
154
155 Transaction::CreatePullRequest {
156 repo_key,
157 title,
158 description: _,
159 author,
160 source_branch,
161 target_branch,
162 source_commit: _,
163 target_commit: _,
164 signer: _,
165 signature: _,
166 } => {
167 info!(
169 repo_key = %repo_key,
170 title = %title,
171 author = %author,
172 source_branch = %source_branch,
173 target_branch = %target_branch,
174 "PR creation requested via consensus"
175 );
176
177 self.realtime.emit_event(
179 format!("repo:{}", repo_key),
180 EventKind::PrOpened,
181 serde_json::json!({
182 "repository": repo_key,
183 "title": title,
184 "author": author,
185 "source_branch": source_branch,
186 "target_branch": target_branch
187 }),
188 );
189
190 Ok(())
191 }
192
193 Transaction::CreateOrganization {
194 name,
195 display_name,
196 creator: _,
197 signature: _,
198 } => {
199 info!(
201 name = %name,
202 display_name = %display_name,
203 "Organization creation requested via consensus"
204 );
205
206 Ok(())
207 }
208
209 _ => {
211 debug!(
212 kind = tx.kind(),
213 "Transaction type not yet fully implemented"
214 );
215 Ok(())
216 }
217 }
218 }
219
220 fn compute_state_root_internal(&self) -> [u8; 32] {
222 use sha2::{Digest, Sha256};
223
224 let mut hasher = Sha256::new();
225
226 let repo_count = self.repos.list().len() as u64;
228 hasher.update(repo_count.to_le_bytes());
229
230 hasher.update(self.height.read().to_le_bytes());
232
233 let result = hasher.finalize();
234 let mut root = [0u8; 32];
235 root.copy_from_slice(&result);
236 root
237 }
238}
239
240#[async_trait]
241impl ConsensusApplication for GutsApplication {
242 async fn on_block_finalized(&self, block: &FinalizedBlock) -> Result<()> {
244 let height = block.height();
245 let tx_count = block.block.tx_count();
246
247 info!(
248 height = height,
249 tx_count = tx_count,
250 block_id = %block.id(),
251 "Applying finalized block"
252 );
253
254 for tx in &block.block.transactions {
256 if let Err(e) = self.apply_transaction(tx) {
257 error!(
258 tx_id = %tx.id(),
259 kind = tx.kind(),
260 error = %e,
261 "Failed to apply transaction"
262 );
263 }
267 }
268
269 *self.height.write() = height;
271
272 let new_root = self.compute_state_root_internal();
274 *self.state_root.write() = new_root;
275
276 debug!(
277 height = height,
278 state_root = hex::encode(new_root),
279 "Block application complete"
280 );
281
282 Ok(())
283 }
284
285 async fn compute_state_root(&self, _transactions: &[Transaction]) -> Result<[u8; 32]> {
287 Ok(*self.state_root.read())
290 }
291
292 async fn verify_transaction(&self, tx: &Transaction) -> Result<()> {
294 match tx {
295 Transaction::CreateRepository { owner, name, .. } => {
296 if self.repos.get(owner, name).is_ok() {
298 return Err(ConsensusError::TransactionFailed(format!(
299 "repository {}/{} already exists",
300 owner, name
301 )));
302 }
303 }
304 Transaction::DeleteRepository { repo_key, .. } => {
305 let parts: Vec<&str> = repo_key.split('/').collect();
306 if parts.len() != 2 {
307 return Err(ConsensusError::TransactionFailed(
308 "invalid repo_key format".into(),
309 ));
310 }
311 if self.repos.get(parts[0], parts[1]).is_err() {
313 return Err(ConsensusError::TransactionFailed(format!(
314 "repository {} does not exist",
315 repo_key
316 )));
317 }
318 }
319 _ => {
320 }
323 }
324
325 Ok(())
326 }
327
328 fn current_height(&self) -> u64 {
330 *self.height.read()
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 fn test_app() -> GutsApplication {
339 GutsApplication::new(
340 Arc::new(RepoStore::new()),
341 Arc::new(CollaborationStore::new()),
342 Arc::new(AuthStore::new()),
343 Arc::new(EventHub::new()),
344 )
345 }
346
347 #[test]
348 fn test_guts_application_creation() {
349 let app = test_app();
350 assert_eq!(app.current_height(), 0);
351 }
352
353 #[tokio::test]
354 async fn test_transaction_verification() {
355 let app = test_app();
356
357 use commonware_cryptography::{ed25519, PrivateKeyExt, Signer};
359 use guts_consensus::{SerializablePublicKey, SerializableSignature};
360
361 let key = ed25519::PrivateKey::from_seed(42);
362 let sig = key.sign(Some(b"_GUTS"), b"test");
363
364 let tx = Transaction::CreateRepository {
365 owner: "alice".to_string(),
366 name: "test-repo".to_string(),
367 description: "A test repository".to_string(),
368 default_branch: "main".to_string(),
369 visibility: "public".to_string(),
370 creator: SerializablePublicKey::from_pubkey(&key.public_key()),
371 signature: SerializableSignature::from_signature(&sig),
372 };
373
374 let result = app.verify_transaction(&tx).await;
376 assert!(result.is_ok());
377
378 app.apply_transaction(&tx).unwrap();
380
381 let result = app.verify_transaction(&tx).await;
383 assert!(result.is_err());
384 }
385
386 #[test]
387 fn test_state_root_computation() {
388 let app = test_app();
389
390 let root1 = app.compute_state_root_internal();
392
393 app.repos.create("test", "alice").unwrap();
395
396 let root2 = app.compute_state_root_internal();
398 assert_ne!(root1, root2);
399 }
400}