Skip to main content

hopr_transport_p2p/
peer_store.rs

1use std::{collections::HashSet, sync::Arc};
2
3use hopr_api::{Multiaddr, PeerId};
4use thiserror::Error;
5
6/// Errors from the network peer store.
7#[derive(Error, Debug)]
8pub enum NetworkError {
9    #[error("performing an operation on own PeerId")]
10    DisallowedOperationOnOwnPeerIdError,
11}
12
13/// Result built on top of [`NetworkError`]
14pub type Result<T> = core::result::Result<T, NetworkError>;
15
16#[cfg(all(feature = "telemetry", not(test)))]
17lazy_static::lazy_static! {
18    static ref METRIC_PEER_COUNT:  hopr_metrics::SimpleGauge =
19         hopr_metrics::SimpleGauge::new("hopr_peer_count", "Number of all peers").unwrap();
20}
21
22/// In-memory store for network peer multiaddresses.
23///
24/// The structure holds the mapping between the [`PeerId`] and its associated reported
25/// multiaddresses recovered from the network discovery mechanism as [`Multiaddr`].
26///
27/// The store can be combined with other data structures to offer a complete view of
28/// the network state in regards to the discovered peers.
29#[derive(Clone, Debug)]
30pub struct NetworkPeerStore {
31    me: PeerId,
32    my_addresses: HashSet<Multiaddr>,
33    addresses: Arc<dashmap::DashMap<PeerId, HashSet<Multiaddr>>>,
34}
35
36impl NetworkPeerStore {
37    pub fn new(me: PeerId, my_addresses: HashSet<Multiaddr>) -> Self {
38        Self {
39            me,
40            my_addresses,
41            addresses: Arc::new(dashmap::DashMap::new()),
42        }
43    }
44
45    #[inline]
46    pub fn me(&self) -> &PeerId {
47        &self.me
48    }
49
50    /// Check whether the peer is present in the network.
51    #[inline]
52    #[tracing::instrument(level = "trace", skip(self), ret(Display))]
53    pub fn has(&self, peer: &PeerId) -> bool {
54        peer == &self.me || self.addresses.contains_key(peer)
55    }
56
57    /// Add a new peer with discovered addresses.
58    ///
59    /// The function is smart and extends the existing multiaddresses if the peer is already present.
60    #[tracing::instrument(level = "debug", skip(self), ret(level = "trace"), err)]
61    pub fn add(&self, peer: PeerId, addresses: HashSet<Multiaddr>) -> Result<()> {
62        if peer == self.me {
63            return Err(NetworkError::DisallowedOperationOnOwnPeerIdError);
64        }
65
66        if let Some(mut unit) = self.addresses.get_mut(&peer) {
67            unit.value_mut().extend(addresses.into_iter());
68        } else {
69            self.addresses.insert(peer, addresses);
70
71            #[cfg(all(feature = "telemetry", not(test)))]
72            METRIC_PEER_COUNT.increment(1.0);
73        }
74
75        Ok(())
76    }
77
78    /// Get multiaddresses of a peer.
79    #[tracing::instrument(level = "trace", skip(self))]
80    pub fn get(&self, peer: &PeerId) -> Option<HashSet<Multiaddr>> {
81        if peer == &self.me {
82            Some(self.my_addresses.clone())
83        } else {
84            self.addresses.get(peer).map(|addrs| addrs.value().clone())
85        }
86    }
87
88    /// Remove the peer from the store.
89    #[tracing::instrument(level = "debug", skip(self))]
90    pub fn remove(&self, peer: &PeerId) -> Result<()> {
91        if peer == &self.me {
92            return Err(NetworkError::DisallowedOperationOnOwnPeerIdError);
93        }
94
95        if self.addresses.remove(peer).is_some() {
96            #[cfg(all(feature = "telemetry", not(test)))]
97            METRIC_PEER_COUNT.decrement(1.0);
98        }
99
100        Ok(())
101    }
102
103    /// Iterator over [`PeerId`] keys in the store.
104    #[inline]
105    pub fn iter_keys(&self) -> impl Iterator<Item = PeerId> + '_ {
106        self.addresses.iter().map(|entry| *entry.key())
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use std::collections::HashSet;
113
114    use anyhow::Context;
115    use hopr_api::PeerId;
116
117    use super::NetworkPeerStore;
118
119    #[test]
120    fn network_peer_store_should_recognize_self() {
121        let me = PeerId::random();
122        let store = NetworkPeerStore::new(me, HashSet::new());
123
124        assert!(store.has(&me));
125    }
126
127    #[test]
128    fn network_peer_store_should_add_and_recognize_peer() {
129        let store = NetworkPeerStore::new(PeerId::random(), HashSet::new());
130
131        let peer = PeerId::random();
132
133        assert!(!store.has(&peer));
134
135        assert!(store.add(peer, HashSet::new()).is_ok());
136
137        assert!(store.has(&peer));
138    }
139
140    #[test]
141    fn network_peer_store_own_peer_should_fail_on_adding() {
142        let me = PeerId::random();
143        let store = NetworkPeerStore::new(me, HashSet::new());
144
145        assert!(store.add(me, HashSet::new()).is_err());
146    }
147
148    #[test]
149    fn network_peer_store_adding_the_same_peer_should_extend_multiaddresses() -> anyhow::Result<()> {
150        let store = NetworkPeerStore::new(PeerId::random(), HashSet::new());
151
152        let peer = PeerId::random();
153        assert!(store.add(peer, HashSet::new()).is_ok());
154
155        assert_eq!(store.get(&peer).context("should contain a value")?, HashSet::new());
156
157        let multiaddresses = HashSet::from(["/ip4/127.0.0.1/tcp/12345".try_into()?]);
158        assert!(store.add(peer, multiaddresses.clone()).is_ok());
159
160        assert_eq!(store.get(&peer).context("should contain a value")?, multiaddresses);
161
162        Ok(())
163    }
164
165    #[test]
166    fn network_peer_store_should_return_own_multiaddresses() -> anyhow::Result<()> {
167        let me = PeerId::random();
168        let multiaddresses = HashSet::from(["/ip4/127.0.0.1/tcp/12345".try_into()?]);
169        let store = NetworkPeerStore::new(me, multiaddresses.clone());
170
171        assert_eq!(store.get(&me), Some(multiaddresses));
172
173        Ok(())
174    }
175
176    #[test]
177    fn network_peer_store_should_return_stored_multiaddress() -> anyhow::Result<()> {
178        let store = NetworkPeerStore::new(PeerId::random(), HashSet::new());
179
180        let peer = PeerId::random();
181        let multiaddresses = HashSet::from(["/ip4/127.0.0.1/tcp/12345".try_into()?]);
182        assert!(store.add(peer, multiaddresses.clone()).is_ok());
183
184        assert_eq!(store.get(&peer), Some(multiaddresses));
185
186        Ok(())
187    }
188
189    #[test]
190    fn network_peer_store_should_fail_on_removing_self() -> anyhow::Result<()> {
191        let me = PeerId::random();
192        let multiaddresses = HashSet::from(["/ip4/127.0.0.1/tcp/12345".try_into()?]);
193        let store = NetworkPeerStore::new(me, multiaddresses.clone());
194
195        assert!(store.remove(&me).is_err());
196
197        Ok(())
198    }
199
200    #[test]
201    fn network_peer_store_should_succeed_on_removing_a_known_peer() -> anyhow::Result<()> {
202        let store = NetworkPeerStore::new(PeerId::random(), HashSet::new());
203
204        let peer = PeerId::random();
205        let multiaddresses = HashSet::from(["/ip4/127.0.0.1/tcp/12345".try_into()?]);
206        assert!(store.add(peer, multiaddresses.clone()).is_ok());
207
208        store.remove(&peer)?;
209
210        Ok(())
211    }
212
213    #[test]
214    fn network_peer_store_should_succeed_on_removing_an_unknown_peer() -> anyhow::Result<()> {
215        let store = NetworkPeerStore::new(PeerId::random(), HashSet::new());
216
217        let peer = PeerId::random();
218
219        store.remove(&peer)?;
220
221        Ok(())
222    }
223
224    #[test]
225    fn network_peer_store_should_be_referencing_the_same_underlying_data() -> anyhow::Result<()> {
226        let store = NetworkPeerStore::new(PeerId::random(), HashSet::new());
227        let store2 = store.clone();
228
229        let peer = PeerId::random();
230
231        store2.add(peer, HashSet::new())?;
232
233        assert_eq!(store.get(&peer).context("should contain a value")?, HashSet::new());
234
235        Ok(())
236    }
237
238    #[test]
239    fn iter_keys_should_return_all_added_peers() -> anyhow::Result<()> {
240        let store = NetworkPeerStore::new(PeerId::random(), HashSet::new());
241
242        let peers: HashSet<PeerId> = (0..3).map(|_| PeerId::random()).collect();
243
244        for peer in &peers {
245            store.add(*peer, HashSet::new())?;
246        }
247
248        let keys: HashSet<PeerId> = store.iter_keys().collect();
249
250        assert_eq!(keys, peers);
251
252        Ok(())
253    }
254
255    #[test]
256    fn iter_keys_should_return_empty_when_no_peers_added() {
257        let store = NetworkPeerStore::new(PeerId::random(), HashSet::new());
258
259        assert_eq!(store.iter_keys().count(), 0);
260    }
261
262    #[test]
263    fn iter_keys_should_not_include_own_peer_id() -> anyhow::Result<()> {
264        let me = PeerId::random();
265        let store = NetworkPeerStore::new(me, HashSet::new());
266
267        let peer = PeerId::random();
268        store.add(peer, HashSet::new())?;
269
270        let keys: Vec<PeerId> = store.iter_keys().collect();
271
272        assert_eq!(keys, vec![peer]);
273        assert!(!keys.contains(&me));
274
275        Ok(())
276    }
277}