node: Send all remotes in inventory
Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
parent
789c50ee35
commit
15eed71588
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<S, T> Iterator for Protocol<S, T> {
|
|||
#[derive(Debug)]
|
||||
pub struct Lookup {
|
||||
/// 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.
|
||||
pub remote: Vec<PeerId>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 type BranchName = String;
|
||||
pub type Inventory = Vec<(ProjId, Refs)>;
|
||||
pub type Inventory = Vec<(ProjId, HashMap<String, Remote<Unverified>>)>;
|
||||
|
||||
/// 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<BranchName, Oid>,
|
||||
}
|
||||
|
||||
impl From<HashMap<BranchName, Oid>> for Refs {
|
||||
fn from(heads: HashMap<BranchName, Oid>) -> Self {
|
||||
Self { heads }
|
||||
}
|
||||
}
|
||||
|
||||
pub type Refs = HashMap<BranchName, Oid>;
|
||||
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<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.
|
||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct Remote<V> {
|
||||
|
|
@ -70,11 +80,22 @@ pub struct Remote<V> {
|
|||
/// 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<V>,
|
||||
}
|
||||
|
||||
impl Remote<Unverified> {
|
||||
pub fn new(refs: HashMap<RefName, Oid>) -> Self {
|
||||
Self {
|
||||
refs,
|
||||
delegate: false,
|
||||
verified: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +113,7 @@ where
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -118,23 +139,22 @@ impl fmt::Debug 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!()
|
||||
}
|
||||
|
||||
fn inventory(&self) -> Result<Inventory, Error> {
|
||||
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![])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<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 {
|
||||
let rng = fastrand::Rng::with_seed(u64::arbitrary(g));
|
||||
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);
|
||||
}
|
||||
}
|
||||
storage::Refs::from(refs)
|
||||
storage::Remote::new(refs)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Unverified>)>,
|
||||
}
|
||||
|
||||
impl MockStorage {
|
||||
pub fn new(inventory: Inventory) -> Self {
|
||||
pub fn new(inventory: Vec<(ProjId, Remotes<Unverified>)>) -> Self {
|
||||
Self { inventory }
|
||||
}
|
||||
|
||||
|
|
@ -20,7 +21,7 @@ impl 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) {
|
||||
return Ok(Some(refs.clone()));
|
||||
}
|
||||
|
|
@ -28,7 +29,13 @@ impl ReadStorage for MockStorage {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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::<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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue