From c2893e5ca0ec97d3bd617b5d1806a089e94d1bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 22 Sep 2023 16:56:19 +0100 Subject: [PATCH] running the api as a proper task in gateway 'run' --- common/pemstore/src/lib.rs | 6 +- gateway/src/commands/upgrade_helpers.rs | 30 +- gateway/src/config/mod.rs | 23 +- gateway/src/config/old_config_v1_1_28.rs | 29 +- gateway/src/config/old_config_v1_1_29.rs | 264 ++++++++++++++++++ gateway/src/config/template.rs | 5 + gateway/src/error.rs | 5 + gateway/src/http/mod.rs | 96 +++++++ gateway/src/main.rs | 63 +---- gateway/src/node/mod.rs | 7 + nym-node/Cargo.toml | 2 + nym-node/src/http/mod.rs | 62 +++- nym-node/src/http/router/api/mod.rs | 6 + .../router/api/v1/network_requester/types.rs | 3 + nym-node/src/http/router/api/v1/roles.rs | 2 +- nym-node/src/http/router/mod.rs | 56 +++- 16 files changed, 577 insertions(+), 82 deletions(-) create mode 100644 gateway/src/config/old_config_v1_1_29.rs diff --git a/common/pemstore/src/lib.rs b/common/pemstore/src/lib.rs index 51b779be29..e883ed60c0 100644 --- a/common/pemstore/src/lib.rs +++ b/common/pemstore/src/lib.rs @@ -51,7 +51,11 @@ where if T::pem_type() != key_pem.tag { return Err(io::Error::new( io::ErrorKind::Other, - "unexpected key pem tag", + format!( + "unexpected key pem tag. Got '{}', expected: '{}'", + key_pem.tag, + T::pem_type() + ), )); } diff --git a/gateway/src/commands/upgrade_helpers.rs b/gateway/src/commands/upgrade_helpers.rs index d8555ca215..7b13cc3f53 100644 --- a/gateway/src/commands/upgrade_helpers.rs +++ b/gateway/src/commands/upgrade_helpers.rs @@ -3,6 +3,7 @@ use crate::config::old_config_v1_1_20::ConfigV1_1_20; use crate::config::old_config_v1_1_28::ConfigV1_1_28; +use crate::config::old_config_v1_1_29::ConfigV1_1_29; use crate::config::{default_config_filepath, Config}; use crate::error::GatewayError; use log::info; @@ -20,7 +21,8 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result { info!("It is going to get updated to the current specification."); let updated_step1: ConfigV1_1_28 = old_config.into(); - let updated: Config = updated_step1.into(); + let updated_step2: ConfigV1_1_29 = updated_step1.into(); + let updated: Config = updated_step2.into(); updated .save_to_default_location() .map_err(|err| GatewayError::ConfigSaveFailure { @@ -42,6 +44,29 @@ fn try_upgrade_v1_1_28_config(id: &str) -> Result { info!("It seems the gateway is using <= v1.1.28 config template."); info!("It is going to get updated to the current specification."); + let updated_step1: ConfigV1_1_29 = old_config.into(); + let updated: Config = updated_step1.into(); + updated + .save_to_default_location() + .map_err(|err| GatewayError::ConfigSaveFailure { + path: default_config_filepath(id), + id: id.to_string(), + source: err, + })?; + + Ok(true) +} + +fn try_upgrade_v1_1_29_config(id: &str) -> Result { + // explicitly load it as v1.1.29 (which is incompatible with the current, i.e. 1.1.30+) + let Ok(old_config) = ConfigV1_1_29::read_from_default_path(id) else { + // if we failed to load it, there might have been nothing to upgrade + // or maybe it was an even older file. in either way. just ignore it and carry on with our day + return Ok(false); + }; + info!("It seems the gateway is using <= v1.1.29 config template."); + info!("It is going to get updated to the current specification."); + let updated: Config = old_config.into(); updated .save_to_default_location() @@ -61,6 +86,9 @@ pub(crate) fn try_upgrade_config(id: &str) -> Result<(), GatewayError> { if try_upgrade_v1_1_28_config(id)? { return Ok(()); } + if try_upgrade_v1_1_29_config(id)? { + return Ok(()); + } Ok(()) } diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 23620953d6..a3975aaa41 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -14,7 +14,7 @@ use nym_config::{ use nym_network_defaults::mainnet; use serde::{Deserialize, Serialize}; use std::io; -use std::net::IpAddr; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::{Path, PathBuf}; use std::time::Duration; use url::Url; @@ -22,6 +22,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop}; pub(crate) mod old_config_v1_1_20; pub(crate) mod old_config_v1_1_28; +pub(crate) mod old_config_v1_1_29; pub mod persistence; mod template; @@ -71,6 +72,9 @@ pub struct Config { #[serde(skip)] pub(crate) save_path: Option, + #[serde(default)] + pub http: Http, + pub gateway: Gateway, pub storage_paths: GatewayPaths, @@ -94,6 +98,7 @@ impl Config { pub fn new>(id: S) -> Self { Config { save_path: None, + http: Default::default(), gateway: Gateway::new_default(id.as_ref()), storage_paths: GatewayPaths::new_default(id.as_ref()), network_requester: Default::default(), @@ -219,6 +224,22 @@ impl Config { } } +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct Http { + /// Socket address this node will use for binding its http API. + /// default: `0.0.0.0:80` + pub bind_address: SocketAddr, +} + +impl Default for Http { + fn default() -> Self { + Http { + bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 80), + } + } +} + // we only really care about the mnemonic being zeroized #[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Zeroize, ZeroizeOnDrop)] pub struct Gateway { diff --git a/gateway/src/config/old_config_v1_1_28.rs b/gateway/src/config/old_config_v1_1_28.rs index 7639d860f9..dff9cc896c 100644 --- a/gateway/src/config/old_config_v1_1_28.rs +++ b/gateway/src/config/old_config_v1_1_28.rs @@ -1,9 +1,10 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::persistence::paths::{GatewayPaths, KeysPaths}; -use crate::config::{Config, Debug, Gateway}; -use nym_bin_common::logging::LoggingSettings; +use crate::config::old_config_v1_1_29::{ + ConfigV1_1_29, DebugV1_1_29, GatewayPathsV1_1_29, GatewayV1_1_29, KeysPathsV1_1_29, + LoggingSettingsV1_1_29, +}; use nym_config::{ must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR, }; @@ -84,11 +85,13 @@ impl ConfigV1_1_28 { } } -impl From for Config { +impl From for ConfigV1_1_29 { fn from(value: ConfigV1_1_28) -> Self { - Config { + ConfigV1_1_29 { + // \/ ADDED save_path: None, - gateway: Gateway { + // /\ ADDED + gateway: GatewayV1_1_29 { version: value.gateway.version, id: value.gateway.id, only_coconut_credentials: value.gateway.only_coconut_credentials, @@ -101,19 +104,25 @@ impl From for Config { statistics_service_url: value.gateway.statistics_service_url, cosmos_mnemonic: value.gateway.cosmos_mnemonic, }, - storage_paths: GatewayPaths { - keys: KeysPaths { + storage_paths: GatewayPathsV1_1_29 { + keys: KeysPathsV1_1_29 { private_identity_key_file: value.storage_paths.keys.private_identity_key_file, public_identity_key_file: value.storage_paths.keys.public_identity_key_file, private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file, public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file, }, clients_storage: value.storage_paths.clients_storage, + + // \/ ADDED network_requester_config: None, + // /\ ADDED }, + + // \/ ADDED network_requester: Default::default(), - logging: LoggingSettings {}, - debug: Debug { + // /\ ADDED + logging: LoggingSettingsV1_1_29 {}, + debug: DebugV1_1_29 { packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff, packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff, initial_connection_timeout: value.debug.initial_connection_timeout, diff --git a/gateway/src/config/old_config_v1_1_29.rs b/gateway/src/config/old_config_v1_1_29.rs new file mode 100644 index 0000000000..49f305f0e9 --- /dev/null +++ b/gateway/src/config/old_config_v1_1_29.rs @@ -0,0 +1,264 @@ +// Copyright 2020-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::paths::{GatewayPaths, KeysPaths}; +use crate::config::{Config, Debug, Gateway, NetworkRequester}; +use nym_bin_common::logging::LoggingSettings; +use nym_config::{ + must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR, +}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::io; +use std::net::IpAddr; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use url::Url; + +const DEFAULT_GATEWAYS_DIR: &str = "gateways"; + +// 'DEBUG' +// where applicable, the below are defined in milliseconds +const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000); +const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000); +const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000); +const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); +const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000; + +const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; +const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100; + +/// Derive default path to gateway's config directory. +/// It should get resolved to `$HOME/.nym/gateways//config` +pub fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_GATEWAYS_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) +} + +/// Derive default path to gateways's config file. +/// It should get resolved to `$HOME/.nym/gateways//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct KeysPathsV1_1_29 { + pub private_identity_key_file: PathBuf, + pub public_identity_key_file: PathBuf, + pub private_sphinx_key_file: PathBuf, + pub public_sphinx_key_file: PathBuf, +} + +fn de_maybe_path<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let path = PathBuf::deserialize(deserializer)?; + if path.as_os_str().is_empty() { + Ok(None) + } else { + Ok(Some(path)) + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct GatewayPathsV1_1_29 { + pub keys: KeysPathsV1_1_29, + + #[serde(alias = "persistent_storage")] + pub clients_storage: PathBuf, + + #[serde(deserialize_with = "de_maybe_path")] + pub network_requester_config: Option, +} + +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct LoggingSettingsV1_1_29 {} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_29 { + #[serde(skip)] + pub save_path: Option, + + pub gateway: GatewayV1_1_29, + + pub storage_paths: GatewayPathsV1_1_29, + + pub network_requester: NetworkRequesterV1_1_29, + + #[serde(default)] + pub logging: LoggingSettingsV1_1_29, + + #[serde(default)] + pub debug: DebugV1_1_29, +} + +impl ConfigV1_1_29 { + pub fn read_from_default_path>(id: P) -> io::Result { + read_config_from_toml_file(default_config_filepath(id)) + } +} + +impl From for Config { + fn from(value: ConfigV1_1_29) -> Self { + Config { + save_path: value.save_path, + + // \/ ADDED + http: Default::default(), + // /\ ADDED + gateway: Gateway { + version: value.gateway.version, + id: value.gateway.id, + only_coconut_credentials: value.gateway.only_coconut_credentials, + listening_address: value.gateway.listening_address, + mix_port: value.gateway.mix_port, + clients_port: value.gateway.clients_port, + enabled_statistics: value.gateway.enabled_statistics, + nym_api_urls: value.gateway.nym_api_urls, + nyxd_urls: value.gateway.nyxd_urls, + statistics_service_url: value.gateway.statistics_service_url, + cosmos_mnemonic: value.gateway.cosmos_mnemonic, + }, + storage_paths: GatewayPaths { + keys: KeysPaths { + private_identity_key_file: value.storage_paths.keys.private_identity_key_file, + public_identity_key_file: value.storage_paths.keys.public_identity_key_file, + private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file, + public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file, + }, + clients_storage: value.storage_paths.clients_storage, + network_requester_config: value.storage_paths.network_requester_config, + }, + network_requester: NetworkRequester { + enabled: value.network_requester.enabled, + }, + logging: LoggingSettings {}, + debug: Debug { + packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff, + initial_connection_timeout: value.debug.initial_connection_timeout, + maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size, + presence_sending_delay: value.debug.presence_sending_delay, + stored_messages_filename_length: value.debug.stored_messages_filename_length, + message_retrieval_limit: value.debug.message_retrieval_limit, + use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version, + }, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct GatewayV1_1_29 { + /// Version of the gateway for which this configuration was created. + pub version: String, + + /// ID specifies the human readable ID of this particular gateway. + pub id: String, + + /// Indicates whether this gateway is accepting only coconut credentials for accessing the + /// the mixnet, or if it also accepts non-paying clients + #[serde(default)] + pub only_coconut_credentials: bool, + + /// Address to which this mixnode will bind to and will be listening for packets. + pub listening_address: IpAddr, + + /// Port used for listening for all mixnet traffic. + /// (default: 1789) + pub mix_port: u16, + + /// Port used for listening for all client-related traffic. + /// (default: 9000) + pub clients_port: u16, + + /// Whether gateway collects and sends anonymized statistics + pub enabled_statistics: bool, + + /// Domain address of the statistics service + pub statistics_service_url: Url, + + /// Addresses to APIs from which the node gets the view of the network. + #[serde(alias = "validator_api_urls")] + pub nym_api_urls: Vec, + + /// Addresses to validators which the node uses to check for double spending of ERC20 tokens. + #[serde(alias = "validator_nymd_urls")] + pub nyxd_urls: Vec, + + /// Mnemonic of a cosmos wallet used in checking for double spending. + // #[deprecated(note = "move to storage")] + // TODO: I don't think this should be stored directly in the config... + pub cosmos_mnemonic: bip39::Mnemonic, +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct NetworkRequesterV1_1_29 { + pub enabled: bool, +} + +#[allow(clippy::derivable_impls)] +impl Default for NetworkRequesterV1_1_29 { + fn default() -> Self { + NetworkRequesterV1_1_29 { enabled: false } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct DebugV1_1_29 { + /// Initial value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + #[serde(with = "humantime_serde")] + pub packet_forwarding_initial_backoff: Duration, + + /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when + /// forwarding sphinx packets. + #[serde(with = "humantime_serde")] + pub packet_forwarding_maximum_backoff: Duration, + + /// Timeout for establishing initial connection when trying to forward a sphinx packet. + #[serde(with = "humantime_serde")] + pub initial_connection_timeout: Duration, + + /// Maximum number of packets that can be stored waiting to get sent to a particular connection. + pub maximum_connection_buffer_size: usize, + + /// Delay between each subsequent presence data being sent. + #[serde(with = "humantime_serde")] + pub presence_sending_delay: Duration, + + /// Length of filenames for new client messages. + pub stored_messages_filename_length: u16, + + /// Number of messages from offline client that can be pulled at once from the storage. + pub message_retrieval_limit: i64, + + /// Specifies whether the mixnode should be using the legacy framing for the sphinx packets. + // it's set to true by default. The reason for that decision is to preserve compatibility with the + // existing nodes whilst everyone else is upgrading and getting the code for handling the new field. + // It shall be disabled in the subsequent releases. + pub use_legacy_framed_packet_version: bool, +} + +impl Default for DebugV1_1_29 { + fn default() -> Self { + DebugV1_1_29 { + packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF, + packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF, + initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT, + presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY, + maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, + stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH, + message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT, + use_legacy_framed_packet_version: false, + } + } +} diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index 7e7563edf9..1bd228a9aa 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -55,6 +55,11 @@ nyxd_urls = [ cosmos_mnemonic = '{{ gateway.cosmos_mnemonic }}' +[http] +# Socket address this node will use for binding its http API. +# default: `0.0.0.0:80` +bind_address = '{{ http.bind_address }}' + [network_requester] # Specifies whether network requester service is enabled in this process. enabled = {{ network_requester.enabled }} diff --git a/gateway/src/error.rs b/gateway/src/error.rs index 80fee0a7fa..a887e61b32 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -106,6 +106,11 @@ pub(crate) enum GatewayError { #[from] source: NyxdError, }, + + // TODO: in the future this should work the other way, i.e. NymNode depending on Gateway errors + #[error(transparent)] + NymNodeError(#[from] nym_node::error::NymNodeError), + #[error("Error verifying hmac digest")] HmacDigestError { #[from] diff --git a/gateway/src/http/mod.rs b/gateway/src/http/mod.rs index 38dc36ebc3..614d649f79 100644 --- a/gateway/src/http/mod.rs +++ b/gateway/src/http/mod.rs @@ -1,2 +1,98 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 + +use crate::config::Config; +use crate::error::GatewayError; +use crate::node::helpers::load_public_key; +use nym_bin_common::bin_info_owned; +use nym_crypto::asymmetric::{encryption, identity}; +use nym_node::http; +use nym_sphinx::addressing::clients::Recipient; +use nym_task::TaskClient; + +fn load_gateway_details( + config: &Config, +) -> Result { + let identity_public_key: identity::PublicKey = load_public_key( + &config.storage_paths.keys.public_identity_key_file, + "gateway identity", + )?; + + let sphinx_public_key: encryption::PublicKey = load_public_key( + &config.storage_paths.keys.public_sphinx_key_file, + "gateway sphinx", + )?; + + Ok(http::api::v1::gateway::types::Gateway { + encoded_identity_key: identity_public_key.to_base58_string(), + encoded_sphinx_key: sphinx_public_key.to_base58_string(), + }) +} + +fn load_network_requester_details( + config: &Config, + network_requester_config: &nym_network_requester::Config, +) -> Result { + let identity_public_key: identity::PublicKey = load_public_key( + &network_requester_config + .storage_paths + .common_paths + .keys + .public_identity_key_file, + "network requester identity", + )?; + + let dh_public_key: encryption::PublicKey = load_public_key( + &network_requester_config + .storage_paths + .common_paths + .keys + .public_encryption_key_file, + "network requester diffie hellman", + )?; + + let gateway_identity_public_key: identity::PublicKey = load_public_key( + &config.storage_paths.keys.public_identity_key_file, + "gateway identity", + )?; + + Ok(http::api::v1::network_requester::types::NetworkRequester { + encoded_identity_key: identity_public_key.to_base58_string(), + encoded_sphinx_key: dh_public_key.to_base58_string(), + address: Recipient::new( + identity_public_key, + dh_public_key, + gateway_identity_public_key, + ) + .to_string(), + }) +} + +pub(crate) fn start_http_api( + gateway_config: &Config, + network_requester_config: Option<&nym_network_requester::Config>, + task_client: TaskClient, +) -> Result<(), GatewayError> { + // is it suboptimal to load all the keys, etc for the second time after they've already been + // retrieved during startup of the rest of the components? + // yes, a bit. + // but in the grand scheme of things performance penalty is negligible since it's only happening on startup + // and makes the code a bit nicer to manage. on top of it, all of it will refactored anyway at some point + // (famous last words, eh? - 22.09.23) + + let mut config = nym_node::http::Config::new(bin_info_owned!()) + .with_gateway(load_gateway_details(gateway_config)?); + + if let Some(nr_config) = network_requester_config { + config = config + .with_network_requester(load_network_requester_details(gateway_config, nr_config)?) + } + + let router = nym_node::http::NymNodeRouter::new(config); + + let server = router + .build_server(&gateway_config.http.bind_address)? + .with_task_client(task_client); + tokio::spawn(async move { server.run().await }); + Ok(()) +} diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 96a0138008..16ef053776 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -8,13 +8,11 @@ use clap::{crate_name, crate_version, Parser}; use colored::Colorize; use lazy_static::lazy_static; use log::error; +use nym_bin_common::bin_info; use nym_bin_common::logging::{maybe_print_banner, setup_logging}; use nym_bin_common::output_format::OutputFormat; -use nym_bin_common::{bin_info, bin_info_owned}; use nym_network_defaults::setup_env; -use nym_node::http::api::v1::roles::NodeRoles; use std::error::Error; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; mod commands; mod config; @@ -51,54 +49,21 @@ struct Cli { async fn main() -> Result<(), Box> { setup_logging(); - let config = nym_node::http::Config { - landing: Default::default(), - policy: Default::default(), - api: nym_node::http::api::Config { - v1_config: nym_node::http::api::v1::Config { - build_information: bin_info_owned!(), - roles: NodeRoles { - mixnode_enabled: false, - gateway_enabled: false, - network_requester_enabled: false, - }, - gateway: nym_node::http::api::v1::gateway::Config { - details: Some(nym_node::http::api::v1::gateway::types::Gateway { - encoded_identity_key: "aaa".to_string(), - encoded_sphinx_key: "bbb".to_string(), - }), - }, - mixnode: Default::default(), - network_requester: Default::default(), - }, - }, - }; - let mut router = nym_node::http::NymNodeRouter::new(config); + let args = Cli::parse(); + setup_env(args.config_env_file.as_ref()); - let address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 12345); - let server = router.build_server(&address)?; - server.await?; + if !args.no_banner { + maybe_print_banner(crate_name!(), crate_version!()); + } - Ok(()) - - // - // - // let args = Cli::parse(); - // setup_env(args.config_env_file.as_ref()); - // - // if !args.no_banner { - // maybe_print_banner(crate_name!(), crate_version!()); - // } - // setup_logging(); - // - // commands::execute(args).await.map_err(|err| { - // if atty::is(atty::Stream::Stdout) { - // let error_message = format!("{err}").red(); - // error!("{error_message}"); - // error!("Exiting..."); - // } - // err - // }) + commands::execute(args).await.map_err(|err| { + if atty::is(atty::Stream::Stdout) { + let error_message = format!("{err}").red(); + error!("{error_message}"); + error!("Exiting..."); + } + err + }) } #[cfg(test)] diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 132671c8a1..f1b803973e 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -6,6 +6,7 @@ use self::storage::PersistentStorage; use crate::commands::helpers::{override_network_requester_config, OverrideNetworkRequesterConfig}; use crate::config::Config; use crate::error::GatewayError; +use crate::http::start_http_api; use crate::node::client_handling::active_clients::ActiveClientsStore; use crate::node::client_handling::embedded_network_requester::{ LocalNetworkRequesterHandle, MessageRouter, @@ -333,6 +334,12 @@ impl Gateway { CoconutVerifier::new(nyxd_client) }; + start_http_api( + &self.config, + self.network_requester_opts.as_ref().map(|o| &o.config), + shutdown.subscribe().named("http-api"), + )?; + let mix_forwarding_channel = self.start_packet_forwarder(shutdown.subscribe().named("PacketForwarder")); diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 86acfc788a..51b9664235 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -19,6 +19,7 @@ serde_yaml = "0.9.25" serde_json = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } +tokio = { workspace = true, features = ["macros"] } # HTTP API: axum = { workspace = true } @@ -38,3 +39,4 @@ utoipa-swagger-ui = { version = "3.1.5", features = ["axum"] } nym-bin-common = { path = "../common/bin-common", features = ["openapi"] } +nym-task = { path = "../common/task" } diff --git a/nym-node/src/http/mod.rs b/nym-node/src/http/mod.rs index db642a1c85..61f85486fe 100644 --- a/nym-node/src/http/mod.rs +++ b/nym-node/src/http/mod.rs @@ -1,19 +1,59 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use axum::routing::IntoMakeService; +use axum::Router; +use hyper::server::conn::AddrIncoming; +use hyper::Server; +use nym_task::TaskClient; +use tracing::{debug, error, info}; + pub mod middleware; pub mod router; pub mod state; -pub use router::{Config, NymNodeRouter, api, landing_page, policy}; +pub use router::{api, landing_page, policy, Config, NymNodeRouter}; -// pub struct Config { -// router_config: router::Config, -// bind_address: SocketAddr, -// } -// -// async fn run(config: Config) -> Result<(), NymNodeError> { -// NymNodeRouter::new(config.router_config) -// .start_server(&config.bind_address) -// .await -// } +pub struct NymNodeHTTPServer { + task_client: Option, + inner: Server>, +} + +impl NymNodeHTTPServer { + pub(crate) fn new(inner: Server>) -> Self { + NymNodeHTTPServer { + task_client: None, + inner, + } + } + + #[must_use] + pub fn with_task_client(mut self, task_client: TaskClient) -> Self { + self.task_client = Some(task_client); + self + } + + async fn run_server_forever(server: Server>) { + if let Err(err) = server.await { + error!("the HTTP server has terminated with the error: {err}"); + } else { + error!("the HTTP server has terminated with producing any errors"); + } + } + + pub async fn run(self) { + info!("Started NymNodeHTTPServer on {}", self.inner.local_addr()); + if let Some(mut task_client) = self.task_client { + tokio::select! { + _ = task_client.recv_with_delay() => { + debug!("NymNodeHTTPServer: Received shutdown"); + } + _ = Self::run_server_forever(self.inner) => { } + } + } else { + Self::run_server_forever(self.inner).await + } + + debug!("NymNodeHTTPServer: Exiting"); + } +} diff --git a/nym-node/src/http/router/api/mod.rs b/nym-node/src/http/router/api/mod.rs index 2b914b0eab..5d626a7844 100644 --- a/nym-node/src/http/router/api/mod.rs +++ b/nym-node/src/http/router/api/mod.rs @@ -26,6 +26,12 @@ pub(super) fn routes(config: Config) -> Router { // .nest(routes::SWAGGER, openapi::route()) } +// #[derive(Debug, Clone, ToSchema)] +// pub struct SignedResponse{ +// encoded_signature: String, +// data: T, +// } + #[derive(Debug, Clone, ToSchema)] pub enum FormattedResponse { Json(Json), diff --git a/nym-node/src/http/router/api/v1/network_requester/types.rs b/nym-node/src/http/router/api/v1/network_requester/types.rs index 5d1277cf1f..af84874fbc 100644 --- a/nym-node/src/http/router/api/v1/network_requester/types.rs +++ b/nym-node/src/http/router/api/v1/network_requester/types.rs @@ -11,4 +11,7 @@ pub struct NetworkRequester { /// Base58-encoded x25519 public key used for performing key exchange with remote clients. pub encoded_sphinx_key: String, + + /// Nym address of this network requester. + pub address: String, } diff --git a/nym-node/src/http/router/api/v1/roles.rs b/nym-node/src/http/router/api/v1/roles.rs index 66617d80b5..6139f01d01 100644 --- a/nym-node/src/http/router/api/v1/roles.rs +++ b/nym-node/src/http/router/api/v1/roles.rs @@ -6,7 +6,7 @@ use axum::extract::Query; use serde::Serialize; use utoipa::ToSchema; -#[derive(Clone, Debug, Copy, ToSchema, Serialize)] +#[derive(Clone, Default, Debug, Copy, ToSchema, Serialize)] pub struct NodeRoles { pub mixnode_enabled: bool, pub gateway_enabled: bool, diff --git a/nym-node/src/http/router/mod.rs b/nym-node/src/http/router/mod.rs index f480eeb56c..8676e9f8fb 100644 --- a/nym-node/src/http/router/mod.rs +++ b/nym-node/src/http/router/mod.rs @@ -2,12 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::NymNodeError; +use crate::http::api::v1::gateway::types::Gateway; +use crate::http::api::v1::mixnode::types::Mixnode; +use crate::http::api::v1::network_requester::types::NetworkRequester; use crate::http::middleware::logging; use crate::http::state::AppState; -use axum::routing::IntoMakeService; +use crate::http::NymNodeHTTPServer; use axum::Router; -use hyper::server::conn::AddrIncoming; -use hyper::Server; +use nym_bin_common::build_information::BinaryBuildInformationOwned; use std::net::SocketAddr; pub mod api; @@ -21,9 +23,6 @@ pub(crate) mod routes { pub(crate) const API: &str = "/api"; } -// TODO: can it be made nicer? -pub type NymNodeHTTPServer = Server>; - #[derive(Debug, Clone)] pub struct Config { pub landing: landing_page::Config, @@ -31,6 +30,45 @@ pub struct Config { pub api: api::Config, } +impl Config { + pub fn new(binary_info: BinaryBuildInformationOwned) -> Self { + Config { + landing: Default::default(), + policy: Default::default(), + api: api::Config { + v1_config: api::v1::Config { + build_information: binary_info, + roles: Default::default(), + gateway: Default::default(), + mixnode: Default::default(), + network_requester: Default::default(), + }, + }, + } + } + + #[must_use] + pub fn with_gateway(mut self, gateway: Gateway) -> Self { + self.api.v1_config.roles.gateway_enabled = true; + self.api.v1_config.gateway.details = Some(gateway); + self + } + + #[must_use] + pub fn with_mixnode(mut self, mixnode: Mixnode) -> Self { + self.api.v1_config.roles.mixnode_enabled = true; + self.api.v1_config.mixnode.details = Some(mixnode); + self + } + + #[must_use] + pub fn with_network_requester(mut self, network_requester: NetworkRequester) -> Self { + self.api.v1_config.roles.network_requester_enabled = true; + self.api.v1_config.network_requester.details = Some(network_requester); + self + } +} + pub struct NymNodeRouter { inner: Router, } @@ -60,11 +98,13 @@ impl NymNodeRouter { self, bind_address: &SocketAddr, ) -> Result { - Ok(axum::Server::try_bind(bind_address) + let axum_server = axum::Server::try_bind(bind_address) .map_err(|source| NymNodeError::HttpBindFailure { bind_address: *bind_address, source, })? - .serve(self.inner.into_make_service())) + .serve(self.inner.into_make_service()); + + Ok(NymNodeHTTPServer::new(axum_server)) } }