Skip to main content

rings_node/
measure.rs

1#![warn(missing_docs)]
2
3//! This module implemented the `Measure` trait for swarm.
4
5use std::sync::Arc;
6use std::sync::Mutex;
7
8use async_trait::async_trait;
9use chrono::DateTime;
10use chrono::Duration;
11use chrono::Utc;
12use dashmap::mapref::one::RefMut;
13use dashmap::DashMap;
14use rings_core::dht::Did;
15use rings_core::measure;
16use rings_core::measure::Measure;
17use rings_core::measure::MeasureCounter;
18use rings_core::storage::KvStorageInterface;
19use rings_derive::MeasureBehaviour;
20
21#[cfg(test)]
22const DURATION: u64 = 1;
23#[cfg(not(test))]
24const DURATION: u64 = 60 * 60;
25
26/// `MeasureStorage` is the type accepted by `PeriodicMeasure::new`.
27/// It's used to store counts in a storage media provided by user.
28#[cfg(feature = "browser")]
29pub type MeasureStorage = Box<dyn KvStorageInterface<u64>>;
30
31/// `MeasureStorage` is the type accepted by `PeriodicMeasure::new`.
32/// It's used to store counts in a storage media provided by user.
33#[cfg(not(feature = "browser"))]
34pub type MeasureStorage = Box<dyn KvStorageInterface<u64> + Sync + Send>;
35
36/// `PeriodicMeasure` is used to assess the reliability of peers by counting their behaviour.
37/// It currently count the number of sent and received messages in a given period (1 hour).
38/// The method [Measure::incr] should be called in the proper places.
39#[derive(MeasureBehaviour)]
40pub struct PeriodicMeasure {
41    storage: MeasureStorage,
42    counters: DashMap<(Did, MeasureCounter), Mutex<PeriodicCounter>>,
43    clock: Arc<dyn MeasureClock>,
44}
45
46// Boundary: wall-clock time is injected here. The counter transition below is
47// pure with respect to its `now` input, so tests can advance time without
48// sleeping while production still reads `Utc::now`.
49trait MeasureClock: Send + Sync {
50    fn now(&self) -> DateTime<Utc>;
51}
52
53struct SystemMeasureClock;
54
55impl MeasureClock for SystemMeasureClock {
56    fn now(&self) -> DateTime<Utc> {
57        Utc::now()
58    }
59}
60
61#[derive(Debug)]
62struct PeriodicCounter {
63    // Invariant: `previous` is the start time of the current counting window;
64    // `count` records the current window; `previous_count` records the most
65    // recently completed window persisted through `barely_get`.
66    period: Duration,
67    count: u64,
68    previous: DateTime<Utc>,
69    previous_count: u64,
70}
71
72impl PeriodicCounter {
73    fn new(period: u64, previous_count: u64, now: DateTime<Utc>) -> Self {
74        Self {
75            period: Duration::seconds(period as i64),
76            count: 0,
77            previous: now,
78            previous_count,
79        }
80    }
81
82    // Reset periodic count on next period
83    fn refresh_at(&mut self, now: DateTime<Utc>) -> bool {
84        if now - self.previous < self.period {
85            return false;
86        }
87
88        self.previous_count = self.count;
89        self.count = 0;
90        self.previous = now;
91        true
92    }
93
94    // If there is no recourd in current period, get previous_count instead
95    fn barely_get(&self) -> u64 {
96        if self.previous_count == 0 {
97            self.count
98        } else {
99            self.previous_count
100        }
101    }
102
103    // Check period, then increase
104    fn incr_at(&mut self, now: DateTime<Utc>) -> (u64, bool) {
105        let is_refreshed = self.refresh_at(now);
106        self.count += 1;
107        (self.barely_get(), is_refreshed)
108    }
109
110    // Check period, return count or previous count
111    fn get_at(&mut self, now: DateTime<Utc>) -> (u64, bool) {
112        let is_refreshed = self.refresh_at(now);
113        (self.barely_get(), is_refreshed)
114    }
115}
116
117impl PeriodicMeasure {
118    /// Create a new `PeriodicMeasure` with the given storage.
119    pub fn new(storage: MeasureStorage) -> Self {
120        Self {
121            storage,
122            counters: DashMap::new(),
123            clock: Arc::new(SystemMeasureClock),
124        }
125    }
126
127    #[cfg(all(test, feature = "node"))]
128    fn new_with_clock(storage: MeasureStorage, clock: Arc<dyn MeasureClock>) -> Self {
129        Self {
130            storage,
131            counters: DashMap::new(),
132            clock,
133        }
134    }
135
136    fn gen_storage_key(did: Did, counter: MeasureCounter) -> String {
137        format!("PeriodicMeasure/counters/{did}/{counter:?}")
138    }
139
140    // Get count from storage, or create a new count instance.
141    async fn ensure_counter(
142        &self,
143        did: Did,
144        counter: MeasureCounter,
145        now: DateTime<Utc>,
146    ) -> RefMut<'_, (Did, MeasureCounter), Mutex<PeriodicCounter>> {
147        let k = Self::gen_storage_key(did, counter);
148        let count = self
149            .storage
150            .get(&k)
151            .await
152            .unwrap_or_else(|e| {
153                log::error!("Failed to get counter: {e:?}");
154                Some(0)
155            })
156            .unwrap_or(0);
157        self.counters
158            .entry((did, counter))
159            .or_insert_with(|| Mutex::new(PeriodicCounter::new(DURATION, count, now)))
160    }
161
162    async fn save_counter(&self, did: Did, counter: MeasureCounter, count: u64) {
163        let k = Self::gen_storage_key(did, counter);
164        self.storage.put(&k, &count).await.unwrap_or_else(|e| {
165            log::error!("Failed to save counter: {e:?}");
166        })
167    }
168}
169
170#[cfg_attr(feature = "node", async_trait)]
171#[cfg_attr(feature = "browser", async_trait(?Send))]
172impl Measure for PeriodicMeasure {
173    /// `incr` increments the counter of the given peer.
174    async fn incr(&self, did: Did, counter: MeasureCounter) {
175        let now = self.clock.now();
176        let (count, is_refreshed) = {
177            let c = self.ensure_counter(did, counter, now).await;
178            let result = if let Ok(mut c) = c.lock() {
179                c.incr_at(now)
180            } else {
181                return;
182            };
183            result
184        };
185        if is_refreshed {
186            self.save_counter(did, counter, count).await;
187        }
188    }
189
190    /// `get_count` returns the counter of a peer in the current or previous period.
191    async fn get_count(&self, did: Did, counter: MeasureCounter) -> u64 {
192        let now = self.clock.now();
193        let (count, is_refreshed) = {
194            let c = self.ensure_counter(did, counter, now).await;
195            let result = if let Ok(mut c) = c.lock() {
196                c.get_at(now)
197            } else {
198                return 0;
199            };
200            result
201        };
202        if is_refreshed {
203            self.save_counter(did, counter, count).await;
204        }
205        count
206    }
207}
208
209#[cfg_attr(feature = "node", async_trait)]
210#[cfg_attr(feature = "browser", async_trait(?Send))]
211impl measure::BehaviourJudgement for PeriodicMeasure {
212    async fn good(&self, did: Did) -> bool {
213        <Self as measure::ConnectBehaviour<{crate::consts::CONNECT_FAILED_LIMIT}>>::good(self, did).await &&
214	    <Self as measure::MessageSendBehaviour<{crate::consts::MSG_SEND_FAILED_LIMIT}>>::good(self, did).await &&
215            <Self as measure::MessageRecvBehaviour<{crate::consts::MSG_RECV_FAILED_LIMIT}>>::good(self, did).await
216    }
217}
218
219#[cfg(test)]
220#[cfg(feature = "node")]
221mod tests {
222    use std::str::FromStr;
223    use std::sync::Arc;
224    use std::sync::Mutex;
225
226    use rings_core::storage::sled::SledStorage;
227    use rings_core::storage::MemStorage;
228
229    use super::*;
230
231    #[derive(Clone)]
232    struct ManualMeasureClock {
233        now: Arc<Mutex<DateTime<Utc>>>,
234    }
235
236    impl ManualMeasureClock {
237        fn new(now: DateTime<Utc>) -> Self {
238            Self {
239                now: Arc::new(Mutex::new(now)),
240            }
241        }
242
243        fn advance(&self, duration: Duration) {
244            let Ok(mut now) = self.now.lock() else {
245                panic!("manual measure clock lock poisoned");
246            };
247            let Some(advanced) = now.checked_add_signed(duration) else {
248                panic!("manual measure clock overflow");
249            };
250            *now = advanced;
251        }
252    }
253
254    impl MeasureClock for ManualMeasureClock {
255        fn now(&self) -> DateTime<Utc> {
256            let Ok(now) = self.now.lock() else {
257                panic!("manual measure clock lock poisoned");
258            };
259            now.to_owned()
260        }
261    }
262
263    fn advance_period(clock: &ManualMeasureClock) {
264        clock.advance(Duration::seconds(DURATION as i64));
265    }
266
267    #[tokio::test]
268    async fn test_measure_counter() {
269        let ms = Box::new(MemStorage::new());
270
271        let did1 = Did::from_str("0x11E807fcc88dD319270493fB2e822e388Fe36ab0").unwrap();
272        let did2 = Did::from_str("0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E").unwrap();
273
274        let clock = ManualMeasureClock::new(Utc::now());
275        let measure = PeriodicMeasure::new_with_clock(ms, Arc::new(clock.clone()));
276        assert_eq!(measure.get_count(did1, MeasureCounter::Sent).await, 0);
277        assert_eq!(measure.get_count(did2, MeasureCounter::Sent).await, 0);
278        assert_eq!(measure.get_count(did1, MeasureCounter::Received).await, 0);
279        assert_eq!(measure.get_count(did2, MeasureCounter::Received).await, 0);
280
281        measure.incr(did1, MeasureCounter::Sent).await;
282        measure.incr(did1, MeasureCounter::Received).await;
283
284        measure.incr(did2, MeasureCounter::Sent).await;
285        measure.incr(did2, MeasureCounter::Sent).await;
286        measure.incr(did2, MeasureCounter::Received).await;
287        measure.incr(did2, MeasureCounter::Received).await;
288        measure.incr(did2, MeasureCounter::Received).await;
289
290        assert_eq!(measure.get_count(did1, MeasureCounter::Sent).await, 1);
291        assert_eq!(measure.get_count(did2, MeasureCounter::Sent).await, 2);
292        assert_eq!(measure.get_count(did1, MeasureCounter::Received).await, 1);
293        assert_eq!(measure.get_count(did2, MeasureCounter::Received).await, 3);
294    }
295
296    #[tokio::test]
297    async fn test_measure_period() {
298        let ms = Box::new(MemStorage::new());
299
300        let did = Did::from_str("0x11E807fcc88dD319270493fB2e822e388Fe36ab0").unwrap();
301
302        let clock = ManualMeasureClock::new(Utc::now());
303        let measure = PeriodicMeasure::new_with_clock(ms, Arc::new(clock.clone()));
304        assert_eq!(measure.get_count(did, MeasureCounter::Sent).await, 0);
305        assert_eq!(measure.get_count(did, MeasureCounter::Received).await, 0);
306
307        measure.incr(did, MeasureCounter::Sent).await;
308        measure.incr(did, MeasureCounter::Sent).await;
309        measure.incr(did, MeasureCounter::Received).await;
310
311        // Will take current count since previous count is 0.
312        assert_eq!(measure.get_count(did, MeasureCounter::Sent).await, 2);
313        assert_eq!(measure.get_count(did, MeasureCounter::Received).await, 1);
314
315        advance_period(&clock);
316
317        measure.incr(did, MeasureCounter::Sent).await;
318        measure.incr(did, MeasureCounter::Received).await;
319        measure.incr(did, MeasureCounter::Received).await;
320        measure.incr(did, MeasureCounter::Received).await;
321
322        // Will take previous count.
323        assert_eq!(measure.get_count(did, MeasureCounter::Sent).await, 2);
324        assert_eq!(measure.get_count(did, MeasureCounter::Received).await, 1);
325
326        advance_period(&clock);
327
328        // Will take previous count.
329        assert_eq!(measure.get_count(did, MeasureCounter::Sent).await, 1);
330        assert_eq!(measure.get_count(did, MeasureCounter::Received).await, 3);
331
332        advance_period(&clock);
333
334        // Will take previous count.
335        assert_eq!(measure.get_count(did, MeasureCounter::Sent).await, 0);
336        assert_eq!(measure.get_count(did, MeasureCounter::Received).await, 0);
337    }
338
339    #[tokio::test]
340    async fn test_persistent_measure_storage() {
341        let ms: MeasureStorage = Box::new(
342            SledStorage::new_with_cap_and_path(4096, "tmp/measure_test_db")
343                .await
344                .unwrap(),
345        );
346        ms.clear().await.unwrap();
347
348        let did = Did::from_str("0x11E807fcc88dD319270493fB2e822e388Fe36ab0").unwrap();
349        let clock = ManualMeasureClock::new(Utc::now());
350        let measure = PeriodicMeasure::new_with_clock(ms, Arc::new(clock.clone()));
351        assert_eq!(measure.get_count(did, MeasureCounter::Sent).await, 0);
352        assert_eq!(measure.get_count(did, MeasureCounter::Received).await, 0);
353
354        measure.incr(did, MeasureCounter::Sent).await;
355        measure.incr(did, MeasureCounter::Sent).await;
356        measure.incr(did, MeasureCounter::Received).await;
357
358        advance_period(&clock);
359
360        // Flush to storage.
361        let c1 = measure.get_count(did, MeasureCounter::Sent).await;
362        assert_eq!(c1, 2);
363        let c2 = measure.get_count(did, MeasureCounter::Received).await;
364        assert_eq!(c2, 1);
365
366        // Release lock of measure storage.
367        drop(measure);
368
369        // Create new measure.
370        let ms2 = Box::new(
371            SledStorage::new_with_cap_and_path(4096, "tmp/measure_test_db")
372                .await
373                .unwrap(),
374        );
375        let measure2 = PeriodicMeasure::new_with_clock(ms2, Arc::new(clock));
376
377        // Will take previous count from storage.
378        assert_eq!(measure2.get_count(did, MeasureCounter::Sent).await, 2);
379        assert_eq!(measure2.get_count(did, MeasureCounter::Received).await, 1);
380    }
381}