diff --git a/Cargo.lock b/Cargo.lock index af146b6bbf..804c13b25c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2919,16 +2919,18 @@ dependencies = [ [[package]] name = "iroh-gossip" version = "0.28.1" -source = "git+https://github.com/n0-computer/iroh-gossip?branch=main#ba0f6b0f54a740d8eae7ee6683f4aa1d8d8c8eb2" +source = "git+https://github.com/n0-computer/iroh-gossip?branch=main#7c90c3f351585e7a364bfbf3941d07592b20dec6" dependencies = [ "anyhow", "async-channel", "bytes", + "clap", "derive_more", "ed25519-dalek", "futures-concurrency", "futures-lite 2.4.0", "futures-util", + "hex", "indexmap 2.6.0", "iroh-base", "iroh-blake3", diff --git a/iroh-cli/Cargo.toml b/iroh-cli/Cargo.toml index 6fd4208499..b1d797440d 100644 --- a/iroh-cli/Cargo.toml +++ b/iroh-cli/Cargo.toml @@ -41,10 +41,10 @@ hex = "0.4.3" human-time = "0.1.6" indicatif = { version = "0.17", features = ["tokio"] } iroh = { version = "0.28.1", path = "../iroh", features = ["metrics"] } -iroh-gossip = "0.28.1" -iroh-docs = { version = "0.28.0", features = ["rpc", "cli"]} -iroh-metrics = { version = "0.28.0" } iroh-blobs = { version = "0.28.1", features = ["cli"] } +iroh-docs = { version = "0.28.0", features = ["cli"] } +iroh-gossip = { version = "0.28.1", features = ["cli"] } +iroh-metrics = { version = "0.28.0" } parking_lot = "0.12.1" pkarr = { version = "2.2.0", default-features = false } portable-atomic = "1" diff --git a/iroh-cli/src/commands.rs b/iroh-cli/src/commands.rs index c1b3be49c4..863c47cfcd 100644 --- a/iroh-cli/src/commands.rs +++ b/iroh-cli/src/commands.rs @@ -14,12 +14,12 @@ use crate::config::NodeConfig; pub(crate) mod console; pub(crate) mod doctor; -pub(crate) mod gossip; pub(crate) mod net; pub(crate) mod rpc; pub(crate) mod start; pub(crate) use iroh_blobs::{cli as blobs, cli::tags}; pub(crate) use iroh_docs::{cli as docs, cli::authors}; +pub(crate) use iroh_gossip::cli as gossip; /// iroh is a tool for building distributed apps. /// diff --git a/iroh-cli/src/commands/gossip.rs b/iroh-cli/src/commands/gossip.rs deleted file mode 100644 index a5f8fb964d..0000000000 --- a/iroh-cli/src/commands/gossip.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! Define the gossiping subcommands. - -use std::str::FromStr as _; - -use anyhow::{Context, Result}; -use bao_tree::blake3; -use clap::{ArgGroup, Subcommand}; -use futures_lite::StreamExt; -use futures_util::SinkExt; -use iroh::{client::Iroh, net::NodeId}; -use iroh_gossip::rpc::client::SubscribeOpts; -use tokio::io::AsyncBufReadExt; - -/// Commands to manage gossiping. -#[derive(Subcommand, Debug, Clone)] -#[allow(clippy::large_enum_variant)] -pub enum GossipCommands { - /// Subscribe to a gossip topic - #[command( - long_about = r#"Subscribe to a gossip topic - -Example usage: - - $ iroh gossip subscribe --topic test --start - -This will print the current node's id. Open another terminal -or another machine and you can join the same topic: - - # on another machine/terminal - $ iroh gossip subscribe --topic test --start - -Any lines entered in stdin will be sent to the given topic -and received messages will be printed to stdout line-by-line. - -The process waits for Ctrl+C to exit."#, - group( - ArgGroup::new("input") - .required(true) - .args(&["topic", "raw_topic"]) - ) - )] - Subscribe { - /// The topic to subscribe to. - /// - /// This will be hashed with BLAKE3 to get the actual topic ID. - #[clap(long)] - topic: Option, - /// The raw topic to subscribe to as hex. Needs to be 32 bytes, i.e. 64 hex characters. - #[clap(long)] - raw_topic: Option, - /// The set of nodes that are also part of the gossip swarm to bootstrap with. - /// - /// If empty, this will bootstrap a new swarm. Running the command will print - /// the node's `NodeId`, which can be used as the bootstrap argument in other nodes. - bootstrap: Vec, - /// If enabled, all gossip events will be printed, including neighbor up/down events. - #[clap(long, short)] - verbose: bool, - }, -} - -impl GossipCommands { - /// Runs the gossip command given the iroh client. - pub async fn run(self, iroh: &Iroh) -> Result<()> { - match self { - Self::Subscribe { - topic, - raw_topic, - bootstrap, - verbose, - } => { - let bootstrap = bootstrap - .into_iter() - .map(|node_id| NodeId::from_str(&node_id).map_err(|e| { - anyhow::anyhow!("Failed to parse bootstrap node id \"{node_id}\": {e}\nMust be a valid base32-encoded iroh node id.") - })) - .collect::>()?; - - let topic = match (topic, raw_topic) { - (Some(topic), None) => blake3::hash(topic.as_bytes()).into(), - (None, Some(raw_topic)) => { - let mut slice = [0; 32]; - hex::decode_to_slice(raw_topic, &mut slice) - .context("failed to decode raw topic")?; - slice.into() - } - _ => anyhow::bail!("either topic or raw_topic must be provided"), - }; - - let opts = SubscribeOpts { - bootstrap, - subscription_capacity: 1024, - }; - - let (mut sink, mut stream) = iroh.gossip().subscribe_with_opts(topic, opts).await?; - let mut input_lines = tokio::io::BufReader::new(tokio::io::stdin()).lines(); - loop { - tokio::select! { - line = input_lines.next_line() => { - let line = line.context("failed to read from stdin")?; - if let Some(line) = line { - sink.send(iroh_gossip::net::Command::Broadcast(line.into())).await?; - } else { - break; - } - } - res = stream.next() => { - let res = res.context("gossip stream ended")?.context("failed to read gossip stream")?; - match res { - iroh_gossip::net::Event::Gossip(event) => { - if verbose { - println!("{:?}", event); - } else if let iroh_gossip::net::GossipEvent::Received(iroh_gossip::net::Message { content, .. }) = event { - println!("{:?}", content); - } - } - iroh_gossip::net::Event::Lagged => { - anyhow::bail!("gossip stream lagged"); - } - }; - } - } - } - } - } - Ok(()) - } -} diff --git a/iroh-cli/src/commands/rpc.rs b/iroh-cli/src/commands/rpc.rs index 646a54a38b..c7c2782482 100644 --- a/iroh-cli/src/commands/rpc.rs +++ b/iroh-cli/src/commands/rpc.rs @@ -99,7 +99,7 @@ impl RpcCommands { Self::Docs { command } => command.run(&iroh.docs(), &iroh.blobs(), env).await, Self::Authors { command } => command.run(&iroh.authors(), env).await, Self::Tags { command } => command.run(&iroh.tags()).await, - Self::Gossip { command } => command.run(iroh).await, + Self::Gossip { command } => command.run(&iroh.gossip()).await, Self::Stats => { let stats = iroh.stats().await?; for (name, details) in stats.iter() {