use crate::util;
use alloc::{
borrow::ToOwned as _,
collections::{BTreeMap, BTreeSet, btree_map},
vec::Vec,
};
use core::{hash::Hash, iter, ops};
use rand::seq::IteratorRandom as _;
use rand_chacha::{
ChaCha20Rng,
rand_core::{RngCore as _, SeedableRng as _},
};
pub use crate::libp2p::PeerId;
#[derive(Debug)]
pub struct BasicPeeringStrategy<TChainId, TInstant> {
peer_ids: slab::Slab<PeerId>,
peer_ids_indices: hashbrown::HashMap<PeerId, usize, util::SipHasherBuild>,
addresses: BTreeMap<(usize, Vec<u8>), u32>,
chains: slab::Slab<TChainId>,
chains_indices: hashbrown::HashMap<TChainId, usize, util::SipHasherBuild>,
peers_chains: BTreeMap<(usize, usize), PeerChainState<TInstant>>,
peers_chains_by_state: BTreeSet<(usize, PeerChainState<TInstant>, usize)>,
randomness: ChaCha20Rng,
}
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
enum PeerChainState<TInstant> {
Assignable,
Banned { expires: TInstant },
Slot,
}
pub struct Config {
pub randomness_seed: [u8; 32],
pub peers_capacity: usize,
pub chains_capacity: usize,
}
impl<TChainId, TInstant> BasicPeeringStrategy<TChainId, TInstant>
where
TChainId: PartialOrd + Ord + Eq + Hash + Clone,
TInstant: PartialOrd + Ord + Eq + Clone,
{
pub fn new(config: Config) -> Self {
let mut randomness = ChaCha20Rng::from_seed(config.randomness_seed);
BasicPeeringStrategy {
peer_ids: slab::Slab::with_capacity(config.peers_capacity),
peer_ids_indices: hashbrown::HashMap::with_capacity_and_hasher(
config.peers_capacity,
util::SipHasherBuild::new({
let mut seed = [0; 16];
randomness.fill_bytes(&mut seed);
seed
}),
),
addresses: BTreeMap::new(),
chains: slab::Slab::with_capacity(config.chains_capacity),
chains_indices: hashbrown::HashMap::with_capacity_and_hasher(
config.chains_capacity,
util::SipHasherBuild::new({
let mut seed = [0; 16];
randomness.fill_bytes(&mut seed);
seed
}),
),
peers_chains: BTreeMap::new(),
peers_chains_by_state: BTreeSet::new(),
randomness,
}
}
pub fn remove_chain_peers(&mut self, chain: &TChainId) {
let Some(chain_index) = self.chains_indices.remove(chain) else {
return;
};
self.chains.remove(chain_index);
let chain_peers = {
let mut in_chain_and_after_chain = self.peers_chains_by_state.split_off(&(
chain_index,
PeerChainState::Assignable,
usize::MIN,
));
let mut after_chain = in_chain_and_after_chain.split_off(&(
chain_index + 1,
PeerChainState::Assignable,
usize::MIN,
));
self.peers_chains_by_state.append(&mut after_chain);
in_chain_and_after_chain
};
for (_, _, peer_id_index) in chain_peers {
let _was_in = self.peers_chains.remove(&(peer_id_index, chain_index));
debug_assert!(_was_in.is_some());
self.try_clean_up_peer_id(peer_id_index);
}
}
pub fn insert_chain_peer(
&mut self,
chain: TChainId,
peer_id: PeerId,
max_peers_per_chain: usize,
) -> InsertChainPeerResult {
let peer_id_index = self.get_or_insert_peer_index(&peer_id);
let chain_index = self.get_or_insert_chain_index(&chain);
if let btree_map::Entry::Vacant(entry) =
self.peers_chains.entry((peer_id_index, chain_index))
{
let peer_to_remove = if self
.peers_chains_by_state
.range(
(chain_index, PeerChainState::Assignable, usize::MIN)
..=(chain_index, PeerChainState::Slot, usize::MAX),
)
.count()
>= max_peers_per_chain
{
self.peers_chains_by_state
.range(
(chain_index, PeerChainState::Assignable, usize::MIN)
..(chain_index, PeerChainState::Slot, usize::MIN),
)
.choose(&mut self.randomness)
.map(|(_, _, peer_index)| *peer_index)
} else {
None
};
let _was_inserted = self.peers_chains_by_state.insert((
chain_index,
PeerChainState::Assignable,
peer_id_index,
));
debug_assert!(_was_inserted);
entry.insert(PeerChainState::Assignable);
let peer_removed = if let Some(peer_to_remove) = peer_to_remove {
let peer_id_to_remove = self.peer_ids[peer_to_remove].clone();
let state = self
.peers_chains
.remove(&(peer_to_remove, chain_index))
.unwrap_or_else(|| unreachable!());
debug_assert!(!matches!(state, PeerChainState::Slot));
let _was_removed =
self.peers_chains_by_state
.remove(&(chain_index, state, peer_to_remove));
debug_assert!(_was_removed);
self.try_clean_up_peer_id(peer_to_remove);
Some(peer_id_to_remove)
} else {
None
};
InsertChainPeerResult::Inserted { peer_removed }
} else {
InsertChainPeerResult::Duplicate
}
}
pub fn unassign_slot_and_remove_chain_peer(
&mut self,
chain: &TChainId,
peer_id: &PeerId,
) -> UnassignSlotAndRemoveChainPeer<TInstant> {
let Some(&peer_id_index) = self.peer_ids_indices.get(peer_id) else {
return UnassignSlotAndRemoveChainPeer::NotAssigned;
};
let Some(&chain_index) = self.chains_indices.get(chain) else {
return UnassignSlotAndRemoveChainPeer::NotAssigned;
};
if let Some(state) = self.peers_chains.remove(&(peer_id_index, chain_index)) {
let _was_removed =
self.peers_chains_by_state
.remove(&(chain_index, state.clone(), peer_id_index));
debug_assert!(_was_removed);
self.try_clean_up_peer_id(peer_id_index);
self.try_clean_up_chain(chain_index);
match state {
PeerChainState::Assignable => UnassignSlotAndRemoveChainPeer::Assigned {
ban_expiration: None,
},
PeerChainState::Banned { expires } => UnassignSlotAndRemoveChainPeer::Assigned {
ban_expiration: Some(expires),
},
PeerChainState::Slot => UnassignSlotAndRemoveChainPeer::HadSlot,
}
} else {
UnassignSlotAndRemoveChainPeer::NotAssigned
}
}
pub fn chain_peers_unordered(&self, chain: &TChainId) -> impl Iterator<Item = &PeerId> {
let Some(&chain_index) = self.chains_indices.get(chain) else {
return either::Right(iter::empty());
};
either::Left(
self.peers_chains_by_state
.range(
(chain_index, PeerChainState::Assignable, usize::MIN)
..=(chain_index, PeerChainState::Slot, usize::MAX),
)
.map(|(_, _, p)| &self.peer_ids[*p]),
)
}
pub fn insert_address(
&mut self,
peer_id: &PeerId,
address: Vec<u8>,
max_addresses: usize,
) -> InsertAddressResult {
let Some(&peer_id_index) = self.peer_ids_indices.get(peer_id) else {
return InsertAddressResult::UnknownPeer;
};
match self.insert_address_inner(peer_id_index, address, max_addresses, 0, false) {
InsertAddressConnectionsResult::AlreadyKnown => InsertAddressResult::AlreadyKnown,
InsertAddressConnectionsResult::Inserted { address_removed } => {
InsertAddressResult::Inserted { address_removed }
}
}
}
pub fn increase_address_connections(
&mut self,
peer_id: &PeerId,
address: Vec<u8>,
max_addresses: usize,
) -> InsertAddressConnectionsResult {
let peer_id_index = self.get_or_insert_peer_index(peer_id);
self.insert_address_inner(peer_id_index, address, max_addresses, 1, true)
}
fn insert_address_inner(
&mut self,
peer_id_index: usize,
address: Vec<u8>,
max_addresses: usize,
initial_num_connections: u32,
increase_if_present: bool,
) -> InsertAddressConnectionsResult {
match self.addresses.entry((peer_id_index, address.clone())) {
btree_map::Entry::Vacant(entry) => {
entry.insert(initial_num_connections);
let address_removed = {
let num_addresses = self
.addresses
.range((peer_id_index, Vec::new())..=(peer_id_index + 1, Vec::new()))
.count();
if num_addresses >= max_addresses {
self.addresses
.range((peer_id_index, Vec::new())..=(peer_id_index + 1, Vec::new()))
.filter(|((_, a), n)| **n == 0 && *a != address)
.choose(&mut self.randomness)
.map(|((_, a), _)| a.clone())
} else {
None
}
};
if let Some(address_removed) = address_removed.as_ref() {
self.addresses
.remove(&(peer_id_index, address_removed.clone()));
}
InsertAddressConnectionsResult::Inserted { address_removed }
}
btree_map::Entry::Occupied(entry) => {
let entry = entry.into_mut();
if increase_if_present {
*entry = entry
.checked_add(1)
.unwrap_or_else(|| panic!("overflow in number of connections"));
}
InsertAddressConnectionsResult::AlreadyKnown
}
}
}
pub fn peer_addresses(&self, peer_id: &PeerId) -> impl Iterator<Item = &[u8]> {
let Some(&peer_id_index) = self.peer_ids_indices.get(peer_id) else {
return either::Right(iter::empty());
};
either::Left(
self.addresses
.range((peer_id_index, Vec::new())..(peer_id_index + 1, Vec::new()))
.map(|((_, a), _)| &a[..]),
)
}
pub fn pick_assignable_peer(
&mut self,
chain: &TChainId,
now: &TInstant,
) -> AssignablePeer<'_, TInstant> {
let Some(&chain_index) = self.chains_indices.get(chain) else {
return AssignablePeer::NoPeer;
};
if let Some((_, _, peer_id_index)) = self
.peers_chains_by_state
.range(
(chain_index, PeerChainState::Assignable, usize::MIN)
..=(
chain_index,
PeerChainState::Banned {
expires: now.clone(),
},
usize::MAX,
),
)
.choose(&mut self.randomness)
{
return AssignablePeer::Assignable(&self.peer_ids[*peer_id_index]);
}
if let Some((_, state, _)) = self
.peers_chains_by_state
.range((
ops::Bound::Excluded((
chain_index,
PeerChainState::Banned {
expires: now.clone(),
},
usize::MAX,
)),
ops::Bound::Excluded((chain_index, PeerChainState::Slot, usize::MIN)),
))
.next()
{
let PeerChainState::Banned { expires } = state else {
unreachable!()
};
AssignablePeer::AllPeersBanned {
next_unban: expires,
}
} else {
AssignablePeer::NoPeer
}
}
pub fn assign_slot(&mut self, chain: &TChainId, peer_id: &PeerId) {
let peer_id_index = self.get_or_insert_peer_index(peer_id);
let chain_index = self.get_or_insert_chain_index(chain);
match self.peers_chains.entry((peer_id_index, chain_index)) {
btree_map::Entry::Occupied(e) => {
let _was_removed = self.peers_chains_by_state.remove(&(
chain_index,
e.get().clone(),
peer_id_index,
));
debug_assert!(_was_removed);
*e.into_mut() = PeerChainState::Slot;
}
btree_map::Entry::Vacant(e) => {
e.insert(PeerChainState::Slot);
}
}
let _was_inserted =
self.peers_chains_by_state
.insert((chain_index, PeerChainState::Slot, peer_id_index));
debug_assert!(_was_inserted);
}
pub fn unassign_slot_and_ban(
&mut self,
chain: &TChainId,
peer_id: &PeerId,
when_unban: TInstant,
) -> UnassignSlotAndBan<TInstant> {
let (Some(&peer_id_index), Some(&chain_index)) = (
self.peer_ids_indices.get(peer_id),
self.chains_indices.get(chain),
) else {
return UnassignSlotAndBan::NotAssigned;
};
if let Some(state) = self.peers_chains.get_mut(&(peer_id_index, chain_index)) {
let return_value = match state {
PeerChainState::Banned { expires } if *expires >= when_unban => {
return UnassignSlotAndBan::AlreadyBanned {
when_unban: expires.clone(),
ban_extended: false,
};
}
PeerChainState::Banned { .. } => UnassignSlotAndBan::AlreadyBanned {
when_unban: when_unban.clone(),
ban_extended: true,
},
PeerChainState::Assignable => UnassignSlotAndBan::Banned { had_slot: false },
PeerChainState::Slot => UnassignSlotAndBan::Banned { had_slot: true },
};
let _was_in =
self.peers_chains_by_state
.remove(&(chain_index, state.clone(), peer_id_index));
debug_assert!(_was_in);
*state = PeerChainState::Banned {
expires: when_unban,
};
let _was_inserted =
self.peers_chains_by_state
.insert((chain_index, state.clone(), peer_id_index));
debug_assert!(_was_inserted);
return_value
} else {
UnassignSlotAndBan::NotAssigned
}
}
pub fn unassign_slots_and_ban(
&'_ mut self,
peer_id: &PeerId,
when_unban: TInstant,
) -> UnassignSlotsAndBanIter<'_, TChainId, TInstant> {
let Some(&peer_id_index) = self.peer_ids_indices.get(peer_id) else {
return UnassignSlotsAndBanIter {
chains: &self.chains,
peers_chains_by_state: &mut self.peers_chains_by_state,
inner_iter: None,
peer_id_index: 0,
when_unban,
};
};
UnassignSlotsAndBanIter {
chains: &self.chains,
peers_chains_by_state: &mut self.peers_chains_by_state,
inner_iter: Some(
self.peers_chains
.range_mut((peer_id_index, usize::MIN)..=(peer_id_index, usize::MAX))
.fuse(),
),
peer_id_index,
when_unban,
}
}
pub fn pick_address_and_add_connection(&mut self, peer_id: &PeerId) -> Option<&[u8]> {
let Some(&peer_id_index) = self.peer_ids_indices.get(peer_id) else {
return None;
};
if let Some(((_, address), num_connections)) = self
.addresses
.range_mut((peer_id_index, Vec::new())..(peer_id_index + 1, Vec::new()))
.filter(|(_, num_connections)| **num_connections == 0)
.choose(&mut self.randomness)
{
*num_connections = 1;
return Some(address);
}
None
}
pub fn decrease_address_connections(
&mut self,
peer_id: &PeerId,
address: &[u8],
) -> Result<(), DecreaseAddressConnectionsError> {
self.decrease_address_connections_inner(peer_id, address, false)
}
pub fn decrease_address_connections_and_remove_if_zero(
&mut self,
peer_id: &PeerId,
address: &[u8],
) -> Result<(), DecreaseAddressConnectionsError> {
self.decrease_address_connections_inner(peer_id, address, true)
}
fn decrease_address_connections_inner(
&mut self,
peer_id: &PeerId,
address: &[u8],
remove_if_reaches_zero: bool,
) -> Result<(), DecreaseAddressConnectionsError> {
let Some(&peer_id_index) = self.peer_ids_indices.get(peer_id) else {
return Err(DecreaseAddressConnectionsError::UnknownAddress);
};
let Some(num_connections) = self.addresses.get_mut(&(peer_id_index, address.to_owned()))
else {
return Err(DecreaseAddressConnectionsError::UnknownAddress);
};
if *num_connections == 0 {
return Err(DecreaseAddressConnectionsError::NotConnected);
}
*num_connections -= 1;
if *num_connections != 0 {
return Ok(());
}
if remove_if_reaches_zero {
self.addresses.remove(&(peer_id_index, address.to_owned()));
}
self.try_clean_up_peer_id(peer_id_index);
Ok(())
}
fn get_or_insert_chain_index(&mut self, chain: &TChainId) -> usize {
debug_assert_eq!(self.chains.len(), self.chains_indices.len());
match self.chains_indices.raw_entry_mut().from_key(chain) {
hashbrown::hash_map::RawEntryMut::Occupied(occupied_entry) => *occupied_entry.get(),
hashbrown::hash_map::RawEntryMut::Vacant(vacant_entry) => {
let idx = self.chains.insert(chain.clone());
vacant_entry.insert(chain.clone(), idx);
idx
}
}
}
fn try_clean_up_chain(&mut self, chain_index: usize) {
if self
.peers_chains_by_state
.range(
(chain_index, PeerChainState::Assignable, usize::MIN)
..=(chain_index, PeerChainState::Slot, usize::MAX),
)
.next()
.is_some()
{
return;
}
let chain_id = self.chains.remove(chain_index);
let _was_in = self.chains_indices.remove(&chain_id);
debug_assert_eq!(_was_in, Some(chain_index));
}
fn get_or_insert_peer_index(&mut self, peer_id: &PeerId) -> usize {
debug_assert_eq!(self.peer_ids.len(), self.peer_ids_indices.len());
match self.peer_ids_indices.raw_entry_mut().from_key(peer_id) {
hashbrown::hash_map::RawEntryMut::Occupied(occupied_entry) => *occupied_entry.get(),
hashbrown::hash_map::RawEntryMut::Vacant(vacant_entry) => {
let idx = self.peer_ids.insert(peer_id.clone());
vacant_entry.insert(peer_id.clone(), idx);
idx
}
}
}
fn try_clean_up_peer_id(&mut self, peer_id_index: usize) {
if self
.peers_chains
.range((peer_id_index, usize::MIN)..=(peer_id_index, usize::MAX))
.next()
.is_some()
{
return;
}
if self
.addresses
.range((peer_id_index, Vec::new())..(peer_id_index + 1, Vec::new()))
.any(|(_, num_connections)| *num_connections >= 1)
{
return;
}
let peer_id = self.peer_ids.remove(peer_id_index);
let _was_in = self.peer_ids_indices.remove(&peer_id);
debug_assert_eq!(_was_in, Some(peer_id_index));
for address in self
.addresses
.range((peer_id_index, Vec::new())..(peer_id_index + 1, Vec::new()))
.map(|((_, a), _)| a.clone())
.collect::<Vec<_>>()
{
let _was_removed = self.addresses.remove(&(peer_id_index, address));
debug_assert!(_was_removed.is_some());
}
}
}
#[derive(Debug, derive_more::Display, derive_more::Error)]
pub enum DecreaseAddressConnectionsError {
UnknownAddress,
NotConnected,
}
pub enum AssignablePeer<'a, TInstant> {
Assignable(&'a PeerId),
AllPeersBanned {
next_unban: &'a TInstant,
},
NoPeer,
}
pub enum InsertChainPeerResult {
Inserted {
peer_removed: Option<PeerId>,
},
Duplicate,
}
pub enum InsertAddressResult {
Inserted {
address_removed: Option<Vec<u8>>,
},
AlreadyKnown,
UnknownPeer,
}
pub enum InsertAddressConnectionsResult {
Inserted {
address_removed: Option<Vec<u8>>,
},
AlreadyKnown,
}
pub enum UnassignSlotAndBan<TInstant> {
NotAssigned,
AlreadyBanned {
when_unban: TInstant,
ban_extended: bool,
},
Banned {
had_slot: bool,
},
}
impl<TInstant> UnassignSlotAndBan<TInstant> {
pub fn had_slot(&self) -> bool {
matches!(self, UnassignSlotAndBan::Banned { had_slot: true })
}
}
pub enum UnassignSlotAndRemoveChainPeer<TInstant> {
NotAssigned,
Assigned {
ban_expiration: Option<TInstant>,
},
HadSlot,
}
pub struct UnassignSlotsAndBanIter<'a, TChainId, TInstant>
where
TInstant: PartialOrd + Ord + Eq + Clone,
{
chains: &'a slab::Slab<TChainId>,
peers_chains_by_state: &'a mut BTreeSet<(usize, PeerChainState<TInstant>, usize)>,
inner_iter:
Option<iter::Fuse<btree_map::RangeMut<'a, (usize, usize), PeerChainState<TInstant>>>>,
peer_id_index: usize,
when_unban: TInstant,
}
pub enum UnassignSlotsAndBan<TInstant> {
AlreadyBanned {
when_unban: TInstant,
ban_extended: bool,
},
Banned {
had_slot: bool,
},
}
impl<'a, TChainId, TInstant> Iterator for UnassignSlotsAndBanIter<'a, TChainId, TInstant>
where
TInstant: PartialOrd + Ord + Eq + Clone,
{
type Item = (&'a TChainId, UnassignSlotsAndBan<TInstant>);
fn next(&mut self) -> Option<Self::Item> {
let inner_iter = self.inner_iter.as_mut()?;
let (&(_, chain_index), state) = inner_iter.next()?;
let return_value = match state {
PeerChainState::Banned { expires } if *expires >= self.when_unban => {
return Some((
&self.chains[chain_index],
UnassignSlotsAndBan::AlreadyBanned {
when_unban: expires.clone(),
ban_extended: false,
},
));
}
PeerChainState::Banned { .. } => UnassignSlotsAndBan::AlreadyBanned {
when_unban: self.when_unban.clone(),
ban_extended: true,
},
PeerChainState::Assignable => UnassignSlotsAndBan::Banned { had_slot: false },
PeerChainState::Slot => UnassignSlotsAndBan::Banned { had_slot: true },
};
let _was_in =
self.peers_chains_by_state
.remove(&(chain_index, state.clone(), self.peer_id_index));
debug_assert!(_was_in);
*state = PeerChainState::Banned {
expires: self.when_unban.clone(),
};
let _was_inserted =
self.peers_chains_by_state
.insert((chain_index, state.clone(), self.peer_id_index));
debug_assert!(_was_inserted);
Some((&self.chains[chain_index], return_value))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner_iter
.as_ref()
.map_or((0, Some(0)), |inner| inner.size_hint())
}
}
impl<'a, TChainId, TInstant> iter::FusedIterator for UnassignSlotsAndBanIter<'a, TChainId, TInstant> where
TInstant: PartialOrd + Ord + Eq + Clone
{
}
impl<'a, TChainId, TInstant> Drop for UnassignSlotsAndBanIter<'a, TChainId, TInstant>
where
TInstant: PartialOrd + Ord + Eq + Clone,
{
fn drop(&mut self) {
while let Some(_) = self.next() {}
}
}
#[cfg(test)]
mod tests {
use super::{
BasicPeeringStrategy, Config, InsertAddressConnectionsResult, InsertAddressResult,
InsertChainPeerResult,
};
use crate::network::service::{PeerId, peer_id::PublicKey};
use core::time::Duration;
#[test]
fn peer_state_ordering() {
use super::PeerChainState;
assert!(PeerChainState::Assignable < PeerChainState::Banned { expires: 0 });
assert!(PeerChainState::Banned { expires: 5 } < PeerChainState::Banned { expires: 7 });
assert!(PeerChainState::Banned { expires: u32::MAX } < PeerChainState::Slot);
}
#[test]
fn addresses_removed_when_peer_has_no_chain_association() {
let mut bps = BasicPeeringStrategy::<u32, Duration>::new(Config {
randomness_seed: [0; 32],
peers_capacity: 0,
chains_capacity: 0,
});
let peer_id = PeerId::from_public_key(&PublicKey::Ed25519([0; 32]));
assert!(matches!(
bps.insert_chain_peer(0, peer_id.clone(), usize::MAX),
InsertChainPeerResult::Inserted { peer_removed: None }
));
assert!(matches!(
bps.insert_address(&peer_id, Vec::new(), usize::MAX),
InsertAddressResult::Inserted {
address_removed: None
}
));
assert_eq!(bps.peer_addresses(&peer_id).count(), 1);
bps.unassign_slot_and_remove_chain_peer(&0, &peer_id);
assert_eq!(bps.peer_addresses(&peer_id).count(), 0);
}
#[test]
fn addresses_not_removed_if_connected_when_peer_has_no_chain_association() {
let mut bps = BasicPeeringStrategy::<u32, Duration>::new(Config {
randomness_seed: [0; 32],
peers_capacity: 0,
chains_capacity: 0,
});
let peer_id = PeerId::from_public_key(&PublicKey::Ed25519([0; 32]));
assert!(matches!(
bps.insert_chain_peer(0, peer_id.clone(), usize::MAX),
InsertChainPeerResult::Inserted { peer_removed: None }
));
assert!(matches!(
bps.increase_address_connections(&peer_id, Vec::new(), usize::MAX),
InsertAddressConnectionsResult::Inserted {
address_removed: None
}
));
assert!(matches!(
bps.insert_address(&peer_id, vec![1], usize::MAX),
InsertAddressResult::Inserted {
address_removed: None
}
));
assert_eq!(bps.peer_addresses(&peer_id).count(), 2);
bps.unassign_slot_and_remove_chain_peer(&0, &peer_id);
assert_eq!(bps.peer_addresses(&peer_id).count(), 2);
bps.decrease_address_connections(&peer_id, &[]).unwrap();
assert_eq!(bps.peer_addresses(&peer_id).count(), 0);
}
#[test]
fn address_not_inserted_when_peer_has_no_chain_association() {
let mut bps = BasicPeeringStrategy::<u32, Duration>::new(Config {
randomness_seed: [0; 32],
peers_capacity: 0,
chains_capacity: 0,
});
let peer_id = PeerId::from_public_key(&PublicKey::Ed25519([0; 32]));
assert!(matches!(
bps.insert_address(&peer_id, Vec::new(), usize::MAX),
InsertAddressResult::UnknownPeer
));
assert_eq!(bps.peer_addresses(&peer_id).count(), 0);
}
#[test]
fn address_connections_inserted_when_peer_has_no_chain_association() {
let mut bps = BasicPeeringStrategy::<u32, Duration>::new(Config {
randomness_seed: [0; 32],
peers_capacity: 0,
chains_capacity: 0,
});
let peer_id = PeerId::from_public_key(&PublicKey::Ed25519([0; 32]));
assert!(matches!(
bps.increase_address_connections(&peer_id, Vec::new(), usize::MAX),
InsertAddressConnectionsResult::Inserted { .. }
));
assert_eq!(bps.peer_addresses(&peer_id).count(), 1);
}
}