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)]
|
#[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
|
||||||
|
|
|
||||||
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue