// This file is part of Substrate.
// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
use libp2p::core::multiaddr::{Multiaddr, Protocol};
use std::collections::HashMap;
use sp_authority_discovery::AuthorityId;
use sc_network::PeerId;
/// Cache for [`AuthorityId`] -> [`Vec`] and [`PeerId`] -> [`AuthorityId`] mappings.
pub(super) struct AddrCache {
authority_id_to_addresses: HashMap>,
peer_id_to_authority_id: HashMap,
}
impl AddrCache {
pub fn new() -> Self {
AddrCache {
authority_id_to_addresses: HashMap::new(),
peer_id_to_authority_id: HashMap::new(),
}
}
/// Inserts the given [`AuthorityId`] and [`Vec`] pair for future lookups by
/// [`AuthorityId`] or [`PeerId`].
pub fn insert(&mut self, authority_id: AuthorityId, mut addresses: Vec) {
if addresses.is_empty() {
return;
}
// Insert into `self.peer_id_to_authority_id`.
let peer_ids = addresses.iter()
.map(|a| peer_id_from_multiaddr(a))
.filter_map(|peer_id| peer_id);
for peer_id in peer_ids {
self.peer_id_to_authority_id.insert(peer_id, authority_id.clone());
}
// Insert into `self.authority_id_to_addresses`.
addresses.sort_unstable_by(|a, b| a.as_ref().cmp(b.as_ref()));
self.authority_id_to_addresses.insert(authority_id, addresses);
}
/// Returns the number of authority IDs in the cache.
pub fn num_ids(&self) -> usize {
self.authority_id_to_addresses.len()
}
/// Returns the addresses for the given [`AuthorityId`].
pub fn get_addresses_by_authority_id(&self, authority_id: &AuthorityId) -> Option<&Vec> {
self.authority_id_to_addresses.get(&authority_id)
}
/// Returns the [`AuthorityId`] for the given [`PeerId`].
pub fn get_authority_id_by_peer_id(&self, peer_id: &PeerId) -> Option<&AuthorityId> {
self.peer_id_to_authority_id.get(peer_id)
}
/// Removes all [`PeerId`]s and [`Multiaddr`]s from the cache that are not related to the given
/// [`AuthorityId`]s.
pub fn retain_ids(&mut self, authority_ids: &Vec) {
// The below logic could be replaced by `BtreeMap::drain_filter` once it stabilized.
let authority_ids_to_remove = self.authority_id_to_addresses.iter()
.filter(|(id, _addresses)| !authority_ids.contains(id))
.map(|entry| entry.0)
.cloned()
.collect::>();
for authority_id_to_remove in authority_ids_to_remove {
// Remove other entries from `self.authority_id_to_addresses`.
let addresses = self.authority_id_to_addresses.remove(&authority_id_to_remove);
// Remove other entries from `self.peer_id_to_authority_id`.
let peer_ids = addresses.iter()
.flatten()
.map(|a| peer_id_from_multiaddr(a))
.filter_map(|peer_id| peer_id);
for peer_id in peer_ids {
if let Some(id) = self.peer_id_to_authority_id.remove(&peer_id) {
debug_assert_eq!(authority_id_to_remove, id);
}
}
}
}
}
fn peer_id_from_multiaddr(addr: &Multiaddr) -> Option {
addr.iter().last().and_then(|protocol| if let Protocol::P2p(multihash) = protocol {
PeerId::from_multihash(multihash).ok()
} else {
None
})
}
#[cfg(test)]
mod tests {
use super::*;
use libp2p::multihash::{self, Multihash};
use quickcheck::{Arbitrary, Gen, QuickCheck, TestResult};
use rand::Rng;
use sp_authority_discovery::{AuthorityId, AuthorityPair};
use sp_core::crypto::Pair;
#[derive(Clone, Debug)]
struct TestAuthorityId(AuthorityId);
impl Arbitrary for TestAuthorityId {
fn arbitrary(g: &mut G) -> Self {
let seed: [u8; 32] = g.gen();
TestAuthorityId(AuthorityPair::from_seed_slice(&seed).unwrap().public())
}
}
#[derive(Clone, Debug)]
struct TestMultiaddr(Multiaddr);
impl Arbitrary for TestMultiaddr {
fn arbitrary(g: &mut G) -> Self {
let seed: [u8; 32] = g.gen();
let peer_id = PeerId::from_multihash(
Multihash::wrap(multihash::Code::Sha2_256.into(), &seed).unwrap()
).unwrap();
let multiaddr = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333".parse::()
.unwrap()
.with(Protocol::P2p(peer_id.into()));
TestMultiaddr(multiaddr)
}
}
#[test]
fn retains_only_entries_of_provided_authority_ids() {
fn property(
first: (TestAuthorityId, TestMultiaddr),
second: (TestAuthorityId, TestMultiaddr),
third: (TestAuthorityId, TestMultiaddr),
) -> TestResult {
let first: (AuthorityId, Multiaddr) = ((first.0).0, (first.1).0);
let second: (AuthorityId, Multiaddr) = ((second.0).0, (second.1).0);
let third: (AuthorityId, Multiaddr) = ((third.0).0, (third.1).0);
let mut cache = AddrCache::new();
cache.insert(first.0.clone(), vec![first.1.clone()]);
cache.insert(second.0.clone(), vec![second.1.clone()]);
cache.insert(third.0.clone(), vec![third.1.clone()]);
assert_eq!(
Some(&vec![third.1.clone()]),
cache.get_addresses_by_authority_id(&third.0),
"Expect `get_addresses_by_authority_id` to return addresses of third authority."
);
assert_eq!(
Some(&third.0),
cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()),
"Expect `get_authority_id_by_peer_id` to return `AuthorityId` of third authority."
);
cache.retain_ids(&vec![first.0, second.0]);
assert_eq!(
None, cache.get_addresses_by_authority_id(&third.0),
"Expect `get_addresses_by_authority_id` to not return `None` for third authority."
);
assert_eq!(
None, cache.get_authority_id_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()),
"Expect `get_authority_id_by_peer_id` to return `None` for third authority."
);
TestResult::passed()
}
QuickCheck::new()
.max_tests(10)
.quickcheck(property as fn(_, _, _) -> TestResult)
}
}