Implement server and node ping command

This commit is contained in:
fluo10 2025-06-02 12:02:04 +09:00
parent 1a99cfd1a2
commit 886ad9c1a6
8 changed files with 140 additions and 87 deletions

View file

@ -0,0 +1,30 @@
use std::path::PathBuf;
use clap::Args;
use libp2p::identity;
use crate::{config::{NodeConfig, RawNodeConfig}, error::Error, global::{DEFAULT_CONFIG_FILE_PATH, DEFAULT_RAW_NODE_CONFIG}};
#[derive(Args, Clone, Debug)]
pub struct ConfigArgs {
#[arg(long)]
pub config: Option<PathBuf>,
#[command(flatten)]
pub node_config: RawNodeConfig,
}
impl ConfigArgs {
pub fn get_config_path_or_default(&self) -> PathBuf {
if let Some(x) = self.config.as_ref() {
x.clone()
} else {
DEFAULT_CONFIG_FILE_PATH.to_path_buf()
}
}
pub async fn try_into_raw_node_config(self) -> Result<RawNodeConfig, Error> {
Ok(RawNodeConfig::read_from(self.get_config_path_or_default()).await? + self.node_config)
}
pub async fn try_into_node_config(self) -> Result<NodeConfig, Error> {
Ok((DEFAULT_RAW_NODE_CONFIG.clone() + self.try_into_raw_node_config().await?).try_into()?)
}
}

View file

@ -1,7 +1,11 @@
use std::path::PathBuf; use std::path::PathBuf;
mod config;
mod init; mod init;
mod node; mod node;
mod server;
pub use config::ConfigArgs;
pub use init::InitArgs; pub use init::InitArgs;
pub use node::{ NodeArgs, NodeCommand, JoinNodeArgs }; pub use node::{ NodeArgs, NodeCommand, JoinNodeArgs };
pub use server::ServerArgs;

View file

