Skip to main content

hoprd_api/
node.rs

1// HashMap is used inside the utoipa macro attribute on the `configuration` endpoint.
2#[allow(unused_imports)]
3use std::collections::HashMap;
4use std::sync::Arc;
5
6use axum::{
7    extract::{Json, State},
8    http::status::StatusCode,
9    response::IntoResponse,
10};
11use hopr_lib::{
12    Address, Multiaddr,
13    api::{network::Health, node::HoprNodeNetworkOperations},
14};
15use serde::Serialize;
16use serde_with::{DisplayFromStr, serde_as};
17
18use crate::{ApiError, ApiErrorStatus, BASE_PATH, InternalState, checksum_address_serializer};
19
20#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
21#[schema(example = json!({
22        "version": "2.1.0",
23    }))]
24#[serde(rename_all = "camelCase")]
25/// Running node version.
26pub(crate) struct NodeVersionResponse {
27    #[schema(example = "2.1.0")]
28    version: String,
29}
30
31/// Get the release version of the running node.
32#[utoipa::path(
33        get,
34        path = const_format::formatcp!("{BASE_PATH}/node/version"),
35        description = "Get the release version of the running node",
36        responses(
37            (status = 200, description = "Fetched node version", body = NodeVersionResponse),
38            (status = 401, description = "Invalid authorization token.", body = ApiError),
39        ),
40        security(
41            ("api_token" = []),
42            ("bearer_token" = [])
43        ),
44        tag = "Node"
45    )]
46pub(super) async fn version() -> impl IntoResponse {
47    let version = hopr_lib::constants::APP_VERSION.to_string();
48    (StatusCode::OK, Json(NodeVersionResponse { version })).into_response()
49}
50
51/// Get the configuration of the running node.
52#[utoipa::path(
53    get,
54    path = const_format::formatcp!("{BASE_PATH}/node/configuration"),
55    description = "Get the configuration of the running node",
56    responses(
57        (status = 200, description = "Fetched node configuration", body = HashMap<String, String>, example = json!({
58        "network": "anvil-localhost",
59        "provider": "http://127.0.0.1:8545",
60        "hoprToken": "0x9a676e781a523b5d0c0e43731313a708cb607508",
61        "hoprChannels": "0x9a9f2ccfde556a7e9ff0848998aa4a0cfd8863ae",
62        "...": "..."
63        })),
64        (status = 401, description = "Invalid authorization token.", body = ApiError),
65    ),
66    security(
67        ("api_token" = []),
68        ("bearer_token" = [])
69    ),
70    tag = "Configuration"
71    )]
72pub(super) async fn configuration(State(state): State<Arc<InternalState>>) -> impl IntoResponse {
73    (StatusCode::OK, Json(state.hoprd_cfg.clone())).into_response()
74}
75
76#[serde_as]
77#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
78#[schema(example = json!({
79        "announcedAddress": [
80            "/ip4/10.0.2.100/tcp/19092"
81        ],
82        "providerUrl": "https://staging.blokli.hoprnet.link",
83        "hoprNetworkName": "rotsee",
84        "channelClosurePeriod": 15,
85        "connectivityStatus": "Green",
86        "hoprNodeSafe": "0x42bc901b1d040f984ed626eff550718498a6798a",
87        "listeningAddress": [
88            "/ip4/10.0.2.100/tcp/19092"
89        ],
90    }))]
91#[serde(rename_all = "camelCase")]
92/// Information about the current node. Covers network, addresses, eligibility, connectivity status, contracts addresses
93/// and indexer state.
94pub(crate) struct NodeInfoResponse {
95    #[serde_as(as = "Vec<DisplayFromStr>")]
96    #[schema(value_type = Vec<String>, example = json!(["/ip4/10.0.2.100/tcp/19092"]))]
97    announced_address: Vec<Multiaddr>,
98    #[serde_as(as = "Vec<DisplayFromStr>")]
99    #[schema(value_type = Vec<String>, example = json!(["/ip4/10.0.2.100/tcp/19092"]))]
100    listening_address: Vec<Multiaddr>,
101    #[schema(value_type = String, example = "https://staging.blokli.hoprnet.link")]
102    provider_url: String,
103    #[schema(value_type = String, example = "rotsee")]
104    hopr_network_name: String,
105    #[serde(serialize_with = "checksum_address_serializer")]
106    #[schema(value_type = String, example = "0x42bc901b1d040f984ed626eff550718498a6798a")]
107    hopr_node_safe: Address,
108    #[serde_as(as = "DisplayFromStr")]
109    #[schema(value_type = String, example = "Green")]
110    connectivity_status: Health,
111    /// Channel closure period in seconds
112    #[schema(example = 15)]
113    channel_closure_period: u64,
114}
115
116/// Get information about this HOPR Node.
117#[utoipa::path(
118        get,
119        path = const_format::formatcp!("{BASE_PATH}/node/info"),
120        description = "Get information about this HOPR Node",
121        responses(
122            (status = 200, description = "Fetched node informations", body = NodeInfoResponse),
123            (status = 422, description = "Unknown failure", body = ApiError)
124        ),
125        security(
126            ("api_token" = []),
127            ("bearer_token" = [])
128        ),
129        tag = "Node"
130    )]
131pub(super) async fn info(State(state): State<Arc<InternalState>>) -> Result<impl IntoResponse, ApiError> {
132    let hopr = state.hopr.clone();
133
134    let safe_config = hopr.get_safe_config();
135
136    let provider_url = state
137        .hoprd_cfg
138        .as_object()
139        .and_then(|cfg| cfg.get("blokli_url"))
140        .and_then(|v| v.as_str());
141
142    match futures::try_join!(hopr.chain_info(), hopr.get_channel_closure_notice_period()) {
143        Ok((info, channel_closure_notice_period)) => {
144            let body = NodeInfoResponse {
145                announced_address: hopr.local_multiaddresses(),
146                listening_address: hopr.local_multiaddresses(),
147                provider_url: provider_url.unwrap_or("n/a").to_owned(),
148                hopr_network_name: info.hopr_network_name,
149                hopr_node_safe: safe_config.safe_address,
150                connectivity_status: hopr.network_health().await,
151                channel_closure_period: channel_closure_notice_period.as_secs(),
152            };
153
154            Ok((StatusCode::OK, Json(body)).into_response())
155        }
156        Err(error) => Ok((StatusCode::UNPROCESSABLE_ENTITY, ApiErrorStatus::from(error)).into_response()),
157    }
158}