hoprd_api/
peers.rs

1use std::sync::Arc;
2
3use axum::{
4    extract::{Json, Path, State},
5    http::status::StatusCode,
6    response::IntoResponse,
7};
8use hopr_db_api::resolver::HoprDbResolverOperations;
9use hopr_lib::{
10    Address, HoprTransportError, Multiaddr, PeerId,
11    errors::{HoprLibError, HoprStatusError},
12};
13use serde::{Deserialize, Serialize};
14use serde_with::{DisplayFromStr, DurationMilliSeconds, serde_as};
15use tracing::debug;
16
17use crate::{ApiError, ApiErrorStatus, BASE_PATH, InternalState};
18
19#[serde_as]
20#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
21#[schema(example = json!({
22    "announced": [
23        "/ip4/10.0.2.100/tcp/19093"
24    ],
25    "observed": [
26        "/ip4/10.0.2.100/tcp/19093"
27    ]
28}))]
29/// Contains the multiaddresses of peers that are `announced` on-chain and `observed` by the node.
30pub(crate) struct NodePeerInfoResponse {
31    #[serde_as(as = "Vec<DisplayFromStr>")]
32    #[schema(value_type = Vec<String>, example = json!(["/ip4/10.0.2.100/tcp/19093"]))]
33    announced: Vec<Multiaddr>,
34    #[serde_as(as = "Vec<DisplayFromStr>")]
35    #[schema(value_type = Vec<String>, example = json!(["/ip4/10.0.2.100/tcp/19093"]))]
36    observed: Vec<Multiaddr>,
37}
38
39#[serde_as]
40#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
41#[serde(rename_all = "camelCase")]
42pub(crate) struct DestinationParams {
43    #[serde_as(as = "DisplayFromStr")]
44    #[schema(value_type = String)]
45    destination: Address,
46}
47
48/// Returns transport-related information about the given peer.
49///
50/// This includes the peer ids that the given peer has `announced` on-chain
51/// and peer ids that are actually `observed` by the transport layer.
52#[utoipa::path(
53    get,
54    path = const_format::formatcp!("{BASE_PATH}/peers/{{destination}}"),
55    params(
56        ("destination" = String, Path, description = "Address of the requested peer", example = "0x07eaf07d6624f741e04f4092a755a9027aaab7f6"),
57    ),
58    responses(
59        (status = 200, description = "Peer information fetched successfully.", body = NodePeerInfoResponse),
60        (status = 400, description = "Invalid destination", body = ApiError),
61        (status = 401, description = "Invalid authorization token.", body = ApiError),
62        (status = 422, description = "Unknown failure", body = ApiError)
63    ),
64    security(
65        ("api_token" = []),
66        ("bearer_token" = [])
67    ),
68    tag = "Peers",
69)]
70pub(super) async fn show_peer_info(
71    Path(DestinationParams { destination }): Path<DestinationParams>,
72    State(state): State<Arc<InternalState>>,
73) -> impl IntoResponse {
74    let hopr = state.hopr.clone();
75
76    match hopr.peer_resolver().resolve_packet_key(&destination).await {
77        Ok(Some(offchain_key)) => Ok((
78            StatusCode::OK,
79            Json(NodePeerInfoResponse {
80                announced: hopr
81                    .multiaddresses_announced_on_chain(&PeerId::from(offchain_key))
82                    .await,
83                observed: hopr.network_observed_multiaddresses(&PeerId::from(offchain_key)).await,
84            }),
85        )),
86        Ok(None) => Err(ApiErrorStatus::PeerNotFound),
87        Err(_) => Err(ApiErrorStatus::PeerNotFound),
88    }
89}
90
91#[serde_as]
92#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
93#[schema(example = json!({
94    "latency": 200,
95    "reportedVersion": "2.1.0"
96}))]
97#[serde(rename_all = "camelCase")]
98/// Contains the latency and the reported version of a peer that has been pinged.
99pub(crate) struct PingResponse {
100    #[serde_as(as = "DurationMilliSeconds<u64>")]
101    #[schema(value_type = u64, example = 200)]
102    latency: std::time::Duration,
103    #[schema(example = "2.1.0")]
104    reported_version: String,
105}
106
107/// Directly pings the given peer.
108#[utoipa::path(
109    post,
110    path = const_format::formatcp!("{BASE_PATH}/peers/{{destination}}/ping"),
111    description = "Directly ping the given peer",
112    params(
113        ("destination" = String, Path, description = "Address of the requested peer", example = "0x07eaf07d6624f741e04f4092a755a9027aaab7f6"),
114    ),
115    responses(
116        (status = 200, description = "Ping successful", body = PingResponse),
117        (status = 400, description = "Invalid peer id", body = ApiError),
118        (status = 401, description = "Invalid authorization token.", body = ApiError),
119        (status = 404, description = "Peer id not found in the network.", body = ApiError),
120        (status = 408, description = "Peer timed out.", body = ApiError),
121        (status = 412, description = "The node is not ready."),
122        (status = 422, description = "Unknown failure", body = ApiError)
123    ),
124    security(
125        ("api_token" = []),
126        ("bearer_token" = [])
127    ),
128    tag = "Peers",
129)]
130pub(super) async fn ping_peer(
131    Path(DestinationParams { destination }): Path<DestinationParams>,
132    State(state): State<Arc<InternalState>>,
133) -> Result<impl IntoResponse, ApiError> {
134    debug!(%destination, "Manually ping peer");
135
136    let hopr = state.hopr.clone();
137
138    match hopr.peer_resolver().resolve_packet_key(&destination).await {
139        Ok(Some(offchain_key)) => match hopr.ping(&PeerId::from(offchain_key)).await {
140            Ok((latency, status)) => {
141                let resp = Json(PingResponse {
142                    latency: latency / 2,
143                    reported_version: status.peer_version.unwrap_or("unknown".into()),
144                });
145                Ok((StatusCode::OK, resp).into_response())
146            }
147            Err(HoprLibError::TransportError(HoprTransportError::Protocol(hopr_lib::ProtocolError::Timeout))) => {
148                Ok((StatusCode::REQUEST_TIMEOUT, ApiErrorStatus::Timeout).into_response())
149            }
150            Err(HoprLibError::TransportError(HoprTransportError::Probe(hopr_lib::ProbeError::Timeout(_)))) => {
151                Ok((StatusCode::REQUEST_TIMEOUT, ApiErrorStatus::Timeout).into_response())
152            }
153            Err(HoprLibError::TransportError(HoprTransportError::Probe(hopr_lib::ProbeError::PingerError(_, e)))) => {
154                Ok((StatusCode::UNPROCESSABLE_ENTITY, ApiErrorStatus::PingError(e)).into_response())
155            }
156            Err(HoprLibError::TransportError(HoprTransportError::Probe(hopr_lib::ProbeError::NonExistingPeer))) => {
157                Ok((StatusCode::NOT_FOUND, ApiErrorStatus::PeerNotFound).into_response())
158            }
159            Err(HoprLibError::StatusError(HoprStatusError::NotThereYet(..))) => {
160                Ok((StatusCode::PRECONDITION_FAILED, ApiErrorStatus::NotReady).into_response())
161            }
162            Err(e) => Ok((StatusCode::UNPROCESSABLE_ENTITY, ApiErrorStatus::from(e)).into_response()),
163        },
164        Ok(None) => Ok((StatusCode::NOT_FOUND, ApiErrorStatus::PeerNotFound).into_response()),
165        Err(_) => Ok((StatusCode::UNPROCESSABLE_ENTITY, ApiErrorStatus::PeerNotFound).into_response()),
166    }
167}