node: Send all remotes in inventory

Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
Alexis Sellier 2022-08-22 17:02:16 +02:00
parent 789c50ee35
commit 15eed71588
No known key found for this signature in database
7 changed files with 143 additions and 88 deletions

View File

@ -65,6 +65,7 @@ impl fmt::Display for Did {
} }
#[derive(Serialize, Deserialize, Eq, Debug, Clone)] #[derive(Serialize, Deserialize, Eq, Debug, Clone)]
#[serde(transparent)]
pub struct UserId(pub VerificationKey); pub struct UserId(pub VerificationKey);
impl std::hash::Hash for UserId { 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 { impl PartialEq for UserId {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.0 == other.0 self.0 == other.0

View File

@ -17,7 +17,7 @@ use crate::collections::{HashMap, HashSet};
use crate::decoder::Decoder; use crate::decoder::Decoder;
use crate::identity::{ProjId, UserId}; use crate::identity::{ProjId, UserId};
use crate::storage; use crate::storage;
use crate::storage::{Inventory, ReadStorage, Refs, WriteStorage}; use crate::storage::{Inventory, ReadStorage, Remotes, Unverified, WriteStorage};
/// Network peer identifier. /// Network peer identifier.
pub type PeerId = IpAddr; pub type PeerId = IpAddr;
@ -586,7 +586,7 @@ impl<S, T> Iterator for Protocol<S, T> {
#[derive(Debug)] #[derive(Debug)]
pub struct Lookup { pub struct Lookup {
/// Whether the project was found locally or not. /// Whether the project was found locally or not.
pub local: Option<Refs>, pub local: Option<Remotes<Unverified>>,
/// A list of remote peers on which the project is known to exist. /// A list of remote peers on which the project is known to exist.
pub remote: Vec<PeerId>, pub remote: Vec<PeerId>,
} }

View File

@ -20,7 +20,7 @@ pub static RAD_ID_GLOB: Lazy<refspec::PatternString> =
pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new(".rad/identity.toml")); pub static IDENTITY_PATH: Lazy<&Path> = Lazy::new(|| Path::new(".rad/identity.toml"));
pub type BranchName = String; pub type BranchName = String;
pub type Inventory = Vec<(ProjId, Refs)>; pub type Inventory = Vec<(ProjId, HashMap<String, Remote<Unverified>>)>;
/// Storage error. /// Storage error.
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -39,29 +39,39 @@ pub enum Error {
InvalidHead, InvalidHead,
} }
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] pub type Refs = HashMap<BranchName, Oid>;
pub struct Refs {
heads: HashMap<BranchName, Oid>,
}
impl From<HashMap<BranchName, Oid>> for Refs {
fn from(heads: HashMap<BranchName, Oid>) -> Self {
Self { heads }
}
}
pub type RemoteId = UserId; pub type RemoteId = UserId;
pub type RefName = String; pub type RefName = String;
/// Verified (used as type witness). /// Verified (used as type witness).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Verified; pub struct Verified;
/// Unverified (used as type witness). /// Unverified (used as type witness).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Unverified; pub struct Unverified;
/// Project remotes. Tracks the git state of a project. /// 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<V>(HashMap<RemoteId, Remote<V>>); pub struct Remotes<V>(HashMap<RemoteId, Remote<V>>);
impl Remotes<Unverified> {
pub fn new(remotes: HashMap<RemoteId, Remote<Unverified>>) -> Self {
Self(remotes)
}
}
#[allow(clippy::from_over_into)]
impl Into<HashMap<String, Remote<Unverified>>> for Remotes<Unverified> {
fn into(self) -> HashMap<String, Remote<Unverified>> {
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. /// A project remote.
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
pub struct Remote<V> { pub struct Remote<V> {
@ -70,11 +80,22 @@ pub struct Remote<V> {
/// Whether this remote is of a project delegate. /// Whether this remote is of a project delegate.
delegate: bool, delegate: bool,
/// Whether the remote is verified or not, ie. whether its signed refs were checked. /// Whether the remote is verified or not, ie. whether its signed refs were checked.
#[serde(skip)]
verified: PhantomData<V>, verified: PhantomData<V>,
} }
impl Remote<Unverified> {
pub fn new(refs: HashMap<RefName, Oid>) -> Self {
Self {
refs,
delegate: false,
verified: PhantomData,
}
}
}
pub trait ReadStorage { pub trait ReadStorage {
fn get(&self, proj: &ProjId) -> Result<Option<Refs>, Error>; fn get(&self, proj: &ProjId) -> Result<Option<Remotes<Unverified>>, Error>;
fn inventory(&self) -> Result<Inventory, Error>; fn inventory(&self) -> Result<Inventory, Error>;
} }
@ -92,7 +113,7 @@ where
self.deref().inventory() self.deref().inventory()
} }
fn get(&self, proj: &ProjId) -> Result<Option<Refs>, Error> { fn get(&self, proj: &ProjId) -> Result<Option<Remotes<Unverified>>, Error> {
self.deref().get(proj) self.deref().get(proj)
} }
} }
@ -118,23 +139,22 @@ impl fmt::Debug for Storage {
} }
impl ReadStorage for Storage { impl ReadStorage for Storage {
fn get(&self, _id: &ProjId) -> Result<Option<Refs>, Error> { fn get(&self, _id: &ProjId) -> Result<Option<Remotes<Unverified>>, Error> {
todo!() todo!()
} }
fn inventory(&self) -> Result<Inventory, Error> { fn inventory(&self) -> Result<Inventory, Error> {
let glob: String = RAD_ID_GLOB.clone().into(); let glob: String = RAD_ID_GLOB.clone().into();
let refs = self.backend.references_glob(glob.as_str())?; let refs = self.backend.references_glob(glob.as_str())?;
let mut projs = Vec::new();
for r in refs { for r in refs {
let r = r?; let r = r?;
let name = r.name().ok_or(Error::InvalidRef)?; 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![])
} }
} }

View File

@ -1,6 +1,6 @@
mod arbitrary; pub(crate) mod arbitrary;
mod assert; pub(crate) mod assert;
mod logger; pub(crate) mod logger;
mod peer; pub(crate) mod peer;
mod storage; pub(crate) mod storage;
mod tests; pub(crate) mod tests;

View File

@ -2,8 +2,25 @@ use crate::collections::HashMap;
use crate::hash; use crate::hash;
use crate::identity::{ProjId, UserId}; use crate::identity::{ProjId, UserId};
use crate::storage; use crate::storage;
use crate::test::storage::MockStorage;
impl quickcheck::Arbitrary for storage::Refs { impl quickcheck::Arbitrary for storage::Remotes<storage::Unverified> {
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let remotes: HashMap<storage::RemoteId, storage::Remote<storage::Unverified>> =
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<storage::Unverified> {
fn arbitrary(g: &mut quickcheck::Gen) -> Self { fn arbitrary(g: &mut quickcheck::Gen) -> Self {
let rng = fastrand::Rng::with_seed(u64::arbitrary(g)); let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
let mut refs: HashMap<storage::BranchName, storage::Oid> = HashMap::with_hasher(rng.into()); let mut refs: HashMap<storage::BranchName, storage::Oid> = HashMap::with_hasher(rng.into());
@ -19,7 +36,7 @@ impl quickcheck::Arbitrary for storage::Refs {
refs.insert(name.to_string(), oid); refs.insert(name.to_string(), oid);
} }
} }
storage::Refs::from(refs) storage::Remote::new(refs)
} }
} }

