ant_node/payment/metrics.rs
1//! Quoting metrics tracking for ant-node.
2//!
3//! Tracks the single piece of state that influences quote pricing today:
4//! the number of records currently stored in the node's close range.
5//! See `payment::pricing::calculate_price` for the formula.
6
7use std::sync::atomic::{AtomicUsize, Ordering};
8
9/// Tracker for quoting metrics.
10///
11/// Holds the `close_records_stored` counter consumed by
12/// [`calculate_price`](crate::payment::pricing::calculate_price).
13#[derive(Debug)]
14pub struct QuotingMetricsTracker {
15 close_records_stored: AtomicUsize,
16}
17
18impl QuotingMetricsTracker {
19 /// Create a new metrics tracker.
20 ///
21 /// # Arguments
22 ///
23 /// * `initial_records` - Initial number of records stored
24 #[must_use]
25 pub fn new(initial_records: usize) -> Self {
26 Self {
27 close_records_stored: AtomicUsize::new(initial_records),
28 }
29 }
30
31 /// Record that a record was stored.
32 pub fn record_store(&self) {
33 self.close_records_stored.fetch_add(1, Ordering::SeqCst);
34 }
35
36 /// Overwrite the counter with an authoritative count of held records.
37 ///
38 /// This is the deletion-aware path and the SINGLE source of truth for the
39 /// priced record count: the handler calls it at quote time with the live
40 /// LMDB entry count (`current_chunks()`), so any record removed from
41 /// storage — by delete, prune, or otherwise — is reflected on the next
42 /// quote with no per-delete bookkeeping to keep in sync. `record_store`
43 /// remains only an optimistic between-quote hint; the resync overwrites it.
44 pub fn set_records(&self, count: usize) {
45 self.close_records_stored.store(count, Ordering::SeqCst);
46 }
47
48 /// Get the number of records stored.
49 #[must_use]
50 pub fn records_stored(&self) -> usize {
51 self.close_records_stored.load(Ordering::SeqCst)
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn test_new_tracker() {
61 let tracker = QuotingMetricsTracker::new(50);
62 assert_eq!(tracker.records_stored(), 50);
63 }
64
65 #[test]
66 fn test_record_store_increments_counter() {
67 let tracker = QuotingMetricsTracker::new(0);
68 assert_eq!(tracker.records_stored(), 0);
69
70 tracker.record_store();
71 assert_eq!(tracker.records_stored(), 1);
72
73 tracker.record_store();
74 tracker.record_store();
75 assert_eq!(tracker.records_stored(), 3);
76 }
77
78 #[test]
79 fn test_set_records_resyncs_to_authoritative_count() {
80 let tracker = QuotingMetricsTracker::new(100);
81 assert_eq!(tracker.records_stored(), 100);
82
83 // Resync down (e.g. after deletions/pruning the store now holds fewer).
84 tracker.set_records(42);
85 assert_eq!(tracker.records_stored(), 42);
86
87 // Resync up (e.g. after new stores).
88 tracker.set_records(57);
89 assert_eq!(tracker.records_stored(), 57);
90
91 // Resync to zero (empty store).
92 tracker.set_records(0);
93 assert_eq!(tracker.records_stored(), 0);
94 }
95}