hopr_transport_p2p/
peer_store.rs1use std::{collections::HashSet, sync::Arc};
2
3use hopr_api::{Multiaddr, PeerId};
4use thiserror::Error;
5
6#[derive(Error, Debug)]
8pub enum NetworkError {
9 #[error("performing an operation on own PeerId")]
10 DisallowedOperationOnOwnPeerIdError,
11}
12
13pub 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#[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 #[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 #[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 #[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 #[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 #[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}