From ccd8b3f75abc114b2ea6a821bf7c31b355129f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 30 Oct 2023 11:15:11 +0000 Subject: [PATCH] config migrations --- mixnode/src/commands/mod.rs | 38 ++-- mixnode/src/commands/upgrade_helpers.rs | 66 ++++++ mixnode/src/config/mod.rs | 62 +++++- mixnode/src/config/old_config_v1_1_21.rs | 25 +-- mixnode/src/config/old_config_v1_1_32.rs | 263 +++++++++++++++++++++++ mixnode/src/config/template.rs | 25 ++- mixnode/src/error.rs | 20 ++ mixnode/src/node/http/mod.rs | 9 +- mixnode/src/node/mod.rs | 2 +- nym-node/src/config/mod.rs | 1 + 10 files changed, 450 insertions(+), 61 deletions(-) create mode 100644 mixnode/src/commands/upgrade_helpers.rs create mode 100644 mixnode/src/config/old_config_v1_1_32.rs diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index abbecb5522f..c2d33d25eb6 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -1,9 +1,9 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::old_config_v1_1_21::ConfigV1_1_21; +use crate::config::default_config_filepath; +use crate::error::MixnodeError; use crate::{config::Config, Cli}; -use anyhow::anyhow; use clap::CommandFactory; use clap::Subcommand; use colored::Colorize; @@ -22,6 +22,7 @@ mod init; mod node_details; mod run; mod sign; +mod upgrade_helpers; #[derive(Subcommand)] pub(crate) enum Commands { @@ -131,32 +132,19 @@ pub(crate) fn version_check(cfg: &Config) -> bool { } } -fn try_upgrade_v1_1_21_config(id: &str) -> std::io::Result<()> { - use nym_config::legacy_helpers::nym_config::MigrationNymConfig; - // explicitly load it as v1.1.21 (which is incompatible with the current, i.e. 1.1.22+) - let Ok(old_config) = ConfigV1_1_21::load_from_file(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(()); - }; - info!("It seems the mixnode is using <= v1.1.21 config template."); - info!("It is going to get updated to the current specification."); - - let updated: Config = old_config.into(); - updated.save_to_default_location() -} - -fn try_load_current_config(id: &str) -> anyhow::Result { - try_upgrade_v1_1_21_config(id)?; +fn try_load_current_config(id: &str) -> Result { + upgrade_helpers::try_upgrade_config(id)?; Config::read_from_default_path(id).map_err(|err| { - let error_msg = - format!( - "Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})", - ); - error!("{error_msg}"); - anyhow!(error_msg) + error!( + "Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})", + ); + MixnodeError::ConfigLoadFailure { + path: default_config_filepath(id), + id: id.to_string(), + source: err, + } }) } diff --git a/mixnode/src/commands/upgrade_helpers.rs b/mixnode/src/commands/upgrade_helpers.rs new file mode 100644 index 00000000000..cf64c271582 --- /dev/null +++ b/mixnode/src/commands/upgrade_helpers.rs @@ -0,0 +1,66 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::old_config_v1_1_21::ConfigV1_1_21; +use crate::config::old_config_v1_1_32::ConfigV1_1_32; +use crate::config::{default_config_filepath, Config}; +use crate::error::MixnodeError; +use log::info; + +fn try_upgrade_v1_1_21_config(id: &str) -> Result { + use nym_config::legacy_helpers::nym_config::MigrationNymConfig; + + // explicitly load it as v1.1.21 (which is incompatible with the current, i.e. 1.1.22+) + let Ok(old_config) = ConfigV1_1_21::load_from_file(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 mixnode is using <= v1.1.21 config template."); + info!("It is going to get updated to the current specification."); + + let updated_step1: ConfigV1_1_32 = old_config.into(); + + let updated: Config = updated_step1.into(); + updated + .save_to_default_location() + .map_err(|err| MixnodeError::ConfigSaveFailure { + path: default_config_filepath(id), + id: id.to_string(), + source: err, + })?; + + Ok(true) +} + +fn try_upgrade_v1_1_32_config(id: &str) -> Result { + // explicitly load it as v1.1.32 (which is incompatible with the current, i.e. 1.1.22+) + let Ok(old_config) = ConfigV1_1_32::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 mixnode is using <= v1.1.32 config template."); + info!("It is going to get updated to the current specification."); + + let updated: Config = old_config.into(); + updated + .save_to_default_location() + .map_err(|err| MixnodeError::ConfigSaveFailure { + path: default_config_filepath(id), + id: id.to_string(), + source: err, + })?; + + Ok(true) +} + +pub(crate) fn try_upgrade_config(id: &str) -> Result<(), MixnodeError> { + if try_upgrade_v1_1_21_config(id)? { + return Ok(()); + } + if try_upgrade_v1_1_32_config(id)? { + return Ok(()); + } + Ok(()) +} diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 77d4b3a076b..97ad26d6996 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -3,6 +3,7 @@ use crate::config::persistence::paths::MixNodePaths; use crate::config::template::CONFIG_TEMPLATE; +use log::{debug, warn}; use nym_bin_common::logging::LoggingSettings; use nym_config::defaults::{ mainnet, DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, @@ -16,13 +17,14 @@ use nym_config::{ use nym_node::config; use serde::{Deserialize, Serialize}; use std::io; -use std::net::IpAddr; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::time::Duration; use url::Url; pub(crate) mod old_config_v1_1_21; +pub(crate) mod old_config_v1_1_32; pub mod persistence; mod template; @@ -71,13 +73,30 @@ pub fn default_data_directory>(id: P) -> PathBuf { .join(DEFAULT_DATA_DIR) } +fn default_mixnode_http_config() -> config::Http { + config::Http { + bind_address: SocketAddr::new( + IpAddr::V4(Ipv4Addr::UNSPECIFIED), + DEFAULT_HTTP_API_LISTENING_PORT, + ), + landing_page_assets_path: None, + } +} + #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { - pub mixnode: MixNode, + // additional metadata holding on-disk location of this config file + #[serde(skip)] + pub(crate) save_path: Option, pub host: config::Host, + #[serde(default = "default_mixnode_http_config")] + pub http: config::Http, + + pub mixnode: MixNode, + pub storage_paths: MixNodePaths, #[serde(default)] @@ -101,11 +120,13 @@ impl Config { let default_mixnode = MixNode::new_default(id.as_ref()); Config { + save_path: None, host: config::Host { // this is a very bad default! public_ips: vec![default_mixnode.listening_address], hostname: None, }, + http: default_mixnode_http_config(), mixnode: default_mixnode, storage_paths: MixNodePaths::new_default(id.as_ref()), verloc: Default::default(), @@ -114,12 +135,24 @@ impl Config { } } + // simple wrapper that reads config file and assigns path location + fn read_from_path>(path: P) -> io::Result { + let path = path.as_ref(); + let mut loaded: Config = read_config_from_toml_file(path)?; + loaded.save_path = Some(path.to_path_buf()); + debug!("loaded config file from {}", path.display()); + Ok(loaded) + } + + // currently this is dead code, but once we allow loading configs from custom paths + // well, we will have to be using it + #[allow(dead_code)] pub fn read_from_toml_file>(path: P) -> io::Result { - read_config_from_toml_file(path) + Self::read_from_path(path) } pub fn read_from_default_path>(id: P) -> io::Result { - Self::read_from_toml_file(default_config_filepath(id)) + Self::read_from_path(default_config_filepath(id)) } pub fn default_location(&self) -> PathBuf { @@ -131,6 +164,15 @@ impl Config { save_formatted_config_to_file(self, config_save_location) } + pub fn try_save(&self) -> io::Result<()> { + if let Some(save_location) = &self.save_path { + save_formatted_config_to_file(self, save_location) + } else { + warn!("config file save location is unknown. falling back to the default"); + self.save_to_default_location() + } + } + // builder methods pub fn with_custom_nym_apis(mut self, nym_api_urls: Vec) -> Self { self.mixnode.nym_api_urls = nym_api_urls; @@ -139,6 +181,10 @@ impl Config { pub fn with_listening_address(mut self, listening_address: IpAddr) -> Self { self.mixnode.listening_address = listening_address; + + let http_port = self.http.bind_address.port(); + self.http.bind_address = SocketAddr::new(listening_address, http_port); + self } @@ -153,7 +199,8 @@ impl Config { } pub fn with_http_api_port(mut self, port: u16) -> Self { - self.mixnode.http_api_port = port; + let http_ip = self.http.bind_address.ip(); + self.http.bind_address = SocketAddr::new(http_ip, port); self } @@ -181,10 +228,6 @@ pub struct MixNode { /// (default: 1790) pub verloc_port: u16, - /// Port used for listening for http requests. - /// (default: 8000) - pub http_api_port: u16, - /// Addresses to nym APIs from which the node gets the view of the network. pub nym_api_urls: Vec, } @@ -197,7 +240,6 @@ impl MixNode { listening_address: inaddr_any(), mix_port: DEFAULT_MIX_LISTENING_PORT, verloc_port: DEFAULT_VERLOC_LISTENING_PORT, - http_api_port: DEFAULT_HTTP_API_LISTENING_PORT, nym_api_urls: vec![Url::from_str(mainnet::NYM_API).expect("Invalid default API URL")], } } diff --git a/mixnode/src/config/old_config_v1_1_21.rs b/mixnode/src/config/old_config_v1_1_21.rs index f776e6e8c60..fc4ae148a05 100644 --- a/mixnode/src/config/old_config_v1_1_21.rs +++ b/mixnode/src/config/old_config_v1_1_21.rs @@ -1,8 +1,10 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::config::old_config_v1_1_32::{ + ConfigV1_1_32, DebugV1_1_32, MixNodeV1_1_32, VerlocV1_1_32, +}; use crate::config::persistence::paths::{KeysPaths, MixNodePaths}; -use crate::config::{Config, Debug, MixNode, Verloc}; use nym_bin_common::logging::LoggingSettings; use nym_config::legacy_helpers::nym_config::MigrationNymConfig; use nym_validator_client::nyxd; @@ -67,13 +69,13 @@ pub struct ConfigV1_1_21 { debug: DebugV1_1_21, } -impl From for Config { +impl From for ConfigV1_1_32 { fn from(value: ConfigV1_1_21) -> Self { let node_description = ConfigV1_1_21::default_config_directory(&value.mixnode.id).join(DESCRIPTION_FILE); - Config { - mixnode: MixNode { + ConfigV1_1_32 { + mixnode: MixNodeV1_1_32 { version: value.mixnode.version, id: value.mixnode.id, listening_address: value.mixnode.listening_address, @@ -82,13 +84,6 @@ impl From for Config { http_api_port: value.mixnode.http_api_port, nym_api_urls: value.mixnode.nym_api_urls, }, - // \/ ADDED - host: nym_node::config::Host { - // this is a very bad default! - public_ips: vec![value.mixnode.listening_address], - hostname: None, - }, - // /\ ADDED storage_paths: MixNodePaths { keys: KeysPaths { private_identity_key_file: value.mixnode.private_identity_key_file, @@ -176,9 +171,9 @@ struct VerlocV1_1_21 { retry_timeout: Duration, } -impl From for Verloc { +impl From for VerlocV1_1_32 { fn from(value: VerlocV1_1_21) -> Self { - Verloc { + VerlocV1_1_32 { packets_per_node: value.packets_per_node, connection_timeout: value.connection_timeout, packet_timeout: value.packet_timeout, @@ -227,9 +222,9 @@ struct DebugV1_1_21 { use_legacy_framed_packet_version: bool, } -impl From for Debug { +impl From for DebugV1_1_32 { fn from(value: DebugV1_1_21) -> Self { - Debug { + DebugV1_1_32 { node_stats_logging_delay: value.node_stats_logging_delay, node_stats_updating_delay: value.node_stats_updating_delay, packet_forwarding_initial_backoff: value.packet_forwarding_initial_backoff, diff --git a/mixnode/src/config/old_config_v1_1_32.rs b/mixnode/src/config/old_config_v1_1_32.rs new file mode 100644 index 00000000000..92947a32341 --- /dev/null +++ b/mixnode/src/config/old_config_v1_1_32.rs @@ -0,0 +1,263 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::persistence::paths::MixNodePaths; +use crate::config::{Config, Debug, MixNode, Verloc}; +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, Serialize}; +use std::io; +use std::net::{IpAddr, SocketAddr}; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use url::Url; + +const DEFAULT_MIXNODES_DIR: &str = "mixnodes"; + +// 'RTT MEASUREMENT' +const DEFAULT_PACKETS_PER_NODE: usize = 100; +const DEFAULT_CONNECTION_TIMEOUT: Duration = Duration::from_millis(5000); +const DEFAULT_PACKET_TIMEOUT: Duration = Duration::from_millis(1500); +const DEFAULT_DELAY_BETWEEN_PACKETS: Duration = Duration::from_millis(50); +const DEFAULT_BATCH_SIZE: usize = 50; +const DEFAULT_TESTING_INTERVAL: Duration = Duration::from_secs(60 * 60 * 12); +const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30); + +// 'DEBUG' +const DEFAULT_NODE_STATS_LOGGING_DELAY: Duration = Duration::from_millis(60_000); +const DEFAULT_NODE_STATS_UPDATING_DELAY: Duration = Duration::from_millis(30_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; + +/// Derive default path to mixnodes's config directory. +/// It should get resolved to `$HOME/.nym/mixnodes//config` +fn default_config_directory>(id: P) -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_MIXNODES_DIR) + .join(id) + .join(DEFAULT_CONFIG_DIR) +} + +/// Derive default path to mixnodes's config file. +/// It should get resolved to `$HOME/.nym/mixnodes//config/config.toml` +fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigV1_1_32 { + pub mixnode: MixNodeV1_1_32, + + // i hope this laziness is not going to backfire... + pub storage_paths: MixNodePaths, + + #[serde(default)] + pub verloc: VerlocV1_1_32, + + #[serde(default)] + pub logging: LoggingSettings, + + #[serde(default)] + pub debug: DebugV1_1_32, +} + +impl ConfigV1_1_32 { + 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_32) -> Self { + Config { + // \/ ADDED + save_path: None, + // /\ ADDED + + // \/ ADDED + host: nym_node::config::Host { + // this is a very bad default! + public_ips: vec![value.mixnode.listening_address], + hostname: None, + }, + // /\ ADDED + + // \/ ADDED + http: nym_node::config::Http { + bind_address: SocketAddr::new( + value.mixnode.listening_address, + value.mixnode.http_api_port, + ), + landing_page_assets_path: None, + }, + // /\ ADDED + mixnode: MixNode { + version: value.mixnode.version, + id: value.mixnode.id, + listening_address: value.mixnode.listening_address, + mix_port: value.mixnode.mix_port, + verloc_port: value.mixnode.verloc_port, + nym_api_urls: value.mixnode.nym_api_urls, + }, + storage_paths: value.storage_paths, + verloc: value.verloc.into(), + logging: value.logging, + debug: value.debug.into(), + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +pub struct MixNodeV1_1_32 { + /// Version of the mixnode for which this configuration was created. + pub version: String, + + /// ID specifies the human readable ID of this particular mixnode. + pub id: String, + + /// 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 verloc traffic. + /// (default: 1790) + pub verloc_port: u16, + + /// Port used for listening for http requests. + /// (default: 8000) + pub http_api_port: u16, + + /// Addresses to nym APIs from which the node gets the view of the network. + pub nym_api_urls: Vec, +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +pub struct VerlocV1_1_32 { + /// Specifies number of echo packets sent to each node during a measurement run. + pub packets_per_node: usize, + + /// Specifies maximum amount of time to wait for the connection to get established. + #[serde(with = "humantime_serde")] + pub connection_timeout: Duration, + + /// Specifies maximum amount of time to wait for the reply packet to arrive before abandoning the test. + #[serde(with = "humantime_serde")] + pub packet_timeout: Duration, + + /// Specifies delay between subsequent test packets being sent (after receiving a reply). + #[serde(with = "humantime_serde")] + pub delay_between_packets: Duration, + + /// Specifies number of nodes being tested at once. + pub tested_nodes_batch_size: usize, + + /// Specifies delay between subsequent test runs. + #[serde(with = "humantime_serde")] + pub testing_interval: Duration, + + /// Specifies delay between attempting to run the measurement again if the previous run failed + /// due to being unable to get the list of nodes. + #[serde(with = "humantime_serde")] + pub retry_timeout: Duration, +} + +impl From for Verloc { + fn from(value: VerlocV1_1_32) -> Self { + Verloc { + packets_per_node: value.packets_per_node, + connection_timeout: value.connection_timeout, + packet_timeout: value.packet_timeout, + delay_between_packets: value.delay_between_packets, + tested_nodes_batch_size: value.tested_nodes_batch_size, + testing_interval: value.testing_interval, + retry_timeout: value.retry_timeout, + } + } +} + +impl Default for VerlocV1_1_32 { + fn default() -> Self { + VerlocV1_1_32 { + packets_per_node: DEFAULT_PACKETS_PER_NODE, + connection_timeout: DEFAULT_CONNECTION_TIMEOUT, + packet_timeout: DEFAULT_PACKET_TIMEOUT, + delay_between_packets: DEFAULT_DELAY_BETWEEN_PACKETS, + tested_nodes_batch_size: DEFAULT_BATCH_SIZE, + testing_interval: DEFAULT_TESTING_INTERVAL, + retry_timeout: DEFAULT_RETRY_TIMEOUT, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct DebugV1_1_32 { + /// Delay between each subsequent node statistics being logged to the console + #[serde(with = "humantime_serde")] + pub node_stats_logging_delay: Duration, + + /// Delay between each subsequent node statistics being updated + #[serde(with = "humantime_serde")] + pub node_stats_updating_delay: Duration, + + /// 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, + + /// 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 From for Debug { + fn from(value: DebugV1_1_32) -> Self { + Debug { + node_stats_logging_delay: value.node_stats_logging_delay, + node_stats_updating_delay: value.node_stats_updating_delay, + packet_forwarding_initial_backoff: value.packet_forwarding_initial_backoff, + packet_forwarding_maximum_backoff: value.packet_forwarding_maximum_backoff, + initial_connection_timeout: value.initial_connection_timeout, + maximum_connection_buffer_size: value.maximum_connection_buffer_size, + use_legacy_framed_packet_version: value.use_legacy_framed_packet_version, + } + } +} + +impl Default for DebugV1_1_32 { + fn default() -> Self { + DebugV1_1_32 { + node_stats_logging_delay: DEFAULT_NODE_STATS_LOGGING_DELAY, + node_stats_updating_delay: DEFAULT_NODE_STATS_UPDATING_DELAY, + 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, + maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE, + use_legacy_framed_packet_version: false, + } + } +} diff --git a/mixnode/src/config/template.rs b/mixnode/src/config/template.rs index a885a1ecbb6..b7f9433ae55 100644 --- a/mixnode/src/config/template.rs +++ b/mixnode/src/config/template.rs @@ -11,6 +11,19 @@ pub(crate) const CONFIG_TEMPLATE: &str = r#" ##### main base mixnode config options ##### +[host] +# Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections. +# currently not in active use for mixnodes +public_ips = [ + {{#each host.public_ips }} + '{{this}}', + {{/each}} +] + +# (temporary) Optional hostname of this node, for example nymtech.net. +# currently not in active use for mixnodes +hostname = '{{ host.hostname }}' + [mixnode] # Version of the mixnode for which this configuration was created. version = '{{ mixnode.version }}' @@ -29,10 +42,6 @@ mix_port = {{ mixnode.mix_port }} # (default: 1790) verloc_port = {{ mixnode.verloc_port }} -# Port used for listening for http requests. -# (default: 8000) -http_api_port = {{ mixnode.http_api_port }} - # Addresses to APIs running on validator from which the node gets the view of the network. nym_api_urls = [ {{#each mixnode.nym_api_urls }} @@ -40,6 +49,14 @@ nym_api_urls = [ {{/each}} ] +[http] +# Socket address this node will use for binding its http API. +# default: `0.0.0.0:8000` +bind_address = '{{ http.bind_address }}' + +# Path to assets directory of custom landing page of this node +landing_page_assets_path = '{{ http.landing_page_assets_path }}' + [storage_paths] # Path to file containing private identity key. diff --git a/mixnode/src/error.rs b/mixnode/src/error.rs index 705dcfc2ce8..a5f80b10d4b 100644 --- a/mixnode/src/error.rs +++ b/mixnode/src/error.rs @@ -24,6 +24,26 @@ pub enum MixnodeError { err: io::Error, }, + #[error( + "failed to load config file for id {id} using path '{}'. detailed message: {source}", path.display() + )] + ConfigLoadFailure { + id: String, + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error( + "failed to save config file for id {id} using path '{}'. detailed message: {source}", path.display() + )] + ConfigSaveFailure { + id: String, + path: PathBuf, + #[source] + source: io::Error, + }, + // 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), diff --git a/mixnode/src/node/http/mod.rs b/mixnode/src/node/http/mod.rs index b2ad16ca04a..a3a0d49ce2a 100644 --- a/mixnode/src/node/http/mod.rs +++ b/mixnode/src/node/http/mod.rs @@ -10,7 +10,6 @@ use nym_node::error::NymNodeError; use nym_node::http::api::api_requests; use nym_node::http::api::api_requests::SignedHostInformation; use nym_task::TaskClient; -use std::net::SocketAddr; pub(crate) mod legacy; @@ -81,10 +80,7 @@ impl<'a> HttpApiBuilder<'a> { } pub(crate) fn start(self, task_client: TaskClient) -> Result<(), MixnodeError> { - let bind_address = SocketAddr::new( - self.mixnode_config.mixnode.listening_address, - self.mixnode_config.mixnode.http_api_port, - ); + let bind_address = self.mixnode_config.http.bind_address; info!("Starting HTTP API on http://{bind_address}",); let config = nym_node::http::Config::new( @@ -95,7 +91,8 @@ impl<'a> HttpApiBuilder<'a> { self.identity_keypair, )?, ) - .with_mixnode(load_mixnode_details(self.mixnode_config)?); + .with_mixnode(load_mixnode_details(self.mixnode_config)?) + .with_landing_page_assets(self.mixnode_config.http.landing_page_assets_path.as_ref()); let router = nym_node::http::NymNodeRouter::new(config, None); let server = router diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 0359e05caed..f1b330e69a0 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -61,7 +61,7 @@ impl MixNode { bind_address: self.config.mixnode.listening_address, version: self.config.mixnode.version.clone(), mix_port: self.config.mixnode.mix_port, - http_api_port: self.config.mixnode.http_api_port, + http_api_port: self.config.http.bind_address.port(), verloc_port: self.config.mixnode.verloc_port, }; diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index 958b912c7b5..ad4a9a8823c 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -43,6 +43,7 @@ impl Host { pub struct Http { /// Socket address this node will use for binding its http API. /// default: `0.0.0.0:8080` + /// note: for legacy reasons, it defaults to port `8000` for mixnodes. pub bind_address: SocketAddr, /// Path to assets directory of custom landing page of this node.