node: add more tests for refs relay

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-10-14 18:51:53 +02:00
parent ba95725028
commit d9517cc966
No known key found for this signature in database
5 changed files with 152 additions and 27 deletions

View File

@ -643,8 +643,9 @@ where
return Ok(false); return Ok(false);
} }
// TODO: Check refs to see if we should try to fetch or not. // TODO: Check refs to see if we should try to fetch or not.
// FIXME: This code is wrong: we shouldn't be fetching from the connected peer, // Refs are only supposed to be relayed by peers who are tracking
// we should fetch from the origin. // the resource. Therefore, it's safe to fetch from the remote
// peer, even though it isn't the announcer.
let updated = self.storage.fetch(message.id, git).unwrap(); let updated = self.storage.fetch(message.id, git).unwrap();
let is_updated = !updated.is_empty(); let is_updated = !updated.is_empty();

View File

@ -122,6 +122,16 @@ pub struct Subscribe {
pub until: Timestamp, pub until: Timestamp,
} }
impl Subscribe {
pub fn all() -> Self {
Self {
filter: Filter::default(),
since: Timestamp::MIN,
until: Timestamp::MAX,
}
}
}
/// Node announcing itself to the network. /// Node announcing itself to the network.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeAnnouncement { pub struct NodeAnnouncement {

View File

