diff --git a/radicle-cli/src/commands/node.rs b/radicle-cli/src/commands/node.rs index 7dd0def4..8d8cda0f 100644 --- a/radicle-cli/src/commands/node.rs +++ b/radicle-cli/src/commands/node.rs @@ -1,7 +1,7 @@ use std::ffi::OsString; use anyhow::anyhow; -use radicle::node::{Address, Node, NodeId, TRACKING_DB_FILE}; +use radicle::node::{Address, Node, NodeId, ROUTING_DB_FILE, TRACKING_DB_FILE}; use crate::terminal as term; use crate::terminal::args::{Args, Error, Help}; @@ -139,8 +139,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { control::connect(&mut node, nid, addr)? } Operation::Routing => { - let node = Node::new(profile.socket()); - routing::run(&node)?; + let store = + radicle::node::routing::Table::reader(profile.home.node().join(ROUTING_DB_FILE))?; + routing::run(&store)?; } Operation::Start => control::start()?, Operation::Status => { diff --git a/radicle-cli/src/commands/node/routing.rs b/radicle-cli/src/commands/node/routing.rs index 221356db..8f6b2bce 100644 --- a/radicle-cli/src/commands/node/routing.rs +++ b/radicle-cli/src/commands/node/routing.rs @@ -1,12 +1,12 @@ -use radicle::{node::Handle as _, Node}; +use radicle::node; use crate::terminal as term; -pub fn run(node: &Node) -> anyhow::Result<()> { +pub fn run(routing: &S) -> anyhow::Result<()> { let mut t = term::Table::new(term::table::TableOptions::default()); t.push(["RID", "NID"]); t.push(["---", "---"]); - for (id, node) in node.routing()? { + for (id, node) in routing.entries()? { t.push([ term::format::highlight(id).to_string(), term::format::node(&node), diff --git a/radicle-node/src/control.rs b/radicle-node/src/control.rs index 6be429af..54f1dbcd 100644 --- a/radicle-node/src/control.rs +++ b/radicle-node/src/control.rs @@ -187,14 +187,6 @@ fn command>( CommandName::Status => { CommandResult::ok().to_writer(writer).ok(); } - CommandName::Routing => match handle.routing() { - Ok(c) => { - for route in c.into_iter() { - serde_json::to_writer(&mut writer, &route)?; - } - } - Err(e) => return Err(CommandError::Runtime(e)), - }, CommandName::Inventory => match handle.inventory() { Ok(c) => { for id in c.iter() { diff --git a/radicle-node/src/runtime/handle.rs b/radicle-node/src/runtime/handle.rs index 4f58f5f0..69648618 100644 --- a/radicle-node/src/runtime/handle.rs +++ b/radicle-node/src/runtime/handle.rs @@ -109,7 +109,6 @@ impl Handle { impl radicle::node::Handle for Handle { type Sessions = Sessions; type Error = Error; - type Routing = chan::Receiver<(Id, NodeId)>; fn is_running(&self) -> bool { true @@ -171,23 +170,6 @@ impl radicle::node::Handle for Handle { receiver.recv().map_err(Error::from) } - fn routing(&self) -> Result { - let (sender, receiver) = chan::unbounded(); - let query: Arc = Arc::new(move |state| { - for (id, node) in state.routing().entries()? { - if sender.send((id, node)).is_err() { - break; - } - } - Ok(()) - }); - let (err_sender, err_receiver) = chan::bounded(1); - self.command(service::Command::QueryState(query, err_sender))?; - err_receiver.recv()??; - - Ok(receiver) - } - fn sessions(&self) -> Result { let (sender, receiver) = chan::unbounded(); let query: Arc = Arc::new(move |state| { diff --git a/radicle-node/src/test/environment.rs b/radicle-node/src/test/environment.rs index 9c93fab8..12ccb322 100644 --- a/radicle-node/src/test/environment.rs +++ b/radicle-node/src/test/environment.rs @@ -17,6 +17,7 @@ use radicle::crypto::{KeyPair, Seed, Signer}; use radicle::git; use radicle::git::refname; use radicle::identity::Id; +use radicle::node::routing::Store; use radicle::node::Handle as _; use radicle::profile::Home; use radicle::profile::Profile; @@ -156,6 +157,14 @@ impl NodeHandle { self } + /// Get routing table entries. + pub fn routing(&self) -> impl Iterator { + radicle::node::routing::Table::reader(self.home.node().join(radicle::node::ROUTING_DB_FILE)) + .unwrap() + .entries() + .unwrap() + } + /// Wait until this node's routing table matches the remotes. pub fn converge<'a>( &'a self, @@ -176,7 +185,7 @@ impl NodeHandle { let mut remaining: BTreeSet<_> = routes.iter().collect(); - for (rid, nid) in self.handle.routing().unwrap() { + for (rid, nid) in self.routing() { if !remaining.remove(&(rid, nid)) { panic!( "Node::routes_to: unexpected route for {}: ({rid}, {nid})", @@ -347,7 +356,7 @@ pub fn converge<'a, G: Signer + cyphernet::Ecdh + 'static>( // First build the set of all routes. for node in &nodes { // Routes from the routing table. - for (rid, seed_id) in node.handle.routing().unwrap() { + for (rid, seed_id) in node.routing() { all_routes.insert((rid, seed_id)); } // Routes from the local inventory. @@ -360,8 +369,8 @@ pub fn converge<'a, G: Signer + cyphernet::Ecdh + 'static>( // its routing table has all routes. If so, remove it from the remaining nodes. while !remaining.is_empty() { remaining.retain(|_, node| { - let routing = node.handle.routing().unwrap(); - let routes = BTreeSet::from_iter(routing.try_iter()); + let routing = node.routing(); + let routes = BTreeSet::from_iter(routing); if routes == all_routes { log::debug!(target: "test", "Node {} has converged", node.id); diff --git a/radicle-node/src/test/handle.rs b/radicle-node/src/test/handle.rs index f2e6067c..31391a9f 100644 --- a/radicle-node/src/test/handle.rs +++ b/radicle-node/src/test/handle.rs @@ -20,7 +20,6 @@ pub struct Handle { impl radicle::node::Handle for Handle { type Error = HandleError; type Sessions = service::Sessions; - type Routing = Vec<(Id, NodeId)>; fn is_running(&self) -> bool { true @@ -68,10 +67,6 @@ impl radicle::node::Handle for Handle { unimplemented!() } - fn routing(&self) -> Result { - unimplemented!(); - } - fn sessions(&self) -> Result { unimplemented!(); } diff --git a/radicle/src/node.rs b/radicle/src/node.rs index e4bf1870..eb75f16c 100644 --- a/radicle/src/node.rs +++ b/radicle/src/node.rs @@ -141,8 +141,6 @@ pub enum CommandName { UntrackNode, /// Get the node's inventory. Inventory, - /// Get the node's routing table. - Routing, /// Get the node's status. Status, /// Shutdown the node. @@ -385,8 +383,6 @@ pub trait Handle { /// The error returned by all methods. type Error: std::error::Error + Send + Sync + 'static; - type Routing: IntoIterator; - /// Check if the node is running. to a peer. fn is_running(&self) -> bool; /// Connect to a peer. @@ -412,8 +408,6 @@ pub trait Handle { fn sync_inventory(&mut self) -> Result; /// Ask the service to shutdown. fn shutdown(self) -> Result<(), Self::Error>; - /// Query the routing table entries. - fn routing(&self) -> Result; /// Query the peer session state. fn sessions(&self) -> Result; /// Query the inventory. @@ -464,7 +458,6 @@ impl Node { impl Handle for Node { type Sessions = (); type Error = Error; - type Routing = Vec<(Id, NodeId)>; fn is_running(&self) -> bool { let Ok(mut lines) = self.call::<&str, CommandResult>(CommandName::Status, []) else { @@ -573,15 +566,6 @@ impl Handle for Node { response.into() } - fn routing(&self) -> Result { - let mut routes = Vec::new(); - for result in self.call::<&str, _>(CommandName::Routing, [])? { - let route = result?; - routes.push(route); - } - Ok(routes) - } - fn sessions(&self) -> Result { todo!(); } diff --git a/radicle/src/node/routing.rs b/radicle/src/node/routing.rs index 47e7f982..2eec8066 100644 --- a/radicle/src/node/routing.rs +++ b/radicle/src/node/routing.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use std::fmt; use std::path::Path; +use std::{fmt, time}; use sqlite as sql; use thiserror::Error; @@ -10,6 +10,9 @@ use crate::{ prelude::{Id, NodeId}, }; +/// How long to wait for the database lock to be released before failing a read. +const DB_READ_TIMEOUT: time::Duration = time::Duration::from_secs(1); + /// An error occuring in peer-to-peer networking code. #[derive(Error, Debug)] pub enum Error { @@ -44,6 +47,17 @@ impl Table { Ok(Self { db }) } + /// Same as [`Self::open`], but in read-only mode. This is useful to have multiple + /// open databases, as no locking is required. + pub fn reader>(path: P) -> Result { + let mut db = + sql::Connection::open_with_flags(path, sqlite::OpenFlags::new().set_read_only())?; + db.set_busy_timeout(DB_READ_TIMEOUT.as_millis() as usize)?; + db.execute(Self::SCHEMA)?; + + Ok(Self { db }) + } + /// Create a new in-memory routing table. pub fn memory() -> Result { let db = sql::Connection::open(":memory:")?;