1use crate::coordinator::coordinator::Coordinator;
8use crate::coordinator::session::{SessionId, SessionRequest, SessionState};
9use std::collections::HashMap;
10use std::sync::mpsc::{Receiver, Sender, channel};
11use std::thread;
12use std::time::Duration;
13
14pub struct AsyncSessionManager {
16 sender: Sender<AsyncOp>,
17}
18
19enum AsyncOp {
20 CreateSession(SessionRequest),
21 SubmitCommitment(String, String, Vec<u8>),
22 SubmitShare(String, String, Vec<u8>),
23 Shutdown,
24}
25
26impl AsyncSessionManager {
27 pub fn spawn(coord: std::sync::Arc<std::sync::Mutex<Coordinator>>) -> Self {
29 let (tx, rx) = channel();
30 thread::spawn(move || {
31 Self::run_loop(coord, rx);
32 });
33 Self { sender: tx }
34 }
35
36 fn run_loop(coord: std::sync::Arc<std::sync::Mutex<Coordinator>>, rx: Receiver<AsyncOp>) {
37 while let Ok(op) = rx.recv() {
38 match op {
39 AsyncOp::CreateSession(req) => {
40 let _ = coord.lock().unwrap().create_session(req);
41 }
42 AsyncOp::SubmitCommitment(sid, signer, bytes) => {
43 use crate::coordinator::session::Commitment;
44 let _ = coord.lock().unwrap().submit_commitment(
45 &sid,
46 Commitment {
47 signer_id: signer,
48 bytes,
49 signer_signature: vec![0u8; 64],
50 submitted_at: chrono::Utc::now(),
51 },
52 );
53 }
54 AsyncOp::SubmitShare(sid, signer, bytes) => {
55 use crate::coordinator::session::Share;
56 let _ = coord.lock().unwrap().submit_share(
57 &sid,
58 Share {
59 signer_id: signer,
60 bytes,
61 signer_signature: vec![0u8; 64],
62 submitted_at: chrono::Utc::now(),
63 },
64 );
65 }
66 AsyncOp::Shutdown => break,
67 }
68 }
69 }
70
71 pub fn create_session(&self, request: SessionRequest) {
73 let _ = self.sender.send(AsyncOp::CreateSession(request));
74 }
75
76 pub fn submit_commitment(&self, session_id: &str, signer_id: &str, bytes: Vec<u8>) {
78 let _ = self.sender.send(AsyncOp::SubmitCommitment(
79 session_id.into(),
80 signer_id.into(),
81 bytes,
82 ));
83 }
84
85 pub fn submit_share(&self, session_id: &str, signer_id: &str, bytes: Vec<u8>) {
87 let _ = self.sender.send(AsyncOp::SubmitShare(
88 session_id.into(),
89 signer_id.into(),
90 bytes,
91 ));
92 }
93
94 pub fn shutdown(&self) {
96 let _ = self.sender.send(AsyncOp::Shutdown);
97 }
98}
99
100pub struct AsyncSessionInfo {
102 pub session_id: SessionId,
103 pub state: SessionState,
104}
105
106#[derive(Default)]
108pub struct PendingOpsTracker {
109 pending: HashMap<SessionId, Vec<String>>,
110}
111
112impl PendingOpsTracker {
113 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn record_op(&mut self, session_id: &str, op: &str) {
118 self.pending
119 .entry(session_id.into())
120 .or_default()
121 .push(op.into());
122 }
123
124 pub fn pending_ops(&self, session_id: &str) -> Vec<String> {
125 self.pending.get(session_id).cloned().unwrap_or_default()
126 }
127
128 pub fn clear_session(&mut self, session_id: &str) {
129 self.pending.remove(session_id);
130 }
131
132 pub fn total_pending(&self) -> usize {
133 self.pending.values().map(|v| v.len()).sum()
134 }
135}
136
137pub fn wait_for_state(
139 coord: &Coordinator,
140 session_id: &str,
141 target: SessionState,
142 timeout: Duration,
143) -> bool {
144 let start = std::time::Instant::now();
145 while start.elapsed() < timeout {
146 if let Some(state) = coord.session_state(session_id) {
147 if state == target {
148 return true;
149 }
150 }
151 thread::sleep(Duration::from_millis(10));
152 }
153 false
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use std::sync::{Arc, Mutex};
160
161 #[test]
162 fn async_create_session() {
163 let coord = Arc::new(Mutex::new(Coordinator::new()));
164 let manager = AsyncSessionManager::spawn(Arc::clone(&coord));
165
166 let request = SessionRequest {
167 quorum_id: "q1".into(),
168 scheme: "CMP20".into(),
169 message: vec![0; 32],
170 threshold: 2,
171 num_parties: 3,
172 unlock_window_minutes: 60,
173 requested_by: "async-test".into(),
174 };
175 manager.create_session(request);
176
177 thread::sleep(Duration::from_millis(50));
179 assert_eq!(coord.lock().unwrap().session_count(), 1);
180 manager.shutdown();
181 }
182
183 #[test]
184 fn pending_ops_tracker() {
185 let mut tracker = PendingOpsTracker::new();
186 tracker.record_op("s1", "create");
187 tracker.record_op("s1", "commit");
188 tracker.record_op("s2", "share");
189 assert_eq!(tracker.pending_ops("s1").len(), 2);
190 assert_eq!(tracker.pending_ops("s2").len(), 1);
191 assert_eq!(tracker.total_pending(), 3);
192 tracker.clear_session("s1");
193 assert_eq!(tracker.total_pending(), 1);
194 }
195
196 #[test]
197 fn wait_for_state_timeout() {
198 let mut coord = Coordinator::new();
199 let req = SessionRequest {
200 quorum_id: "q".into(),
201 scheme: "CMP20".into(),
202 message: vec![0; 32],
203 threshold: 2,
204 num_parties: 3,
205 unlock_window_minutes: 60,
206 requested_by: "test".into(),
207 };
208 let sid = coord.create_session(req).unwrap();
209 let completed = wait_for_state(
211 &coord,
212 &sid,
213 SessionState::Completed,
214 Duration::from_millis(50),
215 );
216 assert!(!completed);
217 }
218
219 #[test]
220 fn async_commitment_submission() {
221 let coord = Arc::new(Mutex::new(Coordinator::new()));
222 let manager = AsyncSessionManager::spawn(Arc::clone(&coord));
223
224 let request = SessionRequest {
225 quorum_id: "q1".into(),
226 scheme: "CMP20".into(),
227 message: vec![0; 32],
228 threshold: 2,
229 num_parties: 3,
230 unlock_window_minutes: 60,
231 requested_by: "test".into(),
232 };
233 let sid = coord.lock().unwrap().create_session(request).unwrap();
234 manager.submit_commitment(&sid, "alice", vec![0xAA; 32]);
235 thread::sleep(Duration::from_millis(50));
236 assert_eq!(
237 coord.lock().unwrap().session_commitment_count(&sid),
238 Some(1)
239 );
240 manager.shutdown();
241 }
242
243 #[test]
244 fn async_share_submission() {
245 let coord = Arc::new(Mutex::new(Coordinator::new()));
246 let manager = AsyncSessionManager::spawn(Arc::clone(&coord));
247
248 let request = SessionRequest {
249 quorum_id: "q1".into(),
250 scheme: "CMP20".into(),
251 message: vec![0; 32],
252 threshold: 2,
253 num_parties: 3,
254 unlock_window_minutes: 60,
255 requested_by: "test".into(),
256 };
257 let sid = coord.lock().unwrap().create_session(request).unwrap();
258 manager.submit_share(&sid, "alice", vec![0xBB; 32]);
259 thread::sleep(Duration::from_millis(50));
260 assert_eq!(coord.lock().unwrap().session_share_count(&sid), Some(1));
261 manager.shutdown();
262 }
263}