feat: expose node's chain address on self-described API (#6815)

* feat: expose nym-nodes' on-chain address on v2 auxiliary endpoint

* moved swagger page outside the v1 route

* fixed swagger endpoint for nym-api

* post rebasing fixes

* remove redundant impl OfflineSigner for Arc<DirectSecp256k1HdWallet>
This commit is contained in:
Jędrzej Stuczyński
2026-06-12 10:36:27 +01:00
committed by GitHub
parent 8a93bce32f
commit 931ec03b28
46 changed files with 320 additions and 193 deletions
@@ -11,6 +11,8 @@ use cosmrs::tx;
use cosmrs::tx::SignDoc; use cosmrs::tx::SignDoc;
use nym_config::defaults; use nym_config::defaults;
use std::borrow::Cow; use std::borrow::Cow;
use std::ops::Deref;
use std::sync::Arc;
use thiserror::Error; use thiserror::Error;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
@@ -395,8 +395,8 @@ impl From<nym_node_requests::api::v1::node::models::AnnouncePorts> for AnnounceP
} }
} }
impl From<nym_node_requests::api::v1::node::models::AuxiliaryDetails> for AuxiliaryDetailsV1 { impl From<nym_node_requests::api::v1::node::models::AuxiliaryDetailsV1> for AuxiliaryDetailsV1 {
fn from(value: nym_node_requests::api::v1::node::models::AuxiliaryDetails) -> Self { fn from(value: nym_node_requests::api::v1::node::models::AuxiliaryDetailsV1) -> Self {
AuxiliaryDetailsV1 { AuxiliaryDetailsV1 {
location: value.location, location: value.location,
announce_ports: value.announce_ports.into(), announce_ports: value.announce_ports.into(),
+1 -1
View File
@@ -41,7 +41,7 @@ use utoipauto::utoipauto;
nym_config::defaults::NymContracts, nym_config::defaults::NymContracts,
ContractVersionSchemaResponse, ContractVersionSchemaResponse,
nym_bin_common::build_information::BinaryBuildInformationOwned, nym_bin_common::build_information::BinaryBuildInformationOwned,
nym_node_requests::api::v1::node::models::AuxiliaryDetails, nym_node_requests::api::v1::node::models::AuxiliaryDetailsV1,
nym_contracts_common::ContractBuildInformation nym_contracts_common::ContractBuildInformation
)) ))
)] )]
+1 -1
View File
@@ -16,7 +16,7 @@ use nym_lp::peer::{DHPublicKey, LpRemotePeer};
use nym_lp_data::packet::version; use nym_lp_data::packet::version;
use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT; use nym_network_defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_node_requests::api::client::NymNodeApiClientExt; use nym_node_requests::api::client::NymNodeApiClientExt;
use nym_node_requests::api::v1::node::models::AuxiliaryDetails as NodeAuxiliaryDetails; use nym_node_requests::api::v1::node::models::AuxiliaryDetailsV1 as NodeAuxiliaryDetails;
use nym_sdk::mixnet::NodeIdentity; use nym_sdk::mixnet::NodeIdentity;
use nym_sdk::mixnet::Recipient; use nym_sdk::mixnet::Recipient;
use nym_validator_client::client::NymApiClientExt; use nym_validator_client::client::NymApiClientExt;
+2 -2
View File
@@ -11,7 +11,7 @@ use crate::api::v1::ip_packet_router::models::IpPacketRouter;
use crate::api::v1::network_requester::exit_policy::models::UsedExitPolicy; use crate::api::v1::network_requester::exit_policy::models::UsedExitPolicy;
use crate::api::v1::network_requester::models::NetworkRequester; use crate::api::v1::network_requester::models::NetworkRequester;
use crate::api::v1::node::models::{ use crate::api::v1::node::models::{
AuxiliaryDetails, NodeDescription, NodeRoles, SignedHostInformation, AuxiliaryDetailsV1, NodeDescription, NodeRoles, SignedHostInformation,
}; };
use crate::api::v1::node_load::models::NodeLoad; use crate::api::v1::node_load::models::NodeLoad;
use crate::routes; use crate::routes;
@@ -55,7 +55,7 @@ pub trait NymNodeApiClientExt: ApiClient {
self.get_json_from(routes::api::v1::roles_absolute()).await self.get_json_from(routes::api::v1::roles_absolute()).await
} }
async fn get_auxiliary_details(&self) -> Result<AuxiliaryDetails, NymNodeApiClientError> { async fn get_auxiliary_details(&self) -> Result<AuxiliaryDetailsV1, NymNodeApiClientError> {
self.get_json_from(routes::api::v1::auxiliary_absolute()) self.get_json_from(routes::api::v1::auxiliary_absolute())
.await .await
} }
@@ -15,6 +15,7 @@ use std::ops::Deref;
pub mod client; pub mod client;
pub mod helpers; pub mod helpers;
pub mod v1; pub mod v1;
pub mod v2;
#[cfg(feature = "client")] #[cfg(feature = "client")]
pub use client::Client; pub use client::Client;
@@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize};
use std::net::IpAddr; use std::net::IpAddr;
pub use crate::api::SignedHostInformation; pub use crate::api::SignedHostInformation;
use crate::api::v2::node::models::AuxiliaryDetailsV2;
pub use nym_bin_common::build_information::BinaryBuildInformationOwned; pub use nym_bin_common::build_information::BinaryBuildInformationOwned;
#[derive(Clone, Default, Debug, Copy, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Default, Debug, Copy, Serialize, Deserialize, JsonSchema)]
@@ -366,7 +367,7 @@ pub struct NodeDescription {
/// Auxiliary details of the associated Nym Node. /// Auxiliary details of the associated Nym Node.
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct AuxiliaryDetails { pub struct AuxiliaryDetailsV1 {
/// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location /// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location
#[cfg_attr(feature = "openapi", schema(example = "PL", value_type = Option<String>))] #[cfg_attr(feature = "openapi", schema(example = "PL", value_type = Option<String>))]
#[schemars(with = "Option<String>")] #[schemars(with = "Option<String>")]
@@ -383,6 +384,16 @@ pub struct AuxiliaryDetails {
pub accepted_operator_terms_and_conditions: bool, pub accepted_operator_terms_and_conditions: bool,
} }
impl From<AuxiliaryDetailsV2> for AuxiliaryDetailsV1 {
fn from(v2: AuxiliaryDetailsV2) -> Self {
Self {
location: v2.location,
announce_ports: v2.announce_ports,
accepted_operator_terms_and_conditions: v2.accepted_operator_terms_and_conditions,
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -0,0 +1,4 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
pub mod node;
@@ -0,0 +1,4 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
pub mod models;
@@ -0,0 +1,30 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::api::v1::node::models::AnnouncePorts;
use celes::Country;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// Auxiliary details of the associated Nym Node.
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct AuxiliaryDetailsV2 {
/// Optional ISO 3166 alpha-2 two-letter country code of the node's **physical** location
#[cfg_attr(feature = "openapi", schema(example = "PL", value_type = Option<String>))]
#[schemars(with = "Option<String>")]
#[schemars(length(equal = 2))]
pub location: Option<Country>,
/// On-chain address of this node
pub address: String,
#[serde(default)]
pub announce_ports: AnnouncePorts,
/// Specifies whether this node operator has agreed to the terms and conditions
/// as defined at <https://nymtech.net/terms-and-conditions/operators/v1.0.0>
// make sure to include the default deserialisation as this field hasn't existed when the struct was first created
#[serde(default)]
pub accepted_operator_terms_and_conditions: bool,
}
+14
View File
@@ -23,8 +23,14 @@ pub mod routes {
pub mod api { pub mod api {
pub const V1: &str = "/v1"; pub const V1: &str = "/v1";
pub const V2: &str = "/v2";
// canonical, version-neutral Swagger UI mount
pub const SWAGGER: &str = "/swagger";
absolute_route!(v1_absolute, super::API, V1); absolute_route!(v1_absolute, super::API, V1);
absolute_route!(v2_absolute, super::API, V2);
absolute_route!(swagger_absolute, super::API, SWAGGER);
pub mod v1 { pub mod v1 {
use super::*; use super::*;
@@ -152,6 +158,14 @@ pub mod routes {
// use super::*; // use super::*;
} }
} }
pub mod v2 {
use super::*;
pub const AUXILIARY: &str = "/auxiliary-details";
absolute_route!(auxiliary_absolute, v2_absolute(), AUXILIARY);
}
} }
} }
+6 -1
View File
@@ -5,15 +5,20 @@ use crate::node::http::state::AppState;
use axum::Router; use axum::Router;
use nym_node_requests::routes; use nym_node_requests::routes;
pub mod openapi;
pub mod v1; pub mod v1;
pub mod v2;
pub use nym_node_requests::api as api_requests; pub use nym_node_requests::api as api_requests;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Config { pub struct Config {
pub v1_config: v1::Config, pub v1_config: v1::Config,
pub v2_config: v2::Config,
} }
pub(super) fn routes(config: Config) -> Router<AppState> { pub(super) fn routes(config: Config) -> Router<AppState> {
Router::new().nest(routes::api::V1, v1::routes(config.v1_config)) Router::new()
.nest(routes::api::V1, v1::routes(config.v1_config))
.nest(routes::api::V2, v2::routes(config.v2_config))
} }
@@ -3,7 +3,7 @@
use axum::Router; use axum::Router;
use nym_node_requests::api as api_requests; use nym_node_requests::api as api_requests;
use nym_node_requests::routes::api::{v1, v1_absolute}; use nym_node_requests::routes;
use utoipa::openapi::security::{Http, HttpAuthScheme}; use utoipa::openapi::security::{Http, HttpAuthScheme};
use utoipa::{Modify, OpenApi, openapi::security::SecurityScheme}; use utoipa::{Modify, OpenApi, openapi::security::SecurityScheme};
use utoipa_swagger_ui::SwaggerUi; use utoipa_swagger_ui::SwaggerUi;
@@ -37,12 +37,14 @@ use utoipa_swagger_ui::SwaggerUi;
crate::node::http::router::api::v1::gateway::client_interfaces::wireguard_details, crate::node::http::router::api::v1::gateway::client_interfaces::wireguard_details,
crate::node::http::router::api::v1::gateway::root::root_gateway, crate::node::http::router::api::v1::gateway::root::root_gateway,
crate::node::http::router::api::v1::lewes_protocol::root::root_lewes_protocol, crate::node::http::router::api::v1::lewes_protocol::root::root_lewes_protocol,
crate::node::http::router::api::v2::node::auxiliary::auxiliary,
), ),
components( components(
schemas( schemas(
nym_http_api_common::Output, nym_http_api_common::Output,
nym_http_api_common::OutputParams, nym_http_api_common::OutputParams,
nym_http_api_common::OutputV2,
nym_http_api_common::OutputParamsV2,
api_requests::v1::health::models::NodeHealth, api_requests::v1::health::models::NodeHealth,
api_requests::v1::health::models::NodeStatus, api_requests::v1::health::models::NodeStatus,
api_requests::v1::node_load::models::NodeLoad, api_requests::v1::node_load::models::NodeLoad,
@@ -56,7 +58,7 @@ use utoipa_swagger_ui::SwaggerUi;
api_requests::v1::node::models::Cpu, api_requests::v1::node::models::Cpu,
api_requests::v1::node::models::CryptoHardware, api_requests::v1::node::models::CryptoHardware,
api_requests::v1::node::models::NodeDescription, api_requests::v1::node::models::NodeDescription,
api_requests::v1::node::models::AuxiliaryDetails, api_requests::v1::node::models::AuxiliaryDetailsV1,
api_requests::v1::metrics::models::LegacyMixingStats, api_requests::v1::metrics::models::LegacyMixingStats,
api_requests::v1::metrics::models::VerlocStats, api_requests::v1::metrics::models::VerlocStats,
api_requests::v1::metrics::models::VerlocResult, api_requests::v1::metrics::models::VerlocResult,
@@ -77,6 +79,7 @@ use utoipa_swagger_ui::SwaggerUi;
api_requests::v1::network_requester::exit_policy::models::UsedExitPolicy, api_requests::v1::network_requester::exit_policy::models::UsedExitPolicy,
api_requests::v1::ip_packet_router::models::IpPacketRouter, api_requests::v1::ip_packet_router::models::IpPacketRouter,
api_requests::v1::lewes_protocol::models::LewesProtocol, api_requests::v1::lewes_protocol::models::LewesProtocol,
api_requests::v2::node::models::AuxiliaryDetailsV2,
), ),
), ),
modifiers(&SecurityAddon), modifiers(&SecurityAddon),
@@ -97,11 +100,14 @@ impl Modify for SecurityAddon {
} }
pub(crate) fn route<S: Send + Sync + 'static + Clone>() -> Router<S> { pub(crate) fn route<S: Send + Sync + 'static + Clone>() -> Router<S> {
// provide absolute path to the openapi.json // SwaggerUi must be mounted with its absolute path: it emits internal redirects
let config = // (e.g. `/swagger` → `/swagger/`) whose `Location` header uses this string
utoipa_swagger_ui::Config::from(format!("{}/api-docs/openapi.json", v1_absolute())); // literally and is not aware of any `.nest()` prefix above it. For the same
SwaggerUi::new(v1::SWAGGER) // reason, this router must be merged at the outer router level — not nested.
.url("/api-docs/openapi.json", ApiDoc::openapi()) let openapi_json = format!("{}/api-docs/openapi.json", routes::API);
let config = utoipa_swagger_ui::Config::from(openapi_json.clone());
SwaggerUi::new(routes::api::swagger_absolute())
.url(openapi_json, ApiDoc::openapi())
.config(config) .config(config)
.into() .into()
} }
@@ -11,7 +11,7 @@ use nym_node_requests::api::v1::authenticator::models::Authenticator;
get, get,
path = "", path = "",
context_path = "/api/v1/authenticator", context_path = "/api/v1/authenticator",
tag = "Authenticator", tag = "v1 / Authenticator",
responses( responses(
(status = 501, description = "the endpoint hasn't been implemented yet"), (status = 501, description = "the endpoint hasn't been implemented yet"),
(status = 200, content( (status = 200, content(
@@ -41,7 +41,7 @@ pub(crate) fn routes<S: Send + Sync + 'static + Clone>(
get, get,
path = "/client-interfaces", path = "/client-interfaces",
context_path = "/api/v1/gateway", context_path = "/api/v1/gateway",
tag = "Gateway", tag = "v1 / Gateway",
responses( responses(
(status = 501, description = "the endpoint hasn't been implemented yet"), (status = 501, description = "the endpoint hasn't been implemented yet"),
(status = 200, content( (status = 200, content(
@@ -67,7 +67,7 @@ pub type ClientInterfacesResponse = FormattedResponse<ClientInterfaces>;
get, get,
path = "/mixnet-websockets", path = "/mixnet-websockets",
context_path = "/api/v1/gateway/client-interfaces", context_path = "/api/v1/gateway/client-interfaces",
tag = "Gateway", tag = "v1 / Gateway",
responses( responses(
(status = 501, description = "the endpoint hasn't been implemented yet"), (status = 501, description = "the endpoint hasn't been implemented yet"),
(status = 200, content( (status = 200, content(
@@ -93,7 +93,7 @@ pub type MixnetWebSocketsResponse = FormattedResponse<WebSockets>;
get, get,
path = "/wireguard", path = "/wireguard",
context_path = "/api/v1/gateway/client-interfaces", context_path = "/api/v1/gateway/client-interfaces",
tag = "Gateway", tag = "v1 / Gateway",
responses( responses(
(status = 501, description = "the endpoint hasn't been implemented yet"), (status = 501, description = "the endpoint hasn't been implemented yet"),
(status = 200, content( (status = 200, content(
@@ -11,7 +11,7 @@ use nym_node_requests::api::v1::gateway::models::Gateway;
get, get,
path = "", path = "",
context_path = "/api/v1/gateway", context_path = "/api/v1/gateway",
tag = "Gateway", tag = "v1 / Gateway",
responses( responses(
(status = 501, description = "the endpoint hasn't been implemented yet"), (status = 501, description = "the endpoint hasn't been implemented yet"),
(status = 200, content( (status = 200, content(
@@ -11,7 +11,7 @@ use nym_node_requests::api::v1::health::models::NodeHealth;
get, get,
path = "/health", path = "/health",
context_path = "/api/v1", context_path = "/api/v1",
tag = "Health", tag = "v1 / Health",
responses( responses(
(status = 200, content( (status = 200, content(
(NodeHealth = "application/json"), (NodeHealth = "application/json"),
@@ -11,7 +11,7 @@ use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
get, get,
path = "", path = "",
context_path = "/api/v1/ip-packet-router", context_path = "/api/v1/ip-packet-router",
tag = "IP Packet Router", tag = "v1 / IP Packet Router",
responses( responses(
(status = 501, description = "the endpoint hasn't been implemented yet"), (status = 501, description = "the endpoint hasn't been implemented yet"),
(status = 200, content( (status = 200, content(
@@ -1,23 +1,15 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net> // Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use crate::node::http::state::AppState;
use axum::Router; use axum::Router;
use axum::routing::get; use axum::routing::get;
use nym_node_requests::api::SignedLewesProtocol;
pub mod root; pub mod root;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct Config { pub struct Config {}
pub details: SignedLewesProtocol,
}
pub(crate) fn routes<S: Send + Sync + 'static + Clone>(config: Config) -> Router<S> { pub(crate) fn routes(_config: Config) -> Router<AppState> {
Router::new().route( Router::new().route("/", get(root::root_lewes_protocol))
"/",
get({
let lp_config = config.details;
move |query| root::root_lewes_protocol(lp_config, query)
}),
)
} }
@@ -1,7 +1,8 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net> // Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use axum::extract::Query; use crate::node::http::state::AppState;
use axum::extract::{Query, State};
use axum::http::StatusCode; use axum::http::StatusCode;
use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_requests::api::{SignedLewesProtocol, SignedLewesProtocolInfo}; use nym_node_requests::api::{SignedLewesProtocol, SignedLewesProtocolInfo};
@@ -11,7 +12,7 @@ use nym_node_requests::api::{SignedLewesProtocol, SignedLewesProtocolInfo};
get, get,
path = "/lewes-protocol", path = "/lewes-protocol",
context_path = "/api/v1", context_path = "/api/v1",
tag = "Lewes Protocol", tag = "v1 / Lewes Protocol",
responses( responses(
(status = 501, description = "the endpoint hasn't been implemented yet"), (status = 501, description = "the endpoint hasn't been implemented yet"),
(status = 200, content( (status = 200, content(
@@ -23,10 +24,10 @@ use nym_node_requests::api::{SignedLewesProtocol, SignedLewesProtocolInfo};
params(OutputParams) params(OutputParams)
)] )]
pub(crate) async fn root_lewes_protocol( pub(crate) async fn root_lewes_protocol(
config: SignedLewesProtocol,
Query(output): Query<OutputParams>, Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> Result<LewesProtocolResponse, StatusCode> { ) -> Result<LewesProtocolResponse, StatusCode> {
Ok(output.to_response(config)) Ok(output.to_response(state.static_information.lewes_protocol.clone()))
} }
pub type LewesProtocolResponse = FormattedResponse<SignedLewesProtocol>; pub type LewesProtocolResponse = FormattedResponse<SignedLewesProtocol>;
+1 -1
View File
@@ -11,7 +11,7 @@ use nym_node_requests::api::v1::node_load::models::NodeLoad;
get, get,
path = "/load", path = "/load",
context_path = "/api/v1", context_path = "/api/v1",
tag = "Node", tag = "v1 / Node",
responses( responses(
(status = 200, content( (status = 200, content(
(NodeLoad = "application/json"), (NodeLoad = "application/json"),
@@ -13,7 +13,7 @@ use nym_node_requests::api::v1::metrics::models::LegacyMixingStats;
get, get,
path = "/mixing", path = "/mixing",
context_path = "/api/v1/metrics", context_path = "/api/v1/metrics",
tag = "Metrics", tag = "v1 / Metrics",
responses( responses(
(status = 200, content( (status = 200, content(
(LegacyMixingStats = "application/json"), (LegacyMixingStats = "application/json"),
@@ -15,7 +15,7 @@ use nym_node_requests::api::v1::metrics::models::packets::{
get, get,
path = "/packets-stats", path = "/packets-stats",
context_path = "/api/v1/metrics", context_path = "/api/v1/metrics",
tag = "Metrics", tag = "v1 / Metrics",
responses( responses(
(status = 200, content( (status = 200, content(
(PacketsStats = "application/json"), (PacketsStats = "application/json"),
@@ -8,7 +8,7 @@ use nym_metrics::metrics;
get, get,
path = "/prometheus", path = "/prometheus",
context_path = "/api/v1/metrics", context_path = "/api/v1/metrics",
tag = "Metrics", tag = "v1 / Metrics",
responses( responses(
(status = 200, body = String), (status = 200, body = String),
(status = 400, description = "`Authorization` header was missing"), (status = 400, description = "`Authorization` header was missing"),
@@ -14,7 +14,7 @@ use time::macros::time;
get, get,
path = "/sessions", path = "/sessions",
context_path = "/api/v1/metrics", context_path = "/api/v1/metrics",
tag = "Metrics", tag = "v1 / Metrics",
responses( responses(
(status = 200, content( (status = 200, content(
(SessionStats = "application/json"), (SessionStats = "application/json"),
@@ -15,7 +15,7 @@ use crate::node::http::state::metrics::MetricsAppState;
get, get,
path = "/verloc", path = "/verloc",
context_path = "/api/v1/metrics", context_path = "/api/v1/metrics",
tag = "Metrics", tag = "v1 / Metrics",
responses( responses(
(status = 200, content( (status = 200, content(
(VerlocStats = "application/json"), (VerlocStats = "application/json"),
@@ -13,7 +13,7 @@ use nym_node_requests::api::v1::metrics::models::WireguardStats;
get, get,
path = "/wireguard-stats", path = "/wireguard-stats",
context_path = "/api/v1/metrics", context_path = "/api/v1/metrics",
tag = "Metrics", tag = "v1 / Metrics",
responses( responses(
(status = 200, content( (status = 200, content(
(WireguardStats = "application/json"), (WireguardStats = "application/json"),
@@ -11,7 +11,7 @@ use nym_node_requests::api::v1::mixnode::models::Mixnode;
get, get,
path = "", path = "",
context_path = "/api/v1/mixnode", context_path = "/api/v1/mixnode",
tag = "Mixnode", tag = "v1 / Mixnode",
responses( responses(
(status = 501, description = "the endpoint hasn't been implemented yet"), (status = 501, description = "the endpoint hasn't been implemented yet"),
(status = 200, content( (status = 200, content(
+7 -2
View File
@@ -3,7 +3,9 @@
use crate::node::http::state::AppState; use crate::node::http::state::AppState;
use axum::Router; use axum::Router;
use axum::response::Redirect;
use axum::routing::get; use axum::routing::get;
use nym_node_requests::routes;
use nym_node_requests::routes::api::v1; use nym_node_requests::routes::api::v1;
pub mod authenticator; pub mod authenticator;
@@ -18,7 +20,6 @@ pub mod mixnode;
pub mod network; pub mod network;
pub mod network_requester; pub mod network_requester;
pub mod node; pub mod node;
pub mod openapi;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Config { pub struct Config {
@@ -34,7 +35,12 @@ pub struct Config {
} }
pub(super) fn routes(config: Config) -> Router<AppState> { pub(super) fn routes(config: Config) -> Router<AppState> {
// legacy redirects: the Swagger UI moved to a version-neutral /api/swagger
let swagger_redirect = get(|| async { Redirect::temporary(&routes::api::swagger_absolute()) });
Router::new() Router::new()
.route(v1::SWAGGER, swagger_redirect.clone())
.route(&format!("{}/", v1::SWAGGER), swagger_redirect)
.route(v1::HEALTH, get(health::root_health)) .route(v1::HEALTH, get(health::root_health))
.route(v1::LOAD, get(load::root_load)) .route(v1::LOAD, get(load::root_load))
.nest(v1::NETWORK, network::routes()) .nest(v1::NETWORK, network::routes())
@@ -59,5 +65,4 @@ pub(super) fn routes(config: Config) -> Router<AppState> {
lewes_protocol::routes(config.lewes_protocol), lewes_protocol::routes(config.lewes_protocol),
) )
.merge(node::routes(config.node)) .merge(node::routes(config.node))
.merge(openapi::route())
} }
@@ -11,7 +11,7 @@ use nym_node_requests::api::v1::network::models::UpgradeModeStatus;
get, get,
path = "/upgrade-mode-status", path = "/upgrade-mode-status",
context_path = "/api/v1/network", context_path = "/api/v1/network",
tag = "Network", tag = "v1 / Network",
responses( responses(
(status = 200, content( (status = 200, content(
(UpgradeModeStatus = "application/json"), (UpgradeModeStatus = "application/json"),
@@ -10,7 +10,7 @@ use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExit
get, get,
path = "/exit-policy", path = "/exit-policy",
context_path = "/api/v1/network-requester", context_path = "/api/v1/network-requester",
tag = "Network Requester", tag = "v1 / Network Requester",
responses( responses(
(status = 200, content( (status = 200, content(
(UsedExitPolicy = "application/json"), (UsedExitPolicy = "application/json"),
@@ -11,7 +11,7 @@ use nym_node_requests::api::v1::network_requester::models::NetworkRequester;
get, get,
path = "", path = "",
context_path = "/api/v1/network-requester", context_path = "/api/v1/network-requester",
tag = "Network Requester", tag = "v1 / Network Requester",
responses( responses(
(status = 501, description = "the endpoint hasn't been implemented yet"), (status = 501, description = "the endpoint hasn't been implemented yet"),
(status = 200, content( (status = 200, content(
@@ -2,30 +2,31 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use crate::node::http::router::types::RequestError; use crate::node::http::router::types::RequestError;
use axum::extract::Query; use crate::node::http::state::AppState;
use axum::extract::{Query, State};
use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_requests::api::v1::node::models::AuxiliaryDetails; use nym_node_requests::api::v1::node::models::AuxiliaryDetailsV1;
/// Returns auxiliary details of this node. /// Returns auxiliary details of this node.
#[utoipa::path( #[utoipa::path(
get, get,
path = "/auxiliary-details", path = "/auxiliary-details",
context_path = "/api/v1", context_path = "/api/v1",
tag = "Node", tag = "v1 / Node",
responses( responses(
(status = 200, content( (status = 200, content(
(AuxiliaryDetails = "application/json"), (AuxiliaryDetailsV1 = "application/json"),
(AuxiliaryDetails = "application/yaml") (AuxiliaryDetailsV1 = "application/yaml")
)), )),
), ),
params(OutputParams) params(OutputParams)
)] )]
pub(crate) async fn auxiliary( pub(crate) async fn auxiliary(
description: AuxiliaryDetails,
Query(output): Query<OutputParams>, Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> Result<AuxiliaryDetailsResponse, RequestError> { ) -> Result<AuxiliaryDetailsResponse, RequestError> {
let output = output.output.unwrap_or_default(); let output = output.output.unwrap_or_default();
Ok(output.to_response(description)) Ok(output.to_response(state.static_information.auxiliary_data.clone().into()))
} }
pub type AuxiliaryDetailsResponse = FormattedResponse<AuxiliaryDetails>; pub type AuxiliaryDetailsResponse = FormattedResponse<AuxiliaryDetailsV1>;
@@ -1,7 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use axum::extract::Query; use crate::node::http::state::AppState;
use axum::extract::{Query, State};
use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned; use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned;
@@ -10,7 +11,7 @@ use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned;
get, get,
path = "/build-information", path = "/build-information",
context_path = "/api/v1", context_path = "/api/v1",
tag = "Node", tag = "v1 / Node",
responses( responses(
(status = 200, content( (status = 200, content(
(BinaryBuildInformationOwned = "application/json"), (BinaryBuildInformationOwned = "application/json"),
@@ -20,11 +21,11 @@ use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned;
params(OutputParams) params(OutputParams)
)] )]
pub(crate) async fn build_information( pub(crate) async fn build_information(
build_information: BinaryBuildInformationOwned,
Query(output): Query<OutputParams>, Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> BuildInformationResponse { ) -> BuildInformationResponse {
let output = output.output.unwrap_or_default(); let output = output.output.unwrap_or_default();
output.to_response(build_information) output.to_response(state.static_information.build_information.clone())
} }
pub type BuildInformationResponse = FormattedResponse<BinaryBuildInformationOwned>; pub type BuildInformationResponse = FormattedResponse<BinaryBuildInformationOwned>;
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use crate::node::http::router::types::RequestError; use crate::node::http::router::types::RequestError;
use axum::extract::Query; use crate::node::http::state::AppState;
use axum::extract::{Query, State};
use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_requests::api::v1::node::models::NodeDescription; use nym_node_requests::api::v1::node::models::NodeDescription;
@@ -11,7 +12,7 @@ use nym_node_requests::api::v1::node::models::NodeDescription;
get, get,
path = "/description", path = "/description",
context_path = "/api/v1", context_path = "/api/v1",
tag = "Node", tag = "v1 / Node",
responses( responses(
(status = 200, content( (status = 200, content(
(NodeDescription = "application/json"), (NodeDescription = "application/json"),
@@ -21,11 +22,11 @@ use nym_node_requests::api::v1::node::models::NodeDescription;
params(OutputParams) params(OutputParams)
)] )]
pub(crate) async fn description( pub(crate) async fn description(
description: NodeDescription,
Query(output): Query<OutputParams>, Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> Result<NodeDescriptionResponse, RequestError> { ) -> Result<NodeDescriptionResponse, RequestError> {
let output = output.output.unwrap_or_default(); let output = output.output.unwrap_or_default();
Ok(output.to_response(description)) Ok(output.to_response(state.static_information.description.clone()))
} }
pub type NodeDescriptionResponse = FormattedResponse<NodeDescription>; pub type NodeDescriptionResponse = FormattedResponse<NodeDescription>;
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use crate::node::http::router::types::RequestError; use crate::node::http::router::types::RequestError;
use axum::extract::Query; use crate::node::http::state::AppState;
use axum::extract::{Query, State};
use axum::http::StatusCode; use axum::http::StatusCode;
use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_requests::api::v1::node::models::HostSystem; use nym_node_requests::api::v1::node::models::HostSystem;
@@ -12,7 +13,7 @@ use nym_node_requests::api::v1::node::models::HostSystem;
get, get,
path = "/system-info", path = "/system-info",
context_path = "/api/v1", context_path = "/api/v1",
tag = "Node", tag = "v1 / Node",
responses( responses(
(status = 200, content( (status = 200, content(
(HostSystem = "application/json"), (HostSystem = "application/json"),
@@ -23,12 +24,12 @@ use nym_node_requests::api::v1::node::models::HostSystem;
params(OutputParams) params(OutputParams)
)] )]
pub(crate) async fn host_system( pub(crate) async fn host_system(
system_info: Option<HostSystem>,
Query(output): Query<OutputParams>, Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> Result<HostSystemResponse, RequestError> { ) -> Result<HostSystemResponse, RequestError> {
let output = output.output.unwrap_or_default(); let output = output.output.unwrap_or_default();
let Some(system_info) = system_info else { let Some(system_info) = state.static_information.system_info.clone() else {
return Err(RequestError::new( return Err(RequestError::new(
"this nym-node does not wish to expose the system information", "this nym-node does not wish to expose the system information",
StatusCode::FORBIDDEN, StatusCode::FORBIDDEN,
@@ -12,7 +12,7 @@ use nym_node_requests::api::{SignedDataHostInfo, v1::node::models::SignedHostInf
get, get,
path = "/host-information", path = "/host-information",
context_path = "/api/v1", context_path = "/api/v1",
tag = "Node", tag = "v1 / Node",
responses( responses(
(status = 200, content( (status = 200, content(
(SignedDataHostInfo = "application/json"), (SignedDataHostInfo = "application/json"),
@@ -10,7 +10,6 @@ use crate::node::http::api::v1::node::roles::roles;
use crate::node::http::state::AppState; use crate::node::http::state::AppState;
use axum::Router; use axum::Router;
use axum::routing::get; use axum::routing::get;
use nym_node_requests::api::v1::node::models;
use nym_node_requests::routes::api::v1; use nym_node_requests::routes::api::v1;
pub mod auxiliary; pub mod auxiliary;
@@ -20,51 +19,15 @@ pub mod hardware;
pub mod host_information; pub mod host_information;
pub mod roles; pub mod roles;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Copy)]
pub struct Config { pub struct Config {}
pub build_information: models::BinaryBuildInformationOwned,
pub system_info: Option<models::HostSystem>,
pub roles: models::NodeRoles,
pub description: models::NodeDescription,
pub auxiliary_details: models::AuxiliaryDetails,
}
pub(super) fn routes(config: Config) -> Router<AppState> { pub(super) fn routes(_config: Config) -> Router<AppState> {
Router::new() Router::new()
.route( .route(v1::BUILD_INFO, get(build_information))
v1::BUILD_INFO, .route(v1::ROLES, get(roles))
get({
let build_info = config.build_information;
move |query| build_information(build_info, query)
}),
)
.route(
v1::ROLES,
get({
let node_roles = config.roles;
move |query| roles(node_roles, query)
}),
)
.route(v1::HOST_INFO, get(host_information)) .route(v1::HOST_INFO, get(host_information))
.route( .route(v1::SYSTEM_INFO, get(host_system))
v1::SYSTEM_INFO, .route(v1::NODE_DESCRIPTION, get(description))
get({ .route(v1::AUXILIARY, get(auxiliary))
let system_info = config.system_info;
move |query| host_system(system_info, query)
}),
)
.route(
v1::NODE_DESCRIPTION,
get({
let node_description = config.description;
move |query| description(node_description, query)
}),
)
.route(
v1::AUXILIARY,
get({
let auxiliary_details = config.auxiliary_details;
move |query| auxiliary(auxiliary_details, query)
}),
)
} }
@@ -1,7 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net> // Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use axum::extract::Query; use crate::node::http::state::AppState;
use axum::extract::{Query, State};
use nym_http_api_common::{FormattedResponse, OutputParams}; use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_requests::api::v1::node::models::NodeRoles; use nym_node_requests::api::v1::node::models::NodeRoles;
@@ -10,7 +11,7 @@ use nym_node_requests::api::v1::node::models::NodeRoles;
get, get,
path = "/roles", path = "/roles",
context_path = "/api/v1", context_path = "/api/v1",
tag = "Node", tag = "v1 / Node",
responses( responses(
(status = 200, content( (status = 200, content(
(NodeRoles = "application/json"), (NodeRoles = "application/json"),
@@ -20,11 +21,11 @@ use nym_node_requests::api::v1::node::models::NodeRoles;
params(OutputParams) params(OutputParams)
)] )]
pub(crate) async fn roles( pub(crate) async fn roles(
node_roles: NodeRoles,
Query(output): Query<OutputParams>, Query(output): Query<OutputParams>,
State(state): State<AppState>,
) -> RolesResponse { ) -> RolesResponse {
let output = output.output.unwrap_or_default(); let output = output.output.unwrap_or_default();
output.to_response(node_roles) output.to_response(state.static_information.roles)
} }
pub type RolesResponse = FormattedResponse<NodeRoles>; pub type RolesResponse = FormattedResponse<NodeRoles>;
@@ -0,0 +1,16 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::http::state::AppState;
use axum::Router;
pub mod node;
#[derive(Debug, Clone)]
pub struct Config {
pub node: node::Config,
}
pub(super) fn routes(config: Config) -> Router<AppState> {
Router::new().merge(node::routes(config.node))
}
@@ -0,0 +1,35 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::http::router::types::RequestError;
use crate::node::http::state::AppState;
use axum::extract::{Query, State};
use nym_http_api_common::{FormattedResponse, OutputParamsV2};
use nym_node_requests::api::v2::node::models::AuxiliaryDetailsV2;
/// Returns auxiliary details of this node.
#[utoipa::path(
get,
path = "/auxiliary-details",
context_path = "/api/v2",
tag = "v2 / Node",
// distinct from v1's `auxiliary`: OpenAPI requires operationId to be unique
// across the whole document, and Swagger UI routes "Try it out" by operationId
operation_id = "v2_auxiliary",
responses(
(status = 200, content(
(AuxiliaryDetailsV2 = "application/json"),
(AuxiliaryDetailsV2 = "application/yaml")
)),
),
params(OutputParamsV2)
)]
pub(crate) async fn auxiliary(
Query(output): Query<OutputParamsV2>,
State(state): State<AppState>,
) -> Result<AuxiliaryDetailsResponse, RequestError> {
let output = output.output.unwrap_or_default();
Ok(output.to_response(state.static_information.auxiliary_data.clone()))
}
pub type AuxiliaryDetailsResponse = FormattedResponse<AuxiliaryDetailsV2>;
@@ -0,0 +1,17 @@
// Copyright 2026 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::http::api::v2::node::auxiliary::auxiliary;
use crate::node::http::state::AppState;
use axum::Router;
use axum::routing::get;
use nym_node_requests::routes::api::v2;
pub mod auxiliary;
#[derive(Debug, Clone, Copy)]
pub struct Config {}
pub(super) fn routes(_config: Config) -> Router<AppState> {
Router::new().route(v2::AUXILIARY, get(auxiliary))
}
@@ -27,7 +27,7 @@ pub(super) async fn default() -> Html<&'static str> {
<div> <div>
<p> default page of the nym node - you can customize it by setting the 'assets' path under '[http]' section of your config. </p> <p> default page of the nym node - you can customize it by setting the 'assets' path under '[http]' section of your config. </p>
You can explore the REST API at <a href = "/api/v1/swagger/">/api/v1/swagger/</a> You can explore the REST API at <a href = "/api/swagger/">/api/swagger/</a>
</div> </div>
"#, "#,
) )
+11 -44
View File
@@ -2,22 +2,18 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use crate::node::http::NymNodeHttpServer; use crate::node::http::NymNodeHttpServer;
use crate::node::http::api::v1::lewes_protocol;
use crate::node::http::error::NymNodeHttpError; use crate::node::http::error::NymNodeHttpError;
use crate::node::http::state::AppState; use crate::node::http::state::AppState;
use axum::Router; use axum::Router;
use axum::response::Redirect; use axum::response::Redirect;
use axum::routing::get; use axum::routing::get;
use nym_bin_common::bin_info_owned;
use nym_http_api_common::middleware::logging; use nym_http_api_common::middleware::logging;
use nym_node_requests::api::SignedLewesProtocol;
use nym_node_requests::api::v1::authenticator::models::Authenticator; use nym_node_requests::api::v1::authenticator::models::Authenticator;
use nym_node_requests::api::v1::gateway::models::{Bridges, Gateway}; use nym_node_requests::api::v1::gateway::models::{Bridges, Gateway};
use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
use nym_node_requests::api::v1::mixnode::models::Mixnode; use nym_node_requests::api::v1::mixnode::models::Mixnode;
use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy; use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy;
use nym_node_requests::api::v1::network_requester::models::NetworkRequester; use nym_node_requests::api::v1::network_requester::models::NetworkRequester;
use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, HostSystem, NodeDescription};
use nym_node_requests::routes; use nym_node_requests::routes;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::Path; use std::path::Path;
@@ -36,18 +32,12 @@ pub struct HttpServerConfig {
} }
impl HttpServerConfig { impl HttpServerConfig {
pub fn new(signed_lewes_protocol: SignedLewesProtocol) -> Self { pub fn new() -> Self {
HttpServerConfig { HttpServerConfig {
landing: Default::default(), landing: Default::default(),
api: api::Config { api: api::Config {
v1_config: api::v1::Config { v1_config: api::v1::Config {
node: api::v1::node::Config { node: api::v1::node::Config {},
build_information: bin_info_owned!(),
system_info: None,
roles: Default::default(),
description: Default::default(),
auxiliary_details: Default::default(),
},
metrics: Default::default(), metrics: Default::default(),
gateway: Default::default(), gateway: Default::default(),
mixnode: Default::default(), mixnode: Default::default(),
@@ -55,9 +45,10 @@ impl HttpServerConfig {
network_requester: Default::default(), network_requester: Default::default(),
ip_packet_router: Default::default(), ip_packet_router: Default::default(),
authenticator: Default::default(), authenticator: Default::default(),
lewes_protocol: lewes_protocol::Config { lewes_protocol: Default::default(),
details: signed_lewes_protocol, },
}, v2_config: api::v2::Config {
node: api::v2::node::Config {},
}, },
}, },
} }
@@ -69,24 +60,6 @@ impl HttpServerConfig {
self self
} }
#[must_use]
pub fn with_system_info(mut self, info: HostSystem) -> Self {
self.api.v1_config.node.system_info = Some(info);
self
}
#[must_use]
pub fn with_description(mut self, description: NodeDescription) -> Self {
self.api.v1_config.node.description = description;
self
}
#[must_use]
pub fn with_auxiliary_details(mut self, auxiliary_details: AuxiliaryDetails) -> Self {
self.api.v1_config.node.auxiliary_details = auxiliary_details;
self
}
#[must_use] #[must_use]
pub fn with_gateway_details(mut self, gateway: Gateway) -> Self { pub fn with_gateway_details(mut self, gateway: Gateway) -> Self {
self.api.v1_config.gateway.details = Some(gateway); self.api.v1_config.gateway.details = Some(gateway);
@@ -179,6 +152,10 @@ impl NymNodeRouter {
) )
.merge(landing_page::routes(config.landing)) .merge(landing_page::routes(config.landing))
.nest(routes::API, api::routes(config.api)) .nest(routes::API, api::routes(config.api))
// openapi must be merged at the outer router level (not nested) —
// SwaggerUi emits internal redirects that use absolute paths
// unaware of any `.nest()` prefix
.merge(api::openapi::route())
.layer(axum::middleware::from_fn(logging::log_request_info)) .layer(axum::middleware::from_fn(logging::log_request_info))
.with_state(state), .with_state(state),
} }
@@ -208,20 +185,10 @@ impl NymNodeRouter {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_node_requests::api::SignedData;
use nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol;
use nym_test_utils::helpers::deterministic_rng;
use std::collections::BTreeMap;
#[test] #[test]
fn router_constructs_without_panic() { fn router_constructs_without_panic() {
let mut rng = deterministic_rng(); let config = HttpServerConfig::new();
let signing = ed25519::KeyPair::new(&mut rng);
let x25519_pub: x25519::DHPublicKey = x25519::PrivateKey::new(&mut rng).public_key().into();
let lp = LewesProtocol::new(false, 0, 0, x25519_pub, BTreeMap::new());
let signed = SignedData::new(lp, signing.private_key()).unwrap();
let config = HttpServerConfig::new(signed);
let _ = NymNodeRouter::new(config, AppState::dummy()); let _ = NymNodeRouter::new(config, AppState::dummy());
} }
} }
+32 -1
View File
@@ -4,9 +4,13 @@
use crate::node::http::state::load::CachedNodeLoad; use crate::node::http::state::load::CachedNodeLoad;
use crate::node::http::state::metrics::MetricsAppState; use crate::node::http::state::metrics::MetricsAppState;
use crate::node::key_rotation::active_keys::ActiveSphinxKeys; use crate::node::key_rotation::active_keys::ActiveSphinxKeys;
use nym_bin_common::build_information::BinaryBuildInformationOwned;
use nym_credential_verification::UpgradeModeState; use nym_credential_verification::UpgradeModeState;
use nym_crypto::asymmetric::ed25519; use nym_crypto::asymmetric::ed25519;
use nym_node_metrics::NymNodeMetrics; use nym_node_metrics::NymNodeMetrics;
use nym_node_requests::api::SignedLewesProtocol;
use nym_node_requests::api::v1::node::models::{HostSystem, NodeDescription, NodeRoles};
use nym_node_requests::api::v2::node::models::AuxiliaryDetailsV2;
use nym_noise_keys::VersionedNoiseKeyV1; use nym_noise_keys::VersionedNoiseKeyV1;
use nym_verloc::measurements::SharedVerlocStats; use nym_verloc::measurements::SharedVerlocStats;
use std::net::IpAddr; use std::net::IpAddr;
@@ -23,6 +27,14 @@ pub(crate) struct StaticNodeInformation {
pub(crate) x25519_versioned_noise_key: Option<VersionedNoiseKeyV1>, pub(crate) x25519_versioned_noise_key: Option<VersionedNoiseKeyV1>,
pub(crate) ip_addresses: Vec<IpAddr>, pub(crate) ip_addresses: Vec<IpAddr>,
pub(crate) hostname: Option<String>, pub(crate) hostname: Option<String>,
// TODO: move other fields here too
pub(crate) build_information: BinaryBuildInformationOwned,
pub(crate) system_info: Option<HostSystem>,
pub(crate) roles: NodeRoles,
pub(crate) description: NodeDescription,
pub(crate) auxiliary_data: AuxiliaryDetailsV2,
pub(crate) lewes_protocol: SignedLewesProtocol,
} }
#[derive(Clone)] #[derive(Clone)]
@@ -77,15 +89,34 @@ impl AppState {
#[cfg(test)] #[cfg(test)]
pub(crate) fn dummy() -> Self { pub(crate) fn dummy() -> Self {
use crate::node::key_rotation::key::SphinxPrivateKey; use crate::node::key_rotation::key::SphinxPrivateKey;
use nym_crypto::asymmetric::x25519;
use rand::rngs::OsRng; use rand::rngs::OsRng;
let ed25519_keys = ed25519::KeyPair::new(&mut OsRng); let mut rng = nym_test_utils::helpers::deterministic_rng();
let ed25519_keys = ed25519::KeyPair::new(&mut rng);
let x25519_pub: x25519::DHPublicKey = x25519::PrivateKey::new(&mut rng).public_key().into();
let lp = nym_node_requests::api::v1::lewes_protocol::models::LewesProtocol::new(
false,
0,
0,
x25519_pub,
std::collections::BTreeMap::new(),
);
let signed =
nym_node_requests::api::SignedData::new(lp, ed25519_keys.private_key()).unwrap();
let attester_pk = *ed25519_keys.public_key(); let attester_pk = *ed25519_keys.public_key();
let static_information = StaticNodeInformation { let static_information = StaticNodeInformation {
ed25519_identity_keys: Arc::new(ed25519_keys), ed25519_identity_keys: Arc::new(ed25519_keys),
x25519_versioned_noise_key: None, x25519_versioned_noise_key: None,
ip_addresses: vec![], ip_addresses: vec![],
hostname: None, hostname: None,
build_information: nym_bin_common::bin_info_owned!(),
system_info: None,
roles: Default::default(),
description: Default::default(),
auxiliary_data: Default::default(),
lewes_protocol: signed,
}; };
let active_sphinx = ActiveSphinxKeys::new_fresh(SphinxPrivateKey::new(&mut OsRng, 0)); let active_sphinx = ActiveSphinxKeys::new_fresh(SphinxPrivateKey::new(&mut OsRng, 0));
+42 -24
View File
@@ -45,12 +45,10 @@ use crate::node::routing_filter::{OpenFilter, RoutingFilter};
use crate::node::shared_network::CachedNetwork; use crate::node::shared_network::CachedNetwork;
use crate::node::shared_network::refresher::{NetworkRefresher, NetworkRefresherConfig}; use crate::node::shared_network::refresher::{NetworkRefresher, NetworkRefresherConfig};
use crate::node::shared_network::topology_provider::{CachedTopologyProvider, LocalGatewayNode}; use crate::node::shared_network::topology_provider::{CachedTopologyProvider, LocalGatewayNode};
use nym_bin_common::bin_info; use nym_bin_common::{bin_info, bin_info_owned};
use nym_config::defaults::NymNetworkDetails; use nym_config::defaults::NymNetworkDetails;
use nym_credential_verification::UpgradeModeState; use nym_credential_verification::UpgradeModeState;
use nym_crypto::asymmetric::{ed25519, x25519}; use nym_crypto::asymmetric::{ed25519, x25519};
pub use nym_gateway::node::ActiveClientsStore;
pub use nym_gateway::node::GatewayStorage;
use nym_gateway::node::wireguard::PeerRegistrator; use nym_gateway::node::wireguard::PeerRegistrator;
use nym_gateway::node::{GatewayTasksBuilder, UpgradeModeCheckRequestSender}; use nym_gateway::node::{GatewayTasksBuilder, UpgradeModeCheckRequestSender};
use nym_kkt::key_utils::{ use nym_kkt::key_utils::{
@@ -69,15 +67,18 @@ use nym_node_metrics::NymNodeMetrics;
use nym_node_metrics::events::MetricEventsSender; use nym_node_metrics::events::MetricEventsSender;
use nym_node_requests::api::SignedData; use nym_node_requests::api::SignedData;
use nym_node_requests::api::v1::lewes_protocol::models::{LPHashFunction, LPKEM, LewesProtocol}; use nym_node_requests::api::v1::lewes_protocol::models::{LPHashFunction, LPKEM, LewesProtocol};
use nym_node_requests::api::v1::node::models::{AnnouncePorts, NodeDescription}; use nym_node_requests::api::v1::node::models::{AnnouncePorts, NodeDescription, NodeRoles};
use nym_noise::config::{NetworkMonitorAgentNode, NoiseConfig, NoiseNetworkView}; use nym_noise::config::{NetworkMonitorAgentNode, NoiseConfig, NoiseNetworkView};
use nym_noise_keys::VersionedNoiseKeyV1; use nym_noise_keys::VersionedNoiseKeyV1;
use nym_sphinx_acknowledgements::AckKey; use nym_sphinx_acknowledgements::AckKey;
use nym_sphinx_addressing::Recipient; use nym_sphinx_addressing::Recipient;
use nym_task::{ShutdownManager, ShutdownToken, ShutdownTracker}; use nym_task::{ShutdownManager, ShutdownToken, ShutdownTracker};
use nym_validator_client::nyxd::AccountId;
use nym_validator_client::nyxd::contract_traits::PagedNetworkMonitorsQueryClient; use nym_validator_client::nyxd::contract_traits::PagedNetworkMonitorsQueryClient;
use nym_validator_client::nyxd::error::NyxdError;
use nym_validator_client::nyxd::nym_network_monitors_contract_common::AuthorisedNetworkMonitor; use nym_validator_client::nyxd::nym_network_monitors_contract_common::AuthorisedNetworkMonitor;
use nym_validator_client::{QueryHttpRpcNyxdClient, UserAgent}; use nym_validator_client::signing::signer::OfflineSigner;
use nym_validator_client::{DirectSecp256k1HdWallet, QueryHttpRpcNyxdClient, UserAgent};
use nym_verloc::measurements::SharedVerlocStats; use nym_verloc::measurements::SharedVerlocStats;
use nym_verloc::{self, measurements::VerlocMeasurer}; use nym_verloc::{self, measurements::VerlocMeasurer};
use nym_wireguard::{WireguardGatewayData, peer_controller::PeerControlRequest}; use nym_wireguard::{WireguardGatewayData, peer_controller::PeerControlRequest};
@@ -95,6 +96,9 @@ use tokio_util::sync::WaitForCancellationFutureOwned;
use tracing::{debug, error, info, trace}; use tracing::{debug, error, info, trace};
use zeroize::Zeroizing; use zeroize::Zeroizing;
pub use nym_gateway::node::ActiveClientsStore;
pub use nym_gateway::node::GatewayStorage;
pub mod bonding_information; pub mod bonding_information;
pub mod description; pub mod description;
pub mod helpers; pub mod helpers;
@@ -892,12 +896,27 @@ impl NymNode {
.collect() .collect()
} }
fn node_chain_address(&self) -> Result<AccountId, NymNodeError> {
let network_details = NymNetworkDetails::new_from_env();
// derive the address (annoyingly, this will derive our private keys that we will rederive
// when starting the gateway, but changing this behaviour requires too much refactoring)
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(
&network_details.chain_details.bech32_account_prefix,
(**self.entry_gateway.mnemonic).clone(),
)
.map_err(NyxdError::from)?;
Ok(wallet.get_accounts()[0].address.clone())
}
pub(crate) async fn build_http_server( pub(crate) async fn build_http_server(
&self, &self,
shutdown: WaitForCancellationFutureOwned, shutdown: WaitForCancellationFutureOwned,
) -> Result<NymNodeHttpServer, NymNodeError> { ) -> Result<NymNodeHttpServer, NymNodeError> {
let auxiliary_details = api_requests::v1::node::models::AuxiliaryDetails { let auxiliary_data = api_requests::v2::node::models::AuxiliaryDetailsV2 {
location: self.config.host.location, location: self.config.host.location,
address: self.node_chain_address()?.to_string(),
announce_ports: AnnouncePorts { announce_ports: AnnouncePorts {
verloc_port: self.config.verloc.announce_port, verloc_port: self.config.verloc.announce_port,
mix_port: self.config.mixnet.announce_port, mix_port: self.config.mixnet.announce_port,
@@ -982,7 +1001,7 @@ impl NymNode {
let signed_lewes_protocol = let signed_lewes_protocol =
SignedData::new(lewes_protocol, self.ed25519_identity_keys.private_key()).unwrap(); SignedData::new(lewes_protocol, self.ed25519_identity_keys.private_key()).unwrap();
let mut config = HttpServerConfig::new(signed_lewes_protocol) let mut config = HttpServerConfig::new()
.with_landing_page_assets(self.config.http.landing_page_assets_path.as_ref()) .with_landing_page_assets(self.config.http.landing_page_assets_path.as_ref())
.with_mixnode_details(mixnode_details) .with_mixnode_details(mixnode_details)
.with_gateway_details(gateway_details) .with_gateway_details(gateway_details)
@@ -990,28 +1009,16 @@ impl NymNode {
.with_ip_packet_router_details(ipr_details) .with_ip_packet_router_details(ipr_details)
.with_authenticator_details(auth_details) .with_authenticator_details(auth_details)
.with_used_exit_policy(exit_policy_details) .with_used_exit_policy(exit_policy_details)
.with_description(self.description.clone())
.with_auxiliary_details(auxiliary_details)
.with_prometheus_bearer_token(self.config.http.access_token.clone()); .with_prometheus_bearer_token(self.config.http.access_token.clone());
if self.config.http.expose_system_info { let system_info = if self.config.http.expose_system_info {
config = config.with_system_info(get_system_info( Some(get_system_info(
self.config.http.expose_system_hardware, self.config.http.expose_system_hardware,
self.config.http.expose_crypto_hardware, self.config.http.expose_crypto_hardware,
)) ))
} } else {
if self.config.modes.mixnode { None
config.api.v1_config.node.roles.mixnode_enabled = true; };
}
if self.config.modes.entry {
config.api.v1_config.node.roles.gateway_enabled = true
}
if self.config.modes.exit {
config.api.v1_config.node.roles.network_requester_enabled = true;
config.api.v1_config.node.roles.ip_packet_router_enabled = true;
}
if let Some(path) = &self.config.gateway_tasks.storage_paths.bridge_client_params { if let Some(path) = &self.config.gateway_tasks.storage_paths.bridge_client_params {
config = config.with_bridge_client_params_file(path); config = config.with_bridge_client_params_file(path);
@@ -1032,6 +1039,17 @@ impl NymNode {
x25519_versioned_noise_key, x25519_versioned_noise_key,
ip_addresses: self.config.host.public_ips.clone(), ip_addresses: self.config.host.public_ips.clone(),
hostname: self.config.host.hostname.clone(), hostname: self.config.host.hostname.clone(),
build_information: bin_info_owned!(),
system_info,
roles: NodeRoles {
mixnode_enabled: self.config.modes.mixnode,
gateway_enabled: self.config.modes.entry,
network_requester_enabled: self.config.modes.exit,
ip_packet_router_enabled: self.config.modes.exit,
},
description: self.description.clone(),
auxiliary_data,
lewes_protocol: signed_lewes_protocol,
}, },
self.active_sphinx_keys()?.clone(), self.active_sphinx_keys()?.clone(),
self.metrics.clone(), self.metrics.clone(),