Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
node: Send all remotes in inventory
Alexis Sellier committed 3 years ago
commit 15eed71588c359dc030f669434c8ad47bc103fc1
parent 789c50ee3589eed1d9aa57373a17ac8c3c2f1dfa
7 files changed +148 -93
modified 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
modified 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<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>,
}
modified node/src/storage.rs
@@ -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![])
    }
}

modified 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;
modified 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<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)
    }
}

modified 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<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)
    }
}

modified 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());
-

-
    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);
+
#[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.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()));
-

-
    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());
-

-
    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()
+
        // 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());
+

+
        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);
+

+
                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));
}