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