1use super::{CertLocalKCutQuery, LocalKCutResponse, LocalKCutResultSummary, UpdateTrigger};
6use crate::instance::WitnessHandle;
7use serde::{Deserialize, Serialize};
8use std::collections::VecDeque;
9use std::sync::{Arc, RwLock};
10use std::time::{SystemTime, UNIX_EPOCH};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct AuditEntry {
15 pub id: u64,
17 pub timestamp: u64,
19 pub entry_type: AuditEntryType,
21 pub data: AuditData,
23}
24
25impl AuditEntry {
26 pub fn new(id: u64, entry_type: AuditEntryType, data: AuditData) -> Self {
28 let timestamp = SystemTime::now()
29 .duration_since(UNIX_EPOCH)
30 .unwrap_or_default()
31 .as_secs();
32
33 Self {
34 id,
35 timestamp,
36 entry_type,
37 data,
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub enum AuditEntryType {
45 WitnessCreated,
47 WitnessUpdated,
49 WitnessEvicted,
51 LocalKCutQuery,
53 LocalKCutResponse,
55 CertificateCreated,
57 MinCutChanged,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub enum AuditData {
64 Witness {
66 hash: u64,
68 boundary: u64,
70 seed: u64,
72 },
73 Query {
75 budget: u64,
77 radius: usize,
79 seeds: Vec<u64>,
81 },
82 Response {
84 found: bool,
86 value: Option<u64>,
88 },
89 MinCut {
91 old_value: u64,
93 new_value: u64,
95 trigger: UpdateTrigger,
97 },
98 Certificate {
100 num_witnesses: usize,
102 num_responses: usize,
104 certified_value: Option<u64>,
106 },
107}
108
109pub struct AuditLogger {
111 entries: Arc<RwLock<VecDeque<AuditEntry>>>,
113 max_entries: usize,
115 next_id: Arc<RwLock<u64>>,
117}
118
119impl AuditLogger {
120 pub fn new(max_entries: usize) -> Self {
122 Self {
123 entries: Arc::new(RwLock::new(VecDeque::with_capacity(max_entries))),
124 max_entries,
125 next_id: Arc::new(RwLock::new(0)),
126 }
127 }
128
129 pub fn log(&self, entry_type: AuditEntryType, data: AuditData) {
131 let mut entries = self.entries.write().unwrap();
132 let mut next_id = self.next_id.write().unwrap();
133
134 let entry = AuditEntry::new(*next_id, entry_type, data);
135 *next_id += 1;
136
137 entries.push_back(entry);
138
139 while entries.len() > self.max_entries {
141 entries.pop_front();
142 }
143 }
144
145 pub fn log_witness_created(&self, witness: &WitnessHandle) {
147 self.log(
148 AuditEntryType::WitnessCreated,
149 AuditData::Witness {
150 hash: self.compute_witness_hash(witness),
151 boundary: witness.boundary_size(),
152 seed: witness.seed(),
153 },
154 );
155 }
156
157 pub fn log_witness_updated(&self, witness: &WitnessHandle) {
159 self.log(
160 AuditEntryType::WitnessUpdated,
161 AuditData::Witness {
162 hash: self.compute_witness_hash(witness),
163 boundary: witness.boundary_size(),
164 seed: witness.seed(),
165 },
166 );
167 }
168
169 pub fn log_witness_evicted(&self, witness: &WitnessHandle) {
171 self.log(
172 AuditEntryType::WitnessEvicted,
173 AuditData::Witness {
174 hash: self.compute_witness_hash(witness),
175 boundary: witness.boundary_size(),
176 seed: witness.seed(),
177 },
178 );
179 }
180
181 pub fn log_query(&self, budget: u64, radius: usize, seeds: Vec<u64>) {
183 self.log(
184 AuditEntryType::LocalKCutQuery,
185 AuditData::Query {
186 budget,
187 radius,
188 seeds,
189 },
190 );
191 }
192
193 pub fn log_response(&self, response: &LocalKCutResponse) {
195 let (found, value) = match &response.result {
196 super::LocalKCutResultSummary::Found { cut_value, .. } => (true, Some(*cut_value)),
197 super::LocalKCutResultSummary::NoneInLocality => (false, None),
198 };
199
200 self.log(
201 AuditEntryType::LocalKCutResponse,
202 AuditData::Response { found, value },
203 );
204 }
205
206 pub fn log_mincut_changed(&self, old_value: u64, new_value: u64, trigger: UpdateTrigger) {
208 self.log(
209 AuditEntryType::MinCutChanged,
210 AuditData::MinCut {
211 old_value,
212 new_value,
213 trigger,
214 },
215 );
216 }
217
218 pub fn log_certificate_created(
220 &self,
221 num_witnesses: usize,
222 num_responses: usize,
223 certified_value: Option<u64>,
224 ) {
225 self.log(
226 AuditEntryType::CertificateCreated,
227 AuditData::Certificate {
228 num_witnesses,
229 num_responses,
230 certified_value,
231 },
232 );
233 }
234
235 pub fn recent(&self, count: usize) -> Vec<AuditEntry> {
237 let entries = self.entries.read().unwrap();
238 let start = entries.len().saturating_sub(count);
239 entries.iter().skip(start).cloned().collect()
240 }
241
242 pub fn by_type(&self, entry_type: AuditEntryType) -> Vec<AuditEntry> {
244 let entries = self.entries.read().unwrap();
245 entries
246 .iter()
247 .filter(|e| e.entry_type == entry_type)
248 .cloned()
249 .collect()
250 }
251
252 pub fn export(&self) -> Vec<AuditEntry> {
254 let entries = self.entries.read().unwrap();
255 entries.iter().cloned().collect()
256 }
257
258 pub fn to_json(&self) -> Result<String, String> {
260 let entries = self.export();
261 serde_json::to_string_pretty(&entries).map_err(|e| e.to_string())
262 }
263
264 pub fn clear(&self) {
266 let mut entries = self.entries.write().unwrap();
267 entries.clear();
268 let mut next_id = self.next_id.write().unwrap();
269 *next_id = 0;
270 }
271
272 pub fn len(&self) -> usize {
274 let entries = self.entries.read().unwrap();
275 entries.len()
276 }
277
278 pub fn is_empty(&self) -> bool {
280 self.len() == 0
281 }
282
283 pub fn capacity(&self) -> usize {
285 self.max_entries
286 }
287
288 fn compute_witness_hash(&self, witness: &WitnessHandle) -> u64 {
290 let seed = witness.seed();
292 let boundary = witness.boundary_size();
293 seed.wrapping_mul(31).wrapping_add(boundary)
294 }
295}
296
297impl Default for AuditLogger {
298 fn default() -> Self {
299 Self::new(1000)
300 }
301}
302
303impl Clone for AuditLogger {
304 fn clone(&self) -> Self {
305 Self {
306 entries: Arc::clone(&self.entries),
307 max_entries: self.max_entries,
308 next_id: Arc::clone(&self.next_id),
309 }
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use crate::certificate::{CertLocalKCutQuery, LocalKCutResultSummary, UpdateType};
317 use roaring::RoaringBitmap;
318
319 #[test]
320 fn test_new_logger() {
321 let logger = AuditLogger::new(100);
322 assert_eq!(logger.capacity(), 100);
323 assert_eq!(logger.len(), 0);
324 assert!(logger.is_empty());
325 }
326
327 #[test]
328 fn test_log_entry() {
329 let logger = AuditLogger::new(10);
330 logger.log(
331 AuditEntryType::WitnessCreated,
332 AuditData::Witness {
333 hash: 123,
334 boundary: 5,
335 seed: 1,
336 },
337 );
338
339 assert_eq!(logger.len(), 1);
340 assert!(!logger.is_empty());
341
342 let entries = logger.export();
343 assert_eq!(entries.len(), 1);
344 assert_eq!(entries[0].entry_type, AuditEntryType::WitnessCreated);
345 }
346
347 #[test]
348 fn test_log_witness_created() {
349 let logger = AuditLogger::new(10);
350 let witness = WitnessHandle::new(1, RoaringBitmap::from_iter([1, 2, 3]), 5);
351
352 logger.log_witness_created(&witness);
353 assert_eq!(logger.len(), 1);
354
355 let entries = logger.by_type(AuditEntryType::WitnessCreated);
356 assert_eq!(entries.len(), 1);
357 }
358
359 #[test]
360 fn test_log_witness_updated() {
361 let logger = AuditLogger::new(10);
362 let witness = WitnessHandle::new(2, RoaringBitmap::from_iter([2, 3]), 3);
363
364 logger.log_witness_updated(&witness);
365 assert_eq!(logger.len(), 1);
366
367 let entries = logger.by_type(AuditEntryType::WitnessUpdated);
368 assert_eq!(entries.len(), 1);
369 }
370
371 #[test]
372 fn test_log_query() {
373 let logger = AuditLogger::new(10);
374 logger.log_query(10, 5, vec![1, 2, 3]);
375
376 let entries = logger.by_type(AuditEntryType::LocalKCutQuery);
377 assert_eq!(entries.len(), 1);
378
379 if let AuditData::Query {
380 budget,
381 radius,
382 seeds,
383 } = &entries[0].data
384 {
385 assert_eq!(*budget, 10);
386 assert_eq!(*radius, 5);
387 assert_eq!(seeds.len(), 3);
388 } else {
389 panic!("Wrong data type");
390 }
391 }
392
393 #[test]
394 fn test_log_response() {
395 let logger = AuditLogger::new(10);
396 let query = CertLocalKCutQuery::new(vec![1], 5, 2);
397 let result = LocalKCutResultSummary::Found {
398 cut_value: 3,
399 witness_hash: 999,
400 };
401 let response = LocalKCutResponse::new(query, result, 100, None);
402
403 logger.log_response(&response);
404
405 let entries = logger.by_type(AuditEntryType::LocalKCutResponse);
406 assert_eq!(entries.len(), 1);
407
408 if let AuditData::Response { found, value } = &entries[0].data {
409 assert!(found);
410 assert_eq!(*value, Some(3));
411 } else {
412 panic!("Wrong data type");
413 }
414 }
415
416 #[test]
417 fn test_log_mincut_changed() {
418 let logger = AuditLogger::new(10);
419 let trigger = UpdateTrigger::new(UpdateType::Insert, 123, (1, 2), 1000);
420
421 logger.log_mincut_changed(10, 8, trigger);
422
423 let entries = logger.by_type(AuditEntryType::MinCutChanged);
424 assert_eq!(entries.len(), 1);
425
426 if let AuditData::MinCut {
427 old_value,
428 new_value,
429 ..
430 } = &entries[0].data
431 {
432 assert_eq!(*old_value, 10);
433 assert_eq!(*new_value, 8);
434 } else {
435 panic!("Wrong data type");
436 }
437 }
438
439 #[test]
440 fn test_log_certificate_created() {
441 let logger = AuditLogger::new(10);
442 logger.log_certificate_created(5, 10, Some(8));
443
444 let entries = logger.by_type(AuditEntryType::CertificateCreated);
445 assert_eq!(entries.len(), 1);
446
447 if let AuditData::Certificate {
448 num_witnesses,
449 num_responses,
450 certified_value,
451 } = &entries[0].data
452 {
453 assert_eq!(*num_witnesses, 5);
454 assert_eq!(*num_responses, 10);
455 assert_eq!(*certified_value, Some(8));
456 } else {
457 panic!("Wrong data type");
458 }
459 }
460
461 #[test]
462 fn test_max_entries() {
463 let logger = AuditLogger::new(3);
464
465 for i in 0..5 {
466 logger.log(
467 AuditEntryType::WitnessCreated,
468 AuditData::Witness {
469 hash: i,
470 boundary: i,
471 seed: i,
472 },
473 );
474 }
475
476 assert_eq!(logger.len(), 3);
478
479 let entries = logger.export();
480 assert!(entries[0].id >= 2);
482 }
483
484 #[test]
485 fn test_recent() {
486 let logger = AuditLogger::new(10);
487
488 for i in 0..5 {
489 logger.log(
490 AuditEntryType::WitnessCreated,
491 AuditData::Witness {
492 hash: i,
493 boundary: i,
494 seed: i,
495 },
496 );
497 }
498
499 let recent = logger.recent(3);
500 assert_eq!(recent.len(), 3);
501
502 assert_eq!(recent[0].id, 2);
504 assert_eq!(recent[1].id, 3);
505 assert_eq!(recent[2].id, 4);
506 }
507
508 #[test]
509 fn test_by_type() {
510 let logger = AuditLogger::new(10);
511
512 logger.log(
513 AuditEntryType::WitnessCreated,
514 AuditData::Witness {
515 hash: 1,
516 boundary: 1,
517 seed: 1,
518 },
519 );
520 logger.log(
521 AuditEntryType::WitnessUpdated,
522 AuditData::Witness {
523 hash: 2,
524 boundary: 2,
525 seed: 2,
526 },
527 );
528 logger.log(
529 AuditEntryType::WitnessCreated,
530 AuditData::Witness {
531 hash: 3,
532 boundary: 3,
533 seed: 3,
534 },
535 );
536
537 let created = logger.by_type(AuditEntryType::WitnessCreated);
538 let updated = logger.by_type(AuditEntryType::WitnessUpdated);
539
540 assert_eq!(created.len(), 2);
541 assert_eq!(updated.len(), 1);
542 }
543
544 #[test]
545 fn test_clear() {
546 let logger = AuditLogger::new(10);
547
548 logger.log(
549 AuditEntryType::WitnessCreated,
550 AuditData::Witness {
551 hash: 1,
552 boundary: 1,
553 seed: 1,
554 },
555 );
556
557 assert_eq!(logger.len(), 1);
558
559 logger.clear();
560
561 assert_eq!(logger.len(), 0);
562 assert!(logger.is_empty());
563 }
564
565 #[test]
566 fn test_json_export() {
567 let logger = AuditLogger::new(10);
568
569 logger.log(
570 AuditEntryType::WitnessCreated,
571 AuditData::Witness {
572 hash: 1,
573 boundary: 5,
574 seed: 2,
575 },
576 );
577
578 let json = logger.to_json().unwrap();
579 assert!(json.contains("WitnessCreated"));
580 assert!(json.contains("boundary"));
582 assert!(json.contains("5"));
583 }
584
585 #[test]
586 fn test_clone() {
587 let logger = AuditLogger::new(10);
588 logger.log(
589 AuditEntryType::WitnessCreated,
590 AuditData::Witness {
591 hash: 1,
592 boundary: 1,
593 seed: 1,
594 },
595 );
596
597 let cloned = logger.clone();
598 assert_eq!(cloned.len(), 1);
599 assert_eq!(cloned.capacity(), 10);
600
601 logger.log(
603 AuditEntryType::WitnessUpdated,
604 AuditData::Witness {
605 hash: 2,
606 boundary: 2,
607 seed: 2,
608 },
609 );
610
611 assert_eq!(cloned.len(), 2);
612 }
613
614 #[test]
615 fn test_entry_timestamps() {
616 let logger = AuditLogger::new(10);
617
618 logger.log(
619 AuditEntryType::WitnessCreated,
620 AuditData::Witness {
621 hash: 1,
622 boundary: 1,
623 seed: 1,
624 },
625 );
626
627 let entries = logger.export();
628 assert!(entries[0].timestamp > 0);
629 }
630}