node: Always send node announcement on handshake
Previously, we only sent the announcement when there were external addresses. But it contains other information like the node alias, therefore we always send it. Since the proof-of-work can be expensive to compute everytime, we add the ability to load the local node announcement from the file system. We also change the proof-of-work parameters so that they are relaxed when running in debug mode, instead of when running tests. Finally, we remove the boolean validation of the PoW. Instead, we store the work per-node, in the database, to be used in the future in case of DoS attacks or network congestion.
This commit is contained in:
parent
e86e061a12
commit
ff7338de17
|
|
@ -4,7 +4,7 @@ use std::io::{BufRead, BufReader};
|
||||||
use std::os::unix::net::UnixListener;
|
use std::os::unix::net::UnixListener;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::{io, net, thread, time};
|
use std::{fs, io, net, thread, time};
|
||||||
|
|
||||||
use crossbeam_channel as chan;
|
use crossbeam_channel as chan;
|
||||||
use cyphernet::Ecdh;
|
use cyphernet::Ecdh;
|
||||||
|
|
@ -16,16 +16,17 @@ use thiserror::Error;
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle::node::address;
|
use radicle::node::address;
|
||||||
use radicle::node::Handle as _;
|
use radicle::node::Handle as _;
|
||||||
use radicle::node::{ADDRESS_DB_FILE, ROUTING_DB_FILE, TRACKING_DB_FILE};
|
use radicle::node::{ADDRESS_DB_FILE, NODE_ANNOUNCEMENT_FILE, ROUTING_DB_FILE, TRACKING_DB_FILE};
|
||||||
use radicle::profile::Home;
|
use radicle::profile::Home;
|
||||||
use radicle::Storage;
|
use radicle::Storage;
|
||||||
|
|
||||||
use crate::control;
|
use crate::control;
|
||||||
use crate::crypto::Signer;
|
use crate::crypto::Signer;
|
||||||
use crate::node::{routing, NodeId};
|
use crate::node::{routing, NodeId};
|
||||||
|
use crate::service::message::NodeAnnouncement;
|
||||||
use crate::service::{tracking, Event};
|
use crate::service::{tracking, Event};
|
||||||
use crate::wire;
|
|
||||||
use crate::wire::Wire;
|
use crate::wire::Wire;
|
||||||
|
use crate::wire::{self, Decode};
|
||||||
use crate::worker;
|
use crate::worker;
|
||||||
use crate::{service, LocalTime};
|
use crate::{service, LocalTime};
|
||||||
|
|
||||||
|
|
@ -147,6 +148,31 @@ impl Runtime {
|
||||||
|
|
||||||
log::info!(target: "node", "Default tracking policy set to '{}'", &config.policy);
|
log::info!(target: "node", "Default tracking policy set to '{}'", &config.policy);
|
||||||
log::info!(target: "node", "Initializing service ({:?})..", network);
|
log::info!(target: "node", "Initializing service ({:?})..", network);
|
||||||
|
|
||||||
|
let announcement = if let Some(ann) = fs::read(&node_dir.join(NODE_ANNOUNCEMENT_FILE))
|
||||||
|
.ok()
|
||||||
|
.and_then(|ann| NodeAnnouncement::decode(&mut ann.as_slice()).ok())
|
||||||
|
.and_then(|ann| {
|
||||||
|
if config.matches(&ann) {
|
||||||
|
Some(ann)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
log::info!(
|
||||||
|
target: "node",
|
||||||
|
"Loaded existing node announcement from file (timestamp={}, work={})",
|
||||||
|
ann.timestamp,
|
||||||
|
ann.work(),
|
||||||
|
);
|
||||||
|
ann
|
||||||
|
} else {
|
||||||
|
config
|
||||||
|
.node(clock.as_secs())
|
||||||
|
.solve(Default::default())
|
||||||
|
.expect("Runtime::init: unable to solve proof-of-work puzzle")
|
||||||
|
};
|
||||||
|
|
||||||
let emitter: Emitter<Event> = Default::default();
|
let emitter: Emitter<Event> = Default::default();
|
||||||
let service = service::Service::new(
|
let service = service::Service::new(
|
||||||
config,
|
config,
|
||||||
|
|
@ -157,6 +183,7 @@ impl Runtime {
|
||||||
tracking,
|
tracking,
|
||||||
signer.clone(),
|
signer.clone(),
|
||||||
rng,
|
rng,
|
||||||
|
announcement,
|
||||||
emitter.clone(),
|
emitter.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use std::collections::hash_map::Entry;
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{fmt, net, str};
|
use std::{fmt, net};
|
||||||
|
|
||||||
use crossbeam_channel as chan;
|
use crossbeam_channel as chan;
|
||||||
use fastrand::Rng;
|
use fastrand::Rng;
|
||||||
|
|
@ -26,7 +26,6 @@ use crate::crypto;
|
||||||
use crate::crypto::{Signer, Verified};
|
use crate::crypto::{Signer, Verified};
|
||||||
use crate::identity::IdentityError;
|
use crate::identity::IdentityError;
|
||||||
use crate::identity::{Doc, Id};
|
use crate::identity::{Doc, Id};
|
||||||
use crate::node;
|
|
||||||
use crate::node::routing;
|
use crate::node::routing;
|
||||||
use crate::node::routing::InsertResult;
|
use crate::node::routing::InsertResult;
|
||||||
use crate::node::{Address, Features, FetchResult, Seed, Seeds};
|
use crate::node::{Address, Features, FetchResult, Seed, Seeds};
|
||||||
|
|
@ -197,6 +196,8 @@ pub struct Service<R, A, S, G> {
|
||||||
clock: LocalTime,
|
clock: LocalTime,
|
||||||
/// I/O outbox.
|
/// I/O outbox.
|
||||||
outbox: Outbox,
|
outbox: Outbox,
|
||||||
|
/// Cached local node announcement.
|
||||||
|
node: NodeAnnouncement,
|
||||||
/// Source of entropy.
|
/// Source of entropy.
|
||||||
rng: Rng,
|
rng: Rng,
|
||||||
/// Fetch requests initiated by user, which are waiting for results.
|
/// Fetch requests initiated by user, which are waiting for results.
|
||||||
|
|
@ -248,6 +249,7 @@ where
|
||||||
tracking: tracking::Config,
|
tracking: tracking::Config,
|
||||||
signer: G,
|
signer: G,
|
||||||
rng: Rng,
|
rng: Rng,
|
||||||
|
node: NodeAnnouncement,
|
||||||
emitter: Emitter<Event>,
|
emitter: Emitter<Event>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let sessions = Sessions::new(rng.clone());
|
let sessions = Sessions::new(rng.clone());
|
||||||
|
|
@ -259,6 +261,7 @@ where
|
||||||
tracking,
|
tracking,
|
||||||
signer,
|
signer,
|
||||||
rng,
|
rng,
|
||||||
|
node,
|
||||||
clock,
|
clock,
|
||||||
routing,
|
routing,
|
||||||
gossip: Gossip::default(),
|
gossip: Gossip::default(),
|
||||||
|
|
@ -397,6 +400,21 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Ensure that our local node is in our address database.
|
||||||
|
self.addresses
|
||||||
|
.insert(
|
||||||
|
&self.node_id(),
|
||||||
|
self.node.features,
|
||||||
|
self.node.alias().unwrap_or_default(),
|
||||||
|
self.node.work(),
|
||||||
|
self.node.timestamp,
|
||||||
|
self.node
|
||||||
|
.addresses
|
||||||
|
.iter()
|
||||||
|
.map(|a| KnownAddress::new(a.clone(), address::Source::Peer)),
|
||||||
|
)
|
||||||
|
.expect("Service::initialize: error adding local node to address database");
|
||||||
|
|
||||||
// Setup subscription filter for tracked repos.
|
// Setup subscription filter for tracked repos.
|
||||||
self.filter = Filter::new(
|
self.filter = Filter::new(
|
||||||
self.tracking
|
self.tracking
|
||||||
|
|
@ -968,7 +986,6 @@ where
|
||||||
AnnouncementMessage::Node(
|
AnnouncementMessage::Node(
|
||||||
ann @ NodeAnnouncement {
|
ann @ NodeAnnouncement {
|
||||||
features,
|
features,
|
||||||
alias,
|
|
||||||
addresses,
|
addresses,
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
|
|
@ -980,12 +997,7 @@ where
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ann.validate() {
|
let alias = match ann.alias() {
|
||||||
warn!(target: "service", "Dropping node announcement from {announcer}: invalid proof-of-work");
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let alias = match str::from_utf8(alias) {
|
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(target: "service", "Dropping node announcement from {announcer}: invalid alias: {e}");
|
warn!(target: "service", "Dropping node announcement from {announcer}: invalid alias: {e}");
|
||||||
|
|
@ -1003,6 +1015,7 @@ where
|
||||||
announcer,
|
announcer,
|
||||||
*features,
|
*features,
|
||||||
alias,
|
alias,
|
||||||
|
ann.work(),
|
||||||
timestamp,
|
timestamp,
|
||||||
addresses
|
addresses
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -1152,11 +1165,11 @@ where
|
||||||
// much bandwidth.
|
// much bandwidth.
|
||||||
|
|
||||||
gossip::handshake(
|
gossip::handshake(
|
||||||
|
self.node.clone(),
|
||||||
self.clock.as_millis(),
|
self.clock.as_millis(),
|
||||||
&self.storage,
|
&self.storage,
|
||||||
&self.signer,
|
&self.signer,
|
||||||
filter,
|
filter,
|
||||||
&self.config,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1287,6 +1300,10 @@ where
|
||||||
warn!(target: "service", "Attempted connection to peer {nid} which already has a session");
|
warn!(target: "service", "Attempted connection to peer {nid} which already has a session");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if nid == self.node_id() {
|
||||||
|
error!(target: "service", "Attempted connection to self");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
let persistent = self.config.is_persistent(&nid);
|
let persistent = self.config.is_persistent(&nid);
|
||||||
|
|
||||||
if let Err(e) = self.addresses.attempted(&nid, &addr, self.time()) {
|
if let Err(e) = self.addresses.attempted(&nid, &addr, self.time()) {
|
||||||
|
|
@ -1426,6 +1443,7 @@ where
|
||||||
// even if it's in a disconnected state. Those sessions are re-attempted automatically.
|
// even if it's in a disconnected state. Those sessions are re-attempted automatically.
|
||||||
entries
|
entries
|
||||||
.filter(|(nid, _)| !self.sessions.contains_key(nid))
|
.filter(|(nid, _)| !self.sessions.contains_key(nid))
|
||||||
|
.filter(|(nid, _)| nid != &self.node_id())
|
||||||
.take(wanted)
|
.take(wanted)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
@ -1796,11 +1814,11 @@ mod gossip {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handshake<G: Signer, S: ReadStorage>(
|
pub fn handshake<G: Signer, S: ReadStorage>(
|
||||||
|
node: NodeAnnouncement,
|
||||||
now: Timestamp,
|
now: Timestamp,
|
||||||
storage: &S,
|
storage: &S,
|
||||||
signer: &G,
|
signer: &G,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
config: &Config,
|
|
||||||
) -> Vec<Message> {
|
) -> Vec<Message> {
|
||||||
let inventory = match storage.inventory() {
|
let inventory = match storage.inventory() {
|
||||||
Ok(i) => i,
|
Ok(i) => i,
|
||||||
|
|
@ -1812,44 +1830,15 @@ mod gossip {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut msgs = vec![
|
vec![
|
||||||
|
Message::node(node, signer),
|
||||||
Message::inventory(gossip::inventory(now, inventory), signer),
|
Message::inventory(gossip::inventory(now, inventory), signer),
|
||||||
Message::subscribe(
|
Message::subscribe(
|
||||||
filter,
|
filter,
|
||||||
now - SUBSCRIBE_BACKLOG_DELTA.as_millis() as u64,
|
now - SUBSCRIBE_BACKLOG_DELTA.as_millis() as u64,
|
||||||
Timestamp::MAX,
|
Timestamp::MAX,
|
||||||
),
|
),
|
||||||
];
|
]
|
||||||
if let Some(m) = gossip::node(now, config) {
|
|
||||||
msgs.push(Message::node(m, signer));
|
|
||||||
};
|
|
||||||
|
|
||||||
msgs
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn node(timestamp: Timestamp, config: &Config) -> Option<NodeAnnouncement> {
|
|
||||||
let features = node::Features::SEED;
|
|
||||||
let alias = config.alias();
|
|
||||||
let addresses: BoundedVec<_, ADDRESS_LIMIT> = config
|
|
||||||
.external_addresses
|
|
||||||
.clone()
|
|
||||||
.try_into()
|
|
||||||
.expect("external addresses are within the limit");
|
|
||||||
|
|
||||||
if addresses.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(
|
|
||||||
NodeAnnouncement {
|
|
||||||
features,
|
|
||||||
timestamp,
|
|
||||||
alias,
|
|
||||||
addresses,
|
|
||||||
nonce: 0,
|
|
||||||
}
|
|
||||||
.solve(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inventory(timestamp: Timestamp, inventory: Vec<Id>) -> InventoryAnnouncement {
|
pub fn inventory(timestamp: Timestamp, inventory: Vec<Id>) -> InventoryAnnouncement {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
use localtime::LocalDuration;
|
use localtime::LocalDuration;
|
||||||
|
|
||||||
|
use radicle::node;
|
||||||
use radicle::node::Address;
|
use radicle::node::Address;
|
||||||
|
|
||||||
|
use crate::bounded::BoundedVec;
|
||||||
|
use crate::service::message::{NodeAnnouncement, ADDRESS_LIMIT};
|
||||||
use crate::service::tracking::{Policy, Scope};
|
use crate::service::tracking::{Policy, Scope};
|
||||||
use crate::service::NodeId;
|
use crate::service::NodeId;
|
||||||
|
|
||||||
|
|
@ -88,13 +91,43 @@ impl Config {
|
||||||
self.connect.iter().any(|(i, _)| i == id)
|
self.connect.iter().any(|(i, _)| i == id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn features(&self) -> node::Features {
|
||||||
|
node::Features::SEED
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a node announcement matches this configuration.
|
||||||
|
pub fn matches(&self, other: &NodeAnnouncement) -> bool {
|
||||||
|
let ann = self.node(other.timestamp);
|
||||||
|
|
||||||
|
ann.features == other.features
|
||||||
|
&& ann.alias == other.alias
|
||||||
|
&& ann.addresses == other.addresses
|
||||||
|
}
|
||||||
|
|
||||||
pub fn alias(&self) -> [u8; 32] {
|
pub fn alias(&self) -> [u8; 32] {
|
||||||
let mut alias = [0u8; 32];
|
let mut alias = [0u8; 32];
|
||||||
|
|
||||||
if let Some(name) = &self.alias {
|
if let Some(name) = &self.alias {
|
||||||
alias[..name.len()].copy_from_slice(name.as_bytes());
|
alias[..name.len()].copy_from_slice(name.as_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
alias
|
alias
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn node(&self, timestamp: node::Timestamp) -> NodeAnnouncement {
|
||||||
|
let features = self.features();
|
||||||
|
let alias = self.alias();
|
||||||
|
let addresses: BoundedVec<_, ADDRESS_LIMIT> = self
|
||||||
|
.external_addresses
|
||||||
|
.clone()
|
||||||
|
.try_into()
|
||||||
|
.expect("external addresses are within the limit");
|
||||||
|
|
||||||
|
NodeAnnouncement {
|
||||||
|
features,
|
||||||
|
timestamp,
|
||||||
|
alias,
|
||||||
|
addresses,
|
||||||
|
nonce: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{fmt, io, mem};
|
use std::{fmt, io, mem, str};
|
||||||
|
|
||||||
use crate::crypto;
|
use crate::crypto;
|
||||||
use crate::crypto::Unverified;
|
use crate::crypto::Unverified;
|
||||||
|
|
@ -66,17 +66,22 @@ pub struct NodeAnnouncement {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeAnnouncement {
|
impl NodeAnnouncement {
|
||||||
/// Validate a node announcement message.
|
/// Calculate the amount of work that went into creating this announcement.
|
||||||
///
|
///
|
||||||
/// Checks that the proof-of-work is valid, by generating a single byte that
|
/// Proof-of-work uses the [`scrypt`] algorithm with the parameters in
|
||||||
/// must be zero.
|
/// [`Announcement::POW_PARAMS`]. The "work" is calculated by counting the number of leading
|
||||||
|
/// zero bits after running `scrypt` on a serialized [`NodeAnnouncement`] using
|
||||||
|
/// [`wire::serialize`].
|
||||||
///
|
///
|
||||||
/// `scrypt(encode(announcement)) == 0`
|
/// In other words, `work = leading-zeros(scrypt(serialize(announcement)))`.
|
||||||
///
|
///
|
||||||
pub fn validate(&self) -> bool {
|
/// Higher numbers mean higher difficulty. For each increase in work, difficulty is doubled.
|
||||||
|
/// For instance, an output of `7` is *four* times more work than an output of `5`.
|
||||||
|
///
|
||||||
|
pub fn work(&self) -> u32 {
|
||||||
let (n, r, p) = Announcement::POW_PARAMS;
|
let (n, r, p) = Announcement::POW_PARAMS;
|
||||||
let params = scrypt::Params::new(n, r, p).expect("proof-of-work parameters are valid");
|
let params = scrypt::Params::new(n, r, p).expect("proof-of-work parameters are valid");
|
||||||
let mut output = [0; 1];
|
let mut output = vec![0; 32];
|
||||||
|
|
||||||
scrypt::scrypt(
|
scrypt::scrypt(
|
||||||
wire::serialize(self).as_ref(),
|
wire::serialize(self).as_ref(),
|
||||||
|
|
@ -86,26 +91,37 @@ impl NodeAnnouncement {
|
||||||
)
|
)
|
||||||
.expect("proof-of-work output vector is a valid length");
|
.expect("proof-of-work output vector is a valid length");
|
||||||
|
|
||||||
output == [0]
|
// Calculate the number of leading zero bits in the output vector.
|
||||||
|
if let Some((zero_bytes, non_zero)) = output.iter().enumerate().find(|(_, &x)| x != 0) {
|
||||||
|
zero_bytes as u32 * 8 + non_zero.leading_zeros()
|
||||||
|
} else {
|
||||||
|
output.len() as u32 * 8
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Solve the proof-of-work of a node announcement by iterating through different nonces.
|
/// Solve the proof-of-work of a node announcement for the given target, by iterating through
|
||||||
pub fn solve(mut self) -> Self {
|
/// different nonces.
|
||||||
|
///
|
||||||
|
/// If the given difficulty target is too high, there may not be a result. In that case, `None`
|
||||||
|
/// is returned.
|
||||||
|
pub fn solve(mut self, target: u32) -> Option<Self> {
|
||||||
loop {
|
loop {
|
||||||
if let Some(nonce) = self.nonce.checked_add(1) {
|
if let Some(nonce) = self.nonce.checked_add(1) {
|
||||||
self.nonce = nonce;
|
self.nonce = nonce;
|
||||||
|
|
||||||
if self.validate() {
|
if self.work() >= target {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If a very high difficulty is chosen, it's possible to iterate through all
|
return None;
|
||||||
// possible values of the nonce without solving the puzzle. However, with "normal"
|
|
||||||
// values, this is virtually impossible.
|
|
||||||
panic!("could not solve proof-of-work!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self
|
Some(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the alias as a UTF-8 string.
|
||||||
|
pub fn alias(&self) -> Result<&str, std::str::Utf8Error> {
|
||||||
|
str::from_utf8(&self.alias)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,9 +323,9 @@ impl Announcement {
|
||||||
///
|
///
|
||||||
/// `15, 8, 1` are usually the recommended parameters.
|
/// `15, 8, 1` are usually the recommended parameters.
|
||||||
///
|
///
|
||||||
#[cfg(test)]
|
#[cfg(debug_assertions)]
|
||||||
pub const POW_PARAMS: (u8, u32, u32) = (1, 1, 1);
|
pub const POW_PARAMS: (u8, u32, u32) = (1, 1, 1);
|
||||||
#[cfg(not(test))]
|
#[cfg(not(debug_assertions))]
|
||||||
pub const POW_PARAMS: (u8, u32, u32) = (15, 8, 1);
|
pub const POW_PARAMS: (u8, u32, u32) = (15, 8, 1);
|
||||||
/// Salt used for generating PoW.
|
/// Salt used for generating PoW.
|
||||||
pub const POW_SALT: &[u8] = &[b'r', b'a', b'd'];
|
pub const POW_SALT: &[u8] = &[b'r', b'a', b'd'];
|
||||||
|
|
@ -608,7 +624,9 @@ mod tests {
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(!ann.validate());
|
assert_eq!(ann.work(), 0);
|
||||||
assert!(ann.solve().validate());
|
assert_eq!(ann.clone().solve(1).unwrap().work(), 4);
|
||||||
|
assert_eq!(ann.clone().solve(8).unwrap().work(), 9);
|
||||||
|
assert_eq!(ann.solve(14).unwrap().work(), 16);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,7 @@ where
|
||||||
// Make sure the peer address is advertized.
|
// Make sure the peer address is advertized.
|
||||||
config.config.external_addresses.push(local_addr.into());
|
config.config.external_addresses.push(local_addr.into());
|
||||||
|
|
||||||
|
let announcement = config.config.node(config.local_time.as_secs());
|
||||||
let emitter: Emitter<Event> = Default::default();
|
let emitter: Emitter<Event> = Default::default();
|
||||||
let service = Service::new(
|
let service = Service::new(
|
||||||
config.config,
|
config.config,
|
||||||
|
|
@ -172,6 +173,7 @@ where
|
||||||
tracking,
|
tracking,
|
||||||
config.signer,
|
config.signer,
|
||||||
config.rng.clone(),
|
config.rng.clone(),
|
||||||
|
announcement,
|
||||||
emitter,
|
emitter,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -213,6 +215,7 @@ where
|
||||||
&peer.node_id(),
|
&peer.node_id(),
|
||||||
radicle::node::Features::default(),
|
radicle::node::Features::default(),
|
||||||
peer.name,
|
peer.name,
|
||||||
|
0,
|
||||||
timestamp,
|
timestamp,
|
||||||
Some(known_address),
|
Some(known_address),
|
||||||
)
|
)
|
||||||
|
|
@ -266,7 +269,8 @@ where
|
||||||
addresses: Some(net::SocketAddr::from((self.ip, node::DEFAULT_PORT)).into()).into(),
|
addresses: Some(net::SocketAddr::from((self.ip, node::DEFAULT_PORT)).into()).into(),
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
}
|
}
|
||||||
.solve(),
|
.solve(0)
|
||||||
|
.unwrap(),
|
||||||
self.signer(),
|
self.signer(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,12 @@ pub const ROUTING_DB_FILE: &str = "routing.db";
|
||||||
pub const ADDRESS_DB_FILE: &str = "addresses.db";
|
pub const ADDRESS_DB_FILE: &str = "addresses.db";
|
||||||
/// Filename of tracking table database under the node directory.
|
/// Filename of tracking table database under the node directory.
|
||||||
pub const TRACKING_DB_FILE: &str = "tracking.db";
|
pub const TRACKING_DB_FILE: &str = "tracking.db";
|
||||||
|
/// Filename of last node announcement, when running in debug mode.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub const NODE_ANNOUNCEMENT_FILE: &str = "announcement.wire.debug";
|
||||||
|
/// Filename of last node announcement.
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
pub const NODE_ANNOUNCEMENT_FILE: &str = "announcement.wire";
|
||||||
|
|
||||||
/// Milliseconds since epoch.
|
/// Milliseconds since epoch.
|
||||||
pub type Timestamp = u64;
|
pub type Timestamp = u64;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ create table if not exists "nodes" (
|
||||||
"features" integer not null,
|
"features" integer not null,
|
||||||
-- Node alias.
|
-- Node alias.
|
||||||
"alias" text default null,
|
"alias" text default null,
|
||||||
|
--- Node announcement proof-of-work.
|
||||||
|
"pow" integer default 0,
|
||||||
-- Node announcement timestamp.
|
-- Node announcement timestamp.
|
||||||
"timestamp" integer not null
|
"timestamp" integer not null
|
||||||
--
|
--
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ impl Store for Book {
|
||||||
fn get(&self, node: &NodeId) -> Result<Option<types::Node>, Error> {
|
fn get(&self, node: &NodeId) -> Result<Option<types::Node>, Error> {
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.db
|
.db
|
||||||
.prepare("SELECT features, alias, timestamp FROM nodes WHERE id = ?")?;
|
.prepare("SELECT features, alias, pow, timestamp FROM nodes WHERE id = ?")?;
|
||||||
|
|
||||||
stmt.bind((1, node))?;
|
stmt.bind((1, node))?;
|
||||||
|
|
||||||
|
|
@ -79,6 +79,7 @@ impl Store for Book {
|
||||||
let features = row.read::<node::Features, _>("features");
|
let features = row.read::<node::Features, _>("features");
|
||||||
let alias = row.read::<&str, _>("alias").to_owned();
|
let alias = row.read::<&str, _>("alias").to_owned();
|
||||||
let timestamp = row.read::<i64, _>("timestamp") as Timestamp;
|
let timestamp = row.read::<i64, _>("timestamp") as Timestamp;
|
||||||
|
let pow = row.read::<i64, _>("pow") as u32;
|
||||||
let mut addrs = Vec::new();
|
let mut addrs = Vec::new();
|
||||||
|
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
|
|
@ -103,6 +104,7 @@ impl Store for Book {
|
||||||
Ok(Some(types::Node {
|
Ok(Some(types::Node {
|
||||||
features,
|
features,
|
||||||
alias,
|
alias,
|
||||||
|
pow,
|
||||||
timestamp,
|
timestamp,
|
||||||
addrs,
|
addrs,
|
||||||
}))
|
}))
|
||||||
|
|
@ -129,22 +131,24 @@ impl Store for Book {
|
||||||
node: &NodeId,
|
node: &NodeId,
|
||||||
features: node::Features,
|
features: node::Features,
|
||||||
alias: &str,
|
alias: &str,
|
||||||
|
pow: u32,
|
||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
addrs: impl IntoIterator<Item = KnownAddress>,
|
addrs: impl IntoIterator<Item = KnownAddress>,
|
||||||
) -> Result<bool, Error> {
|
) -> Result<bool, Error> {
|
||||||
transaction(&self.db, move |db| {
|
transaction(&self.db, move |db| {
|
||||||
let mut stmt = db.prepare(
|
let mut stmt = db.prepare(
|
||||||
"INSERT INTO nodes (id, features, alias, timestamp)
|
"INSERT INTO nodes (id, features, alias, pow, timestamp)
|
||||||
VALUES (?1, ?2, ?3, ?4)
|
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||||
ON CONFLICT DO UPDATE
|
ON CONFLICT DO UPDATE
|
||||||
SET features = ?2, alias = ?3, timestamp = ?4
|
SET features = ?2, alias = ?3, pow = ?4, timestamp = ?5
|
||||||
WHERE timestamp < ?4",
|
WHERE timestamp < ?5",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
stmt.bind((1, node))?;
|
stmt.bind((1, node))?;
|
||||||
stmt.bind((2, features))?;
|
stmt.bind((2, features))?;
|
||||||
stmt.bind((3, alias))?;
|
stmt.bind((3, alias))?;
|
||||||
stmt.bind((4, timestamp as i64))?;
|
stmt.bind((4, pow as i64))?;
|
||||||
|
stmt.bind((5, timestamp as i64))?;
|
||||||
stmt.next()?;
|
stmt.next()?;
|
||||||
|
|
||||||
for addr in addrs {
|
for addr in addrs {
|
||||||
|
|
@ -275,6 +279,7 @@ pub trait Store {
|
||||||
node: &NodeId,
|
node: &NodeId,
|
||||||
features: node::Features,
|
features: node::Features,
|
||||||
alias: &str,
|
alias: &str,
|
||||||
|
pow: u32,
|
||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
addrs: impl IntoIterator<Item = KnownAddress>,
|
addrs: impl IntoIterator<Item = KnownAddress>,
|
||||||
) -> Result<bool, Error>;
|
) -> Result<bool, Error>;
|
||||||
|
|
@ -405,13 +410,14 @@ mod test {
|
||||||
last_attempt: None,
|
last_attempt: None,
|
||||||
};
|
};
|
||||||
let inserted = cache
|
let inserted = cache
|
||||||
.insert(&alice, features, "alice", timestamp, [ka.clone()])
|
.insert(&alice, features, "alice", 16, timestamp, [ka.clone()])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(inserted);
|
assert!(inserted);
|
||||||
|
|
||||||
let node = cache.get(&alice).unwrap().unwrap();
|
let node = cache.get(&alice).unwrap().unwrap();
|
||||||
|
|
||||||
assert_eq!(node.features, features);
|
assert_eq!(node.features, features);
|
||||||
|
assert_eq!(node.pow, 16);
|
||||||
assert_eq!(node.timestamp, timestamp);
|
assert_eq!(node.timestamp, timestamp);
|
||||||
assert_eq!(node.alias.as_str(), "alice");
|
assert_eq!(node.alias.as_str(), "alice");
|
||||||
assert_eq!(node.addrs, vec![ka]);
|
assert_eq!(node.addrs, vec![ka]);
|
||||||
|
|
@ -431,12 +437,12 @@ mod test {
|
||||||
last_attempt: None,
|
last_attempt: None,
|
||||||
};
|
};
|
||||||
let inserted = cache
|
let inserted = cache
|
||||||
.insert(&alice, features, "alice", timestamp, [ka.clone()])
|
.insert(&alice, features, "alice", 0, timestamp, [ka.clone()])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(inserted);
|
assert!(inserted);
|
||||||
|
|
||||||
let inserted = cache
|
let inserted = cache
|
||||||
.insert(&alice, features, "alice", timestamp, [ka])
|
.insert(&alice, features, "alice", 0, timestamp, [ka])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!inserted);
|
assert!(!inserted);
|
||||||
|
|
||||||
|
|
@ -457,31 +463,39 @@ mod test {
|
||||||
};
|
};
|
||||||
|
|
||||||
let updated = cache
|
let updated = cache
|
||||||
.insert(&alice, features, "alice", timestamp, [ka.clone()])
|
.insert(&alice, features, "alice", 0, timestamp, [ka.clone()])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(updated);
|
assert!(updated);
|
||||||
|
|
||||||
let updated = cache
|
let updated = cache
|
||||||
.insert(&alice, features, "~alice~", timestamp, [])
|
.insert(&alice, features, "~alice~", 0, timestamp, [])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!updated, "Can't update using the same timestamp");
|
assert!(!updated, "Can't update using the same timestamp");
|
||||||
|
|
||||||
let updated = cache
|
let updated = cache
|
||||||
.insert(&alice, features, "~alice~", timestamp - 1, [])
|
.insert(&alice, features, "~alice~", 0, timestamp - 1, [])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!updated, "Can't update using a smaller timestamp");
|
assert!(!updated, "Can't update using a smaller timestamp");
|
||||||
|
|
||||||
let node = cache.get(&alice).unwrap().unwrap();
|
let node = cache.get(&alice).unwrap().unwrap();
|
||||||
assert_eq!(node.alias, "alice");
|
assert_eq!(node.alias, "alice");
|
||||||
assert_eq!(node.timestamp, timestamp);
|
assert_eq!(node.timestamp, timestamp);
|
||||||
|
assert_eq!(node.pow, 0);
|
||||||
|
|
||||||
let updated = cache
|
let updated = cache
|
||||||
.insert(&alice, features, "~alice~", timestamp + 1, [])
|
.insert(&alice, features, "~alice~", 0, timestamp + 1, [])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(updated, "Can update with a larger timestamp");
|
assert!(updated, "Can update with a larger timestamp");
|
||||||
|
|
||||||
let updated = cache
|
let updated = cache
|
||||||
.insert(&alice, node::Features::NONE, "~alice~", timestamp + 2, [])
|
.insert(
|
||||||
|
&alice,
|
||||||
|
node::Features::NONE,
|
||||||
|
"~alice~",
|
||||||
|
1,
|
||||||
|
timestamp + 2,
|
||||||
|
[],
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(updated);
|
assert!(updated);
|
||||||
|
|
||||||
|
|
@ -489,6 +503,7 @@ mod test {
|
||||||
assert_eq!(node.features, node::Features::NONE);
|
assert_eq!(node.features, node::Features::NONE);
|
||||||
assert_eq!(node.alias, "~alice~");
|
assert_eq!(node.alias, "~alice~");
|
||||||
assert_eq!(node.timestamp, timestamp + 2);
|
assert_eq!(node.timestamp, timestamp + 2);
|
||||||
|
assert_eq!(node.pow, 1);
|
||||||
assert_eq!(node.addrs, vec![ka]);
|
assert_eq!(node.addrs, vec![ka]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -512,10 +527,10 @@ mod test {
|
||||||
last_attempt: None,
|
last_attempt: None,
|
||||||
};
|
};
|
||||||
cache
|
cache
|
||||||
.insert(&alice, features, "alice", timestamp, [ka.clone()])
|
.insert(&alice, features, "alice", 0, timestamp, [ka.clone()])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
cache
|
cache
|
||||||
.insert(&bob, features, "bob", timestamp, [ka])
|
.insert(&bob, features, "bob", 0, timestamp, [ka])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
assert_eq!(cache.len().unwrap(), 6);
|
assert_eq!(cache.len().unwrap(), 6);
|
||||||
|
|
@ -550,7 +565,7 @@ mod test {
|
||||||
};
|
};
|
||||||
expected.push((id, ka.clone()));
|
expected.push((id, ka.clone()));
|
||||||
cache
|
cache
|
||||||
.insert(&id, features, "alias", timestamp, [ka])
|
.insert(&id, features, "alias", 0, timestamp, [ka])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ pub struct Node {
|
||||||
pub features: node::Features,
|
pub features: node::Features,
|
||||||
/// Advertized addresses
|
/// Advertized addresses
|
||||||
pub addrs: Vec<KnownAddress>,
|
pub addrs: Vec<KnownAddress>,
|
||||||
|
/// Proof-of-work included in node announcement.
|
||||||
|
pub pow: u32,
|
||||||
/// When this data was published.
|
/// When this data was published.
|
||||||
pub timestamp: Timestamp,
|
pub timestamp: Timestamp,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue