diff --git a/Cargo.lock b/Cargo.lock index 0a42e22dc..e8678e11c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2471,9 +2471,13 @@ dependencies = [ "anyhow", "async-trait", "cid", + "clap", "futures", + "home", + "lazy_static", "libipld-cbor", "libp2p", + "noosphere", "noosphere-core", "noosphere-storage", "rand 0.8.5", diff --git a/rust/noosphere-ns/Cargo.toml b/rust/noosphere-ns/Cargo.toml index 96ec420da..9a6e59100 100644 --- a/rust/noosphere-ns/Cargo.toml +++ b/rust/noosphere-ns/Cargo.toml @@ -26,6 +26,7 @@ readme = "README.md" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] anyhow = "^1" tracing = "0.1" +lazy_static = "^1" cid = "~0.8" serde = "^1" serde_json = "^1" @@ -37,9 +38,21 @@ tokio = { version = "1.15", features = ["io-util", "io-std", "sync", "macros", " libp2p = { version = "0.49.0", default-features = false, features = [ "identify", "dns", "tcp", "tokio", "noise", "mplex", "yamux", "kad" ] } noosphere-storage = { version = "0.1.0", path = "../noosphere-storage" } noosphere-core = { version = "0.1.0", path = "../noosphere-core" } +noosphere = { version = "0.1.0-alpha.1", path = "../noosphere", optional = true } +clap = { version = "^4", features = ["derive"], optional = true } +home = { version = "~0.5", optional = true } [dev-dependencies] rand = { version = "0.8.5" } test-log = { version = "0.2.11", default-features = false, features = ["trace"] } tracing-subscriber = { version = "~0.3", features = ["env-filter"] } libipld-cbor = "~0.14" + +[features] +bootstrap = ["clap", "noosphere", "home"] + +[[bin]] +name = "bootstrap" +test = false +bench = false +required-features = ["bootstrap"] diff --git a/rust/noosphere-ns/README.md b/rust/noosphere-ns/README.md index 218af6555..9113db604 100644 --- a/rust/noosphere-ns/README.md +++ b/rust/noosphere-ns/README.md @@ -3,3 +3,9 @@ # noosphere-ns Noosphere's P2P name system. + +## Running a bootstrap node + +``` +cargo run --bin bootstrap --features="bootstrap" +``` \ No newline at end of file diff --git a/rust/noosphere-ns/src/bin/bootstrap/main.rs b/rust/noosphere-ns/src/bin/bootstrap/main.rs new file mode 100644 index 000000000..9cc0bd81b --- /dev/null +++ b/rust/noosphere-ns/src/bin/bootstrap/main.rs @@ -0,0 +1,90 @@ +#![cfg(not(target_arch = "wasm32"))] + +use anyhow::{anyhow, Result}; +use clap::Parser; +use home; +use noosphere::key::{InsecureKeyStorage, KeyStorage}; +use noosphere_ns::{ + dht::{DHTConfig, DHTNode}, + Multiaddr, Validator, BOOTSTRAP_PEERS, +}; +use noosphere_storage::{db::SphereDb, memory::MemoryStorageProvider}; +use std::path::PathBuf; +use tokio::{self, signal}; +use ucan_key_support::ed25519::Ed25519KeyMaterial; + +#[derive(Parser)] +#[clap(name = "bootstrap")] +pub struct CLI { + /// The name of the Noosphere keypair to use stored in `~/.noosphere/keys/`. + #[clap(short, long)] + key: String, + + /// The listening port of this DHT node. + #[clap(short, long)] + port: Option, +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = CLI::parse(); + let key_storage = InsecureKeyStorage::new(&get_keys_dir()?)?; + let key_material: Ed25519KeyMaterial = { + if let Some(km) = key_storage.read_key(&args.key).await?.take() { + km + } else { + println!("Creating key \"{}\" in `~/.noosphere/keys/`.", &args.key); + key_storage.create_key(&args.key).await? + } + }; + let listening_address: Option = args + .port + .or_else(|| Some(0)) + .map(|port| { + format!("/ip4/127.0.0.1/tcp/{}", port) + .parse() + .expect("parseable") + }) + .take(); + + run_bootstrap_node(&key_material, listening_address).await?; + + Ok(()) +} + +fn get_keys_dir() -> Result { + Ok(home::home_dir() + .ok_or_else(|| anyhow!("Could not discover home directory."))? + .join(".noosphere")) +} + +fn filter_bootstrap_peers(self_address: &Multiaddr, peers: &[Multiaddr]) -> Vec { + peers.iter().filter(|addr| *addr != self_address).collect() +} + +async fn run_bootstrap_node( + key_material: &Ed25519KeyMaterial, + listening_address: Option, +) -> Result<()> { + let store = SphereDb::new(&MemoryStorageProvider::default()).await?; + let config = DHTConfig { + listening_address, + ..Default::default() + }; + let mut node = DHTNode::new(key_material, None, Validator::new(&store), &config)?; + let listening_address = node.p2p_address().expect("has address").to_owned(); + let bootstrap_peers = filter_bootstrap_peers(&listening_address, BOOTSTRAP_PEERS); + + println!("Using bootstrap peers at {:#?}", &bootstrap_peers); + node.add_peers(&bootstrap_peers)?; + node.run()?; + println!("Listening on {}...", listening_address); + + println!("Bootstrapping..."); + node.bootstrap().await?; + println!("Bootstrapped."); + + signal::ctrl_c().await?; + node.terminate()?; + Ok(()) +} diff --git a/rust/noosphere-ns/src/lib.rs b/rust/noosphere-ns/src/lib.rs index da3b06cf9..3d4add4ce 100644 --- a/rust/noosphere-ns/src/lib.rs +++ b/rust/noosphere-ns/src/lib.rs @@ -3,6 +3,9 @@ #[macro_use] extern crate tracing; +#[macro_use] +extern crate lazy_static; + mod builder; pub mod dht; mod name_system; @@ -11,6 +14,7 @@ pub mod utils; mod validator; pub use builder::NameSystemBuilder; -pub use libp2p::multiaddr; -pub use name_system::NameSystem; +pub use libp2p::multiaddr::Multiaddr; +pub use name_system::{NameSystem, BOOTSTRAP_PEERS}; pub use records::NSRecord; +pub use validator::Validator; diff --git a/rust/noosphere-ns/src/name_system.rs b/rust/noosphere-ns/src/name_system.rs index 74d2bb9a7..f5190ca6a 100644 --- a/rust/noosphere-ns/src/name_system.rs +++ b/rust/noosphere-ns/src/name_system.rs @@ -17,6 +17,15 @@ use crate::NameSystemBuilder; #[cfg(doc)] use cid::Cid; +pub static BOOTSTRAP_PEERS_ADDRESSES: [&str; 1] = + ["/ip4/134.122.20.28/tcp/6666/p2p/12D3KooWAKxaCWsSGauqhCZXyeYjDSQ6jna2SmVhLn4J7uQFdvot"]; + +lazy_static! { + /// Noosphere Name System's maintained list of peers to + /// bootstrap nodes joining the network. + pub static ref BOOTSTRAP_PEERS: [Multiaddr; 1] = BOOTSTRAP_PEERS_ADDRESSES.map(|addr| addr.parse().expect("parseable")); +} + /// The [NameSystem] is responsible for both propagating and resolving Sphere DIDs /// into an authorized UCAN publish token, resolving into a [Cid] address for /// a sphere's content. These records are propagated and resolved via the @@ -254,3 +263,15 @@ where } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn bootstrap_peers_parseable() { + // Force getting the lazy static ensuring the addresses + // are valid Multiaddrs. + assert_eq!(BOOTSTRAP_PEERS.len(), 1); + } +}