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:
parent
26e3c0fbff
commit
8a3133e137
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue