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