From b7f197d278c36d18761e7eb01cf0bcb950754ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 25 Jun 2021 10:33:12 +0100 Subject: [PATCH] Feature/host ip split (#654) * Updated smart contract to work with future cosmwasm 0.14.0 update * Updated the rest of the codebase * Reordered imports * Missing imports * cargo fmt * More cargo fmt action * Introduced type alias for IdentityKey reference * Constant defined arguments for gateway and mixnode * Spliting host into explicit address and port(s) * Using more type restrictive IpAddr rather than String for listening address * Updated config templates * Tentative upgrade commands, probably to be further changed before release * Fixed mixnet contract test fixtures * Further missing test adjustments --- clients/native/src/commands/init.rs | 4 +- clients/socks5/src/commands/init.rs | 4 +- clients/webassembly/src/client/mod.rs | 2 +- common/mixnet-contract/src/gateway.rs | 24 +-- common/mixnet-contract/src/mixnode.rs | 19 +- common/nymsphinx/src/receiver.rs | 14 +- common/topology/src/gateway.rs | 33 ++- common/topology/src/lib.rs | 45 +++- common/topology/src/mix.rs | 29 ++- contracts/mixnet/src/support/tests.rs | 12 +- contracts/mixnet/src/transactions.rs | 8 +- gateway/src/commands/init.rs | 77 +++---- gateway/src/commands/mod.rs | 79 +++---- gateway/src/commands/run.rs | 97 +++------ gateway/src/commands/upgrade.rs | 96 ++++++++- gateway/src/config/mod.rs | 270 ++++++------------------ gateway/src/config/template.rs | 50 ++--- gateway/src/node/mod.rs | 30 +-- mixnode/src/commands/describe.rs | 7 +- mixnode/src/commands/init.rs | 53 ++--- mixnode/src/commands/mod.rs | 47 +++-- mixnode/src/commands/run.rs | 55 ++--- mixnode/src/commands/sign.rs | 15 +- mixnode/src/commands/upgrade.rs | 114 +++++++--- mixnode/src/config/mod.rs | 164 +++++++------- mixnode/src/config/template.rs | 11 +- mixnode/src/node/mod.rs | 17 +- network-monitor/src/monitor/preparer.rs | 7 +- 28 files changed, 661 insertions(+), 722 deletions(-) diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index a4e9f30027..8558648244 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -88,7 +88,7 @@ async fn register_with_gateway( ) -> SharedKeys { let timeout = Duration::from_millis(1500); let mut gateway_client = GatewayClient::new_init( - gateway.client_listener.clone(), + gateway.clients_address(), gateway.identity_key, our_identity.clone(), timeout, @@ -215,7 +215,7 @@ pub fn execute(matches: &ArgMatches) { .with_gateway_id(gate_details.identity_key.to_base58_string()); let shared_keys = register_with_gateway(&gate_details, key_manager.identity_keypair()).await; - (shared_keys, gate_details.client_listener) + (shared_keys, gate_details.clients_address()) }; // TODO: is there perhaps a way to make it work without having to spawn entire runtime? diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 9fbb003e35..0a73d524bf 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -89,7 +89,7 @@ async fn register_with_gateway( ) -> SharedKeys { let timeout = Duration::from_millis(1500); let mut gateway_client = GatewayClient::new_init( - gateway.client_listener.clone(), + gateway.clients_address(), gateway.identity_key, our_identity.clone(), timeout, @@ -217,7 +217,7 @@ pub fn execute(matches: &ArgMatches) { .with_gateway_id(gate_details.identity_key.to_base58_string()); let shared_keys = register_with_gateway(&gate_details, key_manager.identity_keypair()).await; - (shared_keys, gate_details.client_listener) + (shared_keys, gate_details.clients_address()) }; // TODO: is there perhaps a way to make it work without having to spawn entire runtime? diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs index 3785cc3ee5..4f9fc1e404 100644 --- a/clients/webassembly/src/client/mod.rs +++ b/clients/webassembly/src/client/mod.rs @@ -120,7 +120,7 @@ impl NymClient { let (ack_sender, ack_receiver) = mpsc::unbounded(); let mut gateway_client = GatewayClient::new( - gateway.client_listener.clone(), + gateway.clients_address(), Arc::clone(&client.identity), gateway.identity_key, None, diff --git a/common/mixnet-contract/src/gateway.rs b/common/mixnet-contract/src/gateway.rs index d4bae81d17..c474d20a41 100644 --- a/common/mixnet-contract/src/gateway.rs +++ b/common/mixnet-contract/src/gateway.rs @@ -6,13 +6,12 @@ use cosmwasm_std::{Addr, Coin}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::Display; -use std::io; -use std::net::{SocketAddr, ToSocketAddrs}; #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct Gateway { - pub mix_host: String, - pub clients_host: String, + pub host: String, + pub mix_port: u16, + pub clients_port: u16, pub location: String, pub sphinx_key: SphinxKey, /// Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients @@ -22,29 +21,24 @@ pub struct Gateway { impl Gateway { pub fn new( - mix_host: String, - clients_host: String, + host: String, + mix_port: u16, + clients_port: u16, location: String, sphinx_key: SphinxKey, identity_key: IdentityKey, version: String, ) -> Self { Gateway { - mix_host, - clients_host, + host, + mix_port, + clients_port, location, sphinx_key, identity_key, version, } } - - pub fn try_resolve_hostname(&self) -> Result { - self.mix_host - .to_socket_addrs()? - .next() - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "no valid socket address")) - } } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] diff --git a/common/mixnet-contract/src/mixnode.rs b/common/mixnet-contract/src/mixnode.rs index c4bfa7da41..eb7687ad17 100644 --- a/common/mixnet-contract/src/mixnode.rs +++ b/common/mixnet-contract/src/mixnode.rs @@ -6,12 +6,11 @@ use cosmwasm_std::{Addr, Coin}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::Display; -use std::io; -use std::net::{SocketAddr, ToSocketAddrs}; #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct MixNode { pub host: String, + pub mix_port: u16, pub layer: u64, pub location: String, pub sphinx_key: SphinxKey, @@ -23,6 +22,7 @@ pub struct MixNode { impl MixNode { pub fn new( host: String, + mix_port: u16, layer: u64, location: String, sphinx_key: SphinxKey, @@ -31,6 +31,7 @@ impl MixNode { ) -> Self { MixNode { host, + mix_port, layer, location, sphinx_key, @@ -38,20 +39,8 @@ impl MixNode { version, } } - - pub fn try_resolve_hostname(&self) -> Result { - self.host - .to_socket_addrs()? - .next() - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "no valid socket address")) - } } -/* -By the way, I've also been thinking of some potential changes in the contract we could introduce once we have to make an incompatible upgrade (say to cosmwasm 0.14+). Is there some place I could write them down so that we would not forget about them? Should I make a GitHub issue, a google doc or maybe put it on GitLab? - - */ - #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct MixNodeBond { // TODO: @@ -62,8 +51,6 @@ pub struct MixNodeBond { // // I would also modify the `MixNode` struct: // - remove `location` field - // - repurpose `host` field to hold either ip or hostname (WITHOUT port information) - // - introduce `mix_port` field // - introduce `rest_api_port` field // - [POTENTIALLY] introduce `verloc_port` field or keep it accessible via http api // diff --git a/common/nymsphinx/src/receiver.rs b/common/nymsphinx/src/receiver.rs index e2523fdabf..6898ea6c86 100644 --- a/common/nymsphinx/src/receiver.rs +++ b/common/nymsphinx/src/receiver.rs @@ -217,7 +217,8 @@ mod message_receiver { owner: "foomp1".to_string(), stake: 123, location: "unknown".to_string(), - host: "10.20.30.40:1789".parse().unwrap(), + host: "10.20.30.40".parse().unwrap(), + mix_host: "10.20.30.40:1789".parse().unwrap(), identity_key: identity::PublicKey::from_base58_string( "3ebjp1Fb9hdcS1AR6AZihgeJiMHkB5jjJUsvqNnfQwU7", ) @@ -237,7 +238,8 @@ mod message_receiver { owner: "foomp2".to_string(), stake: 123, location: "unknown".to_string(), - host: "11.21.31.41:1789".parse().unwrap(), + host: "11.21.31.41".parse().unwrap(), + mix_host: "11.21.31.41:1789".parse().unwrap(), identity_key: identity::PublicKey::from_base58_string( "D6YaMzLSY7mANtSQRKXsmMZpqgqiVkeiagKM4V4oFPFr", ) @@ -257,7 +259,8 @@ mod message_receiver { owner: "foomp3".to_string(), stake: 123, location: "unknown".to_string(), - host: "12.22.32.42:1789".parse().unwrap(), + host: "12.22.32.42".parse().unwrap(), + mix_host: "12.22.32.42:1789".parse().unwrap(), identity_key: identity::PublicKey::from_base58_string( "GkWDysw4AjESv1KiAiVn7JzzCMJeksxNSXVfr1PpX8wD", ) @@ -278,8 +281,9 @@ mod message_receiver { owner: "foomp4".to_string(), stake: 123, location: "unknown".to_string(), - client_listener: "ws://1.2.3.4:9000".to_string(), - mixnet_listener: "1.2.3.4:1789".parse().unwrap(), + host: "1.2.3.4".parse().unwrap(), + mix_host: "1.2.3.4:1789".parse().unwrap(), + clients_port: 9000, identity_key: identity::PublicKey::from_base58_string( "FioFa8nMmPpQnYi7JyojoTuwGLeyNS8BF4ChPr29zUML", ) diff --git a/common/topology/src/gateway.rs b/common/topology/src/gateway.rs index 4c62db62f7..b285c9d48e 100644 --- a/common/topology/src/gateway.rs +++ b/common/topology/src/gateway.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::filter; +use crate::{filter, NetworkAddress}; use crypto::asymmetric::{encryption, identity}; use mixnet_contract::GatewayBond; use nymsphinx_addressing::nodes::{NodeIdentity, NymNodeRoutingAddress}; @@ -84,8 +84,11 @@ pub struct Node { // on the network at a type, right? pub stake: u128, pub location: String, - pub client_listener: String, - pub mixnet_listener: SocketAddr, + pub host: NetworkAddress, + // we're keeping this as separate resolved field since we do not want to be resolving the potential + // hostname every time we want to construct a path via this node + pub mix_host: SocketAddr, + pub clients_port: u16, pub identity_key: identity::PublicKey, pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519 pub version: String, @@ -95,6 +98,10 @@ impl Node { pub fn identity(&self) -> &NodeIdentity { &self.identity_key } + + pub fn clients_address(&self) -> String { + format!("ws://{}:{}", self.host, self.clients_port) + } } impl filter::Versioned for Node { @@ -105,7 +112,7 @@ impl filter::Versioned for Node { impl<'a> From<&'a Node> for SphinxNode { fn from(node: &'a Node) -> Self { - let node_address_bytes = NymNodeRoutingAddress::from(node.mixnet_listener) + let node_address_bytes = NymNodeRoutingAddress::from(node.mix_host) .try_into() .unwrap(); @@ -120,6 +127,17 @@ impl<'a> TryFrom<&'a GatewayBond> for Node { if bond.amount.len() > 1 { return Err(GatewayConversionError::InvalidStake); } + + let host: NetworkAddress = bond.gateway.host.parse().map_err(|err| { + GatewayConversionError::InvalidAddress(bond.gateway.host.clone(), err) + })?; + + // try to completely resolve the host in the mix situation to avoid doing it every + // single time we want to construct a path + let mix_host = host.to_socket_addrs(bond.gateway.mix_port).map_err(|err| { + GatewayConversionError::InvalidAddress(bond.gateway.host.clone(), err) + })?[0]; + Ok(Node { owner: bond.owner.as_str().to_owned(), stake: bond @@ -128,10 +146,9 @@ impl<'a> TryFrom<&'a GatewayBond> for Node { .map(|stake| stake.amount.into()) .unwrap_or(0), location: bond.gateway.location.clone(), - client_listener: bond.gateway.clients_host.clone(), - mixnet_listener: bond.gateway.try_resolve_hostname().map_err(|err| { - GatewayConversionError::InvalidAddress(bond.gateway.mix_host.clone(), err) - })?, + host, + mix_host, + clients_port: bond.gateway.clients_port, identity_key: identity::PublicKey::from_base58_string(&bond.gateway.identity_key)?, sphinx_key: encryption::PublicKey::from_base58_string(&bond.gateway.sphinx_key)?, version: bond.gateway.version.clone(), diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index 666d5e3003..bfdca9aaec 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -20,6 +20,10 @@ use nymsphinx_types::Node as SphinxNode; use rand::Rng; use std::collections::HashMap; use std::convert::TryInto; +use std::fmt::{self, Display, Formatter}; +use std::io; +use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; +use std::str::FromStr; pub mod filter; pub mod gateway; @@ -35,6 +39,44 @@ pub enum NymTopologyError { NoMixesOnLayerAvailable(MixLayer), } +#[derive(Debug, Clone)] +pub enum NetworkAddress { + IpAddr(IpAddr), + Hostname(String), +} + +impl NetworkAddress { + pub fn to_socket_addrs(&self, port: u16) -> io::Result> { + match self { + NetworkAddress::IpAddr(addr) => Ok(vec![SocketAddr::new(*addr, port)]), + NetworkAddress::Hostname(hostname) => { + Ok((hostname.as_str(), port).to_socket_addrs()?.collect()) + } + } + } +} + +impl FromStr for NetworkAddress { + type Err = std::io::Error; + + fn from_str(s: &str) -> Result { + if let Ok(ip_addr) = s.parse() { + Ok(NetworkAddress::IpAddr(ip_addr)) + } else { + Ok(NetworkAddress::Hostname(s.to_string())) + } + } +} + +impl Display for NetworkAddress { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + NetworkAddress::IpAddr(ip_addr) => ip_addr.fmt(f), + NetworkAddress::Hostname(hostname) => hostname.fmt(f), + } + } +} + pub type MixLayer = u8; #[derive(Debug, Clone)] @@ -248,7 +290,8 @@ mod converting_mixes_to_vec { owner: "N/A".to_string(), stake: 0, location: "London".to_string(), - host: "3.3.3.3:1789".parse().unwrap(), + host: "3.3.3.3".parse().unwrap(), + mix_host: "3.3.3.3:1789".parse().unwrap(), identity_key: identity::PublicKey::from_base58_string( "3ebjp1Fb9hdcS1AR6AZihgeJiMHkB5jjJUsvqNnfQwU7", ) diff --git a/common/topology/src/mix.rs b/common/topology/src/mix.rs index a97e342745..291b7fabdc 100644 --- a/common/topology/src/mix.rs +++ b/common/topology/src/mix.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::filter; +use crate::{filter, NetworkAddress}; use crypto::asymmetric::{encryption, identity}; use mixnet_contract::MixNodeBond; use nymsphinx_addressing::nodes::NymNodeRoutingAddress; @@ -84,7 +84,10 @@ pub struct Node { // on the network at a type, right? pub stake: u128, pub location: String, - pub host: SocketAddr, + pub host: NetworkAddress, + // we're keeping this as separate resolved field since we do not want to be resolving the potential + // hostname every time we want to construct a path via this node + pub mix_host: SocketAddr, pub identity_key: identity::PublicKey, pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519 pub layer: u64, @@ -99,7 +102,9 @@ impl filter::Versioned for Node { impl<'a> From<&'a Node> for SphinxNode { fn from(node: &'a Node) -> Self { - let node_address_bytes = NymNodeRoutingAddress::from(node.host).try_into().unwrap(); + let node_address_bytes = NymNodeRoutingAddress::from(node.mix_host) + .try_into() + .unwrap(); SphinxNode::new(node_address_bytes, (&node.sphinx_key).into()) } @@ -112,6 +117,19 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node { if bond.amount.len() > 1 { return Err(MixnodeConversionError::InvalidStake); } + + let host: NetworkAddress = bond.mix_node.host.parse().map_err(|err| { + MixnodeConversionError::InvalidAddress(bond.mix_node.host.clone(), err) + })?; + + // try to completely resolve the host in the mix situation to avoid doing it every + // single time we want to construct a path + let mix_host = host + .to_socket_addrs(bond.mix_node.mix_port) + .map_err(|err| { + MixnodeConversionError::InvalidAddress(bond.mix_node.host.clone(), err) + })?[0]; + Ok(Node { owner: bond.owner.as_str().to_owned(), stake: bond @@ -120,9 +138,8 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node { .map(|stake| stake.amount.into()) .unwrap_or(0), location: bond.mix_node.location.clone(), - host: bond.mix_node.try_resolve_hostname().map_err(|err| { - MixnodeConversionError::InvalidAddress(bond.mix_node.host.clone(), err) - })?, + host, + mix_host, identity_key: identity::PublicKey::from_base58_string(&bond.mix_node.identity_key)?, sphinx_key: encryption::PublicKey::from_base58_string(&bond.mix_node.sphinx_key)?, layer: bond.mix_node.layer, diff --git a/contracts/mixnet/src/support/tests.rs b/contracts/mixnet/src/support/tests.rs index 79bb07c816..8b3ee0dc37 100644 --- a/contracts/mixnet/src/support/tests.rs +++ b/contracts/mixnet/src/support/tests.rs @@ -107,6 +107,7 @@ pub mod helpers { pub fn mix_node_fixture() -> MixNode { MixNode::new( "mix.node.org".to_string(), + 1789, 1, "Sweden".to_string(), "sphinx".to_string(), @@ -118,6 +119,7 @@ pub mod helpers { pub fn mixnode_bond_fixture() -> MixNodeBond { let mix_node = MixNode::new( "1.1.1.1".to_string(), + 1789, 1, "London".to_string(), "1234".to_string(), @@ -129,8 +131,9 @@ pub mod helpers { pub fn gateway_fixture() -> Gateway { Gateway::new( - "1.1.1.1:1234".to_string(), - "ws://1.1.1.1:1235".to_string(), + "1.1.1.1".to_string(), + 1789, + 9000, "Sweden".to_string(), "sphinx".to_string(), "identity".to_string(), @@ -140,8 +143,9 @@ pub mod helpers { pub fn gateway_bond_fixture() -> GatewayBond { let gateway = Gateway::new( - "1.1.1.1:1234".to_string(), - "ws://1.1.1.1:1235".to_string(), + "1.1.1.1".to_string(), + 1789, + 9000, "London".to_string(), "sphinx".to_string(), "identity".to_string(), diff --git a/contracts/mixnet/src/transactions.rs b/contracts/mixnet/src/transactions.rs index 3f70ae364d..7640f4ea5a 100644 --- a/contracts/mixnet/src/transactions.rs +++ b/contracts/mixnet/src/transactions.rs @@ -1210,7 +1210,7 @@ pub mod tests { let msg = ExecuteMsg::BondGateway { gateway: Gateway { identity_key: "myAwesomeGateway".to_string(), - mix_host: "1.1.1.1:1789".into(), + host: "1.1.1.1".into(), ..helpers::gateway_fixture() }, }; @@ -1221,7 +1221,7 @@ pub mod tests { let msg = ExecuteMsg::BondGateway { gateway: Gateway { identity_key: "myAwesomeGateway".to_string(), - mix_host: "2.2.2.2:1789".into(), + host: "2.2.2.2".into(), ..helpers::gateway_fixture() }, }; @@ -1230,12 +1230,12 @@ pub mod tests { // make sure the host information was updated assert_eq!( - "2.2.2.2:1789".to_string(), + "2.2.2.2".to_string(), gateways_read(deps.as_ref().storage) .load("myAwesomeGateway".as_bytes()) .unwrap() .gateway - .mix_host + .host ); } diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index de4ce55a24..3e285b65ad 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -1,7 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::commands::override_config; +use crate::commands::*; use crate::config::persistence::pathfinder::GatewayPathfinder; use crate::config::Config; use clap::{App, Arg, ArgMatches}; @@ -12,83 +12,58 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { App::new("init") .about("Initialise the gateway") .arg( - Arg::with_name("id") - .long("id") + Arg::with_name(ID_ARG_NAME) + .long(ID_ARG_NAME) .help("Id of the gateway we want to create config for.") .takes_value(true) .required(true), ) .arg( - Arg::with_name("mix-host") - .long("mix-host") + Arg::with_name(HOST_ARG_NAME) + .long(HOST_ARG_NAME) .help("The custom host on which the gateway will be running for receiving sphinx packets") .takes_value(true) .required(true), ) .arg( - Arg::with_name("mix-port") - .long("mix-port") + Arg::with_name(MIX_PORT_ARG_NAME) + .long(MIX_PORT_ARG_NAME) .help("The port on which the gateway will be listening for sphinx packets") .takes_value(true) ) .arg( - Arg::with_name("clients-host") - .long("clients-host") - .help("The custom host on which the gateway will be running for receiving clients gateway-requests") - .takes_value(true) - .required(true), - ) - .arg( - Arg::with_name("clients-port") - .long("clients-port") + Arg::with_name(CLIENTS_PORT_ARG_NAME) + .long(CLIENTS_PORT_ARG_NAME) .help("The port on which the gateway will be listening for clients gateway-requests") .takes_value(true) ) .arg( - Arg::with_name("mix-announce-host") - .long("mix-announce-host") + Arg::with_name(ANNOUNCE_HOST_ARG_NAME) + .long(ANNOUNCE_HOST_ARG_NAME) .help("The host that will be reported to the directory server") .takes_value(true), ) .arg( - Arg::with_name("mix-announce-port") - .long("mix-announce-port") - .help("The port that will be reported to the directory server") - .takes_value(true), - ) - .arg( - Arg::with_name("clients-announce-host") - .long("clients-announce-host") - .help("The host that will be reported to the directory server") - .takes_value(true), - ) - .arg( - Arg::with_name("clients-announce-port") - .long("clients-announce-port") - .help("The port that will be reported to the directory server") - .takes_value(true), - ) - .arg( - Arg::with_name("inboxes") - .long("inboxes") + Arg::with_name(INBOXES_ARG_NAME) + .long(INBOXES_ARG_NAME) .help("Directory with inboxes where all packets for the clients are stored") .takes_value(true) ) .arg( - Arg::with_name("clients-ledger") - .long("clients-ledger") - .help("Ledger directory containing registered clients") + Arg::with_name(CLIENTS_LEDGER_ARG_NAME) + .long(CLIENTS_LEDGER_ARG_NAME) + .help("Ledger file containing registered clients") .takes_value(true) ) .arg( - Arg::with_name("validators") - .long("validators") + Arg::with_name(VALIDATORS_ARG_NAME) + .long(VALIDATORS_ARG_NAME) .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) .arg( - Arg::with_name("mixnet-contract") - .long("mixnet-contract") + Arg::with_name(CONTRACT_ARG_NAME) + .long(CONTRACT_ARG_NAME) .help("Address of the validator contract managing the network") .takes_value(true), ) @@ -131,21 +106,23 @@ fn show_bonding_info(config: &Config) { "\nTo bond your gateway you will [most likely] need to provide the following: Identity key: {} Sphinx key: {} - Mix Host: {} - Clients Host: {} + Host: {} + Mix Port: {} + Clients Port: {} Location: [physical location of your node's server] Version: {} ", identity_keypair.public_key().to_base58_string(), sphinx_keypair.public_key().to_base58_string(), - config.get_mix_announce_address(), - config.get_clients_announce_address(), + config.get_announce_address(), + config.get_mix_port(), + config.get_clients_port(), config.get_version(), ); } pub fn execute(matches: &ArgMatches) { - let id = matches.value_of("id").unwrap(); + let id = matches.value_of(ID_ARG_NAME).unwrap(); println!("Initialising gateway {}...", id); let already_init = if Config::default_config_file_path(id).exists() { diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 5cb66b2e77..f258495827 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -8,6 +8,16 @@ pub(crate) mod init; pub(crate) mod run; pub(crate) mod upgrade; +pub(crate) const ID_ARG_NAME: &str = "id"; +pub(crate) const HOST_ARG_NAME: &str = "host"; +pub(crate) const MIX_PORT_ARG_NAME: &str = "mix-port"; +pub(crate) const CLIENTS_PORT_ARG_NAME: &str = "clients-port"; +pub(crate) const VALIDATORS_ARG_NAME: &str = "validators"; +pub(crate) const CONTRACT_ARG_NAME: &str = "mixnet-contract"; +pub(crate) const ANNOUNCE_HOST_ARG_NAME: &str = "announce-host"; +pub(crate) const INBOXES_ARG_NAME: &str = "inboxes"; +pub(crate) const CLIENTS_LEDGER_ARG_NAME: &str = "clients-ledger"; + fn parse_validators(raw: &str) -> Vec { raw.split(',') .map(|raw_validator| raw_validator.trim().into()) @@ -15,85 +25,54 @@ fn parse_validators(raw: &str) -> Vec { } pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { - let mut was_mix_host_overridden = false; - if let Some(mix_host) = matches.value_of("mix-host") { - config = config.with_mix_listening_host(mix_host); - was_mix_host_overridden = true; + let mut was_host_overridden = false; + if let Some(host) = matches.value_of(HOST_ARG_NAME) { + config = config.with_listening_address(host); + was_host_overridden = true; } - if let Some(mix_port) = matches.value_of("mix-port").map(|port| port.parse::()) { + if let Some(mix_port) = matches + .value_of(MIX_PORT_ARG_NAME) + .map(|port| port.parse::()) + { if let Err(err) = mix_port { // if port was overridden, it must be parsable panic!("Invalid port value provided - {:?}", err); } - config = config.with_mix_listening_port(mix_port.unwrap()); - } - let mut was_clients_host_overridden = false; - if let Some(clients_host) = matches.value_of("clients-host") { - config = config.with_clients_listening_host(clients_host); - was_clients_host_overridden = true; + config = config.with_mix_port(mix_port.unwrap()); } if let Some(clients_port) = matches - .value_of("clients-port") + .value_of(CLIENTS_PORT_ARG_NAME) .map(|port| port.parse::()) { if let Err(err) = clients_port { // if port was overridden, it must be parsable panic!("Invalid port value provided - {:?}", err); } - config = config.with_clients_listening_port(clients_port.unwrap()); + config = config.with_clients_port(clients_port.unwrap()); } - if let Some(mix_announce_host) = matches.value_of("mix-announce-host") { - config = config.with_mix_announce_host(mix_announce_host); - } else if was_mix_host_overridden { + if let Some(announce_host) = matches.value_of(ANNOUNCE_HOST_ARG_NAME) { + config = config.with_announce_address(announce_host); + } else if was_host_overridden { // make sure our 'mix-announce-host' always defaults to 'mix-host' - config = config.mix_announce_host_from_listening_host() + config = config.announce_host_from_listening_host() } - if let Some(mix_announce_port) = matches - .value_of("mix-announce-port") - .map(|port| port.parse::()) - { - if let Err(err) = mix_announce_port { - // if port was overridden, it must be parsable - panic!("Invalid port value provided - {:?}", err); - } - config = config.with_mix_announce_port(mix_announce_port.unwrap()); - } - - if let Some(clients_announce_host) = matches.value_of("clients-announce-host") { - config = config.with_clients_announce_host(clients_announce_host); - } else if was_clients_host_overridden { - // make sure our 'clients-announce-host' always defaults to 'clients-host' - config = config.clients_announce_host_from_listening_host() - } - - if let Some(clients_announce_port) = matches - .value_of("clients-announce-port") - .map(|port| port.parse::()) - { - if let Err(err) = clients_announce_port { - // if port was overridden, it must be parsable - panic!("Invalid port value provided - {:?}", err); - } - config = config.with_clients_announce_port(clients_announce_port.unwrap()); - } - - if let Some(raw_validators) = matches.value_of("validators") { + if let Some(raw_validators) = matches.value_of(VALIDATORS_ARG_NAME) { config = config.with_custom_validators(parse_validators(raw_validators)); } - if let Some(contract_address) = matches.value_of("mixnet-contract") { + if let Some(contract_address) = matches.value_of(CONTRACT_ARG_NAME) { config = config.with_custom_mixnet_contract(contract_address) } - if let Some(inboxes_dir) = matches.value_of("inboxes") { + if let Some(inboxes_dir) = matches.value_of(INBOXES_ARG_NAME) { config = config.with_custom_clients_inboxes(inboxes_dir); } - if let Some(clients_ledger) = matches.value_of("clients-ledger") { + if let Some(clients_ledger) = matches.value_of(CLIENTS_LEDGER_ARG_NAME) { config = config.with_custom_clients_ledger(clients_ledger); } diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index 46e46b0a7a..1f6136c12e 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -1,7 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::commands::override_config; +use crate::commands::*; use crate::config::persistence::pathfinder::GatewayPathfinder; use crate::config::Config; use crate::node::Gateway; @@ -15,88 +15,58 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { App::new("run") .about("Starts the gateway") .arg( - Arg::with_name("id") - .long("id") + Arg::with_name(ID_ARG_NAME) + .long(ID_ARG_NAME) .help("Id of the gateway we want to run") .takes_value(true) .required(true), ) // the rest of arguments are optional, they are used to override settings in config file .arg( - Arg::with_name("config") - .long("config") - .help("Custom path to the nym gateway configuration file") - .takes_value(true), - ) - .arg( - Arg::with_name("mix-host") - .long("mix-host") + Arg::with_name(HOST_ARG_NAME) + .long(HOST_ARG_NAME) .help("The custom host on which the gateway will be running for receiving sphinx packets") .takes_value(true) ) .arg( - Arg::with_name("mix-port") - .long("mix-port") + Arg::with_name(MIX_PORT_ARG_NAME) + .long(MIX_PORT_ARG_NAME) .help("The port on which the gateway will be listening for sphinx packets") .takes_value(true) ) .arg( - Arg::with_name("clients-host") - .long("clients-host") - .help("The custom host on which the gateway will be running for receiving clients gateway-requests") - .takes_value(true) - ) - .arg( - Arg::with_name("clients-port") - .long("clients-port") + Arg::with_name(CLIENTS_PORT_ARG_NAME) + .long(CLIENTS_PORT_ARG_NAME) .help("The port on which the gateway will be listening for clients gateway-requests") .takes_value(true) ) .arg( - Arg::with_name("mix-announce-host") - .long("mix-announce-host") + Arg::with_name(ANNOUNCE_HOST_ARG_NAME) + .long(ANNOUNCE_HOST_ARG_NAME) .help("The host that will be reported to the directory server") .takes_value(true), ) .arg( - Arg::with_name("mix-announce-port") - .long("mix-announce-port") - .help("The port that will be reported to the directory server") - .takes_value(true), - ) - .arg( - Arg::with_name("clients-announce-host") - .long("clients-announce-host") - .help("The host that will be reported to the directory server") - .takes_value(true), - ) - .arg( - Arg::with_name("clients-announce-port") - .long("clients-announce-port") - .help("The port that will be reported to the directory server") - .takes_value(true), - ) - .arg( - Arg::with_name("inboxes") - .long("inboxes") + Arg::with_name(INBOXES_ARG_NAME) + .long(INBOXES_ARG_NAME) .help("Directory with inboxes where all packets for the clients are stored") .takes_value(true) ) .arg( - Arg::with_name("clients-ledger") - .long("clients-ledger") + Arg::with_name(CLIENTS_LEDGER_ARG_NAME) + .long(CLIENTS_LEDGER_ARG_NAME) .help("Ledger file containing registered clients") .takes_value(true) ) .arg( - Arg::with_name("validators") - .long("validators") + Arg::with_name(VALIDATORS_ARG_NAME) + .long(VALIDATORS_ARG_NAME) .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) .arg( - Arg::with_name("mixnet-contract") - .long("mixnet-contract") + Arg::with_name(CONTRACT_ARG_NAME) + .long(CONTRACT_ARG_NAME) .help("Address of the validator contract managing the network") .takes_value(true), ) @@ -163,7 +133,7 @@ fn version_check(cfg: &Config) -> bool { } pub fn execute(matches: &ArgMatches) { - let id = matches.value_of("id").unwrap(); + let id = matches.value_of(ID_ARG_NAME).unwrap(); println!("Starting gateway {}...", id); @@ -186,14 +156,8 @@ pub fn execute(matches: &ArgMatches) { let sphinx_keypair = load_sphinx_keys(&pathfinder); let identity = load_identity_keys(&pathfinder); - let mix_listening_ip_string = config.get_mix_listening_address().ip().to_string(); - if special_addresses().contains(&mix_listening_ip_string.as_ref()) { - show_binding_warning(mix_listening_ip_string); - } - - let clients_listening_ip_string = config.get_clients_listening_address().ip().to_string(); - if special_addresses().contains(&clients_listening_ip_string.as_ref()) { - show_binding_warning(clients_listening_ip_string); + if special_addresses().contains(&&*config.get_listening_address().to_string()) { + show_binding_warning(config.get_listening_address().to_string()); } println!( @@ -202,21 +166,12 @@ pub fn execute(matches: &ArgMatches) { ); println!( - "Listening for incoming sphinx packets on {}", - config.get_mix_listening_address() + "Listening for incoming packets on {}", + config.get_listening_address() ); println!( - "Announcing the following socket address for sphinx packets: {}", - config.get_mix_announce_address() - ); - - println!( - "Listening for incoming clients packets on {}", - config.get_clients_listening_address() - ); - println!( - "Announcing the following socket address for clients packets: {}", - config.get_clients_announce_address() + "Announcing the following address: {}", + config.get_announce_address() ); println!( diff --git a/gateway/src/commands/upgrade.rs b/gateway/src/commands/upgrade.rs index ee9fa4fb1b..31d79a2cd1 100644 --- a/gateway/src/commands/upgrade.rs +++ b/gateway/src/commands/upgrade.rs @@ -1,14 +1,22 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::DEFAULT_MIXNET_CONTRACT_ADDRESS; -use crate::config::{default_validator_rest_endpoints, Config, MISSING_VALUE}; -use clap::{App, Arg, ArgMatches}; -use config::NymConfig; use std::fmt::Display; use std::process; + +use clap::{App, Arg, ArgMatches}; + +use config::NymConfig; use version_checker::{parse_version, Version}; +use crate::config::DEFAULT_MIXNET_CONTRACT_ADDRESS; +use crate::config::{default_validator_rest_endpoints, Config, MISSING_VALUE}; + +fn fail_upgrade(from_version: D1, to_version: D2) -> ! { + print_failed_upgrade(from_version, to_version); + process::exit(1) +} + fn print_start_upgrade(from: D1, to: D2) { println!( "\n==================\nTrying to upgrade gateway from {} to {} ...", @@ -45,6 +53,16 @@ pub fn command_args<'a, 'b>() -> App<'a, 'b> { .help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'") .takes_value(true) ) + .arg(Arg::with_name("listening-address") + .long("listening-address") + .help("REQUIRED FOR 0.Y.Z UPGRADE. Specifies the listening address of this gateway") + .takes_value(true) + ) + .arg(Arg::with_name("announce-address") + .long("announce-address") + .help("OPTIONAL FOR 0.Y.Z UPGRADE. Specifies the announce address of this gateway. If not provided, it will be set to the same value as listening address") + .takes_value(true) + ) } fn unsupported_upgrade(current_version: Version, config_version: Version) -> ! { @@ -169,8 +187,7 @@ fn minor_010_upgrade( if config.get_validator_mixnet_contract_address() != MISSING_VALUE { eprintln!("existing config seems to have specified mixnet contract address which was only introduced in 0.10.0! Can't perform upgrade."); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); + fail_upgrade(&config_version, &to_version) } println!( @@ -190,8 +207,7 @@ fn minor_010_upgrade( upgraded_config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); + fail_upgrade(&config_version, &to_version) }); print_successful_upgrade(config_version, to_version); @@ -214,8 +230,58 @@ fn patch_010_upgrade( upgraded_config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); + fail_upgrade(&config_version, &to_version) + }); + + print_successful_upgrade(config_version, to_version); + + upgraded_config +} + +// TODO: to be renamed once the release version is decided (so presumably either 0.10.2 or 0.11.0) +fn undetermined_version_upgrade( + config: Config, + matches: &ArgMatches, + config_version: &Version, + package_version: &Version, +) -> Config { + // If we decide this version should be tagged with 0.11.0, then the following code will be used instead: + // let to_version = if package_version.major == 0 && package_version.minor == 11 { + // package_version.clone() + // } else { + // Version::new(0, 11, 0) + // }; + let to_version = package_version; + + print_start_upgrade(&config_version, &to_version); + + // normally the proper approach would have been to keep old structs, parse file according to old structs + // and move it to the new ones + // however, considering that at this point of time we don't have many gateways and all + // are run by us, this would be an unnecessary code complication and so just providing + // addresses again during upgrade is I think a better approach. + let listening_address = matches.value_of("listening-address").unwrap_or_else(|| { + eprintln!( + "trying to upgrade to {} without providing proper listening address!", + to_version + ); + fail_upgrade(&config_version, &to_version) + }); + + let announce_address = if let Some(announce_addr) = matches.value_of("announce-address") { + announce_addr.to_string() + } else { + listening_address.to_string() + }; + + let upgraded_config = config + .with_custom_version(to_version.to_string().as_ref()) + .with_listening_address(listening_address) + .with_announce_address(announce_address); + + upgraded_config.save_to_file(None).unwrap_or_else(|err| { + eprintln!("failed to overwrite config file! - {:?}", err); + fail_upgrade(&config_version, &to_version) }); print_successful_upgrade(config_version, to_version); @@ -235,7 +301,15 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches, package_version: Version config = match config_version.major { 0 => match config_version.minor { 9 => minor_010_upgrade(config, matches, &config_version, &package_version), - 10 => patch_010_upgrade(config, matches, &config_version, &package_version), + 10 => match config_version.patch { + 0 => patch_010_upgrade(config, matches, &config_version, &package_version), + _ => undetermined_version_upgrade( + config, + matches, + &config_version, + &package_version, + ), + }, _ => unsupported_upgrade(config_version, package_version), }, _ => unsupported_upgrade(config_version, package_version), diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 1dad80b084..f05dee99b3 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -3,11 +3,10 @@ use crate::config::template::config_template; use config::{deserialize_duration, deserialize_validators, NymConfig}; -use log::*; +use log::error; use serde::{Deserialize, Serialize}; -use std::net::{IpAddr, SocketAddr}; +use std::net::IpAddr; use std::path::PathBuf; -use std::str::FromStr; use std::time::Duration; pub mod persistence; @@ -21,7 +20,6 @@ const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000; pub(crate) const DEFAULT_VALIDATOR_REST_ENDPOINTS: &[&str] = &[ "http://testnet-finney-validator.nymtech.net:1317", "http://testnet-finney-validator2.nymtech.net:1317", - "http://mixnet.club:1317", ]; pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "hal1k0jntykt7e4g3y88ltc60czgjuqdy4c9c6gv94"; @@ -53,13 +51,22 @@ pub fn missing_vec_string_value() -> Vec { vec![missing_string_value()] } +fn bind_all_address() -> IpAddr { + "0.0.0.0".parse().unwrap() +} + +fn default_mix_port() -> u16 { + DEFAULT_MIX_LISTENING_PORT +} + +fn default_clients_port() -> u16 { + DEFAULT_CLIENT_LISTENING_PORT +} + #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] pub struct Config { gateway: Gateway, - mixnet_endpoint: MixnetEndpoint, - clients_endpoint: ClientsEndpoint, #[serde(default)] @@ -155,167 +162,36 @@ impl Config { self } - pub fn with_mix_listening_host>(mut self, host: S) -> Self { - // see if the provided `host` is just an ip address or ip:port - let host = host.into(); - - // is it ip:port? - match SocketAddr::from_str(host.as_ref()) { - Ok(socket_addr) => { - self.mixnet_endpoint.listening_address = socket_addr; - self - } - // try just for ip - Err(_) => match IpAddr::from_str(host.as_ref()) { - Ok(ip_addr) => { - self.mixnet_endpoint.listening_address.set_ip(ip_addr); - self - } - Err(_) => { - error!( - "failed to make any changes to config - invalid host {}", - host - ); - self - } - }, + pub fn with_listening_address>(mut self, listening_address: S) -> Self { + let listening_address_string = listening_address.into(); + if let Ok(ip_addr) = listening_address_string.parse() { + self.gateway.listening_address = ip_addr + } else { + error!( + "failed to change listening address. the provided value ({}) was invalid", + listening_address_string + ) } - } - - pub fn with_mix_listening_port(mut self, port: u16) -> Self { - self.mixnet_endpoint.listening_address.set_port(port); self } - pub fn with_mix_announce_host>(mut self, host: S) -> Self { - // this is slightly more complicated as we store announce information as String, - // since it might not necessarily be a valid SocketAddr (say `nymtech.net:8080` is a valid - // announce address, yet invalid SocketAddr` - - // first let's see if we received host:port or just host part of an address - let host = host.into(); - match host.split(':').count() { - 1 => { - // we provided only 'host' part so we are going to reuse existing port - self.mixnet_endpoint.announce_address = - format!("{}:{}", host, self.mixnet_endpoint.listening_address.port()); - self - } - 2 => { - // we provided 'host:port' so just put the whole thing there - self.mixnet_endpoint.announce_address = host; - self - } - _ => { - // we provided something completely invalid, so don't try to parse it - error!( - "failed to make any changes to config - invalid announce host {}", - host - ); - self - } - } - } - - pub fn mix_announce_host_from_listening_host(mut self) -> Self { - self.mixnet_endpoint.announce_address = self.mixnet_endpoint.listening_address.to_string(); + pub fn with_announce_address>(mut self, announce_address: S) -> Self { + self.gateway.announce_address = announce_address.into(); self } - pub fn with_mix_announce_port(mut self, port: u16) -> Self { - let current_host: Vec<_> = self.mixnet_endpoint.announce_address.split(':').collect(); - debug_assert_eq!(current_host.len(), 2); - self.mixnet_endpoint.announce_address = format!("{}:{}", current_host[0], port); + pub fn with_mix_port(mut self, port: u16) -> Self { + self.gateway.mix_port = port; self } - pub fn with_clients_listening_host>(mut self, host: S) -> Self { - // see if the provided `host` is just an ip address or ip:port - let host = host.into(); - - // is it ip:port? - match SocketAddr::from_str(host.as_ref()) { - Ok(socket_addr) => { - self.clients_endpoint.listening_address = socket_addr; - self - } - // try just for ip - Err(_) => match IpAddr::from_str(host.as_ref()) { - Ok(ip_addr) => { - self.clients_endpoint.listening_address.set_ip(ip_addr); - self - } - Err(_) => { - error!( - "failed to make any changes to config - invalid host {}", - host - ); - self - } - }, - } - } - - pub fn clients_announce_host_from_listening_host(mut self) -> Self { - self.clients_endpoint.announce_address = format!( - "ws://{}", - self.clients_endpoint.listening_address.to_string() - ); + pub fn with_clients_port(mut self, port: u16) -> Self { + self.gateway.clients_port = port; self } - pub fn with_clients_listening_port(mut self, port: u16) -> Self { - self.clients_endpoint.listening_address.set_port(port); - self - } - - pub fn with_clients_announce_host>(mut self, host: S) -> Self { - // this is slightly more complicated as we store announce information as String, - // since it might not necessarily be a valid SocketAddr (say `nymtech.net:8080` is a valid - // announce address, yet invalid SocketAddr` - - // first let's see if we received host:port or just host part of an address - let host = host.into(); - match host.split(':').count() { - 1 => { - // we provided only 'host' part so we are going to reuse existing port - self.clients_endpoint.announce_address = format!( - "{}:{}", - host, - self.clients_endpoint.listening_address.port() - ); - // make sure it has 'ws' prefix (by extension it also includes 'wss') - if !self.clients_endpoint.announce_address.starts_with("ws") { - self.clients_endpoint.announce_address = - format!("ws://{}", self.clients_endpoint.announce_address); - } - self - } - 2 => { - // we provided 'host:port' so just put the whole thing there - self.clients_endpoint.announce_address = host; - // make sure it has 'ws' prefix (by extension it also includes 'wss') - if !self.clients_endpoint.announce_address.starts_with("ws") { - self.clients_endpoint.announce_address = - format!("ws://{}", self.clients_endpoint.announce_address); - } - self - } - _ => { - // we provided something completely invalid, so don't try to parse it - error!( - "failed to make any changes to config - invalid announce host {}", - host - ); - self - } - } - } - - pub fn with_clients_announce_port(mut self, port: u16) -> Self { - let current_host: Vec<_> = self.clients_endpoint.announce_address.split(':').collect(); - debug_assert_eq!(current_host.len(), 2); - self.clients_endpoint.announce_address = format!("{}:{}", current_host[0], port); + pub fn announce_host_from_listening_host(mut self) -> Self { + self.gateway.announce_address = self.gateway.listening_address.to_string(); self } @@ -363,20 +239,20 @@ impl Config { self.gateway.mixnet_contract_address.clone() } - pub fn get_mix_listening_address(&self) -> SocketAddr { - self.mixnet_endpoint.listening_address + pub fn get_listening_address(&self) -> IpAddr { + self.gateway.listening_address } - pub fn get_mix_announce_address(&self) -> String { - self.mixnet_endpoint.announce_address.clone() + pub fn get_announce_address(&self) -> String { + self.gateway.announce_address.clone() } - pub fn get_clients_listening_address(&self) -> SocketAddr { - self.clients_endpoint.listening_address + pub fn get_mix_port(&self) -> u16 { + self.gateway.mix_port } - pub fn get_clients_announce_address(&self) -> String { - self.clients_endpoint.announce_address.clone() + pub fn get_clients_port(&self) -> u16 { + self.gateway.clients_port } pub fn get_clients_inboxes_dir(&self) -> PathBuf { @@ -429,6 +305,26 @@ pub struct Gateway { /// ID specifies the human readable ID of this particular gateway. id: String, + /// Address to which this mixnode will bind to and will be listening for packets. + #[serde(default = "bind_all_address")] + listening_address: IpAddr, + + /// Optional address announced to the validator for the clients to connect to. + /// It is useful, say, in NAT scenarios or wanting to more easily update actual IP address + /// later on by using name resolvable with a DNS query, such as `nymtech.net`. + #[serde(default = "missing_string_value")] + announce_address: String, + + /// Port used for listening for all mixnet traffic. + /// (default: 1789) + #[serde(default = "default_mix_port")] + mix_port: u16, + + /// Port used for listening for all client-related traffic. + /// (default: 9000) + #[serde(default = "default_clients_port")] + clients_port: u16, + /// Path to file containing private identity key. private_identity_key_file: PathBuf, @@ -481,6 +377,10 @@ impl Default for Gateway { Gateway { version: env!("CARGO_PKG_VERSION").to_string(), id: "".to_string(), + listening_address: bind_all_address(), + announce_address: "127.0.0.1".to_string(), + mix_port: DEFAULT_MIX_LISTENING_PORT, + clients_port: DEFAULT_CLIENT_LISTENING_PORT, private_identity_key_file: Default::default(), public_identity_key_file: Default::default(), private_sphinx_key_file: Default::default(), @@ -493,47 +393,7 @@ impl Default for Gateway { } #[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct MixnetEndpoint { - /// Socket address to which this gateway will bind to - /// and will be listening for sphinx packets coming from the mixnet. - listening_address: SocketAddr, - - /// Optional address announced to the directory server for the clients to connect to. - /// It is useful, say, in NAT scenarios or wanting to more easily update actual IP address - /// later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. - /// Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` - /// are valid announce addresses, while the later will default to whatever port is used for - /// `listening_address`. - announce_address: String, -} - -impl Default for MixnetEndpoint { - fn default() -> Self { - MixnetEndpoint { - listening_address: format!("0.0.0.0:{}", DEFAULT_MIX_LISTENING_PORT) - .parse() - .unwrap(), - announce_address: format!("127.0.0.1:{}", DEFAULT_MIX_LISTENING_PORT), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] pub struct ClientsEndpoint { - /// Socket address to which this gateway will bind to - /// and will be listening for data packets coming from the clients. - listening_address: SocketAddr, - - /// Optional address announced to the directory server for the clients to connect to. - /// It is useful, say, in NAT scenarios or wanting to more easily update actual IP address - /// later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. - /// Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` - /// are valid announce addresses, while the later will default to whatever port is used for - /// `listening_address`. - announce_address: String, - /// Path to the directory with clients inboxes containing messages stored for them. inboxes_directory: PathBuf, @@ -555,10 +415,6 @@ impl ClientsEndpoint { impl Default for ClientsEndpoint { fn default() -> Self { ClientsEndpoint { - listening_address: format!("0.0.0.0:{}", DEFAULT_CLIENT_LISTENING_PORT) - .parse() - .unwrap(), - announce_address: format!("ws://127.0.0.1:{}", DEFAULT_CLIENT_LISTENING_PORT), inboxes_directory: Default::default(), ledger_path: Default::default(), } diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index 000aad2368..2ac303b465 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -19,6 +19,9 @@ version = '{{ gateway.version }}' # Human readable ID of this particular gateway. id = '{{ gateway.id }}' +# Socket address to which this gateway will bind to and will be listening for packets. +listening_address = '{{ gateway.listening_address }}' + # Path to file containing private identity key. private_identity_key_file = '{{ gateway.private_identity_key_file }}' @@ -31,7 +34,22 @@ private_sphinx_key_file = '{{ gateway.private_sphinx_key_file }}' # Path to file containing public sphinx key. public_sphinx_key_file = '{{ gateway.public_sphinx_key_file }}' -# Validator server to which the node will be reporting their presence data. +##### additional gateway config options ##### + +# Optional address announced to the directory server for the clients to connect to. +# It is useful, say, in NAT scenarios or wanting to more easily update actual IP address +# later on by using name resolvable with a DNS query, such as `nymtech.net`. +announce_address = '{{ gateway.announce_address }}' + +# Port used for listening for all mixnet traffic. +# (default: 1789) +mix_port = {{ gateway.mix_port }} + +# Port used for listening for all client websocket traffic. +# (default: 9000) +clients_port = {{ gateway.clients_port }} + +# Validator server to which the node will be getting information about the network. validator_rest_urls = [ {{#each gateway.validator_rest_urls }} '{{this}}', @@ -41,41 +59,15 @@ validator_rest_urls = [ # Address of the validator contract managing the network. mixnet_contract_address = '{{ gateway.mixnet_contract_address }}' +##### advanced configuration options ##### + # nym_home_directory specifies absolute path to the home nym gateway directory. # It is expected to use default value and hence .toml file should not redefine this field. nym_root_directory = '{{ gateway.nym_root_directory }}' - - -##### Mixnet endpoint config options ##### - -[mixnet_endpoint] -# Socket address to which this gateway will bind to -# and will be listening for sphinx packets coming from the mixnet. -listening_address = '{{ mixnet_endpoint.listening_address }}' - -# Optional address announced to the directory server for the clients to connect to. -# It is useful, say, in NAT scenarios or wanting to more easily update actual IP address -# later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. -# Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` -# are valid announce addresses, while the later will default to whatever port is used for -# `listening_address`. -announce_address = '{{ mixnet_endpoint.announce_address }}' - #### Clients endpoint config options ##### [clients_endpoint] -# Socket address to which this gateway will bind to -# and will be listening for sphinx packets coming from the mixnet. -listening_address = '{{ clients_endpoint.listening_address }}' - -# Optional address announced to the directory server for the clients to connect to. -# It is useful, say, in NAT scenarios or wanting to more easily update actual IP address -# later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. -# Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` -# are valid announce addresses, while the later will default to whatever port is used for -# `listening_address`. -announce_address = '{{ clients_endpoint.announce_address }}' # Path to the directory with clients inboxes containing messages stored for them. inboxes_directory = '{{ clients_endpoint.inboxes_directory }}' diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 9d4793e4d4..8552c37a85 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -9,6 +9,7 @@ use crate::node::storage::{inboxes, ClientLedger}; use crypto::asymmetric::{encryption, identity}; use log::*; use mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; +use std::net::SocketAddr; use std::process; use std::sync::Arc; use tokio::runtime::Runtime; @@ -70,9 +71,12 @@ impl Gateway { ack_sender, ); - let listener = mixnet_handling::Listener::new(self.config.get_mix_listening_address()); + let listening_address = SocketAddr::new( + self.config.get_listening_address(), + self.config.get_mix_port(), + ); - listener.start(connection_handler); + mixnet_handling::Listener::new(listening_address).start(connection_handler); } fn start_client_websocket_listener( @@ -82,11 +86,13 @@ impl Gateway { ) { info!("Starting client [web]socket listener..."); - websocket::Listener::new( - self.config.get_clients_listening_address(), - Arc::clone(&self.identity), - ) - .start(clients_handler_sender, forwarding_channel); + let listening_address = SocketAddr::new( + self.config.get_listening_address(), + self.config.get_clients_port(), + ); + + websocket::Listener::new(listening_address, Arc::clone(&self.identity)) + .start(clients_handler_sender, forwarding_channel); } fn start_packet_forwarder(&self) -> MixForwardingSender { @@ -127,9 +133,6 @@ impl Gateway { // TODO: ask DH whether this function still makes sense in ^0.10 async fn check_if_same_ip_gateway_exists(&self) -> Option { - let announced_mix_host = self.config.get_mix_announce_address(); - let announced_clients_host = self.config.get_clients_announce_address(); - let validator_client_config = validator_client::Config::new( self.config.get_validator_rest_endpoints(), self.config.get_validator_mixnet_contract_address(), @@ -144,12 +147,11 @@ impl Gateway { } }; + let our_host = self.config.get_announce_address(); + existing_gateways .iter() - .find(|node| { - node.gateway.mix_host == announced_mix_host - || node.gateway.clients_host == announced_clients_host - }) + .find(|node| node.gateway.host == our_host) .map(|node| node.gateway().identity_key.clone()) } diff --git a/mixnode/src/commands/describe.rs b/mixnode/src/commands/describe.rs index 4887ae832b..2c5494907b 100644 --- a/mixnode/src/commands/describe.rs +++ b/mixnode/src/commands/describe.rs @@ -1,3 +1,4 @@ +use crate::commands::*; use crate::config::Config; use crate::node::node_description::NodeDescription; use clap::{App, Arg, ArgMatches}; @@ -10,8 +11,8 @@ pub fn command_args<'a, 'b>() -> App<'a, 'b> { App::new("describe") .about("Describe your mixnode and tell people why they should delegate stake to you") .arg( - Arg::with_name("id") - .long("id") + Arg::with_name(ID_ARG_NAME) + .long(ID_ARG_NAME) .help("The id of the mixnode you want to describe") .takes_value(true) .required(true), @@ -21,7 +22,7 @@ pub fn command_args<'a, 'b>() -> App<'a, 'b> { pub fn execute(matches: &ArgMatches) { // figure out which node the user is describing let id = matches - .value_of("id") + .value_of(ID_ARG_NAME) .expect("Please provide the id of your mixnode"); // ensure that the mixnode has in fact been initialized diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index c46c908b51..4edba5ecd0 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -1,7 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::commands::override_config; +use crate::commands::*; use crate::config::persistence::pathfinder::MixNodePathfinder; use crate::config::Config; use clap::{App, Arg, ArgMatches}; @@ -15,61 +15,49 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { App::new("init") .about("Initialise the mixnode") .arg( - Arg::with_name("id") - .long("id") + Arg::with_name(ID_ARG_NAME) + .long(ID_ARG_NAME) .help("Id of the nym-mixnode we want to create config for.") .takes_value(true) .required(true), ) .arg( - Arg::with_name("layer") - .long("layer") + Arg::with_name(LAYER_ARG_NAME) + .long(LAYER_ARG_NAME) .help("The mixnet layer of this particular node") .takes_value(true), ) .arg( - Arg::with_name("host") - .long("host") + Arg::with_name(HOST_ARG_NAME) + .long(HOST_ARG_NAME) .help("The host on which the mixnode will be running") .takes_value(true) .required(true), ) .arg( - Arg::with_name("port") - .long("port") + Arg::with_name(MIX_PORT_ARG_NAME) + .long(MIX_PORT_ARG_NAME) .help("The port on which the mixnode will be listening") .takes_value(true), ) .arg( - Arg::with_name("announce-host") - .long("announce-host") + Arg::with_name(ANNOUNCE_HOST_ARG_NAME) + .long(ANNOUNCE_HOST_ARG_NAME) .help("The custom host that will be reported to the directory server") .takes_value(true), ) .arg( - Arg::with_name("announce-port") - .long("announce-port") - .help("The custom port that will be reported to the directory server") - .takes_value(true), - ) - .arg( - Arg::with_name("validators") - .long("validators") + Arg::with_name(VALIDATORS_ARG_NAME) + .long(VALIDATORS_ARG_NAME) .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) .arg( - Arg::with_name("mixnet-contract") - .long("mixnet-contract") + Arg::with_name(CONTRACT_ARG_NAME) + .long(CONTRACT_ARG_NAME) .help("Address of the validator contract managing the network") .takes_value(true), ) - .arg( - Arg::with_name("metrics-server") - .long("metrics-server") - .help("Server to which the node is sending all metrics data") - .takes_value(true), - ) } async fn choose_layer( @@ -78,7 +66,10 @@ async fn choose_layer( mixnet_contract: String, ) -> u64 { let max_layer = DEFAULT_NUM_MIX_HOPS; - if let Some(layer) = matches.value_of("layer").map(|layer| layer.parse::()) { + if let Some(layer) = matches + .value_of(LAYER_ARG_NAME) + .map(|layer| layer.parse::()) + { if let Err(err) = layer { // if layer was overridden, it must be parsable panic!("Invalid layer value provided - {:?}", err); @@ -139,7 +130,8 @@ fn show_bonding_info(config: &Config) { "\nTo bond your mixnode you will need to provide the following: Identity key: {} Sphinx key: {} - Host: {} + Address: {} + Mix port: {} Layer: {} Location: [physical location of your node's server] Version: {} @@ -147,6 +139,7 @@ fn show_bonding_info(config: &Config) { identity_keypair.public_key().to_base58_string(), sphinx_keypair.public_key().to_base58_string(), config.get_announce_address(), + config.get_mix_port(), config.get_layer(), config.get_version(), ); @@ -157,7 +150,7 @@ pub fn execute(matches: &ArgMatches) { // and then removing runtime from mixnode itself in `run` let rt = Runtime::new().unwrap(); rt.block_on(async { - let id = matches.value_of("id").unwrap(); + let id = matches.value_of(ID_ARG_NAME).unwrap(); println!("Initialising mixnode {}...", id); let already_init = if Config::default_config_file_path(id).exists() { diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index 11b5ad56b4..0cd690220b 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -11,6 +11,14 @@ pub(crate) mod run; pub(crate) mod sign; pub(crate) mod upgrade; +pub(crate) const ID_ARG_NAME: &str = "id"; +pub(crate) const HOST_ARG_NAME: &str = "host"; +pub(crate) const LAYER_ARG_NAME: &str = "layer"; +pub(crate) const MIX_PORT_ARG_NAME: &str = "mix-port"; +pub(crate) const VALIDATORS_ARG_NAME: &str = "validators"; +pub(crate) const CONTRACT_ARG_NAME: &str = "mixnet-contract"; +pub(crate) const ANNOUNCE_HOST_ARG_NAME: &str = "announce-host"; + fn parse_validators(raw: &str) -> Vec { raw.split(',') .map(|raw_validator| raw_validator.trim().into()) @@ -20,12 +28,15 @@ fn parse_validators(raw: &str) -> Vec { pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { let max_layer = DEFAULT_NUM_MIX_HOPS; let mut was_host_overridden = false; - if let Some(host) = matches.value_of("host") { - config = config.with_listening_host(host); + if let Some(host) = matches.value_of(HOST_ARG_NAME) { + config = config.with_listening_address(host); was_host_overridden = true; } - if let Some(layer) = matches.value_of("layer").map(|layer| layer.parse::()) { + if let Some(layer) = matches + .value_of(LAYER_ARG_NAME) + .map(|layer| layer.parse::()) + { if let Err(err) = layer { // if layer was overridden, it must be parsable panic!("Invalid layer value provided - {:?}", err); @@ -36,38 +47,30 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi } } - if let Some(port) = matches.value_of("port").map(|port| port.parse::()) { + if let Some(port) = matches + .value_of(MIX_PORT_ARG_NAME) + .map(|port| port.parse::()) + { if let Err(err) = port { // if port was overridden, it must be parsable - panic!("Invalid port value provided - {:?}", err); + panic!("Invalid mix port value provided - {:?}", err); } - config = config.with_listening_port(port.unwrap()); + config = config.with_mix_port(port.unwrap()); } - if let Some(raw_validators) = matches.value_of("validators") { + if let Some(raw_validators) = matches.value_of(VALIDATORS_ARG_NAME) { config = config.with_custom_validators(parse_validators(raw_validators)); } - if let Some(contract_address) = matches.value_of("mixnet-contract") { + if let Some(contract_address) = matches.value_of(CONTRACT_ARG_NAME) { config = config.with_custom_mixnet_contract(contract_address) } - if let Some(announce_host) = matches.value_of("announce-host") { - config = config.with_announce_host(announce_host); + if let Some(announce_host) = matches.value_of(ANNOUNCE_HOST_ARG_NAME) { + config = config.with_announce_address(announce_host); } else if was_host_overridden { // make sure our 'announce-host' always defaults to 'host' - config = config.announce_host_from_listening_host() - } - - if let Some(announce_port) = matches - .value_of("announce-port") - .map(|port| port.parse::()) - { - if let Err(err) = announce_port { - // if port was overridden, it must be parsable - panic!("Invalid port value provided - {:?}", err); - } - config = config.with_announce_port(announce_port.unwrap()); + config = config.announce_address_from_listening_address() } config diff --git a/mixnode/src/commands/run.rs b/mixnode/src/commands/run.rs index 94d20d6510..be4fd76355 100644 --- a/mixnode/src/commands/run.rs +++ b/mixnode/src/commands/run.rs @@ -1,7 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::commands::override_config; +use crate::commands::*; use crate::config::{persistence::pathfinder::MixNodePathfinder, Config}; use crate::node::node_description::NodeDescription; use crate::node::MixNode; @@ -15,61 +15,49 @@ pub fn command_args<'a, 'b>() -> App<'a, 'b> { App::new("run") .about("Starts the mixnode") .arg( - Arg::with_name("id") - .long("id") + Arg::with_name(ID_ARG_NAME) + .long(ID_ARG_NAME) .help("Id of the nym-mixnode we want to run") .takes_value(true) .required(true), ) // the rest of arguments are optional, they are used to override settings in config file .arg( - Arg::with_name("layer") - .long("layer") + Arg::with_name(LAYER_ARG_NAME) + .long(LAYER_ARG_NAME) .help("The mixnet layer of this particular node") .takes_value(true), ) .arg( - Arg::with_name("host") - .long("host") + Arg::with_name(HOST_ARG_NAME) + .long(HOST_ARG_NAME) .help("The custom host on which the mixnode will be running") .takes_value(true), ) .arg( - Arg::with_name("port") - .long("port") + Arg::with_name(MIX_PORT_ARG_NAME) + .long(MIX_PORT_ARG_NAME) .help("The port on which the mixnode will be listening") .takes_value(true), ) .arg( - Arg::with_name("announce-host") - .long("announce-host") + Arg::with_name(ANNOUNCE_HOST_ARG_NAME) + .long(ANNOUNCE_HOST_ARG_NAME) .help("The host that will be reported to the directory server") .takes_value(true), ) .arg( - Arg::with_name("announce-port") - .long("announce-port") - .help("The port that will be reported to the directory server") - .takes_value(true), - ) - .arg( - Arg::with_name("validators") - .long("validators") + Arg::with_name(VALIDATORS_ARG_NAME) + .long(VALIDATORS_ARG_NAME) .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) .arg( - Arg::with_name("mixnet-contract") - .long("mixnet-contract") + Arg::with_name(CONTRACT_ARG_NAME) + .long(CONTRACT_ARG_NAME) .help("Address of the validator contract managing the network") .takes_value(true), ) - .arg( - Arg::with_name("metrics-server") - .long("metrics-server") - .help("Server to which the node is sending all metrics data") - .takes_value(true), - ) } fn show_binding_warning(address: String) { @@ -125,7 +113,7 @@ fn version_check(cfg: &Config) -> bool { } pub fn execute(matches: &ArgMatches) { - let id = matches.value_of("id").unwrap(); + let id = matches.value_of(ID_ARG_NAME).unwrap(); println!("Starting mixnode {}...", id); @@ -148,9 +136,8 @@ pub fn execute(matches: &ArgMatches) { let identity_keypair = load_identity_keys(&pathfinder); let sphinx_keypair = load_sphinx_keys(&pathfinder); - let listening_ip_string = config.get_listening_address().ip().to_string(); - if special_addresses().contains(&listening_ip_string.as_ref()) { - show_binding_warning(listening_ip_string); + if special_addresses().contains(&&*config.get_listening_address().to_string()) { + show_binding_warning(config.get_listening_address().to_string()); } println!( @@ -162,7 +149,7 @@ pub fn execute(matches: &ArgMatches) { config.get_listening_address() ); println!( - "Announcing the following socket address: {}", + "Announcing the following address: {}", config.get_announce_address() ); @@ -170,7 +157,8 @@ pub fn execute(matches: &ArgMatches) { "\nTo bond your mixnode, go to https://web-wallet-finney.nymtech.net/. You will need to provide the following: Identity key: {} Sphinx key: {} - Host: {} + Address: {} + Mix port: {} Layer: {} Location: [physical location of your node's server] Version: {} @@ -178,6 +166,7 @@ pub fn execute(matches: &ArgMatches) { identity_keypair.public_key().to_base58_string(), sphinx_keypair.public_key().to_base58_string(), config.get_announce_address(), + config.get_mix_port(), config.get_layer(), config.get_version(), ); diff --git a/mixnode/src/commands/sign.rs b/mixnode/src/commands/sign.rs index de490bd80d..bca25d4740 100644 --- a/mixnode/src/commands/sign.rs +++ b/mixnode/src/commands/sign.rs @@ -1,3 +1,4 @@ +use crate::commands::*; use crate::config::{persistence::pathfinder::MixNodePathfinder, Config}; use clap::{App, Arg, ArgMatches}; use colored::*; @@ -5,19 +6,21 @@ use config::NymConfig; use crypto::asymmetric::identity; use log::error; +const SIGN_TEXT_ARG_NAME: &str = "text"; + pub fn command_args<'a, 'b>() -> App<'a, 'b> { App::new("sign") .about("Sign text to prove ownership of this mixnode") .arg( - Arg::with_name("id") - .long("id") + Arg::with_name(ID_ARG_NAME) + .long(ID_ARG_NAME) .help("The id of the mixnode you want to sign with") .takes_value(true) .required(true), ) .arg( - Arg::with_name("text") - .long("text") + Arg::with_name(SIGN_TEXT_ARG_NAME) + .long(SIGN_TEXT_ARG_NAME) .help("The text to sign") .takes_value(true) .required(true), @@ -34,8 +37,8 @@ fn load_identity_keys(pathfinder: &MixNodePathfinder) -> identity::KeyPair { } pub fn execute(matches: &ArgMatches) { - let id = matches.value_of("id").unwrap(); - let text = matches.value_of("text").unwrap(); + let id = matches.value_of(ID_ARG_NAME).unwrap(); + let text = matches.value_of(SIGN_TEXT_ARG_NAME).unwrap(); let config = match Config::load_from_file(id) { Ok(cfg) => cfg, diff --git a/mixnode/src/commands/upgrade.rs b/mixnode/src/commands/upgrade.rs index db046f9599..847e407306 100644 --- a/mixnode/src/commands/upgrade.rs +++ b/mixnode/src/commands/upgrade.rs @@ -1,6 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::commands::*; use crate::config::{ default_validator_rest_endpoints, missing_string_value, Config, DEFAULT_MIXNET_CONTRACT_ADDRESS, MISSING_VALUE, @@ -9,10 +10,19 @@ use clap::{App, Arg, ArgMatches}; use config::NymConfig; use crypto::asymmetric::identity; use std::fmt::Display; +use std::net::SocketAddr; use std::path::PathBuf; use std::process; +use std::str::FromStr; use version_checker::{parse_version, Version}; +const CURRENT_VERSION_ARG_NAME: &str = "current-version"; + +fn fail_upgrade(from_version: D1, to_version: D2) -> ! { + print_failed_upgrade(from_version, to_version); + process::exit(1) +} + fn print_start_upgrade(from: D1, to: D2) { println!( "\n==================\nTrying to upgrade mixnode from {} to {} ...", @@ -37,15 +47,15 @@ fn print_successful_upgrade(from: D1, to: D2) { pub fn command_args<'a, 'b>() -> App<'a, 'b> { App::new("upgrade").about("Try to upgrade the mixnode") .arg( - Arg::with_name("id") - .long("id") + Arg::with_name(ID_ARG_NAME) + .long(ID_ARG_NAME) .help("Id of the nym-mixnode we want to upgrade") .takes_value(true) .required(true), ) // the rest of arguments depend on the upgrade path - .arg(Arg::with_name("current version") - .long("current-version") + .arg(Arg::with_name(CURRENT_VERSION_ARG_NAME) + .long(CURRENT_VERSION_ARG_NAME) .help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'") .takes_value(true) ) @@ -123,28 +133,24 @@ fn pre_090_upgrade(from: &str, config: Config) -> Config { if from_version.major == 0 && from_version.minor < 8 { // technically this could be implemented, but is there any point in that? eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead"); - print_failed_upgrade(&from_version, &to_version); - process::exit(1) + fail_upgrade(&from_version, &to_version) } if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 { eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version"); - print_failed_upgrade(&from_version, &to_version); - process::exit(1) + fail_upgrade(&from_version, &to_version) } if config.get_private_identity_key_file() != missing_string_value::() || config.get_public_identity_key_file() != missing_string_value::() { eprintln!("existing config seems to have specified identity keys which were only introduced in 0.9.0! Can't perform upgrade."); - print_failed_upgrade(&from_version, &to_version); - process::exit(1); + fail_upgrade(&from_version, &to_version) } if config.get_validator_rest_endpoints()[0] != missing_string_value::() { eprintln!("existing config seems to have specified new validator rest endpoint which was only introduced in 0.9.0! Can't perform upgrade."); - print_failed_upgrade(&from_version, &to_version); - process::exit(1); + fail_upgrade(&from_version, &to_version) } let mut upgraded_config = config @@ -175,8 +181,7 @@ fn pre_090_upgrade(from: &str, config: Config) -> Config { upgraded_config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&from_version, &to_version); - process::exit(1); + fail_upgrade(&from_version, &to_version) }); print_successful_upgrade(from_version, to_version); @@ -200,8 +205,7 @@ fn minor_010_upgrade( if config.get_validator_mixnet_contract_address() != MISSING_VALUE { eprintln!("existing config seems to have specified mixnet contract address which was only introduced in 0.10.0! Can't perform upgrade."); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); + fail_upgrade(&config_version, to_version) } println!( @@ -221,8 +225,7 @@ fn minor_010_upgrade( upgraded_config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); + fail_upgrade(&config_version, &to_version) }); print_successful_upgrade(config_version, to_version); @@ -246,8 +249,56 @@ fn patch_010_upgrade( upgraded_config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); + fail_upgrade(&config_version, to_version) + }); + + print_successful_upgrade(config_version, to_version); + + upgraded_config +} + +// TODO: to be renamed once the release version is decided (so presumably either 0.10.2 or 0.11.0) +fn undetermined_version_upgrade( + config: Config, + _matches: &ArgMatches, + config_version: &Version, + package_version: &Version, +) -> Config { + // If we decide this version should be tagged with 0.11.0, then the following code will be used instead: + // let to_version = if package_version.major == 0 && package_version.minor == 11 { + // package_version.clone() + // } else { + // Version::new(0, 11, 0) + // }; + let to_version = package_version; + + print_start_upgrade(&config_version, &to_version); + + let current_annnounce_addr = config.get_announce_address(); + // try to parse it as socket address directly + let (announce_address, custom_mix_port) = match SocketAddr::from_str(¤t_annnounce_addr) { + Ok(addr) => (addr.ip().to_string(), addr.port()), + Err(_) => { + let announce_split = current_annnounce_addr.split(':').collect::>(); + if announce_split.len() != 2 { + eprintln!("failed to correctly parse current announce host"); + fail_upgrade(&config_version, &to_version) + } + ( + announce_split[0].to_string(), + announce_split[1].parse().unwrap(), + ) + } + }; + + let upgraded_config = config + .with_custom_version(to_version.to_string().as_ref()) + .with_announce_address(announce_address) + .with_mix_port(custom_mix_port); + + upgraded_config.save_to_file(None).unwrap_or_else(|err| { + eprintln!("failed to overwrite config file! - {:?}", err); + fail_upgrade(&config_version, to_version) }); print_successful_upgrade(config_version, to_version); @@ -267,7 +318,15 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches, package_version: Version config = match config_version.major { 0 => match config_version.minor { 9 => minor_010_upgrade(config, matches, &config_version, &package_version), - 10 => patch_010_upgrade(config, matches, &config_version, &package_version), + 10 => match config_version.patch { + 0 => patch_010_upgrade(config, matches, &config_version, &package_version), + _ => undetermined_version_upgrade( + config, + matches, + &config_version, + &package_version, + ), + }, _ => unsupported_upgrade(config_version, package_version), }, _ => unsupported_upgrade(config_version, package_version), @@ -278,7 +337,7 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches, package_version: Version pub fn execute(matches: &ArgMatches) { let package_version = parse_package_version(); - let id = matches.value_of("id").unwrap(); + let id = matches.value_of(ID_ARG_NAME).unwrap(); let mut existing_config = Config::load_from_file(id).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {:?}", err); @@ -287,12 +346,15 @@ pub fn execute(matches: &ArgMatches) { // versions fields were added in 0.9.0 if existing_config.get_version() == missing_string_value::() { - let self_reported_version = matches.value_of("current version").unwrap_or_else(|| { - eprintln!( + let self_reported_version = + matches + .value_of(CURRENT_VERSION_ARG_NAME) + .unwrap_or_else(|| { + eprintln!( "trying to upgrade from pre v0.9.0 without providing current system version!" ); - process::exit(1) - }); + process::exit(1) + }); // upgrades up to 0.9.0 existing_config = pre_090_upgrade(self_reported_version, existing_config); diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 10d84ed849..d3240c975c 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -3,8 +3,7 @@ use crate::config::template::config_template; use config::{deserialize_duration, deserialize_validators, NymConfig}; -use log::error; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; @@ -16,11 +15,10 @@ mod template; pub(crate) const MISSING_VALUE: &str = "MISSING VALUE"; // 'MIXNODE' -const DEFAULT_LISTENING_PORT: u16 = 1789; +const DEFAULT_MIX_LISTENING_PORT: u16 = 1789; pub(crate) const DEFAULT_VALIDATOR_REST_ENDPOINTS: &[&str] = &[ "http://testnet-finney-validator.nymtech.net:1317", "http://testnet-finney-validator2.nymtech.net:1317", - "http://mixnet.club:1317", ]; pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "hal1k0jntykt7e4g3y88ltc60czgjuqdy4c9c6gv94"; @@ -58,13 +56,37 @@ pub fn missing_vec_string_value() -> Vec { vec![missing_string_value()] } +fn bind_all_address() -> IpAddr { + "0.0.0.0".parse().unwrap() +} + +fn default_mix_port() -> u16 { + DEFAULT_MIX_LISTENING_PORT +} + +// basically a migration helper that deserialises string representation of a maybe socket addr (like "1.1.1.1:1234") +// into just the ipaddr (like "1.1.1.1") +pub(super) fn de_ipaddr_from_maybe_str_socks_addr<'de, D>( + deserializer: D, +) -> Result +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + if let Ok(socket_addr) = SocketAddr::from_str(&s) { + Ok(socket_addr.ip()) + } else { + IpAddr::from_str(&s).map_err(serde::de::Error::custom) + } +} + #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { mixnode: MixNode, #[serde(default)] - rtt_measurement: RttMeasurement, + verloc: Verloc, #[serde(default)] logging: Logging, #[serde(default)] @@ -152,80 +174,34 @@ impl Config { self } - pub fn with_listening_host>(mut self, host: S) -> Self { - // see if the provided `host` is just an ip address or ip:port - let host = host.into(); - - // is it ip:port? - match SocketAddr::from_str(host.as_ref()) { - Ok(socket_addr) => { - self.mixnode.listening_address = socket_addr; - self - } - // try just for ip - Err(_) => match IpAddr::from_str(host.as_ref()) { - Ok(ip_addr) => { - self.mixnode.listening_address.set_ip(ip_addr); - self - } - Err(_) => { - error!( - "failed to make any changes to config - invalid host {}", - host - ); - self - } - }, + pub fn with_listening_address>(mut self, listening_address: S) -> Self { + let listening_address_string = listening_address.into(); + if let Ok(ip_addr) = listening_address_string.parse() { + self.mixnode.listening_address = ip_addr + } else { + error!( + "failed to change listening address. the provided value ({}) was invalid", + listening_address_string + ) } - } - - pub fn with_listening_port(mut self, port: u16) -> Self { - self.mixnode.listening_address.set_port(port); self } - pub fn with_announce_host>(mut self, host: S) -> Self { - // this is slightly more complicated as we store announce information as String, - // since it might not necessarily be a valid SocketAddr (say `nymtech.net:8080` is a valid - // announce address, yet invalid SocketAddr` - - // first lets see if we received host:port or just host part of an address - let host = host.into(); - match host.split(':').count() { - 1 => { - // we provided only 'host' part so we are going to reuse existing port - self.mixnode.announce_address = - format!("{}:{}", host, self.mixnode.listening_address.port()); - self - } - 2 => { - // we provided 'host:port' so just put the whole thing there - self.mixnode.announce_address = host; - self - } - _ => { - // we provided something completely invalid, so don't try to parse it - error!( - "failed to make any changes to config - invalid announce host {}", - host - ); - self - } - } + pub fn with_announce_address>(mut self, announce_address: S) -> Self { + self.mixnode.announce_address = announce_address.into(); + self } - pub fn announce_host_from_listening_host(mut self) -> Self { + pub fn with_mix_port(mut self, port: u16) -> Self { + self.mixnode.mix_port = port; + self + } + + pub fn announce_address_from_listening_address(mut self) -> Self { self.mixnode.announce_address = self.mixnode.listening_address.to_string(); self } - pub fn with_announce_port(mut self, port: u16) -> Self { - let current_host: Vec<_> = self.mixnode.announce_address.split(':').collect(); - debug_assert_eq!(current_host.len(), 2); - self.mixnode.announce_address = format!("{}:{}", current_host[0], port); - self - } - pub fn with_custom_version(mut self, version: &str) -> Self { self.mixnode.version = version.to_string(); self @@ -272,7 +248,7 @@ impl Config { self.mixnode.layer } - pub fn get_listening_address(&self) -> SocketAddr { + pub fn get_listening_address(&self) -> IpAddr { self.mixnode.listening_address } @@ -280,6 +256,10 @@ impl Config { self.mixnode.announce_address.clone() } + pub fn get_mix_port(&self) -> u16 { + self.mixnode.mix_port + } + pub fn get_packet_forwarding_initial_backoff(&self) -> Duration { self.debug.packet_forwarding_initial_backoff } @@ -305,27 +285,27 @@ impl Config { } pub fn get_measurement_packets_per_node(&self) -> usize { - self.rtt_measurement.packets_per_node + self.verloc.packets_per_node } pub fn get_measurement_packet_timeout(&self) -> Duration { - self.rtt_measurement.packet_timeout + self.verloc.packet_timeout } pub fn get_measurement_connection_timeout(&self) -> Duration { - self.rtt_measurement.connection_timeout + self.verloc.connection_timeout } pub fn get_measurement_delay_between_packets(&self) -> Duration { - self.rtt_measurement.delay_between_packets + self.verloc.delay_between_packets } pub fn get_measurement_tested_nodes_batch_size(&self) -> usize { - self.rtt_measurement.tested_nodes_batch_size + self.verloc.tested_nodes_batch_size } pub fn get_measurement_testing_interval(&self) -> Duration { - self.rtt_measurement.testing_interval + self.verloc.testing_interval } pub fn get_measurement_retry_timeout(&self) -> Duration { - self.rtt_measurement.retry_timeout + self.verloc.retry_timeout } // upgrade-specific @@ -349,17 +329,20 @@ pub struct MixNode { /// Layer of this particular mixnode determining its position in the network. layer: u64, - /// Socket address to which this mixnode will bind to and will be listening for packets. - listening_address: SocketAddr, + /// Address to which this mixnode will bind to and will be listening for packets. + #[serde(deserialize_with = "de_ipaddr_from_maybe_str_socks_addr")] + listening_address: IpAddr, /// Optional address announced to the validator for the clients to connect to. /// It is useful, say, in NAT scenarios or wanting to more easily update actual IP address - /// later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. - /// Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` - /// are valid announce addresses, while the later will default to whatever port is used for - /// `listening_address`. + /// later on by using name resolvable with a DNS query, such as `nymtech.net`. announce_address: String, + /// Port used for listening for all mixnet traffic. + /// (default: 1789) + #[serde(default = "default_mix_port")] + mix_port: u16, + /// Path to file containing private identity key. #[serde(default = "missing_string_value")] private_identity_key_file: PathBuf, @@ -374,7 +357,7 @@ pub struct MixNode { /// Path to file containing public sphinx key. public_sphinx_key_file: PathBuf, - /// Validator server to which the node will be reporting their presence data. + /// Validator server from which the node gets the view on the network. #[serde( deserialize_with = "deserialize_validators", default = "missing_vec_string_value", @@ -415,10 +398,9 @@ impl Default for MixNode { version: env!("CARGO_PKG_VERSION").to_string(), id: "".to_string(), layer: 0, - listening_address: format!("0.0.0.0:{}", DEFAULT_LISTENING_PORT) - .parse() - .unwrap(), - announce_address: format!("127.0.0.1:{}", DEFAULT_LISTENING_PORT), + listening_address: bind_all_address(), + announce_address: "127.0.0.1".to_string(), + mix_port: DEFAULT_MIX_LISTENING_PORT, private_identity_key_file: Default::default(), public_identity_key_file: Default::default(), private_sphinx_key_file: Default::default(), @@ -442,7 +424,7 @@ impl Default for Logging { #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -pub struct RttMeasurement { +pub struct Verloc { /// Specifies number of echo packets sent to each node during a measurement run. packets_per_node: usize, @@ -466,9 +448,9 @@ pub struct RttMeasurement { retry_timeout: Duration, } -impl Default for RttMeasurement { +impl Default for Verloc { fn default() -> Self { - RttMeasurement { + Verloc { packets_per_node: DEFAULT_PACKETS_PER_NODE, connection_timeout: DEFAULT_CONNECTION_TIMEOUT, packet_timeout: DEFAULT_PACKET_TIMEOUT, diff --git a/mixnode/src/config/template.rs b/mixnode/src/config/template.rs index 58299e0bba..dfaae35eae 100644 --- a/mixnode/src/config/template.rs +++ b/mixnode/src/config/template.rs @@ -41,13 +41,14 @@ public_sphinx_key_file = '{{ mixnode.public_sphinx_key_file }}' # Optional address announced to the directory server for the clients to connect to. # It is useful, say, in NAT scenarios or wanting to more easily update actual IP address -# later on by using name resolvable with a DNS query, such as `nymtech.net:8080`. -# Additionally a custom port can be provided, so both `nymtech.net:8080` and `nymtech.net` -# are valid announce addresses, while the later will default to whatever port is used for -# `listening_address`. +# later on by using name resolvable with a DNS query, such as `nymtech.net`. announce_address = '{{ mixnode.announce_address }}' -# Validator server to which the node will be reporting their presence data. +# Port used for listening for all mixnet traffic. +# (default: 1789) +mix_port = {{ mixnode.mix_port }} + +# Validator server to which the node will be getting information about the network. validator_rest_urls = [ {{#each mixnode.validator_rest_urls }} '{{this}}', diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 8582c15d0d..3b5ed2219e 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -17,6 +17,7 @@ use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSende use crypto::asymmetric::{encryption, identity}; use log::{error, info, warn}; use mixnode_common::rtt_measurement::{self, AtomicVerlocResult, RttMeasurer}; +use std::net::SocketAddr; use std::process; use std::sync::Arc; use tokio::runtime::Runtime; @@ -61,7 +62,7 @@ impl MixNode { let mut config = rocket::config::Config::release_default(); // bind to the same address as we are using for mixnodes - config.address = self.config.get_listening_address().ip(); + config.address = self.config.get_listening_address(); let verloc_state = VerlocState::new(atomic_verloc_result); let descriptor = self.descriptor.clone(); @@ -106,9 +107,12 @@ impl MixNode { let connection_handler = ConnectionHandler::new(packet_processor, delay_forwarding_channel); - let listener = Listener::new(self.config.get_listening_address()); + let listening_address = SocketAddr::new( + self.config.get_listening_address(), + self.config.get_mix_port(), + ); - listener.start(connection_handler); + Listener::new(listening_address).start(connection_handler); } fn start_packet_delay_forwarder( @@ -147,10 +151,11 @@ impl MixNode { // use the same binding address with the HARDCODED port for time being (I don't like that approach personally) - let listening_address = rtt_measurement::replace_port( + let listening_address = SocketAddr::new( self.config.get_listening_address(), rtt_measurement::DEFAULT_MEASUREMENT_PORT, ); + let config = rtt_measurement::ConfigBuilder::new() .listening_address(listening_address) .packets_per_node(self.config.get_measurement_packets_per_node()) @@ -186,9 +191,11 @@ impl MixNode { } }; + let our_host = self.config.get_announce_address(); + existing_nodes .iter() - .find(|node| node.mix_node.host == self.config.get_announce_address()) + .find(|node| node.mix_node.host == our_host) .map(|node| node.mix_node.identity_key.clone()) } diff --git a/network-monitor/src/monitor/preparer.rs b/network-monitor/src/monitor/preparer.rs index 8905f5d8b5..a1aba7b924 100644 --- a/network-monitor/src/monitor/preparer.rs +++ b/network-monitor/src/monitor/preparer.rs @@ -408,7 +408,7 @@ impl PacketPreparer { gateway_packets.push(mix_packet.pop().unwrap()); } packets.push(GatewayPackets::new( - node.client_listener, + node.clients_address(), node.identity_key, gateway_packets, )) @@ -461,10 +461,7 @@ impl PacketPreparer { // we are not testing the gateway from our 'good' topology -> it's probably // situation similar to using 'good' qa-topology but testing testnet nodes. let main_gateway_packets = GatewayPackets::new( - self.tested_network - .main_v4_gateway() - .client_listener - .clone(), + self.tested_network.main_v4_gateway().clients_address(), main_gateway_id, mix_packets, );