View File

@ -1,14 +1,15 @@
use std::net; use std::net;
use crate::identity::ProjId; 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 struct MockStorage {
pub inventory: Inventory, pub inventory: Vec<(ProjId, Remotes<Unverified>)>,
} }
impl MockStorage { impl MockStorage {
pub fn new(inventory: Inventory) -> Self { pub fn new(inventory: Vec<(ProjId, Remotes<Unverified>)>) -> Self {
Self { inventory } Self { inventory }
} }
@ -20,7 +21,7 @@ impl MockStorage {
} }
impl ReadStorage for MockStorage { impl ReadStorage for MockStorage {
fn get(&self, proj: &ProjId) -> Result<Option<Refs>, Error> { fn get(&self, proj: &ProjId) -> Result<Option<Remotes<Unverified>>, Error> {
if let Some((_, refs)) = self.inventory.iter().find(|(id, _)| id == proj) { if let Some((_, refs)) = self.inventory.iter().find(|(id, _)| id == proj) {
return Ok(Some(refs.clone())); return Ok(Some(refs.clone()));
} }
@ -28,7 +29,13 @@ impl ReadStorage for MockStorage {
} }
fn inventory(&self) -> Result<Inventory, Error> { fn inventory(&self) -> Result<Inventory, Error> {
Ok(self.inventory.clone()) let inventory = self
.inventory
.iter()
.map(|(id, remotes)| (id.clone(), remotes.clone().into()))
.collect::<Vec<_>>();
Ok(inventory)
} }
} }

View File

@ -5,11 +5,10 @@ use nakamoto_net as nakamoto;
use nakamoto_net::simulator; use nakamoto_net::simulator;
use nakamoto_net::simulator::{Peer as _, Simulation}; use nakamoto_net::simulator::{Peer as _, Simulation};
use nakamoto_net::Protocol as _; use nakamoto_net::Protocol as _;
use quickcheck_macros::quickcheck;
use crate::collections::{HashMap, HashSet}; use crate::collections::{HashMap, HashSet};
use crate::protocol::*; use crate::protocol::*;
use crate::storage::{Inventory, ReadStorage}; use crate::storage::ReadStorage;
#[allow(unused)] #[allow(unused)]
use crate::test::logger; use crate::test::logger;
use crate::test::peer::Peer; use crate::test::peer::Peer;
@ -157,68 +156,73 @@ fn test_persistent_peer_reconnect() {
assert_matches!(alice.outbox().next(), None); assert_matches!(alice.outbox().next(), None);
} }
#[quickcheck] #[test]
fn prop_inventory_exchange_dense(alice_inv: Inventory, bob_inv: Inventory, eve_inv: Inventory) { fn prop_inventory_exchange_dense() {
let rng = fastrand::Rng::new(); fn property(alice_inv: MockStorage, bob_inv: MockStorage, eve_inv: MockStorage) {
let alice = Peer::new("alice", [7, 7, 7, 7], MockStorage::new(alice_inv.clone())); let rng = fastrand::Rng::new();
let mut bob = Peer::new("bob", [8, 8, 8, 8], MockStorage::new(bob_inv.clone())); let alice = Peer::new("alice", [7, 7, 7, 7], alice_inv.clone());
let mut eve = Peer::new("eve", [9, 9, 9, 9], MockStorage::new(eve_inv.clone())); let mut bob = Peer::new("bob", [8, 8, 8, 8], bob_inv.clone());
let mut routing = Routing::with_hasher(rng.clone().into()); 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 &[ for (inv, peer) in &[
(alice_inv, alice.addr().ip()), (alice_inv.inventory, alice.addr().ip()),
(bob_inv, bob.addr().ip()), (bob_inv.inventory, bob.addr().ip()),
(eve_inv, eve.addr().ip()), (eve_inv.inventory, eve.addr().ip()),
] { ] {
for (proj, _) in inv { for (proj, _) in inv {
routing routing
.entry(proj.clone()) .entry(proj.clone())
.or_insert_with(|| HashSet::with_hasher(rng.clone().into())) .or_insert_with(|| HashSet::with_hasher(rng.clone().into()))
.insert(*peer); .insert(*peer);
}
} }
}
// Fully-connected. // Fully-connected.
bob.command(Command::Connect(alice.addr())); bob.command(Command::Connect(alice.addr()));
bob.command(Command::Connect(eve.addr())); bob.command(Command::Connect(eve.addr()));
eve.command(Command::Connect(alice.addr())); eve.command(Command::Connect(alice.addr()));
eve.command(Command::Connect(bob.addr())); eve.command(Command::Connect(bob.addr()));
let mut peers: HashMap<_, _> = [(alice.ip, alice), (bob.ip, bob), (eve.ip, eve)] let mut peers: HashMap<_, _> = [(alice.ip, alice), (bob.ip, bob), (eve.ip, eve)]
.into_iter() .into_iter()
.collect(); .collect();
let mut simulator = Simulation::new(LocalTime::now(), rng, simulator::Options::default()) let mut simulator = Simulation::new(LocalTime::now(), rng, simulator::Options::default())
.initialize(peers.values_mut()); .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 (proj_id, remotes) in &routing {
for peer in peers.values() { for peer in peers.values() {
let lookup = peer.lookup(proj_id); let lookup = peer.lookup(proj_id);
if lookup.local.is_some() { if lookup.local.is_some() {
peer.storage() 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()
.get(proj_id) .get(proj_id)
.expect("There are no errors querying storage") .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::<HashSet<_>>(),
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::<HashSet<_>>(),
remotes,
"The remotes match the global routing table"
);
} }
} }
} }
quickcheck::QuickCheck::new()
.gen(quickcheck::Gen::new(8))
.quickcheck(property as fn(MockStorage, MockStorage, MockStorage));
} }