hopr_transport_network/
store.rs

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