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);
}
// 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,
// we should fetch from the origin.
// Refs are only supposed to be relayed by peers who are tracking
// 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 is_updated = !updated.is_empty();

View File

@ -122,6 +122,16 @@ pub struct Subscribe {
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.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeAnnouncement {

View File

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

View File

@ -14,6 +14,7 @@ use log::*;
use nakamoto_net as nakamoto;
use nakamoto_net::{Link, LocalDuration, LocalTime};
use crate::crypto::Signer;
use crate::service::reactor::Io;
use crate::service::{DisconnectReason, Envelope, Event};
use crate::storage::WriteStorage;
@ -29,7 +30,9 @@ pub const MAX_EVENTS: usize = 2048;
type NodeId = std::net::IpAddr;
/// 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
/// current time.
fn init(&mut self);
@ -166,7 +169,7 @@ impl Default for Options {
}
/// 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: Inbox,
/// Events emitted during simulation.
@ -191,9 +194,11 @@ pub struct Simulation<S> {
rng: fastrand::Rng,
/// Storage type.
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.
pub fn new(time: LocalTime, rng: fastrand::Rng, opts: Options) -> Self {
Self {
@ -211,6 +216,7 @@ impl<S: WriteStorage + 'static> Simulation<S> {
time,
rng,
storage: PhantomData,
signer: PhantomData,
}
}
@ -267,7 +273,7 @@ impl<S: WriteStorage + 'static> Simulation<S> {
/// Initialize peers.
pub fn initialize<'a, P>(self, peers: impl IntoIterator<Item = &'a mut P>) -> Self
where
P: Peer<S>,
P: Peer<S, G>,
{
for peer in peers.into_iter() {
peer.init();
@ -281,7 +287,7 @@ impl<S: WriteStorage + 'static> Simulation<S> {
peers: impl IntoIterator<Item = &'a mut P>,
pred: impl Fn(&Self) -> bool,
) where
P: Peer<S>,
P: Peer<S, G>,
{
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.
/// This function should be called until it returns `false`, or some desired state is reached.
/// 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();
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() {
// Configure latencies.
for (i, from) in nodes.keys().enumerate() {

View File

@ -15,6 +15,7 @@ use crate::service::ServiceState as _;
use crate::service::*;
use crate::storage::git::Storage;
use crate::storage::ReadStorage;
use crate::test::arbitrary;
use crate::test::assert_matches;
use crate::test::fixtures;
#[allow(unused)]
@ -77,14 +78,21 @@ fn test_inbound_connection() {
#[test]
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 eve = Peer::new("eve", [9, 9, 9, 9], MockStorage::empty());
let config = Config {
connect: vec![bob.address(), eve.address()],
..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();
@ -158,6 +166,7 @@ fn test_tracking() {
},
[7, 7, 7, 7],
MockStorage::empty(),
MockSigner::default(),
fastrand::Rng::new(),
);
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]
fn test_inventory_relay() {
// Topology is eve <-> alice <-> bob
@ -442,6 +539,7 @@ fn test_persistent_peer_reconnect() {
},
[7, 7, 7, 7],
MockStorage::empty(),
MockSigner::default(),
fastrand::Rng::new(),
);