use clap::Parser;
use discv5::{
enr,
enr::{k256, CombinedKey},
ConfigBuilder, Discv5, Event, ListenConfig,
};
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
time::Duration,
};
use tracing::{info, warn};
#[derive(Parser)]
struct FindNodesArgs {
#[clap(long, default_value_t = SocketKind::Ds)]
socket_kind: SocketKind,
#[clap(long)]
enr_ip4: Option<Ipv4Addr>,
#[clap(long)]
enr_ip6: Option<Ipv6Addr>,
#[clap(long)]
port: Option<u16>,
#[clap(long)]
port6: Option<u16>,
#[clap(long)]
use_test_key: bool,
#[clap(long)]
remote_peer: Vec<discv5::Enr>,
#[clap(long)]
events: bool,
}
#[tokio::main]
async fn main() {
let filter_layer = tracing_subscriber::EnvFilter::try_from_default_env()
.or_else(|_| tracing_subscriber::EnvFilter::try_new("info"))
.unwrap();
let _ = tracing_subscriber::fmt()
.with_env_filter(filter_layer)
.try_init();
let args = FindNodesArgs::parse();
let port = args
.port
.unwrap_or_else(|| (rand::random::<u16>() % 1000) + 9000);
let port6 = args.port.unwrap_or_else(|| loop {
let port6 = (rand::random::<u16>() % 1000) + 9000;
if port6 != port {
return port6;
}
});
let enr_key = if args.use_test_key {
let raw_key =
hex::decode("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
.unwrap();
let secret_key = k256::ecdsa::SigningKey::from_slice(&raw_key).unwrap();
CombinedKey::from(secret_key)
} else {
CombinedKey::generate_secp256k1()
};
let enr = {
let mut builder = enr::Enr::builder();
if let Some(ip4) = args.enr_ip4 {
if ip4.is_unspecified() {
builder.ip4(Ipv4Addr::LOCALHOST).udp4(port);
} else {
builder.ip4(ip4).udp4(port);
}
}
if let Some(ip6) = args.enr_ip6 {
if ip6.is_unspecified() {
builder.ip6(Ipv6Addr::LOCALHOST).udp6(port6);
} else {
builder.ip6(ip6).udp6(port6);
}
}
builder.build(&enr_key).unwrap()
};
let listen_config = match args.socket_kind {
SocketKind::Ip4 => ListenConfig::from_ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port),
SocketKind::Ip6 => ListenConfig::from_ip(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port6),
SocketKind::Ds => ListenConfig::default()
.with_ipv4(Ipv4Addr::UNSPECIFIED, port)
.with_ipv6(Ipv6Addr::UNSPECIFIED, port6),
};
let config = ConfigBuilder::new(listen_config).build();
info!("Node Id: {}", enr.node_id());
if args.enr_ip6.is_some() || args.enr_ip4.is_some() {
info!(
base64_enr = &enr.to_base64(),
ipv6_socket = ?enr.udp6_socket(),
ipv4_socket = ?enr.udp4_socket(),
"Local ENR",
);
}
let mut discv5: Discv5 = Discv5::new(enr, enr_key, config).unwrap();
for enr in args.remote_peer {
info!(
udp4_socket = ?enr.udp4_socket(),
udp6_socket = ?enr.udp6_socket(),
tcp4_port = ?enr.tcp4(),
tcp6_port = ?enr.tcp6(),
"Remote ENR read",
);
if let Err(e) = discv5.add_enr(enr) {
warn!(error = ?e, "Failed to add remote ENR");
return;
};
}
discv5.start().await.unwrap();
let mut event_stream = discv5.event_stream().await.unwrap();
let check_evs = args.events;
let mut query_interval = tokio::time::interval(Duration::from_secs(30));
loop {
tokio::select! {
_ = query_interval.tick() => {
let target_random_node_id = enr::NodeId::random();
let metrics = discv5.metrics();
let connected_peers = discv5.connected_peers();
info!(
connected_peers,
active_sessions = metrics.active_sessions,
unsolicited_requests_per_second = format_args!("{:.2}", metrics.unsolicited_requests_per_second),
"Searching for peers..."
);
match discv5.find_node(target_random_node_id).await {
Err(e) => warn!(error = ?e, "Find Node result failed"),
Ok(v) => {
let node_ids = v.iter().map(|enr| enr.node_id()).collect::<Vec<_>>();
info!(len = node_ids.len(), "Nodes found");
for node_id in node_ids {
info!(%node_id, "Node");
}
}
}
}
Some(discv5_ev) = event_stream.recv() => {
if !check_evs {
continue;
}
match discv5_ev {
Event::Discovered(enr) => info!(%enr, "Enr discovered"),
Event::NodeInserted { node_id, replaced: _ } => info!(%node_id, "Node inserted"),
Event::SessionEstablished(enr, _) => info!(%enr, "Session established"),
Event::SocketUpdated(addr) => info!(%addr, "Socket updated"),
Event::TalkRequest(_) => info!("Talk request received"),
_ => {}
};
}
}
}
}
#[derive(Clone)]
pub enum SocketKind {
Ip4,
Ip6,
Ds,
}
impl std::fmt::Display for SocketKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SocketKind::Ip4 => f.write_str("ip4"),
SocketKind::Ip6 => f.write_str("ip6"),
SocketKind::Ds => f.write_str("ds"),
}
}
}
impl std::str::FromStr for SocketKind {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ip4" => Ok(SocketKind::Ip4),
"ip6" => Ok(SocketKind::Ip6),
"ds" => Ok(SocketKind::Ds),
_ => Err("bad kind"),
}
}
}