sc_authority_discovery/worker/
addr_cache.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use sc_network::{multiaddr::Protocol, Multiaddr};
20use sc_network_types::PeerId;
21use sp_authority_discovery::AuthorityId;
22use std::collections::{hash_map::Entry, HashMap, HashSet};
23
24/// Cache for [`AuthorityId`] -> [`HashSet<Multiaddr>`] and [`PeerId`] -> [`HashSet<AuthorityId>`]
25/// mappings.
26pub(super) struct AddrCache {
27	/// The addresses found in `authority_id_to_addresses` are guaranteed to always match
28	/// the peerids found in `peer_id_to_authority_ids`. In other words, these two hashmaps
29	/// are similar to a bi-directional map.
30	///
31	/// Since we may store the mapping across several sessions, a single
32	/// `PeerId` might correspond to multiple `AuthorityId`s. However,
33	/// it's not expected that a single `AuthorityId` can have multiple `PeerId`s.
34	authority_id_to_addresses: HashMap<AuthorityId, HashSet<Multiaddr>>,
35	peer_id_to_authority_ids: HashMap<PeerId, HashSet<AuthorityId>>,
36}
37
38impl AddrCache {
39	pub fn new() -> Self {
40		AddrCache {
41			authority_id_to_addresses: HashMap::new(),
42			peer_id_to_authority_ids: HashMap::new(),
43		}
44	}
45
46	/// Inserts the given [`AuthorityId`] and [`Vec<Multiaddr>`] pair for future lookups by
47	/// [`AuthorityId`] or [`PeerId`].
48	pub fn insert(&mut self, authority_id: AuthorityId, addresses: Vec<Multiaddr>) {
49		let addresses = addresses.into_iter().collect::<HashSet<_>>();
50		let peer_ids = addresses_to_peer_ids(&addresses);
51
52		if peer_ids.is_empty() {
53			log::debug!(
54				target: super::LOG_TARGET,
55				"Authority({:?}) provides no addresses or addresses without peer ids. Adresses: {:?}",
56				authority_id,
57				addresses,
58			);
59
60			return
61		} else if peer_ids.len() > 1 {
62			log::warn!(
63				target: super::LOG_TARGET,
64				"Authority({:?}) can be reached through multiple peer ids: {:?}",
65				authority_id,
66				peer_ids
67			);
68		}
69
70		log::debug!(
71			target: super::LOG_TARGET,
72			"Found addresses for authority {authority_id:?}: {addresses:?}",
73		);
74
75		let old_addresses = self.authority_id_to_addresses.insert(authority_id.clone(), addresses);
76		let old_peer_ids = addresses_to_peer_ids(&old_addresses.unwrap_or_default());
77
78		// Add the new peer ids
79		peer_ids.difference(&old_peer_ids).for_each(|new_peer_id| {
80			self.peer_id_to_authority_ids
81				.entry(*new_peer_id)
82				.or_default()
83				.insert(authority_id.clone());
84		});
85
86		// Remove the old peer ids
87		self.remove_authority_id_from_peer_ids(&authority_id, old_peer_ids.difference(&peer_ids));
88	}
89
90	/// Remove the given `authority_id` from the `peer_id` to `authority_ids` mapping.
91	///
92	/// If a `peer_id` doesn't have any `authority_id` assigned anymore, it is removed.
93	fn remove_authority_id_from_peer_ids<'a>(
94		&mut self,
95		authority_id: &AuthorityId,
96		peer_ids: impl Iterator<Item = &'a PeerId>,
97	) {
98		peer_ids.for_each(|peer_id| {
99			if let Entry::Occupied(mut e) = self.peer_id_to_authority_ids.entry(*peer_id) {
100				e.get_mut().remove(authority_id);
101
102				// If there are no more entries, remove the peer id.
103				if e.get().is_empty() {
104					e.remove();
105				}
106			}
107		})
108	}
109
110	/// Returns the number of authority IDs in the cache.
111	pub fn num_authority_ids(&self) -> usize {
112		self.authority_id_to_addresses.len()
113	}
114
115	/// Returns the addresses for the given [`AuthorityId`].
116	pub fn get_addresses_by_authority_id(
117		&self,
118		authority_id: &AuthorityId,
119	) -> Option<&HashSet<Multiaddr>> {
120		self.authority_id_to_addresses.get(authority_id)
121	}
122
123	/// Returns the [`AuthorityId`]s for the given [`PeerId`].
124	///
125	/// As the authority id can change between sessions, one [`PeerId`] can be mapped to
126	/// multiple authority ids.
127	pub fn get_authority_ids_by_peer_id(&self, peer_id: &PeerId) -> Option<&HashSet<AuthorityId>> {
128		self.peer_id_to_authority_ids.get(peer_id)
129	}
130
131	/// Removes all [`PeerId`]s and [`Multiaddr`]s from the cache that are not related to the given
132	/// [`AuthorityId`]s.
133	pub fn retain_ids(&mut self, authority_ids: &[AuthorityId]) {
134		// The below logic could be replaced by `BtreeMap::drain_filter` once it stabilized.
135		let authority_ids_to_remove = self
136			.authority_id_to_addresses
137			.iter()
138			.filter(|(id, _addresses)| !authority_ids.contains(id))
139			.map(|entry| entry.0)
140			.cloned()
141			.collect::<Vec<AuthorityId>>();
142
143		for authority_id_to_remove in authority_ids_to_remove {
144			// Remove other entries from `self.authority_id_to_addresses`.
145			let addresses = if let Some(addresses) =
146				self.authority_id_to_addresses.remove(&authority_id_to_remove)
147			{
148				addresses
149			} else {
150				continue
151			};
152
153			self.remove_authority_id_from_peer_ids(
154				&authority_id_to_remove,
155				addresses_to_peer_ids(&addresses).iter(),
156			);
157		}
158	}
159}
160
161fn peer_id_from_multiaddr(addr: &Multiaddr) -> Option<PeerId> {
162	addr.iter().last().and_then(|protocol| {
163		if let Protocol::P2p(multihash) = protocol {
164			PeerId::from_multihash(multihash).ok()
165		} else {
166			None
167		}
168	})
169}
170
171fn addresses_to_peer_ids(addresses: &HashSet<Multiaddr>) -> HashSet<PeerId> {
172	addresses.iter().filter_map(peer_id_from_multiaddr).collect::<HashSet<_>>()
173}
174
175#[cfg(test)]
176mod tests {
177	use super::*;
178
179	use quickcheck::{Arbitrary, Gen, QuickCheck, TestResult};
180	use sc_network_types::multihash::{Code, Multihash};
181
182	use sp_authority_discovery::{AuthorityId, AuthorityPair};
183	use sp_core::crypto::Pair;
184
185	#[derive(Clone, Debug)]
186	struct TestAuthorityId(AuthorityId);
187
188	impl Arbitrary for TestAuthorityId {
189		fn arbitrary(g: &mut Gen) -> Self {
190			let seed = (0..32).map(|_| u8::arbitrary(g)).collect::<Vec<_>>();
191			TestAuthorityId(AuthorityPair::from_seed_slice(&seed).unwrap().public())
192		}
193	}
194
195	#[derive(Clone, Debug)]
196	struct TestMultiaddr(Multiaddr);
197
198	impl Arbitrary for TestMultiaddr {
199		fn arbitrary(g: &mut Gen) -> Self {
200			let seed = (0..32).map(|_| u8::arbitrary(g)).collect::<Vec<_>>();
201			let peer_id =
202				PeerId::from_multihash(Multihash::wrap(Code::Sha2_256.into(), &seed).unwrap())
203					.unwrap();
204			let multiaddr = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333"
205				.parse::<Multiaddr>()
206				.unwrap()
207				.with(Protocol::P2p(peer_id.into()));
208
209			TestMultiaddr(multiaddr)
210		}
211	}
212
213	#[derive(Clone, Debug)]
214	struct TestMultiaddrsSamePeerCombo(Multiaddr, Multiaddr);
215
216	impl Arbitrary for TestMultiaddrsSamePeerCombo {
217		fn arbitrary(g: &mut Gen) -> Self {
218			let seed = (0..32).map(|_| u8::arbitrary(g)).collect::<Vec<_>>();
219			let peer_id =
220				PeerId::from_multihash(Multihash::wrap(Code::Sha2_256.into(), &seed).unwrap())
221					.unwrap();
222			let multiaddr1 = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333"
223				.parse::<Multiaddr>()
224				.unwrap()
225				.with(Protocol::P2p(peer_id.into()));
226			let multiaddr2 = "/ip6/2002:db8:0:0:0:0:0:2/tcp/30133"
227				.parse::<Multiaddr>()
228				.unwrap()
229				.with(Protocol::P2p(peer_id.into()));
230			TestMultiaddrsSamePeerCombo(multiaddr1, multiaddr2)
231		}
232	}
233
234	#[test]
235	fn retains_only_entries_of_provided_authority_ids() {
236		fn property(
237			first: (TestAuthorityId, TestMultiaddr),
238			second: (TestAuthorityId, TestMultiaddr),
239			third: (TestAuthorityId, TestMultiaddr),
240		) -> TestResult {
241			let first: (AuthorityId, Multiaddr) = ((first.0).0, (first.1).0);
242			let second: (AuthorityId, Multiaddr) = ((second.0).0, (second.1).0);
243			let third: (AuthorityId, Multiaddr) = ((third.0).0, (third.1).0);
244
245			let mut cache = AddrCache::new();
246
247			cache.insert(first.0.clone(), vec![first.1.clone()]);
248			cache.insert(second.0.clone(), vec![second.1.clone()]);
249			cache.insert(third.0.clone(), vec![third.1.clone()]);
250
251			assert_eq!(
252				Some(&HashSet::from([third.1.clone()])),
253				cache.get_addresses_by_authority_id(&third.0),
254				"Expect `get_addresses_by_authority_id` to return addresses of third authority.",
255			);
256			assert_eq!(
257				Some(&HashSet::from([third.0.clone()])),
258				cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()),
259				"Expect `get_authority_id_by_peer_id` to return `AuthorityId` of third authority.",
260			);
261
262			cache.retain_ids(&vec![first.0.clone(), second.0]);
263
264			assert_eq!(
265				None,
266				cache.get_addresses_by_authority_id(&third.0),
267				"Expect `get_addresses_by_authority_id` to not return `None` for third authority.",
268			);
269			assert_eq!(
270				None,
271				cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()),
272				"Expect `get_authority_id_by_peer_id` to return `None` for third authority.",
273			);
274
275			TestResult::passed()
276		}
277
278		QuickCheck::new()
279			.max_tests(10)
280			.quickcheck(property as fn(_, _, _) -> TestResult)
281	}
282
283	#[test]
284	fn keeps_consistency_between_authority_id_and_peer_id() {
285		fn property(
286			authority1: TestAuthorityId,
287			authority2: TestAuthorityId,
288			multiaddr1: TestMultiaddr,
289			multiaddr2: TestMultiaddr,
290			multiaddr3: TestMultiaddrsSamePeerCombo,
291		) -> TestResult {
292			let authority1 = authority1.0;
293			let authority2 = authority2.0;
294			let multiaddr1 = multiaddr1.0;
295			let multiaddr2 = multiaddr2.0;
296			let TestMultiaddrsSamePeerCombo(multiaddr3, multiaddr4) = multiaddr3;
297
298			let mut cache = AddrCache::new();
299
300			cache.insert(authority1.clone(), vec![multiaddr1.clone()]);
301			cache.insert(
302				authority1.clone(),
303				vec![multiaddr2.clone(), multiaddr3.clone(), multiaddr4.clone()],
304			);
305
306			assert_eq!(
307				None,
308				cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr1).unwrap())
309			);
310			assert_eq!(
311				Some(&HashSet::from([authority1.clone()])),
312				cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
313			);
314			assert_eq!(
315				Some(&HashSet::from([authority1.clone()])),
316				cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
317			);
318			assert_eq!(
319				Some(&HashSet::from([authority1.clone()])),
320				cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr4).unwrap())
321			);
322
323			cache.insert(authority2.clone(), vec![multiaddr2.clone()]);
324
325			assert_eq!(
326				Some(&HashSet::from([authority2.clone(), authority1.clone()])),
327				cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
328			);
329			assert_eq!(
330				Some(&HashSet::from([authority1.clone()])),
331				cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
332			);
333			assert_eq!(cache.get_addresses_by_authority_id(&authority1).unwrap().len(), 3);
334
335			cache.insert(authority2.clone(), vec![multiaddr2.clone(), multiaddr3.clone()]);
336
337			assert_eq!(
338				Some(&HashSet::from([authority2.clone(), authority1.clone()])),
339				cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
340			);
341			assert_eq!(
342				Some(&HashSet::from([authority2.clone(), authority1.clone()])),
343				cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
344			);
345			assert_eq!(
346				&HashSet::from([multiaddr2.clone(), multiaddr3.clone(), multiaddr4.clone()]),
347				cache.get_addresses_by_authority_id(&authority1).unwrap(),
348			);
349
350			TestResult::passed()
351		}
352
353		QuickCheck::new()
354			.max_tests(10)
355			.quickcheck(property as fn(_, _, _, _, _) -> TestResult)
356	}
357
358	/// As the runtime gives us the current + next authority ids, it can happen that some
359	/// authority changed its session keys. Changing the sessions keys leads to having two
360	/// authority ids that map to the same `PeerId` & addresses.
361	#[test]
362	fn adding_two_authority_ids_for_the_same_peer_id() {
363		let mut addr_cache = AddrCache::new();
364
365		let peer_id = PeerId::random();
366		let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into()));
367
368		let authority_id0 = AuthorityPair::generate().0.public();
369		let authority_id1 = AuthorityPair::generate().0.public();
370
371		addr_cache.insert(authority_id0.clone(), vec![addr.clone()]);
372		addr_cache.insert(authority_id1.clone(), vec![addr.clone()]);
373
374		assert_eq!(2, addr_cache.num_authority_ids());
375		assert_eq!(
376			&HashSet::from([addr.clone()]),
377			addr_cache.get_addresses_by_authority_id(&authority_id0).unwrap()
378		);
379		assert_eq!(
380			&HashSet::from([addr]),
381			addr_cache.get_addresses_by_authority_id(&authority_id1).unwrap()
382		);
383	}
384}