From e8e966b67a0f62d2ae0de74bbe2567745e07556f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 23 Apr 2021 16:41:39 +0100 Subject: [PATCH] Feature/multiple validator endpoints (#583) * Rust half done * Removed the temporary 'test' * Using multiple validator endpoints in the javascript client * Dont attempt any switches with a single validator endpoint * Removed the unused temporary function * Updated validator-client version --- Cargo.lock | 2 + .../src/client/topology_control.rs | 10 +- clients/client-core/src/config/mod.rs | 87 ++++------ clients/native/src/client/config/template.rs | 6 +- clients/native/src/client/mod.rs | 4 +- clients/native/src/commands/init.rs | 16 +- clients/native/src/commands/mod.rs | 12 +- clients/native/src/commands/run.rs | 8 +- clients/native/src/commands/upgrade.rs | 16 +- clients/socks5/src/client/config/template.rs | 6 +- clients/socks5/src/client/mod.rs | 4 +- clients/socks5/src/commands/init.rs | 16 +- clients/socks5/src/commands/mod.rs | 12 +- clients/socks5/src/commands/run.rs | 8 +- clients/socks5/src/commands/upgrade.rs | 16 +- clients/validator/package.json | 2 +- clients/validator/src/index.ts | 148 +++++++++++++++--- clients/validator/src/net-client.ts | 17 +- clients/validator/src/query-client.ts | 7 +- .../validator-client-rest/Cargo.toml | 1 + .../validator-client-rest/src/lib.rs | 88 +++++++++-- common/config/Cargo.toml | 1 + common/config/src/helpers.rs | 95 +++++++++++ common/config/src/lib.rs | 4 + gateway/src/commands/init.rs | 6 +- gateway/src/commands/mod.rs | 10 +- gateway/src/commands/run.rs | 10 +- gateway/src/commands/upgrade.rs | 18 +-- gateway/src/config/mod.rs | 99 ++++-------- gateway/src/config/template.rs | 6 +- gateway/src/node/mod.rs | 4 +- mixnode/src/commands/init.rs | 14 +- mixnode/src/commands/mod.rs | 10 +- mixnode/src/commands/run.rs | 15 +- mixnode/src/commands/upgrade.rs | 18 +-- mixnode/src/config/mod.rs | 99 ++++-------- mixnode/src/config/template.rs | 6 +- mixnode/src/node/mod.rs | 4 +- 38 files changed, 568 insertions(+), 337 deletions(-) create mode 100644 common/config/src/helpers.rs diff --git a/Cargo.lock b/Cargo.lock index a6d484b9d4..8b16138c7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,6 +390,7 @@ name = "config" version = "0.1.0" dependencies = [ "handlebars", + "humantime-serde", "serde", "toml", ] @@ -2864,6 +2865,7 @@ version = "0.1.0" dependencies = [ "base64 0.13.0", "mixnet-contract", + "rand 0.8.3", "reqwest", "serde", "serde_json", diff --git a/clients/client-core/src/client/topology_control.rs b/clients/client-core/src/client/topology_control.rs index 43a7a9081e..c29288cc75 100644 --- a/clients/client-core/src/client/topology_control.rs +++ b/clients/client-core/src/client/topology_control.rs @@ -136,19 +136,19 @@ impl Default for TopologyAccessor { } pub struct TopologyRefresherConfig { - validator_rcp_base_url: String, + available_validators: Vec, mixnet_contract_address: String, refresh_rate: time::Duration, } impl TopologyRefresherConfig { pub fn new( - validator_rcp_base_url: String, + available_validators: Vec, mixnet_contract_address: String, refresh_rate: time::Duration, ) -> Self { TopologyRefresherConfig { - validator_rcp_base_url, + available_validators, mixnet_contract_address, refresh_rate, } @@ -167,7 +167,7 @@ pub struct TopologyRefresher { impl TopologyRefresher { pub fn new(cfg: TopologyRefresherConfig, topology_accessor: TopologyAccessor) -> Self { let validator_client_config = validator_client_rest::Config::new( - cfg.validator_rcp_base_url, + cfg.available_validators, cfg.mixnet_contract_address, ); let validator_client = validator_client_rest::Client::new(validator_client_config); @@ -180,7 +180,7 @@ impl TopologyRefresher { } } - async fn get_current_compatible_topology(&self) -> Option { + async fn get_current_compatible_topology(&mut self) -> Option { // TODO: optimization for the future: // only refresh mixnodes on timer and refresh gateways only when // we have to send to a new, unknown, gateway diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs index 81290e43f0..0f236209a1 100644 --- a/clients/client-core/src/config/mod.rs +++ b/clients/client-core/src/config/mod.rs @@ -12,11 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use config::NymConfig; -use serde::{ - de::{self, IntoDeserializer, Visitor}, - Deserialize, Deserializer, Serialize, -}; +use config::{deserialize_duration, deserialize_validators, NymConfig}; +use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use std::path::PathBuf; use std::time::Duration; @@ -26,8 +23,11 @@ pub mod persistence; pub const MISSING_VALUE: &str = "MISSING VALUE"; // 'CLIENT' -pub const DEFAULT_VALIDATOR_REST_ENDPOINT: &str = - "http://testnet-finney-validator.nymtech.net:1317"; +pub 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"; // 'DEBUG' @@ -44,57 +44,22 @@ const DEFAULT_VPN_KEY_REUSE_LIMIT: usize = 1000; const ZERO_DELAY: Duration = Duration::from_nanos(0); -// custom function is defined to deserialize based on whether field contains a pre 0.9.0 -// u64 interpreted as milliseconds or proper duration introduced in 0.9.0 -// -// TODO: when we get to refactoring down the line, this code can just be removed -// and all Duration fields could just have #[serde(with = "humantime_serde")] instead -// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have, -// for argument sake, 0.11.0 out -fn deserialize_duration<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - struct DurationVisitor; - - impl<'de> Visitor<'de> for DurationVisitor { - type Value = Duration; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("u64 or a duration") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - self.visit_u64(value as u64) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - Ok(Duration::from_millis(Deserialize::deserialize( - value.into_deserializer(), - )?)) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - humantime_serde::deserialize(value.into_deserializer()) - } - } - - deserializer.deserialize_any(DurationVisitor) +// helper function to get default validators as a Vec +pub fn default_validator_rest_endpoints() -> Vec { + DEFAULT_VALIDATOR_REST_ENDPOINTS + .iter() + .map(|&endpoint| endpoint.to_string()) + .collect() } pub fn missing_string_value() -> String { MISSING_VALUE.to_string() } +pub fn missing_vec_string_value() -> Vec { + vec![missing_string_value()] +} + #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { @@ -178,8 +143,8 @@ impl Config { self.client.gateway_listener = gateway_listener.into(); } - pub fn set_custom_validator>(&mut self, validator: S) { - self.client.validator_rest_url = validator.into(); + pub fn set_custom_validators(&mut self, validators: Vec) { + self.client.validator_rest_urls = validators; } pub fn set_mixnet_contract>(&mut self, contract_address: S) { @@ -240,8 +205,8 @@ impl Config { self.client.ack_key_file.clone() } - pub fn get_validator_rest_endpoint(&self) -> String { - self.client.validator_rest_url.clone() + pub fn get_validator_rest_endpoints(&self) -> Vec { + self.client.validator_rest_urls.clone() } pub fn get_validator_mixnet_contract_address(&self) -> String { @@ -345,8 +310,12 @@ pub struct Client { id: String, /// URL to the validator server for obtaining network topology. - #[serde(default = "missing_string_value")] - validator_rest_url: String, + #[serde( + deserialize_with = "deserialize_validators", + default = "missing_vec_string_value", + alias = "validator_rest_url" + )] + validator_rest_urls: Vec, /// Address of the validator contract managing the network. #[serde(default = "missing_string_value")] @@ -403,7 +372,7 @@ impl Default for Client { Client { version: env!("CARGO_PKG_VERSION").to_string(), id: "".to_string(), - validator_rest_url: DEFAULT_VALIDATOR_REST_ENDPOINT.to_string(), + validator_rest_urls: default_validator_rest_endpoints(), mixnet_contract_address: DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(), vpn_mode: false, private_identity_key_file: Default::default(), diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs index 91b1d98b98..2c8a69444d 100644 --- a/clients/native/src/client/config/template.rs +++ b/clients/native/src/client/config/template.rs @@ -31,7 +31,11 @@ version = '{{ client.version }}' id = '{{ client.id }}' # URL to the validator server for obtaining network topology. -validator_rest_url = '{{ client.validator_rest_url }}' +validator_rest_urls = [ + {{#each client.validator_rest_urls }} + '{{this}}', + {{/each}} +] # Address of the validator contract managing the network. mixnet_contract_address = '{{ client.mixnet_contract_address }}' diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index 61a6e7487d..32c0a148b2 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -224,7 +224,7 @@ impl NymClient { // the current global view of topology fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) { let topology_refresher_config = TopologyRefresherConfig::new( - self.config.get_base().get_validator_rest_endpoint(), + self.config.get_base().get_validator_rest_endpoints(), self.config .get_base() .get_validator_mixnet_contract_address(), @@ -236,7 +236,7 @@ impl NymClient { // components depending on topology would see a non-empty view info!( "Obtaining initial network topology from {}", - self.config.get_base().get_validator_rest_endpoint() + self.config.get_base().get_validator_rest_endpoints()[0] ); self.runtime.block_on(topology_refresher.refresh()); diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index ecb313f211..f7558b3bc9 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -44,10 +44,10 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Id of the gateway we are going to connect to.") .takes_value(true) ) - .arg(Arg::with_name("validator") - .long("validator") - .help("Address of the validator server the client is getting topology from") - .takes_value(true), + .arg(Arg::with_name("validators") + .long("validators") + .help("Comma separated list of rest endpoints of the validators") + .takes_value(true), ) .arg(Arg::with_name("mixnet-contract") .long("mixnet-contract") @@ -104,13 +104,13 @@ async fn register_with_gateway( } async fn gateway_details( - validator_server: &str, + validator_servers: Vec, mixnet_contract: &str, chosen_gateway_id: Option<&str>, ) -> gateway::Node { let validator_client_config = - validator_client_rest::Config::new(validator_server, mixnet_contract); - let validator_client = validator_client_rest::Client::new(validator_client_config); + validator_client_rest::Config::new(validator_servers, mixnet_contract); + let mut validator_client = validator_client_rest::Client::new(validator_client_config); let gateways = validator_client.get_gateways().await.unwrap(); let valid_gateways = gateways @@ -206,7 +206,7 @@ pub fn execute(matches: &ArgMatches) { let registration_fut = async { let gate_details = gateway_details( - &config.get_base().get_validator_rest_endpoint(), + config.get_base().get_validator_rest_endpoints(), &config.get_base().get_validator_mixnet_contract_address(), chosen_gateway_id, ) diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index fd0b17d93c..22b8afb5df 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -19,9 +19,17 @@ pub(crate) mod init; pub(crate) mod run; pub(crate) mod upgrade; +fn parse_validators(raw: &str) -> Vec { + raw.split(',') + .map(|raw_validator| raw_validator.trim().into()) + .collect() +} + pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { - if let Some(validator) = matches.value_of("validator") { - config.get_base_mut().set_custom_validator(validator); + if let Some(raw_validators) = matches.value_of("validators") { + config + .get_base_mut() + .set_custom_validators(parse_validators(raw_validators)); } if let Some(contract_address) = matches.value_of("mixnet-contract") { diff --git a/clients/native/src/commands/run.rs b/clients/native/src/commands/run.rs index a40d886ca3..5ab72cf923 100644 --- a/clients/native/src/commands/run.rs +++ b/clients/native/src/commands/run.rs @@ -30,10 +30,10 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .required(true) ) // the rest of arguments are optional, they are used to override settings in config file - .arg(Arg::with_name("validator") - .long("validator") - .help("Address of the validator server the client is getting topology from") - .takes_value(true), + .arg(Arg::with_name("validators") + .long("validators") + .help("Comma separated list rest rest endpoints of the validators") + .takes_value(true), ) .arg(Arg::with_name("mixnet-contract") .long("mixnet-contract") diff --git a/clients/native/src/commands/upgrade.rs b/clients/native/src/commands/upgrade.rs index d27dc5048e..3a55f2de6d 100644 --- a/clients/native/src/commands/upgrade.rs +++ b/clients/native/src/commands/upgrade.rs @@ -14,7 +14,7 @@ use crate::client::config::{Config, MISSING_VALUE}; use clap::{App, Arg, ArgMatches}; -use client_core::config::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DEFAULT_VALIDATOR_REST_ENDPOINT}; +use client_core::config::{default_validator_rest_endpoints, DEFAULT_MIXNET_CONTRACT_ADDRESS}; use config::NymConfig; use std::fmt::Display; use std::process; @@ -136,7 +136,7 @@ fn pre_090_upgrade(from: &str, mut config: Config) -> Config { Version::new(0, 9, 0) }; - if config.get_base().get_validator_rest_endpoint() != MISSING_VALUE { + if config.get_base().get_validator_rest_endpoints()[0] != MISSING_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); @@ -149,13 +149,13 @@ fn pre_090_upgrade(from: &str, mut config: Config) -> Config { .set_custom_version(to_version.to_string().as_ref()); println!( - "Setting validator REST endpoint to to {}", - DEFAULT_VALIDATOR_REST_ENDPOINT + "Setting validator REST endpoint to to {:?}", + default_validator_rest_endpoints() ); config .get_base_mut() - .set_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT); + .set_custom_validators(default_validator_rest_endpoints()); config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); @@ -208,13 +208,13 @@ fn minor_010_upgrade( // The default validator endpoint changed println!( - "Setting validator REST endpoint to to {}", - DEFAULT_VALIDATOR_REST_ENDPOINT + "Setting validator REST endpoint to to {:?}", + default_validator_rest_endpoints() ); config .get_base_mut() - .set_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT); + .set_custom_validators(default_validator_rest_endpoints()); config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs index 7bc6f12aab..614a7f6ed6 100644 --- a/clients/socks5/src/client/config/template.rs +++ b/clients/socks5/src/client/config/template.rs @@ -31,7 +31,11 @@ version = '{{ client.version }}' id = '{{ client.id }}' # URL to the validator server for obtaining network topology. -validator_rest_url = '{{ client.validator_rest_url }}' +validator_rest_urls = [ + {{#each client.validator_rest_urls }} + '{{this}}', + {{/each}} +] # Address of the validator contract managing the network. mixnet_contract_address = '{{ client.mixnet_contract_address }}' diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index 73a4085052..c4c83102bc 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -212,7 +212,7 @@ impl NymClient { // the current global view of topology fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) { let topology_refresher_config = TopologyRefresherConfig::new( - self.config.get_base().get_validator_rest_endpoint(), + self.config.get_base().get_validator_rest_endpoints(), self.config .get_base() .get_validator_mixnet_contract_address(), @@ -224,7 +224,7 @@ impl NymClient { // components depending on topology would see a non-empty view info!( "Obtaining initial network topology from {}", - self.config.get_base().get_validator_rest_endpoint() + self.config.get_base().get_validator_rest_endpoints()[0] ); self.runtime.block_on(topology_refresher.refresh()); diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 0476a46846..583b6ee0e7 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -49,10 +49,10 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Id of the gateway we are going to connect to.") .takes_value(true) ) - .arg(Arg::with_name("validator") - .long("validator") - .help("Address of the validator server the client is getting topology from") - .takes_value(true), + .arg(Arg::with_name("validators") + .long("validators") + .help("Comma separated list of rest endpoints of the validators") + .takes_value(true), ) .arg(Arg::with_name("mixnet-contract") .long("mixnet-contract") @@ -105,13 +105,13 @@ async fn register_with_gateway( } async fn gateway_details( - validator_server: &str, + validator_servers: Vec, mixnet_contract: &str, chosen_gateway_id: Option<&str>, ) -> gateway::Node { let validator_client_config = - validator_client_rest::Config::new(validator_server, mixnet_contract); - let validator_client = validator_client_rest::Client::new(validator_client_config); + validator_client_rest::Config::new(validator_servers, mixnet_contract); + let mut validator_client = validator_client_rest::Client::new(validator_client_config); let gateways = validator_client.get_gateways().await.unwrap(); let valid_gateways = gateways @@ -208,7 +208,7 @@ pub fn execute(matches: &ArgMatches) { let registration_fut = async { let gate_details = gateway_details( - &config.get_base().get_validator_rest_endpoint(), + config.get_base().get_validator_rest_endpoints(), &config.get_base().get_validator_mixnet_contract_address(), chosen_gateway_id, ) diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index 6880939b9d..683dcafe5c 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -19,9 +19,17 @@ pub(crate) mod init; pub(crate) mod run; pub(crate) mod upgrade; +fn parse_validators(raw: &str) -> Vec { + raw.split(',') + .map(|raw_validator| raw_validator.trim().into()) + .collect() +} + pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { - if let Some(validator) = matches.value_of("validator") { - config.get_base_mut().set_custom_validator(validator); + if let Some(raw_validators) = matches.value_of("validators") { + config + .get_base_mut() + .set_custom_validators(parse_validators(raw_validators)); } if let Some(contract_address) = matches.value_of("mixnet-contract") { diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index 170f445d78..b96808bfcf 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -40,10 +40,10 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Address of the socks5 provider to send messages to.") .takes_value(true) ) - .arg(Arg::with_name("validator") - .long("validator") - .help("Address of the validator server the client is getting topology from") - .takes_value(true), + .arg(Arg::with_name("validators") + .long("validators") + .help("Comma separated list of rest endpoints of the validators") + .takes_value(true), ) .arg(Arg::with_name("mixnet-contract") .long("mixnet-contract") diff --git a/clients/socks5/src/commands/upgrade.rs b/clients/socks5/src/commands/upgrade.rs index 6a783d8824..ac535eb606 100644 --- a/clients/socks5/src/commands/upgrade.rs +++ b/clients/socks5/src/commands/upgrade.rs @@ -14,7 +14,7 @@ use crate::client::config::{Config, MISSING_VALUE}; use clap::{App, Arg, ArgMatches}; -use client_core::config::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DEFAULT_VALIDATOR_REST_ENDPOINT}; +use client_core::config::{default_validator_rest_endpoints, DEFAULT_MIXNET_CONTRACT_ADDRESS}; use config::NymConfig; use std::fmt::Display; use std::process; @@ -136,7 +136,7 @@ fn pre_090_upgrade(from: &str, mut config: Config) -> Config { Version::new(0, 9, 0) }; - if config.get_base().get_validator_rest_endpoint() != MISSING_VALUE { + if config.get_base().get_validator_rest_endpoints()[0] != MISSING_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); @@ -149,13 +149,13 @@ fn pre_090_upgrade(from: &str, mut config: Config) -> Config { .set_custom_version(to_version.to_string().as_ref()); println!( - "Setting validator REST endpoint to {}", - DEFAULT_VALIDATOR_REST_ENDPOINT + "Setting validator REST endpoint to {:?}", + default_validator_rest_endpoints() ); config .get_base_mut() - .set_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT); + .set_custom_validators(default_validator_rest_endpoints()); config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); @@ -208,13 +208,13 @@ fn minor_010_upgrade( // The default validator endpoint changed println!( - "Setting validator REST endpoint to to {}", - DEFAULT_VALIDATOR_REST_ENDPOINT + "Setting validator REST endpoint to to {:?}", + default_validator_rest_endpoints() ); config .get_base_mut() - .set_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT); + .set_custom_validators(default_validator_rest_endpoints()); config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); diff --git a/clients/validator/package.json b/clients/validator/package.json index 61d2f74ddb..068f00538f 100644 --- a/clients/validator/package.json +++ b/clients/validator/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/nym-validator-client", - "version": "0.10.1", + "version": "0.10.2", "description": "A TypeScript client for interacting with smart contracts in Nym validators", "repository": "https://github.com/nymtech/nym", "main": "./dist/index.js", diff --git a/clients/validator/src/index.ts b/clients/validator/src/index.ts index 47744d4976..b5d6093988 100644 --- a/clients/validator/src/index.ts +++ b/clients/validator/src/index.ts @@ -34,14 +34,17 @@ export default class ValidatorClient { private readonly defaultGatewayBondingStake: number = 100_000000 private readonly defaultMixnodeBondingStake: number = 100_000000 - url: string; + urls: string[]; private readonly client: INetClient | IQueryClient private mixNodesCache: MixnodesCache; private gatewayCache: GatewaysCache private readonly contractAddress: string; + // for some reason typescript thinks it's better to not be explicit about a trivial type... + // eslint-disable-next-line @typescript-eslint/no-inferrable-types + private failedRequests: number = 0; - private constructor(url: string, client: INetClient | IQueryClient, contractAddress: string, stakeDenom: string) { - this.url = url; + private constructor(urls: string[], client: INetClient | IQueryClient, contractAddress: string, stakeDenom: string) { + this.urls = urls; this.client = client; this.mixNodesCache = new MixnodesCache(client, 100); this.gatewayCache = new GatewaysCache(client, 100); @@ -49,15 +52,112 @@ export default class ValidatorClient { this.stakeDenom = stakeDenom; } - static async connect(contractAddress: string, mnemonic: string, url: string, stakeDenom: string): Promise { + // allows also entering 'string' by itself for backwards compatibility + static async connect(contractAddress: string, mnemonic: string, urls: string | string[], stakeDenom: string): Promise { + const validatorUrls = this.dealWithValidatorUrls(urls) const wallet = await ValidatorClient.buildWallet(mnemonic); - const netClient = await NetClient.connect(wallet, url, stakeDenom); - return new ValidatorClient(url, netClient, contractAddress, stakeDenom); + + // if we have more than a single validator, try to perform initial connection until we succeed or run out of options + if (validatorUrls.length > 1) { + for (let i = 0; i < validatorUrls.length; i++) { + console.log("Attempting initial connection to", validatorUrls[0]) + const netClient = await NetClient.connect(wallet, validatorUrls[0], stakeDenom).catch((_) => ValidatorClient.moveArrayHeadToBack(validatorUrls)) + if (netClient !== undefined) { + return new ValidatorClient(validatorUrls, netClient, contractAddress, stakeDenom); + } + console.log("Initial connection to", validatorUrls[0], "failed") + } + } else { + const netClient = await NetClient.connect(wallet, validatorUrls[0], stakeDenom) + return new ValidatorClient(validatorUrls, netClient, contractAddress, stakeDenom); + } + + throw new Error("None of the provided validators seem to be alive") } - static async connectForQuery(contractAddress: string, url: string, stakeDenom: string): Promise { - const queryClient = await QueryClient.connect(url) - return new ValidatorClient(url, queryClient, contractAddress, stakeDenom) + // allows also entering 'string' by itself for backwards compatibility + static async connectForQuery(contractAddress: string, urls: string | string[], stakeDenom: string): Promise { + const validatorUrls = this.dealWithValidatorUrls(urls) + + // if we have more than a single validator, try to perform initial connection until we succeed or run out of options + if (validatorUrls.length > 1) { + for (let i = 0; i < validatorUrls.length; i++) { + console.log("Attempting initial connection to", validatorUrls[0]) + const queryClient = await QueryClient.connect(validatorUrls[0]).catch((_) => ValidatorClient.moveArrayHeadToBack(validatorUrls)) + if (queryClient !== undefined) { + return new ValidatorClient(validatorUrls, queryClient, contractAddress, stakeDenom) + } + console.log("Initial connection to", validatorUrls[0], "failed") + } + } else { + const queryClient = await QueryClient.connect(validatorUrls[0]) + return new ValidatorClient(validatorUrls, queryClient, contractAddress, stakeDenom) + } + + throw new Error("None of the provided validators seem to be alive") + } + + private static dealWithValidatorUrls(urls: string | string[]): string[] { + let validatorsUrls: string[] = [] + if (typeof urls === "string") { + validatorsUrls = [urls] + } else { + // if the array is empty, just blow up + if (urls.length === 0) { + throw new Error("no validator urls provided") + } + + // no point in shuffling array of size 1 + if (urls.length > 1) { + urls = this.shuffleArray(urls) + } + validatorsUrls = urls + } + + return validatorsUrls + } + + // an error adapter function that upon an error attempts to switch currently used validator to the next one available + // note that it ALWAYS throws an error + async handleRequestFailure(error: Error): Promise { + // don't bother doing any fancy validator switches if we only have 1 validator to choose from + if (this.urls.length > 1) { + this.failedRequests += 1; + // if we exhausted all of available validators, permute the set, maybe the old ones + // are working again next time we try + if (this.failedRequests === this.urls.length) { + this.urls = ValidatorClient.shuffleArray(this.urls) + } else { + // otherwise change the front validator to a 'fresh' one + // during construction we assured we don't have an empty array + ValidatorClient.moveArrayHeadToBack(this.urls) + } + // and change validator to the front one and rethrow the error + return await this.changeValidator(this.urls[0]).then(() => {throw error}) + } else { + // rethrow the error + throw error + } + } + + private async changeValidator(newUrl: string): Promise { + console.log("Changing validator to", newUrl) + return await this.client.changeValidator(newUrl) + } + + // adapted from https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array/6274381#6274381 + static shuffleArray(arr: T[]): T[] { + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [arr[i], arr[j]] = [arr[j], arr[i]]; + } + return arr; + } + + // It is responsibility of the caller to ensure the input array is non-empty + private static moveArrayHeadToBack(arr: T[]) { + const head = arr.shift() + arr.push(head) } public get address(): string { @@ -110,11 +210,11 @@ export default class ValidatorClient { } getBalance(address: string): Promise { - return this.client.getBalance(address, this.stakeDenom); + return this.client.getBalance(address, this.stakeDenom).catch((err) => this.handleRequestFailure(err)); } async getStateParams(): Promise { - return this.client.getStateParams(this.contractAddress) + return this.client.getStateParams(this.contractAddress).catch((err) => this.handleRequestFailure(err)) } @@ -127,7 +227,7 @@ export default class ValidatorClient { * just get things strung together and refresh it manually. */ refreshMixNodes(): Promise { - return this.mixNodesCache.refreshMixNodes(this.contractAddress); + return this.mixNodesCache.refreshMixNodes(this.contractAddress).catch((err) => this.handleRequestFailure(err)); } /** @@ -154,7 +254,7 @@ export default class ValidatorClient { async bond(mixNode: MixNode): Promise { if (this.client instanceof NetClient) { const bond = [this.minimumMixnodeBond()]; - const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { bond_mixnode: { mix_node: mixNode } }, "adding mixnode", bond); + const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { bond_mixnode: { mix_node: mixNode } }, "adding mixnode", bond).catch((err) => this.handleRequestFailure(err)); console.log(`account ${this.client.clientAddress} added mixnode with ${mixNode.host}`); return result; } else { @@ -168,7 +268,7 @@ export default class ValidatorClient { */ async unbond(): Promise { if (this.client instanceof NetClient) { - const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { unbond_mixnode: {} }) + const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, { unbond_mixnode: {} }).catch((err) => this.handleRequestFailure(err)) console.log(`account ${this.client.clientAddress} unbonded mixnode`); return result; } else { @@ -181,7 +281,7 @@ export default class ValidatorClient { */ async ownsMixNode(): Promise { if (this.client instanceof NetClient) { - const result = await this.client.ownsMixNode(this.contractAddress, this.client.clientAddress) + const result = await this.client.ownsMixNode(this.contractAddress, this.client.clientAddress).catch((err) => this.handleRequestFailure(err)) return result.has_node } else { throw new Error("tried to check mixnode ownership for an address-less client") @@ -193,7 +293,7 @@ export default class ValidatorClient { */ async ownsGateway(): Promise { if (this.client instanceof NetClient) { - const result = await this.client.ownsGateway(this.contractAddress, this.client.clientAddress) + const result = await this.client.ownsGateway(this.contractAddress, this.client.clientAddress).catch((err) => this.handleRequestFailure(err)) return result.has_gateway } else { throw new Error("tried to check gateway ownership for an address-less client") @@ -208,7 +308,7 @@ export default class ValidatorClient { * TODO: Similarly to mixnode bonds, this should probably be put on a timer somewhere. */ refreshGateways(): Promise { - return this.gatewayCache.refreshGateways(this.contractAddress); + return this.gatewayCache.refreshGateways(this.contractAddress).catch((err) => this.handleRequestFailure(err)); } /** @@ -235,7 +335,7 @@ export default class ValidatorClient { async bondGateway(gateway: Gateway): Promise { if (this.client instanceof NetClient) { const bond = this.minimumGatewayBond() - const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {bond_gateway: {gateway: gateway}}, "adding gateway", [bond]); + const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {bond_gateway: {gateway: gateway}}, "adding gateway", [bond]).catch((err) => this.handleRequestFailure(err)); console.log(`account ${this.client.clientAddress} added gateway with ${gateway.mix_host}`); return result; } else { @@ -248,7 +348,7 @@ export default class ValidatorClient { */ async unbondGateway(): Promise { if (this.client instanceof NetClient) { - const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {unbond_gateway: {}}) + const result = await this.client.executeContract(this.client.clientAddress, this.contractAddress, {unbond_gateway: {}}).catch((err) => this.handleRequestFailure(err)) console.log(`account ${this.client.clientAddress} unbonded gateway`); return result; } else { @@ -258,7 +358,7 @@ export default class ValidatorClient { async updateStateParams(newParams: StateParams): Promise { if (this.client instanceof NetClient) { - return await this.client.executeContract(this.client.clientAddress, this.contractAddress, {update_state_params: newParams}, "updating contract state"); + return await this.client.executeContract(this.client.clientAddress, this.contractAddress, {update_state_params: newParams}, "updating contract state").catch((err) => this.handleRequestFailure(err)); } else { throw new Error("Tried to update state params with a query client") } @@ -273,7 +373,7 @@ export default class ValidatorClient { */ async send(senderAddress: string, recipientAddress: string, coins: readonly Coin[], memo?: string): Promise { if (this.client instanceof NetClient) { - const result = await this.client.sendTokens(senderAddress, recipientAddress, coins, memo); + const result = await this.client.sendTokens(senderAddress, recipientAddress, coins, memo).catch((err) => this.handleRequestFailure(err)); if (isBroadcastTxFailure(result)) { throw new Error(`Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`) } @@ -285,7 +385,7 @@ export default class ValidatorClient { async upload(senderAddress: string, wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise { if (this.client instanceof NetClient) { - return this.client.upload(senderAddress, wasmCode, meta, memo); + return this.client.upload(senderAddress, wasmCode, meta, memo).catch((err) => this.handleRequestFailure(err)); } else { throw new Error("Tried to upload with a query client"); } @@ -293,7 +393,7 @@ export default class ValidatorClient { public instantiate(senderAddress: string, codeId: number, initMsg: Record, label: string, options?: InstantiateOptions): Promise { if (this.client instanceof NetClient) { - return this.client.instantiate(senderAddress, codeId, initMsg, label, options); + return this.client.instantiate(senderAddress, codeId, initMsg, label, options).catch((err) => this.handleRequestFailure(err)); } else { throw new Error("Tried to instantiate with a query client"); } @@ -301,7 +401,7 @@ export default class ValidatorClient { public migrate(senderAddress: string, contractAddress: string, codeId: number, migrateMsg: Record, memo?: string): Promise { if (this.client instanceof NetClient) { - return this.client.migrate(senderAddress, contractAddress, codeId, migrateMsg, memo) + return this.client.migrate(senderAddress, contractAddress, codeId, migrateMsg, memo).catch((err) => this.handleRequestFailure(err)) } else { throw new Error("Tried to migrate with a query client"); } diff --git a/clients/validator/src/net-client.ts b/clients/validator/src/net-client.ts index 7929d8cc6b..5be1e22c0c 100644 --- a/clients/validator/src/net-client.ts +++ b/clients/validator/src/net-client.ts @@ -25,6 +25,7 @@ export interface INetClient { instantiate(senderAddress: string, codeId: number, initMsg: Record, label: string, options?: InstantiateOptions): Promise; sendTokens(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise; upload(senderAddress: string, wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise; + changeValidator(newUrl: string): Promise } /** @@ -38,12 +39,16 @@ export interface INetClient { export default class NetClient implements INetClient { clientAddress: string; private cosmClient: SigningCosmWasmClient; - private stakeDenom: string; - private constructor(clientAddress: string, cosmClient: SigningCosmWasmClient, stakeDenom: string) { + // helpers for changing validators without having to remake the wallet + private readonly wallet: DirectSecp256k1HdWallet; + private readonly signerOptions: SigningCosmWasmClientOptions; + + private constructor(clientAddress: string, cosmClient: SigningCosmWasmClient, wallet: DirectSecp256k1HdWallet, signerOptions: SigningCosmWasmClientOptions) { this.clientAddress = clientAddress; this.cosmClient = cosmClient; - this.stakeDenom = stakeDenom; + this.wallet = wallet; + this.signerOptions = signerOptions; } public static async connect(wallet: DirectSecp256k1HdWallet, url: string, stakeDenom: string): Promise { @@ -53,7 +58,11 @@ export default class NetClient implements INetClient { gasLimits: nymGasLimits, }; const client = await SigningCosmWasmClient.connectWithSigner(url, wallet, signerOptions); - return new NetClient(address, client, stakeDenom); + return new NetClient(address, client, wallet, signerOptions); + } + + async changeValidator(url: string): Promise { + this.cosmClient = await SigningCosmWasmClient.connectWithSigner(url, this.wallet, this.signerOptions); } public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise { diff --git a/clients/validator/src/query-client.ts b/clients/validator/src/query-client.ts index 5d032d0876..3579173e1e 100644 --- a/clients/validator/src/query-client.ts +++ b/clients/validator/src/query-client.ts @@ -1,5 +1,5 @@ import {Coin} from "@cosmjs/launchpad"; -import {CosmWasmClient} from "@cosmjs/cosmwasm-stargate"; +import { CosmWasmClient, SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; import { GatewayOwnershipResponse, MixOwnershipResponse, @@ -15,6 +15,7 @@ export interface IQueryClient { ownsMixNode(contractAddress: string, address: string): Promise; ownsGateway(contractAddress: string, address: string): Promise; getStateParams(contractAddress: string): Promise; + changeValidator(newUrl: string): Promise } /** @@ -37,6 +38,10 @@ export default class QueryClient implements IQueryClient { return new QueryClient(client) } + async changeValidator(url: string): Promise { + this.cosmClient = await CosmWasmClient.connect(url) + } + public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise { if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here. return this.cosmClient.queryContractSmart(contractAddress, { get_mix_nodes: { limit } }); diff --git a/common/client-libs/validator-client-rest/Cargo.toml b/common/client-libs/validator-client-rest/Cargo.toml index d17a4e5365..466499652d 100644 --- a/common/client-libs/validator-client-rest/Cargo.toml +++ b/common/client-libs/validator-client-rest/Cargo.toml @@ -11,4 +11,5 @@ base64 = "0.13" mixnet-contract = { path = "../../../common/mixnet-contract" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +rand = "0.8" reqwest = { version = "0.11", features = ["json"] } diff --git a/common/client-libs/validator-client-rest/src/lib.rs b/common/client-libs/validator-client-rest/src/lib.rs index 147e8e5caf..4cd2c4b163 100644 --- a/common/client-libs/validator-client-rest/src/lib.rs +++ b/common/client-libs/validator-client-rest/src/lib.rs @@ -5,7 +5,10 @@ use crate::models::{QueryRequest, QueryResponse}; use crate::ValidatorClientError::ValidatorError; use core::fmt::{self, Display, Formatter}; use mixnet_contract::{GatewayBond, HumanAddr, MixNodeBond, PagedGatewayResponse, PagedResponse}; +use rand::seq::SliceRandom; +use rand::thread_rng; use serde::Deserialize; +use std::collections::VecDeque; mod models; pub(crate) mod serde_helpers; @@ -37,17 +40,30 @@ impl Display for ValidatorClientError { } } +fn permute_validators(validators: VecDeque) -> VecDeque { + // even in the best case scenario in the mainnet world, we're not going to have more than ~100 validators, + // hence conversions from and to Vec are fine + let mut vec = Vec::from(validators); + + vec.shuffle(&mut thread_rng()); + + vec.into() +} + pub struct Config { - rpc_server_base_url: String, + initial_rest_servers: Vec, mixnet_contract_address: String, mixnode_page_limit: Option, gateway_page_limit: Option, } impl Config { - pub fn new>(rpc_server_base_url: S, mixnet_contract_address: S) -> Self { + pub fn new>( + rest_servers_available_base_urls: Vec, + mixnet_contract_address: S, + ) -> Self { Config { - rpc_server_base_url: rpc_server_base_url.into(), + initial_rest_servers: rest_servers_available_base_urls, mixnet_contract_address: mixnet_contract_address.into(), mixnode_page_limit: None, gateway_page_limit: None, @@ -67,26 +83,76 @@ impl Config { pub struct Client { config: Config, + // Currently it seems the client is independent of the url hence a single instance seems to be fine reqwest_client: reqwest::Client, + + available_validators_rest_urls: VecDeque, + failed_queries: usize, } impl Client { pub fn new(config: Config) -> Self { let reqwest_client = reqwest::Client::new(); + + // client is only ever created on process startup, so a panic here is fine as it implies + // invalid config. And that can only happen if an user was messing with it by themselves. + if config.initial_rest_servers.is_empty() { + panic!("no validator servers provided") + } + + let mut available_validators_rest_urls = config.initial_rest_servers.clone().into(); + available_validators_rest_urls = permute_validators(available_validators_rest_urls); + Client { config, reqwest_client, + available_validators_rest_urls, + failed_queries: 0, } } + fn permute_validators(&mut self) { + if self.available_validators_rest_urls.len() == 1 { + return; + } + self.available_validators_rest_urls = + permute_validators(std::mem::take(&mut self.available_validators_rest_urls)); + } + fn base_query_path(&self) -> String { format!( "{}/wasm/contract/{}/smart", - self.config.rpc_server_base_url, self.config.mixnet_contract_address + self.available_validators_rest_urls[0], self.config.mixnet_contract_address ) } - async fn query_validator(&self, query: String) -> Result + async fn query_validators(&mut self, query: String) -> Result + where + for<'a> T: Deserialize<'a>, + { + // if we fail to query the first validator, push it to the back + let res = self.query_front_validator(query).await; + + // don't bother doing any fancy validator switches if we only have 1 validator to choose from + if self.available_validators_rest_urls.len() > 1 { + if res.is_err() { + let front = self.available_validators_rest_urls.pop_front().unwrap(); + self.available_validators_rest_urls.push_back(front); + self.failed_queries += 1; + } + + // if we exhausted all of available validators, permute the set, maybe the old ones + // are working again next time we try + if self.failed_queries == self.available_validators_rest_urls.len() { + self.permute_validators(); + self.failed_queries = 0 + } + } + + res + } + + async fn query_front_validator(&self, query: String) -> Result where for<'a> T: Deserialize<'a>, { @@ -108,7 +174,7 @@ impl Client { } async fn get_mix_nodes_paged( - &self, + &mut self, start_after: Option, ) -> Result { let query_content_json = serde_json::to_string(&QueryRequest::GetMixNodes { @@ -120,10 +186,10 @@ impl Client { // we need to encode our json request let query_content = base64::encode(query_content_json); - self.query_validator(query_content).await + self.query_validators(query_content).await } - pub async fn get_mix_nodes(&self) -> Result, ValidatorClientError> { + pub async fn get_mix_nodes(&mut self) -> Result, ValidatorClientError> { let mut mixnodes = Vec::new(); let mut start_after = None; loop { @@ -141,7 +207,7 @@ impl Client { } async fn get_gateways_paged( - &self, + &mut self, start_after: Option, ) -> Result { let query_content_json = serde_json::to_string(&QueryRequest::GetGateways { @@ -153,10 +219,10 @@ impl Client { // we need to encode our json request let query_content = base64::encode(query_content_json); - self.query_validator(query_content).await + self.query_validators(query_content).await } - pub async fn get_gateways(&self) -> Result, ValidatorClientError> { + pub async fn get_gateways(&mut self) -> Result, ValidatorClientError> { let mut gateways = Vec::new(); let mut start_after = None; loop { diff --git a/common/config/Cargo.toml b/common/config/Cargo.toml index b264012a61..6557eddd14 100644 --- a/common/config/Cargo.toml +++ b/common/config/Cargo.toml @@ -8,5 +8,6 @@ edition = "2018" [dependencies] handlebars = "3.0.1" +humantime-serde = "1.0" serde = { version = "1.0", features = ["derive"] } toml = "0.5.6" diff --git a/common/config/src/helpers.rs b/common/config/src/helpers.rs new file mode 100644 index 0000000000..1c659dd166 --- /dev/null +++ b/common/config/src/helpers.rs @@ -0,0 +1,95 @@ +// Copyright 2020 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use serde::de::SeqAccess; +use serde::{ + de::{self, IntoDeserializer, Visitor}, + Deserialize, Deserializer, +}; +use std::time::Duration; + +// custom function is defined to deserialize based on whether field contains a pre 0.9.0 +// u64 interpreted as milliseconds or proper duration introduced in 0.9.0 +// +// TODO: when we get to refactoring down the line, this code can just be removed +// and all Duration fields could just have #[serde(with = "humantime_serde")] instead +// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have, +// for argument sake, 0.11.0 out +pub fn deserialize_duration<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + struct DurationVisitor; + + impl<'de> Visitor<'de> for DurationVisitor { + type Value = Duration; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("u64 or a duration") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + self.visit_u64(value as u64) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(Duration::from_millis(Deserialize::deserialize( + value.into_deserializer(), + )?)) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + humantime_serde::deserialize(value.into_deserializer()) + } + } + + deserializer.deserialize_any(DurationVisitor) +} + +// custom function is defined to deserialize based on whether field contains a single validator rest endpoint +// or an array of multiple values +pub fn deserialize_validators<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct ValidatorsVisitor; + + impl<'de> Visitor<'de> for ValidatorsVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("String or Vec") + } + + fn visit_str(self, value: &str) -> Result, E> + where + E: de::Error, + { + Ok(vec![value.to_string()]) + } + + fn visit_seq(self, mut seq: A) -> Result, A::Error> + where + A: SeqAccess<'de>, + { + let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(10)); + + while let Some(next) = seq.next_element()? { + vec.push(next) + } + + Ok(vec) + } + } + + deserializer.deserialize_any(ValidatorsVisitor) +} diff --git a/common/config/src/lib.rs b/common/config/src/lib.rs index 99c5e2d3e2..eb6d3f768c 100644 --- a/common/config/src/lib.rs +++ b/common/config/src/lib.rs @@ -7,6 +7,10 @@ use serde::Serialize; use std::path::PathBuf; use std::{fs, io}; +pub mod helpers; + +pub use helpers::{deserialize_duration, deserialize_validators}; + pub trait NymConfig: Default + Serialize + DeserializeOwned { fn template() -> &'static str; diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index eb3de0b6df..1c235fb531 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -81,9 +81,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .takes_value(true) ) .arg( - Arg::with_name("validator") - .long("validator") - .help("REST endpoint of the validator the node is registering presence with") + Arg::with_name("validators") + .long("validators") + .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) .arg( diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 60d0a124ba..5cb66b2e77 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -8,6 +8,12 @@ pub(crate) mod init; pub(crate) mod run; pub(crate) mod upgrade; +fn parse_validators(raw: &str) -> Vec { + raw.split(',') + .map(|raw_validator| raw_validator.trim().into()) + .collect() +} + 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") { @@ -75,8 +81,8 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi config = config.with_clients_announce_port(clients_announce_port.unwrap()); } - if let Some(validator) = matches.value_of("validator") { - config = config.with_custom_validator(validator); + if let Some(raw_validators) = matches.value_of("validators") { + config = config.with_custom_validators(parse_validators(raw_validators)); } if let Some(contract_address) = matches.value_of("mixnet-contract") { diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index 0b92605631..46e46b0a7a 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -89,9 +89,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .takes_value(true) ) .arg( - Arg::with_name("validator") - .long("validator") - .help("REST endpoint of the validator the node is registering presence with") + Arg::with_name("validators") + .long("validators") + .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) .arg( @@ -197,8 +197,8 @@ pub fn execute(matches: &ArgMatches) { } println!( - "Validator REST endpoint: {}", - config.get_validator_rest_endpoint() + "Validator servers: {:?}", + config.get_validator_rest_endpoints() ); println!( diff --git a/gateway/src/commands/upgrade.rs b/gateway/src/commands/upgrade.rs index 822231daed..ec891cccd2 100644 --- a/gateway/src/commands/upgrade.rs +++ b/gateway/src/commands/upgrade.rs @@ -1,8 +1,8 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::{Config, MISSING_VALUE}; -use crate::config::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DEFAULT_VALIDATOR_REST_ENDPOINT}; +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; @@ -127,20 +127,20 @@ fn pre_090_upgrade(from: &str, config: Config) -> Config { print_start_upgrade(&from_version, &to_version); - if config.get_validator_rest_endpoint() != MISSING_VALUE { + if config.get_validator_rest_endpoints()[0] != MISSING_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); } println!( - "Setting validator REST endpoint to to {}", - DEFAULT_VALIDATOR_REST_ENDPOINT + "Setting validator REST endpoint to to {:?}", + default_validator_rest_endpoints() ); let upgraded_config = config .with_custom_version(to_version.to_string().as_ref()) - .with_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT); + .with_custom_validators(default_validator_rest_endpoints()); upgraded_config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); @@ -174,8 +174,8 @@ fn minor_010_upgrade( } println!( - "Setting validator REST endpoint to {}", - DEFAULT_VALIDATOR_REST_ENDPOINT + "Setting validator REST endpoint to {:?}", + default_validator_rest_endpoints() ); println!( @@ -185,7 +185,7 @@ fn minor_010_upgrade( let upgraded_config = config .with_custom_version(to_version.to_string().as_ref()) - .with_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT) + .with_custom_validators(default_validator_rest_endpoints()) .with_custom_mixnet_contract(DEFAULT_MIXNET_CONTRACT_ADDRESS); upgraded_config.save_to_file(None).unwrap_or_else(|err| { diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 4dd2cc25e2..bd4973bdf7 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -2,12 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::template::config_template; -use config::NymConfig; +use config::{deserialize_duration, deserialize_validators, NymConfig}; use log::*; -use serde::{ - de::{self, IntoDeserializer, Visitor}, - Deserialize, Deserializer, Serialize, -}; +use serde::{Deserialize, Serialize}; use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; @@ -21,8 +18,11 @@ pub(crate) const MISSING_VALUE: &str = "MISSING VALUE"; // 'GATEWAY' const DEFAULT_MIX_LISTENING_PORT: u16 = 1789; const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000; -pub(crate) const DEFAULT_VALIDATOR_REST_ENDPOINT: &str = - "http://testnet-finney-validator.nymtech.net:1317"; +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"; // 'DEBUG' @@ -37,6 +37,22 @@ const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 128; const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: u16 = 5; +// helper function to get default validators as a Vec +pub fn default_validator_rest_endpoints() -> Vec { + DEFAULT_VALIDATOR_REST_ENDPOINTS + .iter() + .map(|&endpoint| endpoint.to_string()) + .collect() +} + +pub fn missing_string_value() -> String { + MISSING_VALUE.to_string() +} + +pub fn missing_vec_string_value() -> Vec { + vec![missing_string_value()] +} + #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { @@ -83,57 +99,6 @@ impl NymConfig for Config { } } -// custom function is defined to deserialize based on whether field contains a pre 0.9.0 -// u64 interpreted as milliseconds or proper duration introduced in 0.9.0 -// -// TODO: when we get to refactoring down the line, this code can just be removed -// and all Duration fields could just have #[serde(with = "humantime_serde")] instead -// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have, -// for argument sake, 0.11.0 out -fn deserialize_duration<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - struct DurationVisitor; - - impl<'de> Visitor<'de> for DurationVisitor { - type Value = Duration; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("u64 or a duration") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - self.visit_u64(value as u64) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - Ok(Duration::from_millis(Deserialize::deserialize( - value.into_deserializer(), - )?)) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - humantime_serde::deserialize(value.into_deserializer()) - } - } - - deserializer.deserialize_any(DurationVisitor) -} - -pub fn missing_string_value() -> String { - MISSING_VALUE.to_string() -} - impl Config { pub fn new>(id: S) -> Self { Config::default().with_id(id) @@ -180,8 +145,8 @@ impl Config { self } - pub fn with_custom_validator>(mut self, validator: S) -> Self { - self.gateway.validator_rest_url = validator.into(); + pub fn with_custom_validators(mut self, validators: Vec) -> Self { + self.gateway.validator_rest_urls = validators; self } @@ -392,8 +357,8 @@ impl Config { self.gateway.public_sphinx_key_file.clone() } - pub fn get_validator_rest_endpoint(&self) -> String { - self.gateway.validator_rest_url.clone() + pub fn get_validator_rest_endpoints(&self) -> Vec { + self.gateway.validator_rest_urls.clone() } pub fn get_validator_mixnet_contract_address(&self) -> String { @@ -479,8 +444,12 @@ pub struct Gateway { public_sphinx_key_file: PathBuf, /// Validator server to which the node will be reporting their presence data. - #[serde(default = "missing_string_value")] - validator_rest_url: String, + #[serde( + deserialize_with = "deserialize_validators", + default = "missing_vec_string_value", + alias = "validator_rest_url" + )] + validator_rest_urls: Vec, /// Address of the validator contract managing the network. #[serde(default = "missing_string_value")] @@ -518,7 +487,7 @@ impl Default for Gateway { public_identity_key_file: Default::default(), private_sphinx_key_file: Default::default(), public_sphinx_key_file: Default::default(), - validator_rest_url: DEFAULT_VALIDATOR_REST_ENDPOINT.to_string(), + validator_rest_urls: default_validator_rest_endpoints(), mixnet_contract_address: DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(), nym_root_directory: Config::default_root_directory(), } diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index 45907d2f9a..000aad2368 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -32,7 +32,11 @@ private_sphinx_key_file = '{{ gateway.private_sphinx_key_file }}' public_sphinx_key_file = '{{ gateway.public_sphinx_key_file }}' # Validator server to which the node will be reporting their presence data. -validator_rest_url = '{{ gateway.validator_rest_url }}' +validator_rest_urls = [ + {{#each gateway.validator_rest_urls }} + '{{this}}', + {{/each}} +] # Address of the validator contract managing the network. mixnet_contract_address = '{{ gateway.mixnet_contract_address }}' diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 9e6275ddfe..73c96d3d3e 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -131,10 +131,10 @@ impl Gateway { let announced_clients_host = self.config.get_clients_announce_address(); let validator_client_config = validator_client_rest::Config::new( - self.config.get_validator_rest_endpoint(), + self.config.get_validator_rest_endpoints(), self.config.get_validator_mixnet_contract_address(), ); - let validator_client = validator_client_rest::Client::new(validator_client_config); + let mut validator_client = validator_client_rest::Client::new(validator_client_config); let existing_gateways = match validator_client.get_gateways().await { Ok(gateways) => gateways, diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index fc39f0ca49..e880e7921d 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -54,9 +54,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .takes_value(true), ) .arg( - Arg::with_name("validator") - .long("validator") - .help("REST endpoint of the validator the node is registering presence with") + Arg::with_name("validators") + .long("validators") + .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) .arg( @@ -75,7 +75,7 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { async fn choose_layer( matches: &ArgMatches<'_>, - validator_server: String, + validator_servers: Vec, mixnet_contract: String, ) -> u64 { let max_layer = DEFAULT_NUM_MIX_HOPS; @@ -91,8 +91,8 @@ async fn choose_layer( } let validator_client_config = - validator_client_rest::Config::new(validator_server, mixnet_contract); - let validator_client = validator_client_rest::Client::new(validator_client_config); + validator_client_rest::Config::new(validator_servers, mixnet_contract); + let mut validator_client = validator_client_rest::Client::new(validator_client_config); let mixnodes = validator_client .get_mix_nodes() @@ -187,7 +187,7 @@ pub fn execute(matches: &ArgMatches) { let mut config = Config::new(id); config = override_config(config, matches); - let layer = choose_layer(matches, config.get_validator_rest_endpoint(), config.get_validator_mixnet_contract_address()).await; + let layer = choose_layer(matches, config.get_validator_rest_endpoints(), config.get_validator_mixnet_contract_address()).await; // TODO: I really don't like how we override config and are presumably done with it // only to change it here config = config.with_layer(layer); diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index fb4fed914a..83415938ff 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -10,6 +10,12 @@ pub(crate) mod run; pub(crate) mod sign; pub(crate) mod upgrade; +fn parse_validators(raw: &str) -> Vec { + raw.split(',') + .map(|raw_validator| raw_validator.trim().into()) + .collect() +} + pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Config { let max_layer = DEFAULT_NUM_MIX_HOPS; let mut was_host_overridden = false; @@ -37,8 +43,8 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi config = config.with_listening_port(port.unwrap()); } - if let Some(validator) = matches.value_of("validator") { - config = config.with_custom_validator(validator); + if let Some(raw_validators) = matches.value_of("validators") { + config = config.with_custom_validators(parse_validators(raw_validators)); } if let Some(contract_address) = matches.value_of("mixnet-contract") { diff --git a/mixnode/src/commands/run.rs b/mixnode/src/commands/run.rs index dadce82ab3..7ba64f3cbf 100644 --- a/mixnode/src/commands/run.rs +++ b/mixnode/src/commands/run.rs @@ -52,9 +52,9 @@ pub fn command_args<'a, 'b>() -> App<'a, 'b> { .takes_value(true), ) .arg( - Arg::with_name("validator") - .long("validator") - .help("REST endpoint of the validator the node is registering presence with") + Arg::with_name("validators") + .long("validators") + .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) .arg( @@ -153,13 +153,10 @@ pub fn execute(matches: &ArgMatches) { } println!( - "Directory server [presence]: {}", - config.get_validator_rest_endpoint() - ); - println!( - "Directory server [metrics]: {}", - config.get_metrics_server() + "Validator servers: {:?}", + config.get_validator_rest_endpoints() ); + println!("Metrics server: {}", config.get_metrics_server()); println!( "Listening for incoming packets on {}", diff --git a/mixnode/src/commands/upgrade.rs b/mixnode/src/commands/upgrade.rs index 0a8f56f090..cbdc2a4374 100644 --- a/mixnode/src/commands/upgrade.rs +++ b/mixnode/src/commands/upgrade.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::{ - missing_string_value, Config, DEFAULT_METRICS_SERVER, DEFAULT_MIXNET_CONTRACT_ADDRESS, - DEFAULT_VALIDATOR_REST_ENDPOINT, MISSING_VALUE, + default_validator_rest_endpoints, missing_string_value, Config, DEFAULT_METRICS_SERVER, + DEFAULT_MIXNET_CONTRACT_ADDRESS, MISSING_VALUE, }; use clap::{App, Arg, ArgMatches}; use config::NymConfig; @@ -147,7 +147,7 @@ fn pre_090_upgrade(from: &str, config: Config) -> Config { process::exit(1); } - if config.get_validator_rest_endpoint() != missing_string_value::() { + 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); @@ -156,12 +156,12 @@ fn pre_090_upgrade(from: &str, config: Config) -> Config { let mut upgraded_config = config .with_custom_version(to_version.to_string().as_ref()) .with_custom_metrics_server(DEFAULT_METRICS_SERVER) - .with_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT); + .with_custom_validators(default_validator_rest_endpoints()); println!("Setting metrics server to {}", DEFAULT_METRICS_SERVER); println!( - "Setting validator REST endpoint to {}", - DEFAULT_VALIDATOR_REST_ENDPOINT + "Setting validator REST endpoints to {:?}", + default_validator_rest_endpoints() ); println!("Generating new identity..."); @@ -213,8 +213,8 @@ fn minor_010_upgrade( } println!( - "Setting validator REST endpoint to {}", - DEFAULT_VALIDATOR_REST_ENDPOINT + "Setting validator REST endpoint to {:?}", + default_validator_rest_endpoints() ); println!( @@ -224,7 +224,7 @@ fn minor_010_upgrade( let upgraded_config = config .with_custom_version(to_version.to_string().as_ref()) - .with_custom_validator(DEFAULT_VALIDATOR_REST_ENDPOINT) + .with_custom_validators(default_validator_rest_endpoints()) .with_custom_mixnet_contract(DEFAULT_MIXNET_CONTRACT_ADDRESS); upgraded_config.save_to_file(None).unwrap_or_else(|err| { diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index dd863e2333..c8882e1e46 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -2,12 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::template::config_template; -use config::NymConfig; +use config::{deserialize_duration, deserialize_validators, NymConfig}; use log::*; -use serde::{ - de::{self, IntoDeserializer, Visitor}, - Deserialize, Deserializer, Serialize, -}; +use serde::{Deserialize, Serialize}; use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; @@ -20,8 +17,11 @@ pub(crate) const MISSING_VALUE: &str = "MISSING VALUE"; // 'MIXNODE' const DEFAULT_LISTENING_PORT: u16 = 1789; -pub(crate) const DEFAULT_VALIDATOR_REST_ENDPOINT: &str = - "http://testnet-finney-validator.nymtech.net:1317"; +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(crate) const DEFAULT_METRICS_SERVER: &str = "http://testnet-metrics.nymtech.net:8080"; pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "hal1k0jntykt7e4g3y88ltc60czgjuqdy4c9c6gv94"; @@ -33,6 +33,22 @@ const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500 const DEFAULT_CACHE_ENTRY_TTL: Duration = Duration::from_millis(30_000); const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 128; +// helper function to get default validators as a Vec +pub fn default_validator_rest_endpoints() -> Vec { + DEFAULT_VALIDATOR_REST_ENDPOINTS + .iter() + .map(|&endpoint| endpoint.to_string()) + .collect() +} + +pub fn missing_string_value>() -> T { + MISSING_VALUE.to_string().into() +} + +pub fn missing_vec_string_value() -> Vec { + vec![missing_string_value()] +} + #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { @@ -75,57 +91,6 @@ impl NymConfig for Config { } } -// custom function is defined to deserialize based on whether field contains a pre 0.9.0 -// u64 interpreted as milliseconds or proper duration introduced in 0.9.0 -// -// TODO: when we get to refactoring down the line, this code can just be removed -// and all Duration fields could just have #[serde(with = "humantime_serde")] instead -// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have, -// for argument sake, 0.11.0 out -fn deserialize_duration<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - struct DurationVisitor; - - impl<'de> Visitor<'de> for DurationVisitor { - type Value = Duration; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("u64 or a duration") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - self.visit_u64(value as u64) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - Ok(Duration::from_millis(Deserialize::deserialize( - value.into_deserializer(), - )?)) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - humantime_serde::deserialize(value.into_deserializer()) - } - } - - deserializer.deserialize_any(DurationVisitor) -} - -pub fn missing_string_value>() -> T { - MISSING_VALUE.to_string().into() -} - impl Config { pub fn new>(id: S) -> Self { Config::default().with_id(id) @@ -166,8 +131,8 @@ impl Config { self } - pub fn with_custom_validator>(mut self, validator: S) -> Self { - self.mixnode.validator_rest_url = validator.into(); + pub fn with_custom_validators(mut self, validators: Vec) -> Self { + self.mixnode.validator_rest_urls = validators; self } @@ -282,8 +247,8 @@ impl Config { self.mixnode.public_sphinx_key_file.clone() } - pub fn get_validator_rest_endpoint(&self) -> String { - self.mixnode.validator_rest_url.clone() + pub fn get_validator_rest_endpoints(&self) -> Vec { + self.mixnode.validator_rest_urls.clone() } pub fn get_validator_mixnet_contract_address(&self) -> String { @@ -381,8 +346,12 @@ pub struct MixNode { public_sphinx_key_file: PathBuf, /// Validator server to which the node will be reporting their presence data. - #[serde(default = "missing_string_value")] - validator_rest_url: String, + #[serde( + deserialize_with = "deserialize_validators", + default = "missing_vec_string_value", + alias = "validator_rest_url" + )] + validator_rest_urls: Vec, /// Address of the validator contract managing the network. #[serde(default = "missing_string_value")] @@ -429,7 +398,7 @@ impl Default for MixNode { public_identity_key_file: Default::default(), private_sphinx_key_file: Default::default(), public_sphinx_key_file: Default::default(), - validator_rest_url: DEFAULT_VALIDATOR_REST_ENDPOINT.to_string(), + validator_rest_urls: default_validator_rest_endpoints(), mixnet_contract_address: DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(), metrics_server_url: DEFAULT_METRICS_SERVER.to_string(), nym_root_directory: Config::default_root_directory(), diff --git a/mixnode/src/config/template.rs b/mixnode/src/config/template.rs index 080db9b3e3..32c9b9b4eb 100644 --- a/mixnode/src/config/template.rs +++ b/mixnode/src/config/template.rs @@ -48,7 +48,11 @@ public_sphinx_key_file = '{{ mixnode.public_sphinx_key_file }}' announce_address = '{{ mixnode.announce_address }}' # Validator server to which the node will be reporting their presence data. -validator_rest_url = '{{ mixnode.validator_rest_url }}' +validator_rest_urls = [ + {{#each mixnode.validator_rest_urls }} + '{{this}}', + {{/each}} +] # Metrics server to which the node will be reporting their metrics data. metrics_server_url = '{{ mixnode.metrics_server_url }}' diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 3c70e1c9d9..921013c0f6 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -89,10 +89,10 @@ impl MixNode { // TODO: ask DH whether this function still makes sense in ^0.10 async fn check_if_same_ip_node_exists(&mut self) -> Option { let validator_client_config = validator_client_rest::Config::new( - self.config.get_validator_rest_endpoint(), + self.config.get_validator_rest_endpoints(), self.config.get_validator_mixnet_contract_address(), ); - let validator_client = validator_client_rest::Client::new(validator_client_config); + let mut validator_client = validator_client_rest::Client::new(validator_client_config); let existing_nodes = match validator_client.get_mix_nodes().await { Ok(nodes) => nodes,