mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-28 09:51:05 +00:00
a6952c7469
Fixes: https://github.com/paritytech/polkadot-sdk/issues/2138. Especially on restart AuthorithyDiscovery cache is not populated so we create an invalid topology and messages won't be routed correctly for the entire session. This PR proposes to try to fix this by updating the topology as soon as we now the Authority/PeerId mapping, that should impact the situation dramatically. [This issue was hit yesterday](https://grafana.teleport.parity.io/goto/o9q2625Sg?orgId=1), on Westend and resulted in stalling the finality. # TODO - [x] Unit tests - [x] Test impact on versi --------- Signed-off-by: Alexandru Gheorghe <alexandru.gheorghe@parity.io>
652 lines
20 KiB
Rust
652 lines
20 KiB
Rust
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// This file is part of Polkadot.
|
|
|
|
// Polkadot 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.
|
|
|
|
// Polkadot 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 Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//! Grid topology support implementation
|
|
//! The basic operation of the 2D grid topology is that:
|
|
//! * A validator producing a message sends it to its row-neighbors and its column-neighbors
|
|
//! * A validator receiving a message originating from one of its row-neighbors sends it to its
|
|
//! column-neighbors
|
|
//! * A validator receiving a message originating from one of its column-neighbors sends it to its
|
|
//! row-neighbors
|
|
//!
|
|
//! This grid approach defines 2 unique paths for every validator to reach every other validator in
|
|
//! at most 2 hops.
|
|
//!
|
|
//! However, we also supplement this with some degree of random propagation:
|
|
//! every validator, upon seeing a message for the first time, propagates it to 8 random peers.
|
|
//! This inserts some redundancy in case the grid topology isn't working or is being attacked -
|
|
//! an adversary doesn't know which peers a validator will send to.
|
|
//! This is combined with the property that the adversary doesn't know which validators will elect
|
|
//! to check a block.
|
|
|
|
use crate::PeerId;
|
|
use polkadot_primitives::{AuthorityDiscoveryId, SessionIndex, ValidatorIndex};
|
|
use rand::{CryptoRng, Rng};
|
|
use std::{
|
|
collections::{hash_map, HashMap, HashSet},
|
|
fmt::Debug,
|
|
};
|
|
|
|
const LOG_TARGET: &str = "parachain::grid-topology";
|
|
|
|
/// The sample rate for randomly propagating messages. This
|
|
/// reduces the left tail of the binomial distribution but also
|
|
/// introduces a bias towards peers who we sample before others
|
|
/// (i.e. those who get a block before others).
|
|
pub const DEFAULT_RANDOM_SAMPLE_RATE: usize = crate::MIN_GOSSIP_PEERS;
|
|
|
|
/// The number of peers to randomly propagate messages to.
|
|
pub const DEFAULT_RANDOM_CIRCULATION: usize = 4;
|
|
|
|
/// Information about a peer in the gossip topology for a session.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct TopologyPeerInfo {
|
|
/// The validator's known peer IDs.
|
|
pub peer_ids: Vec<PeerId>,
|
|
/// The index of the validator in the discovery keys of the corresponding
|
|
/// `SessionInfo`. This can extend _beyond_ the set of active parachain validators.
|
|
pub validator_index: ValidatorIndex,
|
|
/// The authority discovery public key of the validator in the corresponding
|
|
/// `SessionInfo`.
|
|
pub discovery_id: AuthorityDiscoveryId,
|
|
}
|
|
|
|
/// Topology representation for a session.
|
|
#[derive(Default, Clone, Debug, PartialEq)]
|
|
pub struct SessionGridTopology {
|
|
/// An array mapping validator indices to their indices in the
|
|
/// shuffling itself. This has the same size as the number of validators
|
|
/// in the session.
|
|
shuffled_indices: Vec<usize>,
|
|
/// The canonical shuffling of validators for the session.
|
|
canonical_shuffling: Vec<TopologyPeerInfo>,
|
|
/// The list of peer-ids in an efficient way to search.
|
|
peer_ids: HashSet<PeerId>,
|
|
}
|
|
|
|
impl SessionGridTopology {
|
|
/// Create a new session grid topology.
|
|
pub fn new(shuffled_indices: Vec<usize>, canonical_shuffling: Vec<TopologyPeerInfo>) -> Self {
|
|
let mut peer_ids = HashSet::new();
|
|
for peer_info in canonical_shuffling.iter() {
|
|
for peer_id in peer_info.peer_ids.iter() {
|
|
peer_ids.insert(*peer_id);
|
|
}
|
|
}
|
|
SessionGridTopology { shuffled_indices, canonical_shuffling, peer_ids }
|
|
}
|
|
|
|
/// Updates the known peer ids for the passed authorithies ids.
|
|
pub fn update_authority_ids(
|
|
&mut self,
|
|
peer_id: PeerId,
|
|
ids: &HashSet<AuthorityDiscoveryId>,
|
|
) -> bool {
|
|
let mut updated = false;
|
|
if !self.peer_ids.contains(&peer_id) {
|
|
for peer in self
|
|
.canonical_shuffling
|
|
.iter_mut()
|
|
.filter(|peer| ids.contains(&peer.discovery_id))
|
|
{
|
|
peer.peer_ids.push(peer_id);
|
|
self.peer_ids.insert(peer_id);
|
|
updated = true;
|
|
}
|
|
}
|
|
updated
|
|
}
|
|
/// Produces the outgoing routing logic for a particular peer.
|
|
///
|
|
/// Returns `None` if the validator index is out of bounds.
|
|
pub fn compute_grid_neighbors_for(&self, v: ValidatorIndex) -> Option<GridNeighbors> {
|
|
if self.shuffled_indices.len() != self.canonical_shuffling.len() {
|
|
return None
|
|
}
|
|
let shuffled_val_index = *self.shuffled_indices.get(v.0 as usize)?;
|
|
|
|
let neighbors = matrix_neighbors(shuffled_val_index, self.shuffled_indices.len())?;
|
|
|
|
let mut grid_subset = GridNeighbors::empty();
|
|
for r_n in neighbors.row_neighbors {
|
|
let n = &self.canonical_shuffling[r_n];
|
|
grid_subset.validator_indices_x.insert(n.validator_index);
|
|
for p in &n.peer_ids {
|
|
grid_subset.peers_x.insert(*p);
|
|
}
|
|
}
|
|
|
|
for c_n in neighbors.column_neighbors {
|
|
let n = &self.canonical_shuffling[c_n];
|
|
grid_subset.validator_indices_y.insert(n.validator_index);
|
|
for p in &n.peer_ids {
|
|
grid_subset.peers_y.insert(*p);
|
|
}
|
|
}
|
|
|
|
Some(grid_subset)
|
|
}
|
|
|
|
/// Tells if a given peer id is validator in a session
|
|
pub fn is_validator(&self, peer: &PeerId) -> bool {
|
|
self.peer_ids.contains(peer)
|
|
}
|
|
}
|
|
|
|
struct MatrixNeighbors<R, C> {
|
|
row_neighbors: R,
|
|
column_neighbors: C,
|
|
}
|
|
|
|
/// Compute the row and column neighbors of `val_index` in a matrix
|
|
fn matrix_neighbors(
|
|
val_index: usize,
|
|
len: usize,
|
|
) -> Option<MatrixNeighbors<impl Iterator<Item = usize>, impl Iterator<Item = usize>>> {
|
|
if val_index >= len {
|
|
return None
|
|
}
|
|
|
|
// e.g. for size 11 the matrix would be
|
|
//
|
|
// 0 1 2
|
|
// 3 4 5
|
|
// 6 7 8
|
|
// 9 10
|
|
//
|
|
// and for index 10, the neighbors would be 1, 4, 7, 9
|
|
|
|
let sqrt = (len as f64).sqrt() as usize;
|
|
let our_row = val_index / sqrt;
|
|
let our_column = val_index % sqrt;
|
|
let row_neighbors = our_row * sqrt..std::cmp::min(our_row * sqrt + sqrt, len);
|
|
let column_neighbors = (our_column..len).step_by(sqrt);
|
|
|
|
Some(MatrixNeighbors {
|
|
row_neighbors: row_neighbors.filter(move |i| *i != val_index),
|
|
column_neighbors: column_neighbors.filter(move |i| *i != val_index),
|
|
})
|
|
}
|
|
|
|
/// Information about the grid neighbors for a particular node in the topology.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct GridNeighbors {
|
|
/// Represent peers in the X axis
|
|
pub peers_x: HashSet<PeerId>,
|
|
/// Represent validators in the X axis
|
|
pub validator_indices_x: HashSet<ValidatorIndex>,
|
|
/// Represent peers in the Y axis
|
|
pub peers_y: HashSet<PeerId>,
|
|
/// Represent validators in the Y axis
|
|
pub validator_indices_y: HashSet<ValidatorIndex>,
|
|
}
|
|
|
|
impl GridNeighbors {
|
|
/// Utility function for creating an empty set of grid neighbors.
|
|
/// Useful for testing.
|
|
pub fn empty() -> Self {
|
|
GridNeighbors {
|
|
peers_x: HashSet::new(),
|
|
validator_indices_x: HashSet::new(),
|
|
peers_y: HashSet::new(),
|
|
validator_indices_y: HashSet::new(),
|
|
}
|
|
}
|
|
|
|
/// Given the originator of a message as a validator index, indicates the part of the topology
|
|
/// we're meant to send the message to.
|
|
pub fn required_routing_by_index(
|
|
&self,
|
|
originator: ValidatorIndex,
|
|
local: bool,
|
|
) -> RequiredRouting {
|
|
if local {
|
|
return RequiredRouting::GridXY
|
|
}
|
|
|
|
let grid_x = self.validator_indices_x.contains(&originator);
|
|
let grid_y = self.validator_indices_y.contains(&originator);
|
|
|
|
match (grid_x, grid_y) {
|
|
(false, false) => RequiredRouting::None,
|
|
(true, false) => RequiredRouting::GridY, // messages from X go to Y
|
|
(false, true) => RequiredRouting::GridX, // messages from Y go to X
|
|
(true, true) => RequiredRouting::GridXY, /* if the grid works as expected, this
|
|
* shouldn't happen. */
|
|
}
|
|
}
|
|
|
|
/// Given the originator of a message as a peer index, indicates the part of the topology
|
|
/// we're meant to send the message to.
|
|
pub fn required_routing_by_peer_id(&self, originator: PeerId, local: bool) -> RequiredRouting {
|
|
if local {
|
|
return RequiredRouting::GridXY
|
|
}
|
|
|
|
let grid_x = self.peers_x.contains(&originator);
|
|
let grid_y = self.peers_y.contains(&originator);
|
|
|
|
match (grid_x, grid_y) {
|
|
(false, false) => RequiredRouting::None,
|
|
(true, false) => RequiredRouting::GridY, // messages from X go to Y
|
|
(false, true) => RequiredRouting::GridX, // messages from Y go to X
|
|
(true, true) => {
|
|
gum::debug!(
|
|
target: LOG_TARGET,
|
|
?originator,
|
|
"Grid topology is unexpected, play it safe and send to X AND Y"
|
|
);
|
|
RequiredRouting::GridXY
|
|
}, /* if the grid works as expected, this
|
|
* shouldn't happen. */
|
|
}
|
|
}
|
|
|
|
/// Get a filter function based on this topology and the required routing
|
|
/// which returns `true` for peers that are within the required routing set
|
|
/// and false otherwise.
|
|
pub fn route_to_peer(&self, required_routing: RequiredRouting, peer: &PeerId) -> bool {
|
|
match required_routing {
|
|
RequiredRouting::All => true,
|
|
RequiredRouting::GridX => self.peers_x.contains(peer),
|
|
RequiredRouting::GridY => self.peers_y.contains(peer),
|
|
RequiredRouting::GridXY => self.peers_x.contains(peer) || self.peers_y.contains(peer),
|
|
RequiredRouting::None | RequiredRouting::PendingTopology => false,
|
|
}
|
|
}
|
|
|
|
/// Returns the difference between this and the `other` topology as a vector of peers
|
|
pub fn peers_diff(&self, other: &Self) -> Vec<PeerId> {
|
|
self.peers_x
|
|
.iter()
|
|
.chain(self.peers_y.iter())
|
|
.filter(|peer_id| !(other.peers_x.contains(peer_id) || other.peers_y.contains(peer_id)))
|
|
.cloned()
|
|
.collect::<Vec<_>>()
|
|
}
|
|
|
|
/// A convenience method that returns total number of peers in the topology
|
|
pub fn len(&self) -> usize {
|
|
self.peers_x.len().saturating_add(self.peers_y.len())
|
|
}
|
|
}
|
|
|
|
/// An entry tracking a session grid topology and some cached local neighbors.
|
|
#[derive(Debug)]
|
|
pub struct SessionGridTopologyEntry {
|
|
topology: SessionGridTopology,
|
|
local_neighbors: GridNeighbors,
|
|
local_index: Option<ValidatorIndex>,
|
|
}
|
|
|
|
impl SessionGridTopologyEntry {
|
|
/// Access the local grid neighbors.
|
|
pub fn local_grid_neighbors(&self) -> &GridNeighbors {
|
|
&self.local_neighbors
|
|
}
|
|
|
|
/// Access the local grid neighbors mutably.
|
|
pub fn local_grid_neighbors_mut(&mut self) -> &mut GridNeighbors {
|
|
&mut self.local_neighbors
|
|
}
|
|
|
|
/// Access the underlying topology.
|
|
pub fn get(&self) -> &SessionGridTopology {
|
|
&self.topology
|
|
}
|
|
|
|
/// Tells if a given peer id is validator in a session
|
|
pub fn is_validator(&self, peer: &PeerId) -> bool {
|
|
self.topology.is_validator(peer)
|
|
}
|
|
|
|
/// Updates the known peer ids for the passed authorithies ids.
|
|
pub fn update_authority_ids(
|
|
&mut self,
|
|
peer_id: PeerId,
|
|
ids: &HashSet<AuthorityDiscoveryId>,
|
|
) -> bool {
|
|
let peer_id_updated = self.topology.update_authority_ids(peer_id, ids);
|
|
// If we added a new peer id we need to recompute the grid neighbors, so that
|
|
// neighbors_x and neighbors_y reflect the right peer ids.
|
|
if peer_id_updated {
|
|
if let Some(local_index) = self.local_index.as_ref() {
|
|
if let Some(new_grid) = self.topology.compute_grid_neighbors_for(*local_index) {
|
|
self.local_neighbors = new_grid;
|
|
}
|
|
}
|
|
}
|
|
peer_id_updated
|
|
}
|
|
}
|
|
|
|
/// A set of topologies indexed by session
|
|
#[derive(Default)]
|
|
pub struct SessionGridTopologies {
|
|
inner: HashMap<SessionIndex, (Option<SessionGridTopologyEntry>, usize)>,
|
|
}
|
|
|
|
impl SessionGridTopologies {
|
|
/// Returns a topology for the specific session index
|
|
pub fn get_topology(&self, session: SessionIndex) -> Option<&SessionGridTopologyEntry> {
|
|
self.inner.get(&session).and_then(|val| val.0.as_ref())
|
|
}
|
|
|
|
/// Updates the known peer ids for the passed authorithies ids.
|
|
pub fn update_authority_ids(
|
|
&mut self,
|
|
peer_id: PeerId,
|
|
ids: &HashSet<AuthorityDiscoveryId>,
|
|
) -> bool {
|
|
self.inner
|
|
.iter_mut()
|
|
.map(|(_, topology)| {
|
|
topology.0.as_mut().map(|topology| topology.update_authority_ids(peer_id, ids))
|
|
})
|
|
.any(|updated| updated.unwrap_or_default())
|
|
}
|
|
|
|
/// Increase references counter for a specific topology
|
|
pub fn inc_session_refs(&mut self, session: SessionIndex) {
|
|
self.inner.entry(session).or_insert((None, 0)).1 += 1;
|
|
}
|
|
|
|
/// Decrease references counter for a specific topology
|
|
pub fn dec_session_refs(&mut self, session: SessionIndex) {
|
|
if let hash_map::Entry::Occupied(mut occupied) = self.inner.entry(session) {
|
|
occupied.get_mut().1 = occupied.get().1.saturating_sub(1);
|
|
if occupied.get().1 == 0 {
|
|
let _ = occupied.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Insert a new topology, no-op if already present.
|
|
pub fn insert_topology(
|
|
&mut self,
|
|
session: SessionIndex,
|
|
topology: SessionGridTopology,
|
|
local_index: Option<ValidatorIndex>,
|
|
) {
|
|
let entry = self.inner.entry(session).or_insert((None, 0));
|
|
if entry.0.is_none() {
|
|
let local_neighbors = local_index
|
|
.and_then(|l| topology.compute_grid_neighbors_for(l))
|
|
.unwrap_or_else(GridNeighbors::empty);
|
|
|
|
entry.0 = Some(SessionGridTopologyEntry { topology, local_neighbors, local_index });
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A simple storage for a topology and the corresponding session index
|
|
#[derive(Debug)]
|
|
struct GridTopologySessionBound {
|
|
entry: SessionGridTopologyEntry,
|
|
session_index: SessionIndex,
|
|
}
|
|
|
|
/// A storage for the current and maybe previous topology
|
|
#[derive(Debug)]
|
|
pub struct SessionBoundGridTopologyStorage {
|
|
current_topology: GridTopologySessionBound,
|
|
prev_topology: Option<GridTopologySessionBound>,
|
|
}
|
|
|
|
impl Default for SessionBoundGridTopologyStorage {
|
|
fn default() -> Self {
|
|
// having this struct be `Default` is objectively stupid
|
|
// but used in a few places
|
|
SessionBoundGridTopologyStorage {
|
|
current_topology: GridTopologySessionBound {
|
|
// session 0 is valid so we should use the upper bound
|
|
// as the default instead of the lower bound.
|
|
session_index: SessionIndex::max_value(),
|
|
entry: SessionGridTopologyEntry {
|
|
topology: SessionGridTopology {
|
|
shuffled_indices: Vec::new(),
|
|
canonical_shuffling: Vec::new(),
|
|
peer_ids: Default::default(),
|
|
},
|
|
local_neighbors: GridNeighbors::empty(),
|
|
local_index: None,
|
|
},
|
|
},
|
|
prev_topology: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SessionBoundGridTopologyStorage {
|
|
/// Return a grid topology based on the session index:
|
|
/// If we need a previous session and it is registered in the storage, then return that session.
|
|
/// Otherwise, return a current session to have some grid topology in any case
|
|
pub fn get_topology_or_fallback(&self, idx: SessionIndex) -> &SessionGridTopologyEntry {
|
|
self.get_topology(idx).unwrap_or(&self.current_topology.entry)
|
|
}
|
|
|
|
/// Return the grid topology for the specific session index, if no such a session is stored
|
|
/// returns `None`.
|
|
pub fn get_topology(&self, idx: SessionIndex) -> Option<&SessionGridTopologyEntry> {
|
|
if let Some(prev_topology) = &self.prev_topology {
|
|
if idx == prev_topology.session_index {
|
|
return Some(&prev_topology.entry)
|
|
}
|
|
}
|
|
if self.current_topology.session_index == idx {
|
|
return Some(&self.current_topology.entry)
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Update the current topology preserving the previous one
|
|
pub fn update_topology(
|
|
&mut self,
|
|
session_index: SessionIndex,
|
|
topology: SessionGridTopology,
|
|
local_index: Option<ValidatorIndex>,
|
|
) {
|
|
let local_neighbors = local_index
|
|
.and_then(|l| topology.compute_grid_neighbors_for(l))
|
|
.unwrap_or_else(GridNeighbors::empty);
|
|
|
|
let old_current = std::mem::replace(
|
|
&mut self.current_topology,
|
|
GridTopologySessionBound {
|
|
entry: SessionGridTopologyEntry { topology, local_neighbors, local_index },
|
|
session_index,
|
|
},
|
|
);
|
|
self.prev_topology.replace(old_current);
|
|
}
|
|
|
|
/// Returns a current grid topology
|
|
pub fn get_current_topology(&self) -> &SessionGridTopologyEntry {
|
|
&self.current_topology.entry
|
|
}
|
|
|
|
/// Access the current grid topology mutably. Dangerous and intended
|
|
/// to be used in tests.
|
|
pub fn get_current_topology_mut(&mut self) -> &mut SessionGridTopologyEntry {
|
|
&mut self.current_topology.entry
|
|
}
|
|
}
|
|
|
|
/// A representation of routing based on sample
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct RandomRouting {
|
|
/// The number of peers to target.
|
|
target: usize,
|
|
/// The number of peers this has been sent to.
|
|
sent: usize,
|
|
/// Sampling rate
|
|
sample_rate: usize,
|
|
}
|
|
|
|
impl Default for RandomRouting {
|
|
fn default() -> Self {
|
|
RandomRouting {
|
|
target: DEFAULT_RANDOM_CIRCULATION,
|
|
sent: 0_usize,
|
|
sample_rate: DEFAULT_RANDOM_SAMPLE_RATE,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RandomRouting {
|
|
/// Perform random sampling for a specific peer
|
|
/// Returns `true` for a lucky peer
|
|
pub fn sample(&self, n_peers_total: usize, rng: &mut (impl CryptoRng + Rng)) -> bool {
|
|
if n_peers_total == 0 || self.sent >= self.target {
|
|
false
|
|
} else if self.sample_rate > n_peers_total {
|
|
true
|
|
} else {
|
|
rng.gen_ratio(self.sample_rate as _, n_peers_total as _)
|
|
}
|
|
}
|
|
|
|
/// Increase number of messages being sent
|
|
pub fn inc_sent(&mut self) {
|
|
self.sent += 1
|
|
}
|
|
}
|
|
|
|
/// Routing mode
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub enum RequiredRouting {
|
|
/// We don't know yet, because we're waiting for topology info
|
|
/// (race condition between learning about the first blocks in a new session
|
|
/// and getting the topology for that session)
|
|
PendingTopology,
|
|
/// Propagate to all peers of any kind.
|
|
All,
|
|
/// Propagate to all peers sharing either the X or Y dimension of the grid.
|
|
GridXY,
|
|
/// Propagate to all peers sharing the X dimension of the grid.
|
|
GridX,
|
|
/// Propagate to all peers sharing the Y dimension of the grid.
|
|
GridY,
|
|
/// No required propagation.
|
|
None,
|
|
}
|
|
|
|
impl RequiredRouting {
|
|
/// Whether the required routing set is definitely empty.
|
|
pub fn is_empty(self) -> bool {
|
|
match self {
|
|
RequiredRouting::PendingTopology | RequiredRouting::None => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use rand::SeedableRng;
|
|
use rand_chacha::ChaCha12Rng;
|
|
|
|
fn dummy_rng() -> ChaCha12Rng {
|
|
rand_chacha::ChaCha12Rng::seed_from_u64(12345)
|
|
}
|
|
|
|
#[test]
|
|
fn test_random_routing_sample() {
|
|
// This test is fragile as it relies on a specific ChaCha12Rng
|
|
// sequence that might be implementation defined even for a static seed
|
|
let mut rng = dummy_rng();
|
|
let mut random_routing = RandomRouting { target: 4, sent: 0, sample_rate: 8 };
|
|
|
|
assert_eq!(random_routing.sample(16, &mut rng), true);
|
|
random_routing.inc_sent();
|
|
assert_eq!(random_routing.sample(16, &mut rng), false);
|
|
assert_eq!(random_routing.sample(16, &mut rng), false);
|
|
assert_eq!(random_routing.sample(16, &mut rng), true);
|
|
random_routing.inc_sent();
|
|
assert_eq!(random_routing.sample(16, &mut rng), true);
|
|
random_routing.inc_sent();
|
|
assert_eq!(random_routing.sample(16, &mut rng), false);
|
|
assert_eq!(random_routing.sample(16, &mut rng), false);
|
|
assert_eq!(random_routing.sample(16, &mut rng), false);
|
|
assert_eq!(random_routing.sample(16, &mut rng), true);
|
|
random_routing.inc_sent();
|
|
|
|
for _ in 0..16 {
|
|
assert_eq!(random_routing.sample(16, &mut rng), false);
|
|
}
|
|
}
|
|
|
|
fn run_random_routing(
|
|
random_routing: &mut RandomRouting,
|
|
rng: &mut (impl CryptoRng + Rng),
|
|
npeers: usize,
|
|
iters: usize,
|
|
) -> usize {
|
|
let mut ret = 0_usize;
|
|
|
|
for _ in 0..iters {
|
|
if random_routing.sample(npeers, rng) {
|
|
random_routing.inc_sent();
|
|
ret += 1;
|
|
}
|
|
}
|
|
|
|
ret
|
|
}
|
|
|
|
#[test]
|
|
fn test_random_routing_distribution() {
|
|
let mut rng = dummy_rng();
|
|
|
|
let mut random_routing = RandomRouting { target: 4, sent: 0, sample_rate: 8 };
|
|
assert_eq!(run_random_routing(&mut random_routing, &mut rng, 100, 10000), 4);
|
|
|
|
let mut random_routing = RandomRouting { target: 8, sent: 0, sample_rate: 100 };
|
|
assert_eq!(run_random_routing(&mut random_routing, &mut rng, 100, 10000), 8);
|
|
|
|
let mut random_routing = RandomRouting { target: 0, sent: 0, sample_rate: 100 };
|
|
assert_eq!(run_random_routing(&mut random_routing, &mut rng, 100, 10000), 0);
|
|
|
|
let mut random_routing = RandomRouting { target: 10, sent: 0, sample_rate: 10 };
|
|
assert_eq!(run_random_routing(&mut random_routing, &mut rng, 10, 100), 10);
|
|
}
|
|
|
|
#[test]
|
|
fn test_matrix_neighbors() {
|
|
for (our_index, len, expected_row, expected_column) in vec![
|
|
(0usize, 1usize, vec![], vec![]),
|
|
(1, 2, vec![], vec![0usize]),
|
|
(0, 9, vec![1, 2], vec![3, 6]),
|
|
(9, 10, vec![], vec![0, 3, 6]),
|
|
(10, 11, vec![9], vec![1, 4, 7]),
|
|
(7, 11, vec![6, 8], vec![1, 4, 10]),
|
|
]
|
|
.into_iter()
|
|
{
|
|
let matrix = matrix_neighbors(our_index, len).unwrap();
|
|
let mut row_result: Vec<_> = matrix.row_neighbors.collect();
|
|
let mut column_result: Vec<_> = matrix.column_neighbors.collect();
|
|
row_result.sort();
|
|
column_result.sort();
|
|
|
|
assert_eq!(row_result, expected_row);
|
|
assert_eq!(column_result, expected_column);
|
|
}
|
|
}
|
|
}
|