node: Add `NodeId`

Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
Alexis Sellier 2022-08-30 21:53:54 +02:00
parent 66d580019b
commit ad1f25b3f5
No known key found for this signature in database
9 changed files with 182 additions and 75 deletions

View File

@ -4,10 +4,19 @@ use ed25519_consensus as ed25519;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
#[derive(Serialize, Deserialize, Eq, Debug, Clone)] pub trait Signer {
/// Return this signer's public/verification key.
fn public_key(&self) -> &PublicKey;
}
/// The public/verification key.
#[derive(Serialize, Deserialize, Eq, Debug, Copy, Clone)]
#[serde(transparent)] #[serde(transparent)]
pub struct PublicKey(pub ed25519::VerificationKey); pub struct PublicKey(pub ed25519::VerificationKey);
/// The private/signing key.
pub type SecretKey = ed25519::SigningKey;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum PublicKeyError { pub enum PublicKeyError {
#[error("invalid length {0}")] #[error("invalid length {0}")]
@ -36,6 +45,12 @@ impl PartialEq for PublicKey {
} }
} }
impl From<ed25519::VerificationKey> for PublicKey {
fn from(other: ed25519::VerificationKey) -> Self {
Self(other)
}
}
impl PublicKey { impl PublicKey {
pub fn encode(&self) -> String { pub fn encode(&self) -> String {
multibase::encode(multibase::Base::Base58Btc, &self.0) multibase::encode(multibase::Base::Base58Btc, &self.0)

View File

@ -37,7 +37,7 @@ pub fn list_remotes(url: &Url) -> Result<Remotes<Unverified>, ListRefsError> {
for r in refs { for r in refs {
let (id, refname) = parse_ref::<UserId>(r.name())?; let (id, refname) = parse_ref::<UserId>(r.name())?;
let entry = remotes let entry = remotes
.entry(id.clone()) .entry(id)
.or_insert_with(|| Remote::new(id, HashMap::default())); .or_insert_with(|| Remote::new(id, HashMap::default()));
entry.refs.insert(refname.to_string(), r.oid().into()); entry.refs.insert(refname.to_string(), r.oid().into());

View File

@ -123,8 +123,8 @@ mod test {
let mut hm = HashSet::new(); let mut hm = HashSet::new();
assert!(hm.insert(a.clone())); assert!(hm.insert(a));
assert!(hm.insert(b.clone())); assert!(hm.insert(b));
assert!(!hm.insert(a)); assert!(!hm.insert(a));
assert!(!hm.insert(b)); assert!(!hm.insert(b));
} }

View File

@ -16,15 +16,16 @@ use crate::address_book::AddressBook;
use crate::address_manager::AddressManager; use crate::address_manager::AddressManager;
use crate::clock::RefClock; use crate::clock::RefClock;
use crate::collections::{HashMap, HashSet}; use crate::collections::{HashMap, HashSet};
use crate::crypto;
use crate::decoder::Decoder; use crate::decoder::Decoder;
use crate::identity::{ProjId, UserId}; use crate::identity::{ProjId, UserId};
use crate::storage::{self, WriteRepository}; use crate::storage::{self, WriteRepository};
use crate::storage::{Inventory, ReadStorage, Remotes, Unverified, WriteStorage}; use crate::storage::{Inventory, ReadStorage, Remotes, Unverified, WriteStorage};
/// Network peer identifier. /// Network node identifier.
pub type PeerId = IpAddr; pub type NodeId = crypto::PublicKey;
/// Network routing table. Keeps track of where projects are hosted. /// Network routing table. Keeps track of where projects are hosted.
pub type Routing = HashMap<ProjId, HashSet<PeerId>>; pub type Routing = HashMap<ProjId, HashSet<NodeId>>;
/// Seconds since epoch. /// Seconds since epoch.
pub type Timestamp = u64; pub type Timestamp = u64;
@ -69,6 +70,8 @@ pub struct Envelope {
pub enum Message { pub enum Message {
/// Say hello to a peer. This is the first message sent to a peer after connection. /// Say hello to a peer. This is the first message sent to a peer after connection.
Hello { Hello {
// TODO: This is currently untrusted.
id: NodeId,
timestamp: Timestamp, timestamp: Timestamp,
version: u32, version: u32,
addrs: Vec<Address>, addrs: Vec<Address>,
@ -87,7 +90,7 @@ pub enum Message {
timestamp: Timestamp, timestamp: Timestamp,
/// Original peer this inventory came from. We don't set this when we /// Original peer this inventory came from. We don't set this when we
/// are the originator, only when relaying. /// are the originator, only when relaying.
origin: Option<PeerId>, origin: Option<NodeId>,
}, },
} }
@ -101,8 +104,9 @@ impl From<Message> for Envelope {
} }
impl Message { impl Message {
pub fn hello(timestamp: Timestamp, addrs: Vec<Address>, git: Url) -> Self { pub fn hello(id: NodeId, timestamp: Timestamp, addrs: Vec<Address>, git: Url) -> Self {
Self::Hello { Self::Hello {
id,
timestamp, timestamp,
version: PROTOCOL_VERSION, version: PROTOCOL_VERSION,
addrs, addrs,
@ -110,7 +114,7 @@ impl Message {
} }
} }
pub fn inventory<S, T>(ctx: &mut Context<S, T>) -> Result<Self, storage::Error> pub fn inventory<S, T, G>(ctx: &mut Context<S, T, G>) -> Result<Self, storage::Error>
where where
T: storage::ReadStorage, T: storage::ReadStorage,
{ {
@ -219,11 +223,11 @@ impl Config {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Protocol<S, T> { pub struct Protocol<S, T, G> {
/// Peers currently or recently connected. /// Peers currently or recently connected.
peers: Peers, peers: Peers,
/// Protocol state that isn't peer-specific. /// Protocol state that isn't peer-specific.
context: Context<S, T>, context: Context<S, T, G>,
/// Whether our local inventory no long represents what we have announced to the network. /// Whether our local inventory no long represents what we have announced to the network.
out_of_sync: bool, out_of_sync: bool,
/// Last time the protocol was idle. /// Last time the protocol was idle.
@ -238,12 +242,19 @@ pub struct Protocol<S, T> {
start_time: LocalTime, start_time: LocalTime,
} }
impl<T: ReadStorage + WriteStorage, S: address_book::Store> Protocol<S, T> { impl<T: ReadStorage + WriteStorage, S: address_book::Store, G: crypto::Signer> Protocol<S, T, G> {
pub fn new(config: Config, clock: RefClock, storage: T, addresses: S, rng: Rng) -> Self { pub fn new(
config: Config,
clock: RefClock,
storage: T,
addresses: S,
signer: G,
rng: Rng,
) -> Self {
let addrmgr = AddressManager::new(addresses); let addrmgr = AddressManager::new(addresses);
Self { Self {
context: Context::new(config, clock, storage, addrmgr, rng.clone()), context: Context::new(config, clock, storage, addrmgr, signer, rng.clone()),
peers: Peers::new(rng), peers: Peers::new(rng),
out_of_sync: false, out_of_sync: false,
last_idle: LocalTime::default(), last_idle: LocalTime::default(),
@ -254,15 +265,15 @@ impl<T: ReadStorage + WriteStorage, S: address_book::Store> Protocol<S, T> {
} }
} }
pub fn disconnect(&mut self, peer: &PeerId, reason: DisconnectReason) { pub fn disconnect(&mut self, remote: &IpAddr, reason: DisconnectReason) {
if let Some(addr) = self.peers.get(peer).map(|p| p.addr) { if let Some(addr) = self.peers.get(remote).map(|p| p.addr) {
self.context.disconnect(addr, reason); self.context.disconnect(addr, reason);
} }
} }
pub fn providers(&self, proj: &ProjId) -> Box<dyn Iterator<Item = &Peer> + '_> { pub fn providers(&self, proj: &ProjId) -> Box<dyn Iterator<Item = &Peer> + '_> {
if let Some(peers) = self.routing.get(proj) { if let Some(peers) = self.routing.get(proj) {
Box::new(peers.iter().filter_map(|id| self.peers.get(id))) Box::new(peers.iter().filter_map(|id| self.peers.by_id(id)))
} else { } else {
Box::new(std::iter::empty()) Box::new(std::iter::empty())
} }
@ -302,7 +313,7 @@ impl<T: ReadStorage + WriteStorage, S: address_book::Store> Protocol<S, T> {
/// Returns a sorted list from the closest peer to the furthest. /// Returns a sorted list from the closest peer to the furthest.
/// Peers with more trackings in common score score higher. /// Peers with more trackings in common score score higher.
#[allow(unused)] #[allow(unused)]
pub fn closest_peers(&self, n: usize) -> Vec<PeerId> { pub fn closest_peers(&self, n: usize) -> Vec<NodeId> {
todo!() todo!()
} }
@ -348,7 +359,7 @@ impl<T: ReadStorage + WriteStorage, S: address_book::Store> Protocol<S, T> {
.context .context
.routing .routing
.get(proj) .get(proj)
.map_or(vec![], |r| r.iter().copied().collect()), .map_or(vec![], |r| r.iter().cloned().collect()),
} }
} }
@ -403,10 +414,11 @@ impl<T: ReadStorage + WriteStorage, S: address_book::Store> Protocol<S, T> {
} }
} }
impl<S, T> nakamoto::Protocol for Protocol<S, T> impl<S, T, G> nakamoto::Protocol for Protocol<S, T, G>
where where
T: ReadStorage + WriteStorage + 'static, T: ReadStorage + WriteStorage + 'static,
S: address_book::Store, S: address_book::Store,
G: crypto::Signer,
{ {
type Event = (); type Event = ();
type Command = Command; type Command = Command;
@ -488,11 +500,11 @@ where
} }
fn attempted(&mut self, addr: &std::net::SocketAddr) { fn attempted(&mut self, addr: &std::net::SocketAddr) {
let id = addr.ip(); let ip = addr.ip();
let persistent = self.context.config.is_persistent(addr); let persistent = self.context.config.is_persistent(addr);
let mut peer = self let mut peer = self
.peers .peers
.entry(id) .entry(ip)
.or_insert_with(|| Peer::new(*addr, Link::Outbound, persistent)); .or_insert_with(|| Peer::new(*addr, Link::Outbound, persistent));
peer.attempts += 1; peer.attempts += 1;
@ -504,9 +516,9 @@ where
_local_addr: &std::net::SocketAddr, _local_addr: &std::net::SocketAddr,
link: Link, link: Link,
) { ) {
let id = addr.ip(); let ip = addr.ip();
debug!("Connected to {} ({:?})", id, link); debug!("Connected to {} ({:?})", ip, link);
// For outbound connections, we are the first to say "Hello". // For outbound connections, we are the first to say "Hello".
// For inbound connections, we wait for the remote to say "Hello" first. // For inbound connections, we wait for the remote to say "Hello" first.
@ -514,11 +526,12 @@ where
if link.is_outbound() { if link.is_outbound() {
let git = self.config.git_url.clone(); let git = self.config.git_url.clone();
if let Some(peer) = self.peers.get_mut(&id) { if let Some(peer) = self.peers.get_mut(&ip) {
self.context.write_all( self.context.write_all(
peer.addr, peer.addr,
[ [
Message::hello( Message::hello(
self.context.id(),
self.context.timestamp(), self.context.timestamp(),
self.context.config.listen.clone(), self.context.config.listen.clone(),
git, git,
@ -531,7 +544,7 @@ where
} }
} else { } else {
self.peers.insert( self.peers.insert(
id, ip,
Peer::new( Peer::new(
addr, addr,
Link::Inbound, Link::Inbound,
@ -547,11 +560,11 @@ where
reason: nakamoto::DisconnectReason<Self::DisconnectReason>, reason: nakamoto::DisconnectReason<Self::DisconnectReason>,
) { ) {
let since = self.local_time(); let since = self.local_time();
let id = addr.ip(); let ip = addr.ip();
debug!("Disconnected from {} ({})", id, reason); debug!("Disconnected from {} ({})", ip, reason);
if let Some(peer) = self.peers.get_mut(&id) { if let Some(peer) = self.peers.get_mut(&ip) {
peer.state = PeerState::Disconnected { since }; peer.state = PeerState::Disconnected { since };
// Attempt to re-connect to persistent peers. // Attempt to re-connect to persistent peers.
@ -566,7 +579,7 @@ where
} }
// TODO: Eventually we want a delay before attempting a reconnection, // TODO: Eventually we want a delay before attempting a reconnection,
// with exponential back-off. // with exponential back-off.
debug!("Reconnecting to {} (attempts={})...", id, peer.attempts); debug!("Reconnecting to {} (attempts={})...", ip, peer.attempts);
// TODO: Try to reconnect only if the peer was attempted. A disconnect without // TODO: Try to reconnect only if the peer was attempted. A disconnect without
// even a successful attempt means that we're unlikely to be able to reconnect. // even a successful attempt means that we're unlikely to be able to reconnect.
@ -614,7 +627,7 @@ where
Ok(Some(msg)) => { Ok(Some(msg)) => {
let peers = negotiated let peers = negotiated
.iter() .iter()
.filter(|(id, _)| *id != peer.id()) .filter(|(ip, _)| *ip != peer.ip())
.map(|(_, addr)| *addr) .map(|(_, addr)| *addr)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -629,15 +642,15 @@ where
} }
} }
impl<S, T> Deref for Protocol<S, T> { impl<S, T, G> Deref for Protocol<S, T, G> {
type Target = Context<S, T>; type Target = Context<S, T, G>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.context &self.context
} }
} }
impl<S, T> DerefMut for Protocol<S, T> { impl<S, T, G> DerefMut for Protocol<S, T, G> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.context &mut self.context
} }
@ -673,7 +686,7 @@ impl fmt::Display for DisconnectReason {
} }
} }
impl<S, T> Iterator for Protocol<S, T> { impl<S, T, G> Iterator for Protocol<S, T, G> {
type Item = Io<(), DisconnectReason>; type Item = Io<(), DisconnectReason>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
@ -687,14 +700,16 @@ pub struct Lookup {
/// Whether the project was found locally or not. /// Whether the project was found locally or not.
pub local: Option<Remotes<Unverified>>, 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<NodeId>,
} }
/// Global protocol state used across peers. /// Global protocol state used across peers.
#[derive(Debug)] #[derive(Debug)]
pub struct Context<S, T> { pub struct Context<S, T, G> {
/// Protocol configuration. /// Protocol configuration.
config: Config, config: Config,
/// Our cryptographic signer and key.
signer: G,
/// Tracks the location of projects. /// Tracks the location of projects.
routing: Routing, routing: Routing,
/// Outgoing I/O queue. /// Outgoing I/O queue.
@ -709,19 +724,22 @@ pub struct Context<S, T> {
rng: Rng, rng: Rng,
} }
impl<S, T> Context<S, T> impl<S, T, G> Context<S, T, G>
where where
T: storage::ReadStorage + storage::WriteStorage, T: storage::ReadStorage + storage::WriteStorage,
G: crypto::Signer,
{ {
pub(crate) fn new( pub(crate) fn new(
config: Config, config: Config,
clock: RefClock, clock: RefClock,
storage: T, storage: T,
addrmgr: AddressManager<S>, addrmgr: AddressManager<S>,
signer: G,
rng: Rng, rng: Rng,
) -> Self { ) -> Self {
Self { Self {
config, config,
signer,
clock, clock,
routing: HashMap::with_hasher(rng.clone().into()), routing: HashMap::with_hasher(rng.clone().into()),
io: VecDeque::new(), io: VecDeque::new(),
@ -731,8 +749,12 @@ where
} }
} }
pub(crate) fn id(&self) -> NodeId {
*self.signer.public_key()
}
/// Process a peer inventory announcement by updating our routing table. /// Process a peer inventory announcement by updating our routing table.
fn process_inventory(&mut self, inventory: &Inventory, from: PeerId, remote: &Url) { fn process_inventory(&mut self, inventory: &Inventory, from: NodeId, remote: &Url) {
for proj_id in inventory { for proj_id in inventory {
let inventory = self let inventory = self
.routing .routing
@ -760,7 +782,7 @@ where
} }
} }
impl<S, T> Context<S, T> { impl<S, T, G> Context<S, T, G> {
/// Get current local timestamp. /// Get current local timestamp.
pub(crate) fn timestamp(&self) -> Timestamp { pub(crate) fn timestamp(&self) -> Timestamp {
self.clock.local_time().as_secs() self.clock.local_time().as_secs()
@ -800,13 +822,24 @@ impl<S, T> Context<S, T> {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Peers(AddressBook<PeerId, Peer>); /// Holds currently (or recently) connected peers.
pub struct Peers(AddressBook<IpAddr, Peer>);
impl Peers { impl Peers {
pub fn new(rng: Rng) -> Self { pub fn new(rng: Rng) -> Self {
Self(AddressBook::new(rng)) Self(AddressBook::new(rng))
} }
pub fn by_id(&self, id: &NodeId) -> Option<&Peer> {
self.0.values().find(|p| {
if let PeerState::Negotiated { id: _id, .. } = &p.state {
_id == id
} else {
false
}
})
}
/// Iterator over fully negotiated peers. /// Iterator over fully negotiated peers.
pub fn negotiated(&self) -> impl Iterator<Item = (&IpAddr, &Peer)> + Clone { pub fn negotiated(&self) -> impl Iterator<Item = (&IpAddr, &Peer)> + Clone {
self.0.iter().filter(move |(_, p)| p.is_negotiated()) self.0.iter().filter(move |(_, p)| p.is_negotiated())
@ -814,7 +847,7 @@ impl Peers {
} }
impl Deref for Peers { impl Deref for Peers {
type Target = AddressBook<PeerId, Peer>; type Target = AddressBook<IpAddr, Peer>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
@ -828,6 +861,7 @@ impl DerefMut for Peers {
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
#[allow(clippy::large_enum_variant)]
enum PeerState { enum PeerState {
/// Initial peer state. For outgoing peers this /// Initial peer state. For outgoing peers this
/// means we've attempted a connection. For incoming /// means we've attempted a connection. For incoming
@ -837,6 +871,8 @@ enum PeerState {
Initial, Initial,
/// State after successful handshake. /// State after successful handshake.
Negotiated { Negotiated {
/// The peer's unique identifier.
id: NodeId,
since: LocalTime, since: LocalTime,
/// Addresses this peer is reachable on. /// Addresses this peer is reachable on.
addrs: Vec<Address>, addrs: Vec<Address>,
@ -892,7 +928,7 @@ impl Peer {
} }
} }
fn id(&self) -> PeerId { fn ip(&self) -> IpAddr {
self.addr.ip() self.addr.ip()
} }
@ -900,23 +936,25 @@ impl Peer {
matches!(self.state, PeerState::Negotiated { .. }) matches!(self.state, PeerState::Negotiated { .. })
} }
fn received<S, T>( fn received<S, T, G>(
&mut self, &mut self,
envelope: Envelope, envelope: Envelope,
ctx: &mut Context<S, T>, ctx: &mut Context<S, T, G>,
) -> Result<Option<Message>, PeerError> ) -> Result<Option<Message>, PeerError>
where where
T: storage::ReadStorage + storage::WriteStorage, T: storage::ReadStorage + storage::WriteStorage,
G: crypto::Signer,
{ {
if envelope.magic != NETWORK_MAGIC { if envelope.magic != NETWORK_MAGIC {
return Err(PeerError::WrongMagic(envelope.magic)); return Err(PeerError::WrongMagic(envelope.magic));
} }
debug!("Received {:?} from {}", &envelope.msg, self.id()); debug!("Received {:?} from {}", &envelope.msg, self.ip());
match (&self.state, envelope.msg) { match (&self.state, envelope.msg) {
( (
PeerState::Initial, PeerState::Initial,
Message::Hello { Message::Hello {
id,
timestamp, timestamp,
version, version,
addrs, addrs,
@ -938,7 +976,7 @@ impl Peer {
ctx.write_all( ctx.write_all(
self.addr, self.addr,
[ [
Message::hello(now, ctx.config.listen.clone(), git), Message::hello(ctx.id(), now, ctx.config.listen.clone(), git),
Message::get_inventory([]), Message::get_inventory([]),
], ],
); );
@ -947,6 +985,7 @@ impl Peer {
// set after the first message is received only. Setting it here would // set after the first message is received only. Setting it here would
// mean that messages received right after the handshake could be ignored. // mean that messages received right after the handshake could be ignored.
self.state = PeerState::Negotiated { self.state = PeerState::Negotiated {
id,
since: ctx.clock.local_time(), since: ctx.clock.local_time(),
addrs, addrs,
git, git,
@ -955,7 +994,7 @@ impl Peer {
(PeerState::Initial, _) => { (PeerState::Initial, _) => {
debug!( debug!(
"Disconnecting peer {} for sending us a message before handshake", "Disconnecting peer {} for sending us a message before handshake",
self.id() self.ip()
); );
return Err(PeerError::Misbehavior); return Err(PeerError::Misbehavior);
} }
@ -965,7 +1004,7 @@ impl Peer {
ctx.write(self.addr, inventory); ctx.write(self.addr, inventory);
} }
( (
PeerState::Negotiated { git, .. }, PeerState::Negotiated { id, git, .. },
Message::Inventory { Message::Inventory {
timestamp, timestamp,
inv, inv,
@ -986,13 +1025,13 @@ impl Peer {
} else { } else {
return Ok(None); return Ok(None);
} }
ctx.process_inventory(&inv, origin.unwrap_or_else(|| self.id()), git); ctx.process_inventory(&inv, origin.unwrap_or(*id), git);
if ctx.config.relay { if ctx.config.relay {
return Ok(Some(Message::Inventory { return Ok(Some(Message::Inventory {
timestamp, timestamp,
inv, inv,
origin: origin.or_else(|| Some(self.id())), origin: origin.or(Some(*id)),
})); }));
} }
} }
@ -1007,12 +1046,12 @@ impl Peer {
(PeerState::Negotiated { .. }, Message::Hello { .. }) => { (PeerState::Negotiated { .. }, Message::Hello { .. }) => {
debug!( debug!(
"Disconnecting peer {} for sending us a redundant handshake message", "Disconnecting peer {} for sending us a redundant handshake message",
self.id() self.ip()
); );
return Err(PeerError::Misbehavior); return Err(PeerError::Misbehavior);
} }
(PeerState::Disconnected { .. }, msg) => { (PeerState::Disconnected { .. }, msg) => {
debug!("Ignoring {:?} from disconnected peer {}", msg, self.id()); debug!("Ignoring {:?} from disconnected peer {}", msg, self.ip());
} }
} }
Ok(None) Ok(None)

View File

@ -164,7 +164,7 @@ impl ReadRepository for Repository {
let name = r.name().ok_or(Error::InvalidRef)?; let name = r.name().ok_or(Error::InvalidRef)?;
let (id, refname) = git::parse_ref::<UserId>(name)?; let (id, refname) = git::parse_ref::<UserId>(name)?;
let entry = remotes let entry = remotes
.entry(id.clone()) .entry(id)
.or_insert_with(|| Remote::new(id, HashMap::default())); .or_insert_with(|| Remote::new(id, HashMap::default()));
let oid = r.target().ok_or(Error::InvalidRef)?; let oid = r.target().ok_or(Error::InvalidRef)?;

View File

@ -1,5 +1,6 @@
pub(crate) mod arbitrary; pub(crate) mod arbitrary;
pub(crate) mod assert; pub(crate) mod assert;
pub(crate) mod crypto;
pub(crate) mod fixtures; pub(crate) mod fixtures;
pub(crate) mod logger; pub(crate) mod logger;
pub(crate) mod peer; pub(crate) mod peer;

38
node/src/test/crypto.rs Normal file
View File

@ -0,0 +1,38 @@
use crate::crypto::{PublicKey, SecretKey, Signer};
#[derive(Debug)]
pub struct MockSigner {
key: PublicKey,
}
impl MockSigner {
pub fn new(rng: &mut fastrand::Rng) -> Self {
let mut bytes: [u8; 32] = [0; 32];
for byte in &mut bytes {
*byte = rng.u8(..);
}
let sk = SecretKey::from(bytes);
Self {
key: sk.verification_key().into(),
}
}
}
impl Default for MockSigner {
fn default() -> Self {
let bytes: [u8; 32] = [0; 32];
let sk = SecretKey::from(bytes);
Self {
key: sk.verification_key().into(),
}
}
}
impl Signer for MockSigner {
fn public_key(&self) -> &PublicKey {
&self.key
}
}

View File

@ -12,10 +12,11 @@ use crate::collections::HashMap;
use crate::decoder::Decoder; use crate::decoder::Decoder;
use crate::protocol::*; use crate::protocol::*;
use crate::storage::{ReadStorage, WriteStorage}; use crate::storage::{ReadStorage, WriteStorage};
use crate::test::crypto::MockSigner;
use crate::*; use crate::*;
/// Protocol instantiation used for testing. /// Protocol instantiation used for testing.
pub type Protocol<S> = crate::protocol::Protocol<HashMap<net::IpAddr, KnownAddress>, S>; pub type Protocol<S> = crate::protocol::Protocol<HashMap<net::IpAddr, KnownAddress>, S, MockSigner>;
#[derive(Debug)] #[derive(Debug)]
pub struct Peer<S> { pub struct Peer<S> {
@ -80,7 +81,7 @@ where
ip: impl Into<net::IpAddr>, ip: impl Into<net::IpAddr>,
addrs: Vec<(net::SocketAddr, Source)>, addrs: Vec<(net::SocketAddr, Source)>,
storage: S, storage: S,
rng: fastrand::Rng, mut rng: fastrand::Rng,
) -> Self { ) -> Self {
let addrs = addrs let addrs = addrs
.into_iter() .into_iter()
@ -88,7 +89,8 @@ where
.collect(); .collect();
let local_time = LocalTime::now(); let local_time = LocalTime::now();
let clock = RefClock::from(local_time); let clock = RefClock::from(local_time);
let protocol = Protocol::new(config, clock, storage, addrs, rng.clone()); let signer = MockSigner::new(&mut rng);
let protocol = Protocol::new(config, clock, storage, addrs, signer, rng.clone());
let ip = ip.into(); let ip = ip.into();
let local_addr = net::SocketAddr::new(ip, rng.u16(..)); let local_addr = net::SocketAddr::new(ip, rng.u16(..));
@ -120,6 +122,10 @@ where
self.config().git_url.clone() self.config().git_url.clone()
} }
pub fn id(&self) -> NodeId {
self.protocol.id()
}
pub fn receive(&mut self, peer: &net::SocketAddr, msg: Message) { pub fn receive(&mut self, peer: &net::SocketAddr, msg: Message) {
let bytes = serde_json::to_vec(&Envelope { let bytes = serde_json::to_vec(&Envelope {
magic: NETWORK_MAGIC, magic: NETWORK_MAGIC,
@ -130,23 +136,25 @@ where
self.protocol.received_bytes(peer, &bytes); self.protocol.received_bytes(peer, &bytes);
} }
pub fn connect_from(&mut self, remote: &net::SocketAddr) { pub fn connect_from(&mut self, peer: &Self) {
let remote = simulator::Peer::<Protocol<S>>::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();
self.initialize(); self.initialize();
self.protocol.connected(*remote, &local, Link::Inbound); self.protocol.connected(remote, &local, Link::Inbound);
self.receive( self.receive(
remote, &remote,
Message::hello( Message::hello(
peer.id(),
self.local_time().as_secs(), self.local_time().as_secs(),
vec![Address::Tcp { addr: *remote }], vec![Address::Tcp { addr: remote }],
git, git,
), ),
); );
let mut msgs = self.messages(remote); let mut msgs = self.messages(&remote);
msgs.find(|m| matches!(m, Message::Hello { .. })) msgs.find(|m| matches!(m, Message::Hello { .. }))
.expect("`hello` is sent"); .expect("`hello` is sent");
msgs.find(|m| matches!(m, Message::GetInventory { .. })) msgs.find(|m| matches!(m, Message::GetInventory { .. }))
@ -171,6 +179,7 @@ where
self.receive( self.receive(
&remote, &remote,
Message::hello( Message::hello(
peer.id(),
self.local_time().as_secs(), self.local_time().as_secs(),
peer.config().listen.clone(), peer.config().listen.clone(),
git, git,

View File

@ -51,8 +51,8 @@ fn test_inbound_connection() {
let bob = Peer::new("bob", [9, 9, 9, 9], MockStorage::empty()); let bob = Peer::new("bob", [9, 9, 9, 9], MockStorage::empty());
let eve = Peer::new("eve", [7, 7, 7, 7], MockStorage::empty()); let eve = Peer::new("eve", [7, 7, 7, 7], MockStorage::empty());
alice.connect_from(&bob.addr()); alice.connect_from(&bob);
alice.connect_from(&eve.addr()); alice.connect_from(&eve);
let peers = alice let peers = alice
.protocol .protocol
@ -108,7 +108,12 @@ fn test_handshake_invalid_timestamp() {
alice.connected(bob.addr(), &local, Link::Inbound); alice.connected(bob.addr(), &local, Link::Inbound);
alice.receive( alice.receive(
&bob.addr(), &bob.addr(),
Message::hello(alice.timestamp() - time_delta, vec![], bob.git_url()), Message::hello(
bob.id(),
alice.timestamp() - time_delta,
vec![],
bob.git_url(),
),
); );
assert_matches!(alice.outbox().next(), Some(Io::Disconnect(addr, _)) if addr == bob.addr()); assert_matches!(alice.outbox().next(), Some(Io::Disconnect(addr, _)) if addr == bob.addr());
} }
@ -144,7 +149,7 @@ fn test_inventory_sync() {
for proj in &projs { for proj in &projs {
let providers = alice.routing().get(proj).unwrap(); let providers = alice.routing().get(proj).unwrap();
assert!(providers.contains(&bob.ip)); assert!(providers.contains(&bob.id()));
} }
let a = alice let a = alice
@ -192,7 +197,7 @@ fn test_inventory_relay() {
// Inventory from Bob relayed to Eve. // Inventory from Bob relayed to Eve.
alice.connect_to(&bob); alice.connect_to(&bob);
alice.connect_from(&eve.addr()); alice.connect_from(&eve);
alice.receive( alice.receive(
&bob.addr(), &bob.addr(),
Message::Inventory { Message::Inventory {
@ -204,7 +209,7 @@ fn test_inventory_relay() {
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(&eve.addr()).next(),
Some(Message::Inventory { timestamp, origin, .. }) Some(Message::Inventory { timestamp, origin, .. })
if origin == Some(bob.ip) && timestamp == now if origin == Some(bob.id()) && timestamp == now
); );
assert_matches!( assert_matches!(
alice.messages(&bob.addr()).next(), alice.messages(&bob.addr()).next(),
@ -237,7 +242,7 @@ fn test_inventory_relay() {
assert_matches!( assert_matches!(
alice.messages(&eve.addr()).next(), alice.messages(&eve.addr()).next(),
Some(Message::Inventory { timestamp, origin, .. }) Some(Message::Inventory { timestamp, origin, .. })
if origin == Some(bob.ip) && timestamp == now + 1, if origin == Some(bob.id()) && timestamp == now + 1,
"Sending a new inventory does trigger the relay" "Sending a new inventory does trigger the relay"
); );
@ -253,7 +258,7 @@ fn test_inventory_relay() {
assert_matches!( assert_matches!(
alice.messages(&bob.addr()).next(), alice.messages(&bob.addr()).next(),
Some(Message::Inventory { timestamp, origin, .. }) Some(Message::Inventory { timestamp, origin, .. })
if origin == Some(eve.ip) && timestamp == now if origin == Some(eve.id()) && timestamp == now
); );
} }
@ -334,9 +339,9 @@ fn prop_inventory_exchange_dense() {
let mut routing = Routing::with_hasher(rng.clone().into()); let mut routing = Routing::with_hasher(rng.clone().into());
for (inv, peer) in &[ for (inv, peer) in &[
(alice_inv.inventory, alice.addr().ip()), (alice_inv.inventory, alice.id()),
(bob_inv.inventory, bob.addr().ip()), (bob_inv.inventory, bob.id()),
(eve_inv.inventory, eve.addr().ip()), (eve_inv.inventory, eve.id()),
] { ] {
for (proj, _) in inv { for (proj, _) in inv {
routing routing
@ -352,7 +357,7 @@ fn prop_inventory_exchange_dense() {
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.id(), alice), (bob.id(), bob), (eve.id(), 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())