node: Include addresses in `Command::Seeds` result

This will allow the CLI to connect to seeds on the go.

We also do some refactoring of some of the code involved in fulfilling
the `Seeds` command.
This commit is contained in:
Alexis Sellier 2023-07-24 12:56:53 +02:00
parent 26e3c0fbff
commit 8a3133e137
No known key found for this signature in database
6 changed files with 68 additions and 66 deletions

View File

@ -149,12 +149,12 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
fn announce(rid: Id, timeout: time::Duration, mut node: Node) -> anyhow::Result<()> {
let seeds = node.seeds(rid)?;
if !seeds.has_connections() {
let connected = seeds.connected().map(|s| s.nid).collect::<Vec<_>>();
if connected.is_empty() {
term::info!("Not connected to any seeds.");
return Ok(());
}
let connected = seeds.connected().cloned().collect::<Vec<_>>();
let mut spinner = term::spinner(format!("Syncing with {} node(s)..", connected.len()));
let result = node.announce(rid, connected, timeout, |event| match event {
node::AnnounceEvent::Announced => {}
@ -210,12 +210,10 @@ pub fn fetch_all(rid: Id, node: &mut Node) -> Result<FetchResults, node::Error>
let seeds = node.seeds(rid)?;
let mut results = FetchResults::default();
if seeds.has_connections() {
// Fetch from all seeds.
for seed in seeds.connected() {
let result = fetch_from(rid, seed, node)?;
results.push(*seed, result);
}
// Fetch from connected seeds.
for seed in seeds.connected() {
let result = fetch_from(rid, &seed.nid, node)?;
results.push(seed.nid, result);
}
Ok(results)
}

View File

@ -636,8 +636,9 @@ fn test_clone_without_seeds() {
let rid = alice.project("heartwood", "Radicle Heartwood Protocol & Stack");
let mut alice = alice.spawn();
let seeds = alice.handle.seeds(rid).unwrap();
let connected = seeds.connected().collect::<Vec<_>>();
assert!(!seeds.has_connections());
assert!(connected.is_empty());
alice
.rad("clone", &[rid.to_string().as_str()], working.as_path())

View File

@ -17,6 +17,7 @@ use crossbeam_channel as chan;
use fastrand::Rng;
use localtime::{LocalDuration, LocalTime};
use log::*;
use nonempty::NonEmpty;
use radicle::node::address;
use radicle::node::address::{AddressBook, KnownAddress};
@ -502,10 +503,11 @@ where
}
Command::Seeds(rid, resp) => match self.seeds(&rid) {
Ok(seeds) => {
let (connected, disconnected) = seeds.partition();
debug!(
target: "service",
"Found {} connected seed(s) and {} disconnected seed(s) for {}",
seeds.connected().count(), seeds.disconnected().count(), rid
connected.len(), disconnected.len(), rid
);
resp.send(seeds).ok();
}
@ -1322,28 +1324,25 @@ where
}
fn seeds(&self, rid: &Id) -> Result<Seeds, Error> {
#[derive(Default)]
pub struct Stats {
connected: usize,
disconnected: usize,
}
let seeds = match self.routing.get(rid) {
Ok(seeds) => seeds.into_iter().fold(Seeds::default(), |mut seeds, node| {
if node != self.node_id() {
let addrs: Vec<KnownAddress> = self
.addresses
.get(&node)
.ok()
.flatten()
.map(|n| n.addrs)
.unwrap_or(vec![]);
let (_, seeds) = match self.routing.get(rid) {
Ok(seeds) => seeds.into_iter().fold(
(Stats::default(), Seeds::default()),
|(mut stats, mut seeds), node| {
if node != self.node_id() {
if self.sessions.is_connected(&node) {
seeds.insert(Seed::Connected(node));
stats.connected += 1;
} else if self.sessions.is_disconnected(&node) {
seeds.insert(Seed::Disconnected(node));
stats.disconnected += 1;
}
if let Some(s) = self.sessions.get(&node) {
seeds.insert(Seed::new(node, addrs, Some(s.state.clone())));
} else {
seeds.insert(Seed::new(node, addrs, None));
}
(stats, seeds)
},
),
}
seeds
}),
Err(err) => {
return Err(Error::Routing(err));
}
@ -1464,9 +1463,9 @@ where
for rid in missing {
match self.seeds(&rid) {
Ok(seeds) => {
if seeds.has_connections() {
for seed in seeds.connected() {
self.fetch(rid, seed);
if let Some(connected) = NonEmpty::from_vec(seeds.connected().collect()) {
for seed in connected {
self.fetch(rid, &seed.nid);
}
} else {
// TODO: We should make sure that this fetch is retried later, either

View File

@ -509,7 +509,7 @@ fn push_ref(
/// Sync with the network.
fn sync(rid: Id, mut node: radicle::Node) -> Result<(), radicle::node::Error> {
let seeds = node.seeds(rid)?;
let connected = seeds.connected().cloned().collect::<Vec<_>>();
let connected = seeds.connected().map(|s| s.nid).collect::<Vec<_>>();
if connected.is_empty() {
eprintln!("Not connected to any seeds.");

View File

@ -6,7 +6,7 @@ pub mod events;
pub mod routing;
pub mod tracking;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::io::{BufRead, BufReader};
use std::ops::Deref;
use std::os::unix::net::UnixStream;
@ -25,6 +25,7 @@ use crate::crypto::PublicKey;
use crate::identity::Id;
use crate::storage::RefUpdate;
pub use address::KnownAddress;
pub use config::Config;
pub use cyphernet::addr::PeerAddr;
pub use events::{Event, Events};
@ -359,49 +360,50 @@ pub struct Session {
pub state: State,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
#[serde(tag = "state", content = "id")]
pub enum Seed {
Disconnected(NodeId),
Connected(NodeId),
pub struct Seed {
pub nid: NodeId,
pub addrs: Vec<KnownAddress>,
pub state: Option<State>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Seeds(BTreeSet<Seed>);
impl Seed {
/// Check if this is a "connected" seed.
pub fn is_connected(&self) -> bool {
matches!(self.state, Some(State::Connected { .. }))
}
pub fn new(nid: NodeId, addrs: Vec<KnownAddress>, state: Option<State>) -> Self {
Self { nid, addrs, state }
}
}
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Seeds(BTreeMap<NodeId, Seed>);
impl Seeds {
pub fn insert(&mut self, seed: Seed) {
self.0.insert(seed);
self.0.insert(seed.nid, seed);
}
pub fn connected(&self) -> impl Iterator<Item = &NodeId> {
self.0.iter().filter_map(|s| match s {
Seed::Connected(node) => Some(node),
Seed::Disconnected(_) => None,
})
/// Partitions the list of seeds into connected and disconnected seeds.
/// Note that the disconnected seeds may be in a "connecting" state.
pub fn partition(&self) -> (Vec<Seed>, Vec<Seed>) {
self.0.values().cloned().partition(|s| s.is_connected())
}
pub fn disconnected(&self) -> impl Iterator<Item = &NodeId> {
self.0.iter().filter_map(|s| match s {
Seed::Disconnected(node) => Some(node),
Seed::Connected(_) => None,
})
/// Return connected seeds.
pub fn connected(&self) -> impl Iterator<Item = &Seed> {
self.0.values().filter(|s| s.is_connected())
}
pub fn has_connections(&self) -> bool {
self.0.iter().any(|s| match s {
Seed::Connected(_) => true,
Seed::Disconnected(_) => false,
})
pub fn iter(&self) -> impl Iterator<Item = &Seed> {
self.0.values()
}
pub fn is_connected(&self, node: &NodeId) -> bool {
self.0.contains(&Seed::Connected(*node))
}
pub fn is_disconnected(&self, node: &NodeId) -> bool {
self.0.contains(&Seed::Disconnected(*node))
pub fn is_connected(&self, nid: &NodeId) -> bool {
self.0.get(nid).map_or(false, |s| s.is_connected())
}
}

View File

@ -90,7 +90,8 @@ pub struct Node {
}
/// A known address.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct KnownAddress {
/// Network address.
pub addr: Address,
@ -115,7 +116,8 @@ impl KnownAddress {
}
/// Address source. Specifies where an address originated from.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum Source {
/// An address that was shared by another peer.
Peer,