diff --git a/radicle-cli/tests/commands.rs b/radicle-cli/tests/commands.rs index f2350ea0..b557e743 100644 --- a/radicle-cli/tests/commands.rs +++ b/radicle-cli/tests/commands.rs @@ -56,6 +56,7 @@ fn formula(root: &Path, test: impl AsRef) -> Result = [ + let mut peers: RandomMap<_, _> = [ (alice.node_id(), alice), (bob.node_id(), bob), (eve.node_id(), eve), @@ -1439,7 +1439,7 @@ fn prop_inventory_exchange_dense() { "There are remote locations for the project" ); assert_eq!( - &lookup.remote.into_iter().collect::>(), + &lookup.remote.into_iter().collect::>(), remotes, "The remotes match the global routing table" ); diff --git a/radicle-node/src/wire/protocol.rs b/radicle-node/src/wire/protocol.rs index a41ca959..0090fefc 100644 --- a/radicle-node/src/wire/protocol.rs +++ b/radicle-node/src/wire/protocol.rs @@ -21,7 +21,7 @@ use netservices::session::{ProtocolArtifact, Socks5Session}; use netservices::{NetConnection, NetProtocol, NetReader, NetWriter}; use reactor::Timestamp; -use radicle::collections::HashMap; +use radicle::collections::RandomMap; use radicle::node::{address, routing, NodeId}; use radicle::storage::WriteStorage; @@ -74,7 +74,7 @@ struct Streams { /// Active streams and their associated worker channels. /// Note that the gossip and control streams are not included here as they are always /// implied to exist. - streams: HashMap, + streams: RandomMap, /// Connection direction. link: Link, /// Sequence number used to compute the next stream id. @@ -85,7 +85,7 @@ impl Streams { /// Create a new [`Streams`] object, passing the connection link. fn new(link: Link) -> Self { Self { - streams: HashMap::default(), + streams: RandomMap::default(), link, seq: 0, } @@ -257,7 +257,7 @@ impl Peer { } } -struct Peers(HashMap); +struct Peers(RandomMap); impl Peers { fn get_mut(&mut self, fd: &RawFd) -> Option<&mut Peer> { @@ -352,7 +352,7 @@ where signer, proxy, actions: VecDeque::new(), - peers: Peers(HashMap::default()), + peers: Peers(RandomMap::default()), } } diff --git a/radicle/src/collections.rs b/radicle/src/collections.rs index 6e30f2b1..ccd1994d 100644 --- a/radicle/src/collections.rs +++ b/radicle/src/collections.rs @@ -2,10 +2,10 @@ use siphasher::sip::SipHasher13; /// A `HashMap` which uses [`fastrand::Rng`] for its random state. -pub type HashMap = std::collections::HashMap; +pub type RandomMap = std::collections::HashMap; /// A `HashSet` which uses [`fastrand::Rng`] for its random state. -pub type HashSet = std::collections::HashSet; +pub type RandomSet = std::collections::HashSet; /// Random hasher state. #[derive(Clone)] @@ -16,7 +16,7 @@ pub struct RandomState { impl Default for RandomState { fn default() -> Self { - Self::new(fastrand::Rng::new()) + Self::new(crate::profile::env::rng()) } } diff --git a/radicle/src/git.rs b/radicle/src/git.rs index 5cfdc0b8..6d4694ad 100644 --- a/radicle/src/git.rs +++ b/radicle/src/git.rs @@ -6,7 +6,7 @@ use std::str::FromStr; use git_ext::ref_format as format; use once_cell::sync::Lazy; -use crate::collections::HashMap; +use crate::collections::RandomMap; use crate::crypto::PublicKey; use crate::storage; use crate::storage::refs::Refs; @@ -367,9 +367,9 @@ pub mod refs { } /// List remote refs of a project, given the remote URL. -pub fn remote_refs(url: &Url) -> Result, ListRefsError> { +pub fn remote_refs(url: &Url) -> Result, ListRefsError> { let url = url.to_string(); - let mut remotes = HashMap::default(); + let mut remotes = RandomMap::default(); let mut remote = git2::Remote::create_detached(url)?; remote.connect(git2::Direction::Fetch)?; diff --git a/radicle/src/node/address/types.rs b/radicle/src/node/address/types.rs index 7c17b9ea..e048b737 100644 --- a/radicle/src/node/address/types.rs +++ b/radicle/src/node/address/types.rs @@ -3,7 +3,7 @@ use std::ops::{Deref, DerefMut}; use localtime::LocalTime; use nonempty::NonEmpty; -use crate::collections::HashMap; +use crate::collections::RandomMap; use crate::node; use crate::node::{Address, Alias}; use crate::prelude::Timestamp; @@ -11,7 +11,7 @@ use crate::prelude::Timestamp; /// A map with the ability to randomly select values. #[derive(Debug, Clone)] pub struct AddressBook { - inner: HashMap, + inner: RandomMap, rng: fastrand::Rng, } @@ -19,7 +19,7 @@ impl AddressBook { /// Create a new address book. pub fn new(rng: fastrand::Rng) -> Self { Self { - inner: HashMap::with_hasher(rng.clone().into()), + inner: RandomMap::with_hasher(rng.clone().into()), rng, } } @@ -61,7 +61,7 @@ impl AddressBook { } impl Deref for AddressBook { - type Target = HashMap; + type Target = RandomMap; fn deref(&self) -> &Self::Target { &self.inner diff --git a/radicle/src/profile.rs b/radicle/src/profile.rs index 91c3fe3b..c870eb1d 100644 --- a/radicle/src/profile.rs +++ b/radicle/src/profile.rs @@ -37,6 +37,8 @@ pub mod env { pub const RAD_SOCKET: &str = "RAD_SOCKET"; /// Passphrase for the encrypted radicle secret key. pub const RAD_PASSPHRASE: &str = "RAD_PASSPHRASE"; + /// RNG seed. Must be convertible to a `u64`. + pub const RAD_RNG_SEED: &str = "RAD_RNG_SEED"; /// Get the radicle passphrase from the environment. pub fn passphrase() -> Option { @@ -45,6 +47,17 @@ pub mod env { }; Some(super::Passphrase::from(passphrase)) } + + /// Get a random number generator from the environment. + pub fn rng() -> fastrand::Rng { + let Ok(seed) = std::env::var(RAD_RNG_SEED) else { + return fastrand::Rng::new(); + }; + fastrand::Rng::with_seed( + seed.parse() + .expect("env::rng: invalid seed specified in `RAD_RNG_SEED`"), + ) + } } #[derive(Debug, Error)] diff --git a/radicle/src/storage.rs b/radicle/src/storage.rs index d96757a2..7cb0fa65 100644 --- a/radicle/src/storage.rs +++ b/radicle/src/storage.rs @@ -14,7 +14,7 @@ use crypto::{PublicKey, Signer, Unverified, Verified}; pub use git::VerifyError; pub use radicle_git_ext::Oid; -use crate::collections::HashMap; +use crate::collections::RandomMap; use crate::git::ext as git_ext; use crate::git::{refspec::Refspec, PatternString, Qualified, RefError, RefString}; use crate::identity; @@ -168,7 +168,7 @@ impl fmt::Display for RefUpdate { /// Project remotes. Tracks the git state of a project. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Remotes(HashMap>); +pub struct Remotes(RandomMap>); impl FromIterator<(RemoteId, Remote)> for Remotes { fn from_iter)>>(iter: T) -> Self { @@ -177,7 +177,7 @@ impl FromIterator<(RemoteId, Remote)> for Remotes { } impl Deref for Remotes { - type Target = HashMap>; + type Target = RandomMap>; fn deref(&self) -> &Self::Target { &self.0 @@ -185,7 +185,7 @@ impl Deref for Remotes { } impl Remotes { - pub fn new(remotes: HashMap>) -> Self { + pub fn new(remotes: RandomMap>) -> Self { Self(remotes) } } @@ -202,7 +202,7 @@ impl Remotes { impl Default for Remotes { fn default() -> Self { - Self(HashMap::default()) + Self(RandomMap::default()) } } @@ -215,9 +215,9 @@ impl IntoIterator for Remotes { } } -impl From> for HashMap { +impl From> for RandomMap { fn from(other: Remotes) -> Self { - let mut remotes = HashMap::with_hasher(fastrand::Rng::new().into()); + let mut remotes = RandomMap::with_hasher(fastrand::Rng::new().into()); for (k, v) in other.into_iter() { remotes.insert(k, v.refs.into()); diff --git a/radicle/src/test/arbitrary.rs b/radicle/src/test/arbitrary.rs index 3195cc1b..693b1bc8 100644 --- a/radicle/src/test/arbitrary.rs +++ b/radicle/src/test/arbitrary.rs @@ -9,7 +9,7 @@ use crypto::{PublicKey, Unverified, Verified}; use nonempty::NonEmpty; use qcheck::Arbitrary; -use crate::collections::HashMap; +use crate::collections::RandomMap; use crate::identity::{ doc::{Doc, Id}, project::Project, @@ -82,7 +82,7 @@ pub fn gen(size: usize) -> T { impl Arbitrary for storage::Remotes { fn arbitrary(g: &mut qcheck::Gen) -> Self { - let remotes: HashMap> = + let remotes: RandomMap> = Arbitrary::arbitrary(g); storage::Remotes::new(remotes)