@ -7,7 +7,9 @@ use libp2p::{
}; };
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use crate::error::Error; use crate::{cli::ServerArgs, error::Error};
use super::ConfigArgs;
#[derive(Args, Debug)] #[derive(Args, Debug)]
pub struct NodeArgs { pub struct NodeArgs {
@ -18,13 +20,13 @@ pub struct NodeArgs {
#[derive(Args, Debug)] #[derive(Args, Debug)]
pub struct JoinNodeArgs { pub struct JoinNodeArgs {
#[arg(long)] #[arg(long)]
pub endpoint: IpAddr, pub peer_ip: IpAddr,
#[arg(long)] #[arg(long)]
pub port: u16, pub peer_port: u16,
#[arg(long)] //#[arg(long)]
pub peer_id: String, //pub peer_id: String,
#[arg(long)] #[command(flatten)]
pub config: Option<PathBuf>, pub config: ConfigArgs,
} }
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
@ -36,28 +38,14 @@ pub enum NodeCommand {
impl JoinNodeArgs { impl JoinNodeArgs {
pub async fn ping(self) -> Result<(), Error> { pub async fn ping(self) -> Result<(), Error> {
let _ = tracing_subscriber::fmt() let mut swarm = self.config.try_into_node_config().await?.try_into_swarm().await?;
.with_env_filter(EnvFilter::from_default_env())
.try_init();
let mut swarm = libp2p::SwarmBuilder::with_new_identity()
.with_tokio()
.with_tcp(
tcp::Config::default(),
noise::Config::new,
yamux::Config::default,
)?
.with_behaviour(|_| ping::Behaviour::default())?
.build();
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
let mut remote: Multiaddr = Multiaddr::empty(); let mut remote: Multiaddr = Multiaddr::empty();
remote.push(match self.endpoint { remote.push(match self.peer_ip {
IpAddr::V4(x) => Protocol::Ip4(x), IpAddr::V4(x) => Protocol::Ip4(x),
IpAddr::V6(x) => Protocol::Ip6(x), IpAddr::V6(x) => Protocol::Ip6(x),
}); });
remote.push(Protocol::Tcp(self.port)); remote.push(Protocol::Tcp(self.peer_port));
let addr = remote.to_string(); let addr = remote.to_string();
swarm.dial(remote)?; swarm.dial(remote)?;
println!("Dialed {addr}"); println!("Dialed {addr}");

View file

@ -0,0 +1,26 @@
use clap::Args;
use futures::StreamExt;
use libp2p::{noise, ping, swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, Swarm};
use tracing_subscriber::EnvFilter;
use crate::error::Error;
use super::ConfigArgs;
#[derive(Args, Debug)]
pub struct ServerArgs {
#[command(flatten)]
config: ConfigArgs,
}
impl ServerArgs {
pub async fn start_server(self) -> Result<(), Error>{
let mut swarm = self.config.try_into_node_config().await?.try_into_swarm().await?;
loop{
match swarm.select_next_some().await {
SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {address:?}"),
SwarmEvent::Behaviour(event) => println!("{event:?}"),
_ => {}
}
}
}
}

View file

@ -14,43 +14,4 @@ pub struct PartialConfig {
node: Option<NodeConfig>, node: Option<NodeConfig>,
} }
impl PartialConfig {
pub fn new() -> Self {
PartialConfig {
node: Some(NodeConfig::default()),
}
}
pub async fn read_or_create<T>(path: T) -> Result<Self, Error>
where
T: AsRef<Path>
{
if !path.as_ref().exists() {
Self::new().write_to(&path).await?;
}
Self::read_from(&path).await
}
pub async fn read_from<T>(path:T) -> Result<Self, Error>
where
T: AsRef<Path>
{
let mut file = File::open(path.as_ref()).await?;
let mut content = String::new();
file.read_to_string(&mut content).await?;
let config: PartialConfig = toml::from_str(&content)?;
Ok(config)
}
pub async fn write_to<T>(&self, path:T) -> Result<(), Error>
where
T: AsRef<Path>
{
if !path.as_ref().exists() {
let _ = File::create(&path).await?;
}
let mut file = File::open(&path).await?;
file.write_all(toml::to_string(self)?.as_bytes()).await?;
Ok(())
}
}

View file

@ -1,13 +1,14 @@
use std::{net::IpAddr, path::{Path, PathBuf}}; use std::{net::IpAddr, ops, path::{Path, PathBuf}};
use base64::{prelude::BASE64_STANDARD, Engine}; use base64::{prelude::BASE64_STANDARD, Engine};
use clap::Args; use clap::Args;
use libp2p::identity::{self, DecodingError, Keypair}; use libp2p::{identity::{self, DecodingError, Keypair}, noise, ping, tcp, yamux, Swarm};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}}; use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}};
use tracing_subscriber::EnvFilter;
use crate::{error::Error, global::DEFAULT_DATABASE_FILE_PATH}; use crate::{cli::ConfigArgs, error::Error, global::DEFAULT_DATABASE_FILE_PATH};
use super::{PartialConfig, DEFAULT_LISTEN_IPS, DEFAULT_PORT}; use super::{PartialConfig, DEFAULT_LISTEN_IPS, DEFAULT_PORT};
@ -20,26 +21,33 @@ fn keypair_to_base64(keypair: &Keypair) -> Result<String, Error> {
fn base64_to_keypair(base64: &str) -> Result<Keypair, Error> { fn base64_to_keypair(base64: &str) -> Result<Keypair, Error> {
let vec = BASE64_STANDARD.decode(base64)?; let vec = BASE64_STANDARD.decode(base64)?;
Ok(Keypair::from_protobuf_encoding(&vec)?) Ok(Keypair::from_protobuf_encoding(&vec)?)
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct NodeConfig { pub struct NodeConfig {
#[serde(with = "keypair_parser")] #[serde(with = "keypair_parser")]
secret: Keypair, pub secret: Keypair,
database_path: PathBuf, pub database_path: PathBuf,
listen_ips: Vec<IpAddr>, pub listen_ips: Vec<IpAddr>,
port: u16, pub port: u16,
} }
impl Default for NodeConfig { impl NodeConfig {
fn default() -> NodeConfig{ pub async fn try_into_swarm (self) -> Result<Swarm<ping::Behaviour>, Error> {
NodeConfig { let _ = tracing_subscriber::fmt()
secret: identity::Keypair::generate_ed25519(), .with_env_filter(EnvFilter::from_default_env())
database_path: DEFAULT_DATABASE_FILE_PATH.to_path_buf(), .try_init();
listen_ips: DEFAULT_LISTEN_IPS.to_vec(), let mut swarm = libp2p::SwarmBuilder::with_existing_identity(self.secret)
port: DEFAULT_PORT, .with_tokio()
} .with_tcp(
tcp::Config::default(),
noise::Config::new,
yamux::Config::default,
)?
.with_behaviour(|_| ping::Behaviour::default())?
.build();
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
Ok(swarm)
} }
} }
@ -78,16 +86,16 @@ mod keypair_parser {
} }
} }
#[derive(Args, Debug, Deserialize, Serialize)] #[derive(Args, Clone, Debug, Deserialize, Serialize)]
pub struct RawNodeConfig { pub struct RawNodeConfig {
#[arg(skip)] #[arg(skip)]
secret: Option<String>, pub secret: Option<String>,
#[arg(long)] #[arg(long)]
database_path: Option<PathBuf>, pub database_path: Option<PathBuf>,
#[arg(long)] #[arg(long)]
listen_ips: Option<Vec<IpAddr>>, pub listen_ips: Option<Vec<IpAddr>>,
#[arg(long)] #[arg(long)]
port: Option<u16>, pub port: Option<u16>,
} }
impl RawNodeConfig { impl RawNodeConfig {
@ -138,8 +146,33 @@ impl RawNodeConfig {
file.write_all(toml::to_string(self)?.as_bytes()).await?; file.write_all(toml::to_string(self)?.as_bytes()).await?;
Ok(()) Ok(())
} }
pub fn merge(&mut self, another: RawNodeConfig) {
if let Some(x) = another.secret {
self.secret = Some(x);
};
if let Some(x) = another.database_path {
self.database_path = Some(x);
};
if let Some(x) = another.listen_ips {
self.listen_ips = Some(x);
};
if let Some(x) = another.port {
self.port = Some(x);
};
}
} }
impl ops::Add<RawNodeConfig> for RawNodeConfig {
type Output = RawNodeConfig;
fn add(mut self, another: RawNodeConfig) -> RawNodeConfig {
self.merge(another);
self
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use libp2p::identity; use libp2p::identity;

View file

@ -1,6 +1,6 @@
use std::{net::{IpAddr, Ipv4Addr}, path::PathBuf, sync::LazyLock}; use std::{net::{IpAddr, Ipv4Addr}, path::PathBuf, sync::LazyLock};
use crate::config::NodeConfig; use crate::config::{NodeConfig, RawNodeConfig};
use sea_orm::DatabaseConnection; use sea_orm::DatabaseConnection;
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
@ -69,4 +69,13 @@ impl Global {
pub async fn get_or_try_init_node_config(&self, config: NodeConfig) -> &NodeConfig { pub async fn get_or_try_init_node_config(&self, config: NodeConfig) -> &NodeConfig {
self.node_config.get_or_init(|| async {config}).await self.node_config.get_or_init(|| async {config}).await
} }
} }
pub static DEFAULT_RAW_NODE_CONFIG: LazyLock<RawNodeConfig> = LazyLock::new(|| {
RawNodeConfig {
secret: None,
database_path: Some(DEFAULT_DATABASE_FILE_PATH.to_path_buf()),
listen_ips: Some(DEFAULT_LISTEN_IPS.to_vec()),
port: Some(DEFAULT_PORT),
}
});

View file

@ -1,5 +1,5 @@
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use lazy_supplements::{cli::{InitArgs, NodeArgs, NodeCommand}, *}; use lazy_supplements::{cli::{InitArgs, NodeArgs, NodeCommand, ServerArgs}, *};
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
struct Cli { struct Cli {
@ -11,6 +11,7 @@ struct Cli {
enum Command { enum Command {
Node(NodeArgs), Node(NodeArgs),
Init(InitArgs), Init(InitArgs),
Server(ServerArgs),
} }
@ -22,5 +23,6 @@ async fn main() {
NodeCommand::Join(y) => println!("{y:?}"), NodeCommand::Join(y) => println!("{y:?}"),
}, },
Command::Init(x) => x.init_config().await, Command::Init(x) => x.init_config().await,
Command::Server(x) => x.start_server().await.unwrap(),
} }
} }