Skip to main content

fedimint_core/
peer_availability.rs

1//! Guardians' views of each other's availability.
2//!
3//! The public
4//! [`PEER_AVAILABILITY_ENDPOINT`](crate::endpoint_constants::PEER_AVAILABILITY_ENDPOINT)
5//! serves them.
6//! Each guardian reports when it last saw each peer take part in consensus
7//! and, per window, how much of the window it observed itself
8//! (`coverage_bps`) and the fraction of that time each peer took part. A
9//! single report is one guardian's opinion; clients combine the reports with
10//! [`merge_peer_availability`].
11
12use std::collections::BTreeMap;
13
14use serde::{Deserialize, Serialize};
15
16use crate::{NumPeers, PeerId};
17
18/// Time constants of the availability windows in seconds: one week and 30
19/// days. Recent outages show in `last_seen_secs` instead.
20pub const PEER_AVAILABILITY_WINDOWS_SECS: [u64; 2] = [604_800, 2_592_000];
21
22/// One guardian's view of its peers' availability.
23#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
24pub struct PeerAvailability {
25    /// Unix time this guardian last received a consensus message from each
26    /// peer, as of its last persisted update (every 10 minutes)
27    pub last_seen_secs: BTreeMap<PeerId, u64>,
28    /// One entry per window in [`PEER_AVAILABILITY_WINDOWS_SECS`]
29    pub windows: Vec<WindowAvailability>,
30}
31
32/// The combined view of several guardians, see [`merge_peer_availability`].
33#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
34pub struct MergedPeerAvailability {
35    /// Unix time each peer last sent a consensus message to the reporters
36    pub last_seen_secs: BTreeMap<PeerId, u64>,
37    /// Per window in [`PEER_AVAILABILITY_WINDOWS_SECS`]: each peer's
38    /// availability in basis points
39    pub windows: Vec<BTreeMap<PeerId, u16>>,
40}
41
42/// One guardian's view of its peers' availability over one window.
43#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
44pub struct WindowAvailability {
45    /// Time constant of the moving average
46    pub window_secs: u64,
47    /// Share of the window this guardian observed itself, in basis points
48    pub coverage_bps: u16,
49    /// Share of the observed time each peer took part in consensus, in basis
50    /// points
51    pub peers: BTreeMap<PeerId, u16>,
52}
53
54/// Combines the reports of several guardians into one view.
55///
56/// A peer's own report about itself is ignored. Of the other reports the
57/// [`NumPeers::one_honest`]-th lowest value is used, so
58/// [`NumPeers::max_evil`] lying guardians cannot push a value below every
59/// honest report. With at least [`NumPeers::threshold`] usable reports they
60/// cannot push it above every honest report either. With fewer, for example
61/// while a guardian is offline, they can.
62///
63/// For availability, windows whose `window_secs` does not match
64/// [`PEER_AVAILABILITY_WINDOWS_SECS`] are ignored, and so are reports that
65/// cover less than half of the [`NumPeers::one_honest`]-th best coverage.
66/// A peer with fewer than [`NumPeers::one_honest`] usable reports is omitted.
67pub fn merge_peer_availability(
68    reports: &BTreeMap<PeerId, PeerAvailability>,
69    num_peers: NumPeers,
70) -> MergedPeerAvailability {
71    let one_honest = num_peers.one_honest();
72    // The other reporters' values about `peer`, sorted ascending
73    let others = |peer: PeerId, values: &mut dyn Iterator<Item = (PeerId, u64)>| {
74        let mut values: Vec<u64> = values
75            .filter(|(reporter, _)| *reporter != peer)
76            .map(|(_, value)| value)
77            .collect();
78        values.sort_unstable();
79        values
80    };
81
82    let last_seen_secs = num_peers
83        .peer_ids()
84        .filter_map(|peer| {
85            let mut values = reports.iter().filter_map(|(reporter, report)| {
86                Some((*reporter, *report.last_seen_secs.get(&peer)?))
87            });
88            Some((peer, *others(peer, &mut values).get(one_honest - 1)?))
89        })
90        .collect();
91
92    let windows = PEER_AVAILABILITY_WINDOWS_SECS
93        .iter()
94        .enumerate()
95        .map(|(window, window_secs)| {
96            let windows: Vec<(PeerId, &WindowAvailability)> = reports
97                .iter()
98                .filter_map(|(reporter, report)| Some((*reporter, report.windows.get(window)?)))
99                .filter(|(_, window)| window.window_secs == *window_secs && 0 < window.coverage_bps)
100                .collect();
101
102            num_peers
103                .peer_ids()
104                .filter_map(|peer| {
105                    let mut coverages = windows
106                        .iter()
107                        .map(|(reporter, window)| (*reporter, u64::from(window.coverage_bps)));
108                    let min_coverage = others(peer, &mut coverages)
109                        .iter()
110                        .rev()
111                        .nth(one_honest - 1)?
112                        .div_ceil(2);
113                    let mut values = windows
114                        .iter()
115                        .filter(|(_, window)| min_coverage <= u64::from(window.coverage_bps))
116                        .filter_map(|(reporter, window)| {
117                            Some((*reporter, u64::from(*window.peers.get(&peer)?)))
118                        });
119                    let value = *others(peer, &mut values).get(one_honest - 1)?;
120                    Some((peer, u16::try_from(value).expect("came from a u16")))
121                })
122                .collect()
123        })
124        .collect();
125
126    MergedPeerAvailability {
127        last_seen_secs,
128        windows,
129    }
130}
131
132#[cfg(test)]
133mod tests;