diff --git a/bootstrap/CHANGELOG.md b/bootstrap/CHANGELOG.md index a7131c618..4f89791b9 100644 --- a/bootstrap/CHANGELOG.md +++ b/bootstrap/CHANGELOG.md @@ -2,6 +2,7 @@ ## [0.4.0] +- Add `/p2p/local/info` endpoint - Add webrtc support to bootstrap ## [0.3.0](https://github.com/availproject/avail-light/releases/tag/avail-light-bootstrap-v0.3.0) - 2024-09-27 diff --git a/bootstrap/src/main.rs b/bootstrap/src/main.rs index c38d50a86..c559f6b73 100644 --- a/bootstrap/src/main.rs +++ b/bootstrap/src/main.rs @@ -86,7 +86,7 @@ async fn run() -> Result<()> { .await .context("Failed to initialize P2P Network Service.")?; - tokio::spawn(server::run((&cfg).into())); + tokio::spawn(server::run((&cfg).into(), network_client.clone())); let ot_metrics = telemetry::otlp::initialize( cfg.ot_collector_endpoint, diff --git a/bootstrap/src/p2p.rs b/bootstrap/src/p2p.rs index 78bca0de4..8bc93e6a3 100644 --- a/bootstrap/src/p2p.rs +++ b/bootstrap/src/p2p.rs @@ -17,9 +17,10 @@ mod client; mod event_loop; use crate::{ - p2p::client::{Client, Command}, + p2p::client::Command, types::{LibP2PConfig, SecretKey}, }; +pub use client::Client; use event_loop::EventLoop; use libp2p_allow_block_list as allow_block_list; use tracing::info; diff --git a/bootstrap/src/p2p/client.rs b/bootstrap/src/p2p/client.rs index 93858dd68..7828989ea 100644 --- a/bootstrap/src/p2p/client.rs +++ b/bootstrap/src/p2p/client.rs @@ -2,6 +2,13 @@ use anyhow::{Context, Result}; use libp2p::{Multiaddr, PeerId}; use tokio::sync::{mpsc, oneshot}; +#[derive(Debug)] +pub struct PeerInfo { + pub peer_id: String, + pub local_listeners: Vec, + pub external_listeners: Vec, +} + #[derive(Clone)] pub struct Client { command_sender: mpsc::Sender, @@ -121,6 +128,15 @@ impl Client { .context("Command receiver not to be dropped.")?; response_receiver.await.context("Sender not to be dropped.") } + + pub async fn get_local_peer_info(&self) -> Result { + let (response_sender, response_receiver) = oneshot::channel(); + self.command_sender + .send(Command::GetLocalPeerInfo { response_sender }) + .await + .context("Command receiver not to be dropped.")?; + response_receiver.await.context("Sender not to be dropped.") + } } #[derive(Debug)] @@ -152,4 +168,7 @@ pub enum Command { GetMultiaddress { response_sender: oneshot::Sender>, }, + GetLocalPeerInfo { + response_sender: oneshot::Sender, + }, } diff --git a/bootstrap/src/p2p/event_loop.rs b/bootstrap/src/p2p/event_loop.rs index e341b3880..e0bf8eca1 100644 --- a/bootstrap/src/p2p/event_loop.rs +++ b/bootstrap/src/p2p/event_loop.rs @@ -21,7 +21,10 @@ use tracing::{debug, info, trace}; use crate::types::AgentVersion; -use super::{client::Command, Behaviour, BehaviourEvent}; +use super::{ + client::{Command, PeerInfo}, + Behaviour, BehaviourEvent, +}; enum QueryChannel { Bootstrap(oneshot::Sender>), @@ -349,6 +352,22 @@ impl EventLoop { let last_address = self.swarm.external_addresses().last(); _ = response_sender.send(last_address.cloned()); }, + Command::GetLocalPeerInfo { response_sender } => { + let local_listeners: Vec = + self.swarm.listeners().map(ToString::to_string).collect(); + + let external_addresses: Vec = self + .swarm + .external_addresses() + .map(ToString::to_string) + .collect(); + + _ = response_sender.send(PeerInfo { + peer_id: self.swarm.local_peer_id().to_string(), + local_listeners, + external_listeners: external_addresses, + }); + }, } } diff --git a/bootstrap/src/server.rs b/bootstrap/src/server.rs index fbeb3f4bf..6c6382e55 100644 --- a/bootstrap/src/server.rs +++ b/bootstrap/src/server.rs @@ -1,18 +1,59 @@ +use serde::{Deserialize, Serialize}; use std::net::SocketAddr; use tracing::info; -use warp::Filter; +use warp::{reply::Reply, Filter}; -use crate::types::Addr; +use crate::{p2p, types::Addr}; -pub async fn run(addr: Addr) { +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Listeners { + pub local: Vec, + pub external: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PeerInfoResponse { + peer_id: String, + listeners: Listeners, +} + +impl Reply for PeerInfoResponse { + fn into_response(self) -> warp::reply::Response { + warp::reply::json(&self).into_response() + } +} + +async fn get_peer_info(p2p_client: p2p::Client) -> Result { + let local_info = p2p_client + .get_local_peer_info() + .await + .map_err(|error| error.to_string())?; + + Ok(PeerInfoResponse { + peer_id: local_info.peer_id, + listeners: Listeners { + local: local_info.local_listeners, + external: local_info.external_listeners, + }, + }) +} + +pub async fn run(addr: Addr, p2p_client: p2p::Client) { let health_route = warp::head() .or(warp::get()) .and(warp::path("health")) .map(|_| warp::reply::with_status("", warp::http::StatusCode::OK)); + let p2p_local_info_route = warp::path!("p2p" / "local" / "info") + .and(warp::get()) + .and(warp::any().map(move || p2p_client.clone())) + .then(get_peer_info); + info!("HTTP server running on http://{addr}. Health endpoint available at '/health'."); let socket_addr: SocketAddr = addr.try_into().unwrap(); - warp::serve(health_route).run(socket_addr).await; + warp::serve(health_route.or(p2p_local_info_route)) + .run(socket_addr) + .await; }