Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions polkadot/node/network/gossip-support/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" }
assert_matches = "1.4.0"
async-trait = "0.1.57"
lazy_static = "1.4.0"
quickcheck = "1.0.3"
14 changes: 12 additions & 2 deletions polkadot/node/network/gossip-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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;
}
Expand All @@ -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
Expand Down
22 changes: 22 additions & 0 deletions polkadot/node/network/gossip-support/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ use assert_matches::assert_matches;
use async_trait::async_trait;
use futures::{executor, future, Future};
use lazy_static::lazy_static;
use quickcheck::quickcheck;
use rand::seq::SliceRandom as _;

use sc_network::multiaddr::Protocol;
use sp_authority_discovery::AuthorityPair as AuthorityDiscoveryPair;
Expand Down Expand Up @@ -710,3 +712,23 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() {
assert_eq!(state.last_session_index, Some(1));
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
}
}