From 15eed71588c359dc030f669434c8ad47bc103fc1 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Mon, 22 Aug 2022 17:02:16 +0200 Subject: [PATCH] node: Send all remotes in inventory Signed-off-by: Alexis Sellier --- node/src/identity.rs | 7 +++ node/src/protocol.rs | 4 +- node/src/storage.rs | 60 +++++++++++++------- node/src/test.rs | 12 ++-- node/src/test/arbitrary.rs | 21 ++++++- node/src/test/storage.rs | 17 ++++-- node/src/test/tests.rs | 110 +++++++++++++++++++------------------ 7 files changed, 143 insertions(+), 88 deletions(-) diff --git a/node/src/identity.rs b/node/src/identity.rs index 9127d1b0..284be12f 100644 --- a/node/src/identity.rs +++ b/node/src/identity.rs @@ -65,6 +65,7 @@ impl fmt::Display for Did { } #[derive(Serialize, Deserialize, Eq, Debug, Clone)] +#[serde(transparent)] pub struct UserId(pub VerificationKey); impl std::hash::Hash for UserId { @@ -73,6 +74,12 @@ impl std::hash::Hash for UserId { } } +impl fmt::Display for UserId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.encode()) + } +} + impl PartialEq for UserId { fn eq(&self, other: &Self) -> bool { self.0 == other.0 diff --git a/node/src/protocol.rs b/node/src/protocol.rs index ad170bf2..9502e329 100644 --- a/node/src/protocol.rs +++ b/node/src/protocol.rs @@ -17,7 +17,7 @@ use crate::collections::{HashMap, HashSet}; use crate::decoder::Decoder; use crate::identity::{ProjId, UserId}; use crate::storage; -use crate::storage::{Inventory, ReadStorage, Refs, WriteStorage}; +use crate::storage::{Inventory, ReadStorage, Remotes, Unverified, WriteStorage}; /// Network peer identifier. pub type PeerId = IpAddr; @@ -586,7 +586,7 @@ impl Iterator for Protocol { #[derive(Debug)] pub struct Lookup { /// Whether the project was found locally or not. - pub local: Option, + pub local: Option>, /// A list of remote peers on which the project is known to exist. pub remote: Vec, } diff --git a/node/src/storage.rs b/node/src/storage.rs index a2223e6f..4075b5ff 100644 --- a/node/src/storage.rs +++ b/node/src/storage.rs @@ -20,7 +20,7 @@ pub static RAD_ID_GLOB: Lazy = pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new(".rad/identity.toml")); pub type BranchName = String; -pub type Inventory = Vec<(ProjId, Refs)>; +pub type Inventory = Vec<(ProjId, HashMap>)>; /// Storage error. #[derive(Error, Debug)] @@ -39,29 +39,39 @@ pub enum Error { InvalidHead, } -#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] -pub struct Refs { - heads: HashMap, -} - -impl From> for Refs { - fn from(heads: HashMap) -> Self { - Self { heads } - } -} - +pub type Refs = HashMap; pub type RemoteId = UserId; pub type RefName = String; /// Verified (used as type witness). +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Verified; /// Unverified (used as type witness). +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Unverified; /// Project remotes. Tracks the git state of a project. -#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct Remotes(HashMap>); +impl Remotes { + pub fn new(remotes: HashMap>) -> Self { + Self(remotes) + } +} + +#[allow(clippy::from_over_into)] +impl Into>> for Remotes { + fn into(self) -> HashMap> { + let mut remotes = HashMap::with_hasher(fastrand::Rng::new().into()); + + for (k, v) in self.0 { + remotes.insert(k.to_string(), v); + } + remotes + } +} + /// A project remote. #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] pub struct Remote { @@ -70,11 +80,22 @@ pub struct Remote { /// Whether this remote is of a project delegate. delegate: bool, /// Whether the remote is verified or not, ie. whether its signed refs were checked. + #[serde(skip)] verified: PhantomData, } +impl Remote { + pub fn new(refs: HashMap) -> Self { + Self { + refs, + delegate: false, + verified: PhantomData, + } + } +} + pub trait ReadStorage { - fn get(&self, proj: &ProjId) -> Result, Error>; + fn get(&self, proj: &ProjId) -> Result>, Error>; fn inventory(&self) -> Result; } @@ -92,7 +113,7 @@ where self.deref().inventory() } - fn get(&self, proj: &ProjId) -> Result, Error> { + fn get(&self, proj: &ProjId) -> Result>, Error> { self.deref().get(proj) } } @@ -118,23 +139,22 @@ impl fmt::Debug for Storage { } impl ReadStorage for Storage { - fn get(&self, _id: &ProjId) -> Result, Error> { + fn get(&self, _id: &ProjId) -> Result>, Error> { todo!() } fn inventory(&self) -> Result { let glob: String = RAD_ID_GLOB.clone().into(); let refs = self.backend.references_glob(glob.as_str())?; - let mut projs = Vec::new(); for r in refs { let r = r?; let name = r.name().ok_or(Error::InvalidRef)?; - let id = ProjId::from_ref(name)?; + let _id = ProjId::from_ref(name)?; - projs.push((id, Refs::default())); + todo!(); } - Ok(projs) + Ok(vec![]) } } diff --git a/node/src/test.rs b/node/src/test.rs index f60b8250..db9f6ec9 100644 --- a/node/src/test.rs +++ b/node/src/test.rs @@ -1,6 +1,6 @@ -mod arbitrary; -mod assert; -mod logger; -mod peer; -mod storage; -mod tests; +pub(crate) mod arbitrary; +pub(crate) mod assert; +pub(crate) mod logger; +pub(crate) mod peer; +pub(crate) mod storage; +pub(crate) mod tests; diff --git a/node/src/test/arbitrary.rs b/node/src/test/arbitrary.rs index 07e49348..9c1084ab 100644 --- a/node/src/test/arbitrary.rs +++ b/node/src/test/arbitrary.rs @@ -2,8 +2,25 @@ use crate::collections::HashMap; use crate::hash; use crate::identity::{ProjId, UserId}; use crate::storage; +use crate::test::storage::MockStorage; -impl quickcheck::Arbitrary for storage::Refs { +impl quickcheck::Arbitrary for storage::Remotes { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let remotes: HashMap> = + quickcheck::Arbitrary::arbitrary(g); + + storage::Remotes::new(remotes) + } +} + +impl quickcheck::Arbitrary for MockStorage { + fn arbitrary(g: &mut quickcheck::Gen) -> Self { + let inventory = quickcheck::Arbitrary::arbitrary(g); + MockStorage::new(inventory) + } +} + +impl quickcheck::Arbitrary for storage::Remote { fn arbitrary(g: &mut quickcheck::Gen) -> Self { let rng = fastrand::Rng::with_seed(u64::arbitrary(g)); let mut refs: HashMap = HashMap::with_hasher(rng.into()); @@ -19,7 +36,7 @@ impl quickcheck::Arbitrary for storage::Refs { refs.insert(name.to_string(), oid); } } - storage::Refs::from(refs) + storage::Remote::new(refs) } } diff --git a/node/src/test/storage.rs b/node/src/test/storage.rs index e1254183..7873b0a2 100644 --- a/node/src/test/storage.rs +++ b/node/src/test/storage.rs @@ -1,14 +1,15 @@ use std::net; use crate::identity::ProjId; -use crate::storage::{Error, Inventory, ReadStorage, Refs, WriteStorage}; +use crate::storage::{Error, Inventory, ReadStorage, Remotes, Unverified, WriteStorage}; +#[derive(Clone, Debug)] pub struct MockStorage { - pub inventory: Inventory, + pub inventory: Vec<(ProjId, Remotes)>, } impl MockStorage { - pub fn new(inventory: Inventory) -> Self { + pub fn new(inventory: Vec<(ProjId, Remotes)>) -> Self { Self { inventory } } @@ -20,7 +21,7 @@ impl MockStorage { } impl ReadStorage for MockStorage { - fn get(&self, proj: &ProjId) -> Result, Error> { + fn get(&self, proj: &ProjId) -> Result>, Error> { if let Some((_, refs)) = self.inventory.iter().find(|(id, _)| id == proj) { return Ok(Some(refs.clone())); } @@ -28,7 +29,13 @@ impl ReadStorage for MockStorage { } fn inventory(&self) -> Result { - Ok(self.inventory.clone()) + let inventory = self + .inventory + .iter() + .map(|(id, remotes)| (id.clone(), remotes.clone().into())) + .collect::>(); + + Ok(inventory) } } diff --git a/node/src/test/tests.rs b/node/src/test/tests.rs index 78927579..f7f747d9 100644 --- a/node/src/test/tests.rs +++ b/node/src/test/tests.rs @@ -5,11 +5,10 @@ use nakamoto_net as nakamoto; use nakamoto_net::simulator; use nakamoto_net::simulator::{Peer as _, Simulation}; use nakamoto_net::Protocol as _; -use quickcheck_macros::quickcheck; use crate::collections::{HashMap, HashSet}; use crate::protocol::*; -use crate::storage::{Inventory, ReadStorage}; +use crate::storage::ReadStorage; #[allow(unused)] use crate::test::logger; use crate::test::peer::Peer; @@ -157,68 +156,73 @@ fn test_persistent_peer_reconnect() { assert_matches!(alice.outbox().next(), None); } -#[quickcheck] -fn prop_inventory_exchange_dense(alice_inv: Inventory, bob_inv: Inventory, eve_inv: Inventory) { - let rng = fastrand::Rng::new(); - let alice = Peer::new("alice", [7, 7, 7, 7], MockStorage::new(alice_inv.clone())); - let mut bob = Peer::new("bob", [8, 8, 8, 8], MockStorage::new(bob_inv.clone())); - let mut eve = Peer::new("eve", [9, 9, 9, 9], MockStorage::new(eve_inv.clone())); - let mut routing = Routing::with_hasher(rng.clone().into()); +#[test] +fn prop_inventory_exchange_dense() { + fn property(alice_inv: MockStorage, bob_inv: MockStorage, eve_inv: MockStorage) { + let rng = fastrand::Rng::new(); + let alice = Peer::new("alice", [7, 7, 7, 7], alice_inv.clone()); + let mut bob = Peer::new("bob", [8, 8, 8, 8], bob_inv.clone()); + let mut eve = Peer::new("eve", [9, 9, 9, 9], eve_inv.clone()); + let mut routing = Routing::with_hasher(rng.clone().into()); - for (inv, peer) in &[ - (alice_inv, alice.addr().ip()), - (bob_inv, bob.addr().ip()), - (eve_inv, eve.addr().ip()), - ] { - for (proj, _) in inv { - routing - .entry(proj.clone()) - .or_insert_with(|| HashSet::with_hasher(rng.clone().into())) - .insert(*peer); + for (inv, peer) in &[ + (alice_inv.inventory, alice.addr().ip()), + (bob_inv.inventory, bob.addr().ip()), + (eve_inv.inventory, eve.addr().ip()), + ] { + for (proj, _) in inv { + routing + .entry(proj.clone()) + .or_insert_with(|| HashSet::with_hasher(rng.clone().into())) + .insert(*peer); + } } - } - // Fully-connected. - bob.command(Command::Connect(alice.addr())); - bob.command(Command::Connect(eve.addr())); - eve.command(Command::Connect(alice.addr())); - eve.command(Command::Connect(bob.addr())); + // Fully-connected. + bob.command(Command::Connect(alice.addr())); + bob.command(Command::Connect(eve.addr())); + eve.command(Command::Connect(alice.addr())); + eve.command(Command::Connect(bob.addr())); - let mut peers: HashMap<_, _> = [(alice.ip, alice), (bob.ip, bob), (eve.ip, eve)] - .into_iter() - .collect(); - let mut simulator = Simulation::new(LocalTime::now(), rng, simulator::Options::default()) - .initialize(peers.values_mut()); + let mut peers: HashMap<_, _> = [(alice.ip, alice), (bob.ip, bob), (eve.ip, eve)] + .into_iter() + .collect(); + let mut simulator = Simulation::new(LocalTime::now(), rng, simulator::Options::default()) + .initialize(peers.values_mut()); - simulator.run_while(peers.values_mut(), |s| !s.is_settled()); + simulator.run_while(peers.values_mut(), |s| !s.is_settled()); - for (proj_id, remotes) in &routing { - for peer in peers.values() { - let lookup = peer.lookup(proj_id); + for (proj_id, remotes) in &routing { + for peer in peers.values() { + let lookup = peer.lookup(proj_id); - if lookup.local.is_some() { - peer.storage() - .get(proj_id) - .expect("There are no errors querying storage") - .expect("The project is available locally"); - } else { - for remote in &lookup.remote { - peers[remote] - .storage() + if lookup.local.is_some() { + peer.storage() .get(proj_id) .expect("There are no errors querying storage") - .expect("The project is available remotely"); + .expect("The project is available locally"); + } else { + for remote in &lookup.remote { + peers[remote] + .storage() + .get(proj_id) + .expect("There are no errors querying storage") + .expect("The project is available remotely"); + } + assert!( + !lookup.remote.is_empty(), + "There are remote locations for the project" + ); + assert_eq!( + &lookup.remote.into_iter().collect::>(), + remotes, + "The remotes match the global routing table" + ); } - assert!( - !lookup.remote.is_empty(), - "There are remote locations for the project" - ); - assert_eq!( - &lookup.remote.into_iter().collect::>(), - remotes, - "The remotes match the global routing table" - ); } } } + quickcheck::QuickCheck::new() + .gen(quickcheck::Gen::new(8)) + .quickcheck(property as fn(MockStorage, MockStorage, MockStorage)); }