Skip to content
Snippets Groups Projects
Unverified Commit 1f2ccaea authored by asynchronous rob's avatar asynchronous rob Committed by GitHub
Browse files

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.
parent 6742aba0
Branches
No related merge requests found
Pipeline #420396 passed with stages
in 54 minutes and 40 seconds
...@@ -4844,6 +4844,16 @@ dependencies = [ ...@@ -4844,6 +4844,16 @@ dependencies = [
"syn 2.0.38", "syn 2.0.38",
] ]
[[package]]
name = "env_logger"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"log",
"regex",
]
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.9.3" version = "0.9.3"
...@@ -11905,6 +11915,7 @@ dependencies = [ ...@@ -11905,6 +11915,7 @@ dependencies = [
"polkadot-node-subsystem-test-helpers", "polkadot-node-subsystem-test-helpers",
"polkadot-node-subsystem-util", "polkadot-node-subsystem-util",
"polkadot-primitives", "polkadot-primitives",
"quickcheck",
"rand 0.8.5", "rand 0.8.5",
"rand_chacha 0.3.1", "rand_chacha 0.3.1",
"sc-network", "sc-network",
...@@ -13731,6 +13742,8 @@ version = "1.0.3" ...@@ -13731,6 +13742,8 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
dependencies = [ dependencies = [
"env_logger 0.8.4",
"log",
"rand 0.8.5", "rand 0.8.5",
] ]
......
...@@ -35,3 +35,4 @@ polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } ...@@ -35,3 +35,4 @@ polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" }
assert_matches = "1.4.0" assert_matches = "1.4.0"
async-trait = "0.1.57" async-trait = "0.1.57"
lazy_static = "1.4.0" lazy_static = "1.4.0"
quickcheck = "1.0.3"
...@@ -32,7 +32,7 @@ use std::{ ...@@ -32,7 +32,7 @@ use std::{
use futures::{channel::oneshot, select, FutureExt as _}; use futures::{channel::oneshot, select, FutureExt as _};
use futures_timer::Delay; use futures_timer::Delay;
use rand::{seq::SliceRandom as _, SeedableRng}; use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha20Rng; use rand_chacha::ChaCha20Rng;
use sc_network::{config::parse_addr, Multiaddr}; use sc_network::{config::parse_addr, Multiaddr};
...@@ -607,7 +607,7 @@ async fn update_gossip_topology( ...@@ -607,7 +607,7 @@ async fn update_gossip_topology(
.map(|(i, a)| (a.clone(), ValidatorIndex(i as _))) .map(|(i, a)| (a.clone(), ValidatorIndex(i as _)))
.collect(); .collect();
canonical_shuffling.shuffle(&mut rng); fisher_yates_shuffle(&mut rng, &mut canonical_shuffling[..]);
for (i, (_, validator_index)) in canonical_shuffling.iter().enumerate() { for (i, (_, validator_index)) in canonical_shuffling.iter().enumerate() {
shuffled_indices[validator_index.0 as usize] = i; shuffled_indices[validator_index.0 as usize] = i;
} }
...@@ -627,6 +627,16 @@ async fn update_gossip_topology( ...@@ -627,6 +627,16 @@ async fn update_gossip_topology(
Ok(()) 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)] #[overseer::subsystem(GossipSupport, error = SubsystemError, prefix = self::overseer)]
impl<Context, AD> GossipSupport<AD> impl<Context, AD> GossipSupport<AD>
where where
......
...@@ -22,6 +22,8 @@ use assert_matches::assert_matches; ...@@ -22,6 +22,8 @@ use assert_matches::assert_matches;
use async_trait::async_trait; use async_trait::async_trait;
use futures::{executor, future, Future}; use futures::{executor, future, Future};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use quickcheck::quickcheck;
use rand::seq::SliceRandom as _;
use sc_network::multiaddr::Protocol; use sc_network::multiaddr::Protocol;
use sp_authority_discovery::AuthorityPair as AuthorityDiscoveryPair; use sp_authority_discovery::AuthorityPair as AuthorityDiscoveryPair;
...@@ -710,3 +712,23 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { ...@@ -710,3 +712,23 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() {
assert_eq!(state.last_session_index, Some(1)); assert_eq!(state.last_session_index, Some(1));
assert!(state.last_failure.is_none()); assert!(state.last_failure.is_none());
} }
// note: this test was added at a time where the default `rand::SliceRandom::shuffle`
// function was used to shuffle authorities for the topology and ensures backwards compatibility.
//
// in the same commit, an explicit fisher-yates implementation was added in place of the unspecified
// behavior of that function. If this test begins to fail at some point in the future, it can simply
// be removed as the desired behavior has been preserved.
quickcheck! {
fn rng_shuffle_equals_fisher_yates(x: Vec<i32>, seed_base: u8) -> bool {
let mut rng1: ChaCha20Rng = SeedableRng::from_seed([seed_base; 32]);
let mut rng2: ChaCha20Rng = SeedableRng::from_seed([seed_base; 32]);
let mut data1 = x.clone();
let mut data2 = x;
data1.shuffle(&mut rng1);
crate::fisher_yates_shuffle(&mut rng2, &mut data2[..]);
data1 == data2
}
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment