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