1use super::{LocalKCutResponse, UpdateTrigger, CertLocalKCutQuery, LocalKCutResultSummary};
6use crate::instance::WitnessHandle;
7use std::collections::VecDeque;
8use std::sync::{Arc, RwLock};
9use std::time::{SystemTime, UNIX_EPOCH};
10use serde::{Deserialize, Serialize};
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 { budget, radius, seeds } = &entries[0].data {
380 assert_eq!(*budget, 10);
381 assert_eq!(*radius, 5);
382 assert_eq!(seeds.len(), 3);
383 } else {
384 panic!("Wrong data type");
385 }
386 }
387
388 #[test]
389 fn test_log_response() {
390 let logger = AuditLogger::new(10);
391 let query = CertLocalKCutQuery::new(vec![1], 5, 2);
392 let result = LocalKCutResultSummary::Found {
393 cut_value: 3,
394 witness_hash: 999,
395 };
396 let response = LocalKCutResponse::new(query, result, 100, None);
397
398 logger.log_response(&response);
399
400 let entries = logger.by_type(AuditEntryType::LocalKCutResponse);
401 assert_eq!(entries.len(), 1);
402
403 if let AuditData::Response { found, value } = &entries[0].data {
404 assert!(found);
405 assert_eq!(*value, Some(3));
406 } else {
407 panic!("Wrong data type");
408 }
409 }
410
411 #[test]
412 fn test_log_mincut_changed() {
413 let logger = AuditLogger::new(10);
414 let trigger = UpdateTrigger::new(UpdateType::Insert, 123, (1, 2), 1000);
415
416 logger.log_mincut_changed(10, 8, trigger);
417
418 let entries = logger.by_type(AuditEntryType::MinCutChanged);
419 assert_eq!(entries.len(), 1);
420
421 if let AuditData::MinCut { old_value, new_value, .. } = &entries[0].data {
422 assert_eq!(*old_value, 10);
423 assert_eq!(*new_value, 8);
424 } else {
425 panic!("Wrong data type");
426 }
427 }
428
429 #[test]
430 fn test_log_certificate_created() {
431 let logger = AuditLogger::new(10);
432 logger.log_certificate_created(5, 10, Some(8));
433
434 let entries = logger.by_type(AuditEntryType::CertificateCreated);
435 assert_eq!(entries.len(), 1);
436
437 if let AuditData::Certificate {
438 num_witnesses,
439 num_responses,
440 certified_value,
441 } = &entries[0].data
442 {
443 assert_eq!(*num_witnesses, 5);
444 assert_eq!(*num_responses, 10);
445 assert_eq!(*certified_value, Some(8));
446 } else {
447 panic!("Wrong data type");
448 }
449 }
450
451 #[test]
452 fn test_max_entries() {
453 let logger = AuditLogger::new(3);
454
455 for i in 0..5 {
456 logger.log(
457 AuditEntryType::WitnessCreated,
458 AuditData::Witness {
459 hash: i,
460 boundary: i,
461 seed: i,
462 },
463 );
464 }
465
466 assert_eq!(logger.len(), 3);
468
469 let entries = logger.export();
470 assert!(entries[0].id >= 2);
472 }
473
474 #[test]
475 fn test_recent() {
476 let logger = AuditLogger::new(10);
477
478 for i in 0..5 {
479 logger.log(
480 AuditEntryType::WitnessCreated,
481 AuditData::Witness {
482 hash: i,
483 boundary: i,
484 seed: i,
485 },
486 );
487 }
488
489 let recent = logger.recent(3);
490 assert_eq!(recent.len(), 3);
491
492 assert_eq!(recent[0].id, 2);
494 assert_eq!(recent[1].id, 3);
495 assert_eq!(recent[2].id, 4);
496 }
497
498 #[test]
499 fn test_by_type() {
500 let logger = AuditLogger::new(10);
501
502 logger.log(
503 AuditEntryType::WitnessCreated,
504 AuditData::Witness { hash: 1, boundary: 1, seed: 1 },
505 );
506 logger.log(
507 AuditEntryType::WitnessUpdated,
508 AuditData::Witness { hash: 2, boundary: 2, seed: 2 },
509 );
510 logger.log(
511 AuditEntryType::WitnessCreated,
512 AuditData::Witness { hash: 3, boundary: 3, seed: 3 },
513 );
514
515 let created = logger.by_type(AuditEntryType::WitnessCreated);
516 let updated = logger.by_type(AuditEntryType::WitnessUpdated);
517
518 assert_eq!(created.len(), 2);
519 assert_eq!(updated.len(), 1);
520 }
521
522 #[test]
523 fn test_clear() {
524 let logger = AuditLogger::new(10);
525
526 logger.log(
527 AuditEntryType::WitnessCreated,
528 AuditData::Witness { hash: 1, boundary: 1, seed: 1 },
529 );
530
531 assert_eq!(logger.len(), 1);
532
533 logger.clear();
534
535 assert_eq!(logger.len(), 0);
536 assert!(logger.is_empty());
537 }
538
539 #[test]
540 fn test_json_export() {
541 let logger = AuditLogger::new(10);
542
543 logger.log(
544 AuditEntryType::WitnessCreated,
545 AuditData::Witness { hash: 1, boundary: 5, seed: 2 },
546 );
547
548 let json = logger.to_json().unwrap();
549 assert!(json.contains("WitnessCreated"));
550 assert!(json.contains("boundary"));
552 assert!(json.contains("5"));
553 }
554
555 #[test]
556 fn test_clone() {
557 let logger = AuditLogger::new(10);
558 logger.log(
559 AuditEntryType::WitnessCreated,
560 AuditData::Witness { hash: 1, boundary: 1, seed: 1 },
561 );
562
563 let cloned = logger.clone();
564 assert_eq!(cloned.len(), 1);
565 assert_eq!(cloned.capacity(), 10);
566
567 logger.log(
569 AuditEntryType::WitnessUpdated,
570 AuditData::Witness { hash: 2, boundary: 2, seed: 2 },
571 );
572
573 assert_eq!(cloned.len(), 2);
574 }
575
576 #[test]
577 fn test_entry_timestamps() {
578 let logger = AuditLogger::new(10);
579
580 logger.log(
581 AuditEntryType::WitnessCreated,
582 AuditData::Witness { hash: 1, boundary: 1, seed: 1 },
583 );
584
585 let entries = logger.export();
586 assert!(entries[0].timestamp > 0);
587 }
588}