Remove dependency on rand's SliceRandom shuffle implementation in gossip-support (#2555)

In gossip-support, we shuffled the list of authorities using the
`rand::seq::SliceRandom::shuffle`. This function's behavior is
unspecified beyond being `O(n)` and could change in the future, leading
to network issues between nodes using different shuffling algorithms. In
practice, the implementation was a Fisher-Yates shuffle. This PR
replaces the call with a re-implementation of Fisher-Yates and adds a
test to ensure the behavior is the same between the two at the moment.
This commit is contained in:
asynchronous rob
2023-11-30 11:55:33 -06:00
committed by GitHub
parent 6742aba05f
commit 1f2ccaea05
4 changed files with 48 additions and 2 deletions
@@ -32,7 +32,7 @@ use std::{
use futures::{channel::oneshot, select, FutureExt as _};
use futures_timer::Delay;
use rand::{seq::SliceRandom as _, SeedableRng};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use sc_network::{config::parse_addr, Multiaddr};
@@ -607,7 +607,7 @@ async fn update_gossip_topology(
.map(|(i, a)| (a.clone(), ValidatorIndex(i as _)))
.collect();
canonical_shuffling.shuffle(&mut rng);
fisher_yates_shuffle(&mut rng, &mut canonical_shuffling[..]);
for (i, (_, validator_index)) in canonical_shuffling.iter().enumerate() {
shuffled_indices[validator_index.0 as usize] = i;
}
@@ -627,6 +627,16 @@ async fn update_gossip_topology(
Ok(())
}
// Durstenfeld algorithm for the Fisher-Yates shuffle
// https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
fn fisher_yates_shuffle<T, R: Rng + ?Sized>(rng: &mut R, items: &mut [T]) {
for i in (1..items.len()).rev() {
// invariant: elements with index > i have been locked in place.
let index = rng.gen_range(0u32..(i as u32 + 1));
items.swap(i, index as usize);
}
}
#[overseer::subsystem(GossipSupport, error = SubsystemError, prefix = self::overseer)]
impl<Context, AD> GossipSupport<AD>
where