@ -7,8 +7,10 @@ use log::*;
use crate::address; use crate::address;
use crate::clock::{RefClock, Timestamp}; use crate::clock::{RefClock, Timestamp};
use crate::crypto::Signer;
use crate::git; use crate::git;
use crate::git::Url; use crate::git::Url;
use crate::identity::Id;
use crate::node; use crate::node;
use crate::prelude::NodeId; use crate::prelude::NodeId;
use crate::service; use crate::service;
@ -23,12 +25,12 @@ use crate::test::simulator;
use crate::{Link, LocalTime}; use crate::{Link, LocalTime};
/// Service instantiation used for testing. /// Service instantiation used for testing.
pub type Service<S> = service::Service<routing::Table, address::Book, S, MockSigner>; pub type Service<S, G> = service::Service<routing::Table, address::Book, S, G>;
#[derive(Debug)] #[derive(Debug)]
pub struct Peer<S> { pub struct Peer<S, G> {
pub name: &'static str, pub name: &'static str,
pub service: Service<S>, pub service: Service<S, G>,
pub ip: net::IpAddr, pub ip: net::IpAddr,
pub rng: fastrand::Rng, pub rng: fastrand::Rng,
pub local_time: LocalTime, pub local_time: LocalTime,
@ -37,9 +39,10 @@ pub struct Peer<S> {
initialized: bool, initialized: bool,
} }
impl<S> simulator::Peer<S> for Peer<S> impl<S, G> simulator::Peer<S, G> for Peer<S, G>
where where
S: WriteStorage + 'static, S: WriteStorage + 'static,
G: Signer + 'static,
{ {
fn init(&mut self) { fn init(&mut self) {
self.initialize() self.initialize()
@ -50,21 +53,21 @@ where
} }
} }
impl<S> Deref for Peer<S> { impl<S, G> Deref for Peer<S, G> {
type Target = Service<S>; type Target = Service<S, G>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.service &self.service
} }
} }
impl<S> DerefMut for Peer<S> { impl<S, G> DerefMut for Peer<S, G> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.service &mut self.service
} }
} }
impl<S> Peer<S> impl<S> Peer<S, MockSigner>
where where
S: WriteStorage + 'static, S: WriteStorage + 'static,
{ {
@ -75,6 +78,8 @@ where
..git::Url::default() ..git::Url::default()
}; };
let mut rng = fastrand::Rng::new();
let signer = MockSigner::new(&mut rng);
Self::config( Self::config(
name, name,
@ -84,20 +89,27 @@ where
}, },
ip, ip,
storage, storage,
fastrand::Rng::new(), signer,
rng,
) )
} }
}
impl<S, G> Peer<S, G>
where
S: WriteStorage + 'static,
G: Signer + 'static,
{
pub fn config( pub fn config(
name: &'static str, name: &'static str,
config: Config, config: Config,
ip: impl Into<net::IpAddr>, ip: impl Into<net::IpAddr>,
storage: S, storage: S,
mut rng: fastrand::Rng, signer: G,
rng: fastrand::Rng,
) -> Self { ) -> Self {
let local_time = LocalTime::now(); let local_time = LocalTime::now();
let clock = RefClock::from(local_time); let clock = RefClock::from(local_time);
let signer = MockSigner::new(&mut rng);
let routing = routing::Table::memory().unwrap(); let routing = routing::Table::memory().unwrap();
let addrs = address::Book::memory().unwrap(); let addrs = address::Book::memory().unwrap();
let service = Service::new(config, clock, routing, storage, addrs, signer, rng.clone()); let service = Service::new(config, clock, routing, storage, addrs, signer, rng.clone());
@ -170,9 +182,7 @@ where
) )
} }
pub fn refs_announcement(&self) -> Message { pub fn refs_announcement(&self, id: Id) -> Message {
let inv = self.inventory().unwrap();
let id = inv[self.rng.usize(..inv.len())];
let refs = BTreeMap::new().into(); let refs = BTreeMap::new().into();
let ann = AnnouncementMessage::from(RefsAnnouncement { let ann = AnnouncementMessage::from(RefsAnnouncement {
id, id,
@ -185,7 +195,7 @@ where
} }
pub fn connect_from(&mut self, peer: &Self) { pub fn connect_from(&mut self, peer: &Self) {
let remote = simulator::Peer::<S>::addr(peer); let remote = simulator::Peer::<S, G>::addr(peer);
let local = net::SocketAddr::new(self.ip, self.rng.u16(..)); let local = net::SocketAddr::new(self.ip, self.rng.u16(..));
let git = format!("file:///{}.git", remote.ip()); let git = format!("file:///{}.git", remote.ip());
let git = Url::from_bytes(git.as_bytes()).unwrap(); let git = Url::from_bytes(git.as_bytes()).unwrap();
@ -213,7 +223,7 @@ where
} }
pub fn connect_to(&mut self, peer: &Self) { pub fn connect_to(&mut self, peer: &Self) {
let remote = simulator::Peer::<S>::addr(peer); let remote = simulator::Peer::<S, G>::addr(peer);
self.initialize(); self.initialize();
self.service.attempted(&remote); self.service.attempted(&remote);

View File

@ -14,6 +14,7 @@ use log::*;
use nakamoto_net as nakamoto; use nakamoto_net as nakamoto;
use nakamoto_net::{Link, LocalDuration, LocalTime}; use nakamoto_net::{Link, LocalDuration, LocalTime};
use crate::crypto::Signer;
use crate::service::reactor::Io; use crate::service::reactor::Io;
use crate::service::{DisconnectReason, Envelope, Event}; use crate::service::{DisconnectReason, Envelope, Event};
use crate::storage::WriteStorage; use crate::storage::WriteStorage;
@ -29,7 +30,9 @@ pub const MAX_EVENTS: usize = 2048;
type NodeId = std::net::IpAddr; type NodeId = std::net::IpAddr;
/// A simulated peer. Service instances have to be wrapped in this type to be simulated. /// A simulated peer. Service instances have to be wrapped in this type to be simulated.
pub trait Peer<S>: Deref<Target = Service<S>> + DerefMut<Target = Service<S>> + 'static { pub trait Peer<S, G>:
Deref<Target = Service<S, G>> + DerefMut<Target = Service<S, G>> + 'static
{
/// Initialize the peer. This should at minimum initialize the service with the /// Initialize the peer. This should at minimum initialize the service with the
/// current time. /// current time.
fn init(&mut self); fn init(&mut self);
@ -166,7 +169,7 @@ impl Default for Options {
} }
/// A peer-to-peer node simulation. /// A peer-to-peer node simulation.
pub struct Simulation<S> { pub struct Simulation<S, G> {
/// Inbox of inputs to be delivered by the simulation. /// Inbox of inputs to be delivered by the simulation.
inbox: Inbox, inbox: Inbox,
/// Events emitted during simulation. /// Events emitted during simulation.
@ -191,9 +194,11 @@ pub struct Simulation<S> {
rng: fastrand::Rng, rng: fastrand::Rng,
/// Storage type. /// Storage type.
storage: PhantomData<S>, storage: PhantomData<S>,
/// Signer type.
signer: PhantomData<G>,
} }
impl<S: WriteStorage + 'static> Simulation<S> { impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
/// Create a new simulation. /// Create a new simulation.
pub fn new(time: LocalTime, rng: fastrand::Rng, opts: Options) -> Self { pub fn new(time: LocalTime, rng: fastrand::Rng, opts: Options) -> Self {
Self { Self {
@ -211,6 +216,7 @@ impl<S: WriteStorage + 'static> Simulation<S> {
time, time,
rng, rng,
storage: PhantomData, storage: PhantomData,
signer: PhantomData,
} }
} }
@ -267,7 +273,7 @@ impl<S: WriteStorage + 'static> Simulation<S> {
/// Initialize peers. /// Initialize peers.
pub fn initialize<'a, P>(self, peers: impl IntoIterator<Item = &'a mut P>) -> Self pub fn initialize<'a, P>(self, peers: impl IntoIterator<Item = &'a mut P>) -> Self
where where
P: Peer<S>, P: Peer<S, G>,
{ {
for peer in peers.into_iter() { for peer in peers.into_iter() {
peer.init(); peer.init();
@ -281,7 +287,7 @@ impl<S: WriteStorage + 'static> Simulation<S> {
peers: impl IntoIterator<Item = &'a mut P>, peers: impl IntoIterator<Item = &'a mut P>,
pred: impl Fn(&Self) -> bool, pred: impl Fn(&Self) -> bool,
) where ) where
P: Peer<S>, P: Peer<S, G>,
{ {
let mut nodes: BTreeMap<_, _> = peers.into_iter().map(|p| (p.addr().ip(), p)).collect(); let mut nodes: BTreeMap<_, _> = peers.into_iter().map(|p| (p.addr().ip(), p)).collect();
@ -295,12 +301,12 @@ impl<S: WriteStorage + 'static> Simulation<S> {
/// Process one scheduled input from the inbox, using the provided peers. /// Process one scheduled input from the inbox, using the provided peers.
/// This function should be called until it returns `false`, or some desired state is reached. /// This function should be called until it returns `false`, or some desired state is reached.
/// Returns `true` if there are more messages to process. /// Returns `true` if there are more messages to process.
pub fn step<'a, P: Peer<S>>(&mut self, peers: impl IntoIterator<Item = &'a mut P>) -> bool { pub fn step<'a, P: Peer<S, G>>(&mut self, peers: impl IntoIterator<Item = &'a mut P>) -> bool {
let mut nodes: BTreeMap<_, _> = peers.into_iter().map(|p| (p.addr().ip(), p)).collect(); let mut nodes: BTreeMap<_, _> = peers.into_iter().map(|p| (p.addr().ip(), p)).collect();
self.step_(&mut nodes) self.step_(&mut nodes)
} }
fn step_<P: Peer<S>>(&mut self, nodes: &mut BTreeMap<NodeId, &mut P>) -> bool { fn step_<P: Peer<S, G>>(&mut self, nodes: &mut BTreeMap<NodeId, &mut P>) -> bool {
if !self.opts.latency.is_empty() { if !self.opts.latency.is_empty() {
// Configure latencies. // Configure latencies.
for (i, from) in nodes.keys().enumerate() { for (i, from) in nodes.keys().enumerate() {

View File

@ -15,6 +15,7 @@ use crate::service::ServiceState as _;
use crate::service::*; use crate::service::*;
use crate::storage::git::Storage; use crate::storage::git::Storage;
use crate::storage::ReadStorage; use crate::storage::ReadStorage;
use crate::test::arbitrary;
use crate::test::assert_matches; use crate::test::assert_matches;
use crate::test::fixtures; use crate::test::fixtures;
#[allow(unused)] #[allow(unused)]
@ -77,14 +78,21 @@ fn test_inbound_connection() {
#[test] #[test]
fn test_persistent_peer_connect() { fn test_persistent_peer_connect() {
let rng = fastrand::Rng::new(); let mut rng = fastrand::Rng::new();
let bob = Peer::new("bob", [8, 8, 8, 8], MockStorage::empty()); let bob = Peer::new("bob", [8, 8, 8, 8], MockStorage::empty());
let eve = Peer::new("eve", [9, 9, 9, 9], MockStorage::empty()); let eve = Peer::new("eve", [9, 9, 9, 9], MockStorage::empty());
let config = Config { let config = Config {
connect: vec![bob.address(), eve.address()], connect: vec![bob.address(), eve.address()],
..Config::default() ..Config::default()
}; };
let mut alice = Peer::config("alice", config, [7, 7, 7, 7], MockStorage::empty(), rng); let mut alice = Peer::config(
"alice",
config,
[7, 7, 7, 7],
MockStorage::empty(),
MockSigner::new(&mut rng),
rng,
);
alice.initialize(); alice.initialize();
@ -158,6 +166,7 @@ fn test_tracking() {
}, },
[7, 7, 7, 7], [7, 7, 7, 7],
MockStorage::empty(), MockStorage::empty(),
MockSigner::default(),
fastrand::Rng::new(), fastrand::Rng::new(),
); );
let proj_id: identity::Id = test::arbitrary::gen(1); let proj_id: identity::Id = test::arbitrary::gen(1);
@ -334,6 +343,94 @@ fn test_announcement_relay() {
); );
} }
#[test]
fn test_refs_announcement_relay() {
let tmp = tempfile::tempdir().unwrap();
let mut alice = Peer::new(
"alice",
[7, 7, 7, 7],
Storage::open(tmp.path().join("alice")).unwrap(),
);
let eve = Peer::new(
"eve",
[8, 8, 8, 8],
Storage::open(tmp.path().join("eve")).unwrap(),
);
let bob = {
let mut rng = fastrand::Rng::new();
let signer = MockSigner::new(&mut rng);
let storage = fixtures::storage(tmp.path().join("bob"), &signer).unwrap();
Peer::config(
"bob",
Config {
git_url: git::Url {
scheme: git::url::Scheme::File,
path: storage.path().to_string_lossy().to_string().into(),
..git::Url::default()
},
..Config::default()
},
[9, 9, 9, 9],
storage,
signer,
rng,
)
};
let bob_inv = bob.inventory().unwrap();
alice.track(bob_inv[0]);
alice.track(bob_inv[1]);
alice.track(bob_inv[2]);
alice.connect_to(&bob);
alice.connect_to(&eve);
alice.receive(&eve.addr(), Message::Subscribe(Subscribe::all()));
alice.receive(&bob.addr(), bob.refs_announcement(bob_inv[0]));
assert_matches!(
alice.messages(&eve.addr()).next(),
Some(Message::Announcement(_)),
"A refs announcement from Bob is relayed to Eve"
);
alice.receive(&bob.addr(), bob.refs_announcement(bob_inv[0]));
assert!(
alice.messages(&eve.addr()).next().is_none(),
"The same ref announement is not relayed"
);
alice.receive(&bob.addr(), bob.refs_announcement(bob_inv[1]));
assert_matches!(
alice.messages(&eve.addr()).next(),
Some(Message::Announcement(_)),
"But a different one is"
);
alice.receive(&bob.addr(), bob.refs_announcement(bob_inv[2]));
assert_matches!(
alice.messages(&eve.addr()).next(),
Some(Message::Announcement(_)),
"And a third one is as well"
);
}
#[test]
fn test_refs_gossip_no_subscribe() {
let mut alice = Peer::new("alice", [7, 7, 7, 7], MockStorage::empty());
let bob = Peer::new("bob", [8, 8, 8, 8], MockStorage::empty());
let eve = Peer::new("eve", [9, 9, 9, 9], MockStorage::empty());
let id = arbitrary::gen(1);
alice.track(id);
alice.connect_to(&bob);
alice.connect_to(&eve);
alice.receive(&bob.addr(), bob.refs_announcement(id));
assert!(alice.messages(&eve.addr()).next().is_none());
}
#[test] #[test]
fn test_inventory_relay() { fn test_inventory_relay() {
// Topology is eve <-> alice <-> bob // Topology is eve <-> alice <-> bob
@ -442,6 +539,7 @@ fn test_persistent_peer_reconnect() {
}, },
[7, 7, 7, 7], [7, 7, 7, 7],
MockStorage::empty(), MockStorage::empty(),
MockSigner::default(),
fastrand::Rng::new(), fastrand::Rng::new(),
); );