diff --git a/Cargo.lock b/Cargo.lock index 415c50c821..f760cb6aa3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,7 +413,7 @@ dependencies = [ "tempfile", "tokio", "topology", - "validator-client-rest", + "validator-client", ] [[package]] @@ -1528,7 +1528,7 @@ dependencies = [ "serde", "tokio", "tokio-util", - "validator-client-rest", + "validator-client", "version-checker", ] @@ -1650,7 +1650,7 @@ dependencies = [ "tokio-tungstenite", "topology", "url", - "validator-client-rest", + "validator-client", "version-checker", "websocket-requests", ] @@ -1702,7 +1702,7 @@ dependencies = [ "tokio-stream", "tokio-tungstenite", "tokio-util", - "validator-client-rest", + "validator-client", "version-checker", ] @@ -1734,7 +1734,7 @@ dependencies = [ "tokio", "tokio-util", "topology", - "validator-client-rest", + "validator-client", "version-checker", ] @@ -1749,10 +1749,12 @@ dependencies = [ "gateway-client", "gateway-requests", "log", + "mixnet-contract", "nymsphinx", "pin-project", "pretty_env_logger", "rand 0.7.3", + "reqwest", "serde", "serde_json", "tokio", @@ -1808,7 +1810,7 @@ dependencies = [ "socks5-requests", "tokio", "topology", - "validator-client-rest", + "validator-client", "version-checker", ] @@ -3396,20 +3398,6 @@ checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" [[package]] name = "validator-client" version = "0.1.0" -dependencies = [ - "crypto", - "log", - "mockito", - "reqwest", - "schemars", - "serde", - "tokio", - "topology", -] - -[[package]] -name = "validator-client-rest" -version = "0.1.0" dependencies = [ "base64 0.13.0", "mixnet-contract", diff --git a/Cargo.toml b/Cargo.toml index b22e7fa34b..20024c072f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ members = [ "common/client-libs/metrics-client", "common/client-libs/mixnet-client", "common/client-libs/validator-client", - "common/client-libs/validator-client-rest", "common/config", "common/crypto", "common/mixnet-contract", diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml index 61d5221557..c27777dcb1 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -25,7 +25,7 @@ nonexhaustive-delayqueue = { path = "../../common/nonexhaustive-delayqueue" } nymsphinx = { path = "../../common/nymsphinx" } pemstore = { path = "../../common/pemstore" } topology = { path = "../../common/topology" } -validator-client-rest = { path = "../../common/client-libs/validator-client-rest" } +validator-client = { path = "../../common/client-libs/validator-client" } [dev-dependencies] tempfile = "3.1.0" \ No newline at end of file diff --git a/clients/client-core/src/client/topology_control.rs b/clients/client-core/src/client/topology_control.rs index c29288cc75..e19d031eb5 100644 --- a/clients/client-core/src/client/topology_control.rs +++ b/clients/client-core/src/client/topology_control.rs @@ -156,7 +156,7 @@ impl TopologyRefresherConfig { } pub struct TopologyRefresher { - validator_client: validator_client_rest::Client, + validator_client: validator_client::Client, topology_accessor: TopologyAccessor, refresh_rate: Duration, @@ -166,11 +166,9 @@ pub struct TopologyRefresher { impl TopologyRefresher { pub fn new(cfg: TopologyRefresherConfig, topology_accessor: TopologyAccessor) -> Self { - let validator_client_config = validator_client_rest::Config::new( - cfg.available_validators, - cfg.mixnet_contract_address, - ); - let validator_client = validator_client_rest::Client::new(validator_client_config); + let validator_client_config = + validator_client::Config::new(cfg.available_validators, cfg.mixnet_contract_address); + let validator_client = validator_client::Client::new(validator_client_config); TopologyRefresher { validator_client, diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index ebc641deb6..6aa3cbba48 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -39,7 +39,7 @@ nymsphinx = { path = "../../common/nymsphinx" } pemstore = { path = "../../common/pemstore" } topology = { path = "../../common/topology" } websocket-requests = { path = "websocket-requests" } -validator-client-rest = { path = "../../common/client-libs/validator-client-rest" } +validator-client = { path = "../../common/client-libs/validator-client" } version-checker = { path = "../../common/version-checker" } [dev-dependencies] diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index f7558b3bc9..a4e9f30027 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -108,9 +108,8 @@ async fn gateway_details( mixnet_contract: &str, chosen_gateway_id: Option<&str>, ) -> gateway::Node { - let 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 validator_client_config = validator_client::Config::new(validator_servers, mixnet_contract); + let mut validator_client = validator_client::Client::new(validator_client_config); let gateways = validator_client.get_gateways().await.unwrap(); let valid_gateways = gateways diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 5a6c0c66b6..90a5e04547 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -33,5 +33,5 @@ socks5-requests = { path = "../../common/socks5/requests" } topology = { path = "../../common/topology" } pemstore = { path = "../../common/pemstore" } proxy-helpers = { path = "../../common/socks5/proxy-helpers" } -validator-client-rest = { path = "../../common/client-libs/validator-client-rest" } +validator-client = { path = "../../common/client-libs/validator-client" } version-checker = { path = "../../common/version-checker" } diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 583b6ee0e7..9fbb003e35 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -109,9 +109,8 @@ async fn gateway_details( mixnet_contract: &str, chosen_gateway_id: Option<&str>, ) -> gateway::Node { - let 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 validator_client_config = validator_client::Config::new(validator_servers, mixnet_contract); + let mut validator_client = validator_client::Client::new(validator_client_config); let gateways = validator_client.get_gateways().await.unwrap(); let valid_gateways = gateways diff --git a/clients/socks5/src/socks/request.rs b/clients/socks5/src/socks/request.rs index 14cda99989..92d687aa90 100644 --- a/clients/socks5/src/socks/request.rs +++ b/clients/socks5/src/socks/request.rs @@ -6,6 +6,7 @@ use tokio::io::{AsyncRead, AsyncReadExt}; /// A Socks5 request hitting the proxy. pub(crate) struct SocksRequest { + #[allow(dead_code)] pub version: u8, pub command: SocksCommand, pub addr_type: AddrType, diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs index b991599fde..3785cc3ee5 100644 --- a/clients/webassembly/src/client/mod.rs +++ b/clients/webassembly/src/client/mod.rs @@ -15,7 +15,6 @@ use crypto::asymmetric::{encryption, identity}; use futures::channel::mpsc; use gateway_client::GatewayClient; -use js_sys::Promise; use nymsphinx::acknowledgements::AckKey; use nymsphinx::addressing::clients::Recipient; use nymsphinx::params::PacketMode; @@ -24,9 +23,9 @@ use rand::rngs::OsRng; use received_processor::ReceivedMessagesProcessor; use std::sync::Arc; use std::time::Duration; -use topology::{gateway, NymTopology}; +use topology::{gateway, nym_topology_from_bonds, NymTopology}; use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::{future_to_promise, spawn_local}; +use wasm_bindgen_futures::spawn_local; use wasm_utils::{console_log, console_warn}; pub(crate) mod received_processor; @@ -40,6 +39,7 @@ const DEFAULT_VPN_KEY_REUSE_LIMIT: usize = 1000; #[wasm_bindgen] pub struct NymClient { validator_server: String, + mixnet_contract_address: String, // TODO: technically this doesn't need to be an Arc since wasm is run on a single thread // however, once we eventually combine this code with the native-client's, it will make things @@ -64,7 +64,7 @@ pub struct NymClient { #[wasm_bindgen] impl NymClient { #[wasm_bindgen(constructor)] - pub fn new(validator_server: String) -> Self { + pub fn new(validator_server: String, mixnet_contract_address: String) -> Self { let mut rng = OsRng; // for time being generate new keys each time... let identity = identity::KeyPair::new(&mut rng); @@ -76,6 +76,7 @@ impl NymClient { encryption_keys: Arc::new(encryption_keys), ack_key: Arc::new(ack_key), validator_server, + mixnet_contract_address, message_preparer: None, // received_keys: Default::default(), topology: None, @@ -232,26 +233,42 @@ impl NymClient { self.topology = Some(topology) } - pub fn get_full_topology_json(&self) -> Promise { - let validator_client_config = validator_client::Config::new(self.validator_server.clone()); - let validator_client = validator_client::Client::new(validator_client_config); - future_to_promise(async move { - let topology = &validator_client.get_active_topology().await.unwrap(); - Ok(JsValue::from_serde(&topology).unwrap()) - }) - } + // when updated to 0.10.0, to prevent headache later on, this function requires those two imports: + // use js_sys::Promise; + // use wasm_bindgen_futures::future_to_promise; + // + // pub fn get_full_topology_json(&self) -> Promise { + // let validator_client_config = validator_client::Config::new( + // vec![self.validator_server.clone()], + // &self.mixnet_contract_address, + // ); + // let validator_client = validator_client::Client::new(validator_client_config); + // + // future_to_promise(async move { + // let topology = &validator_client.get_active_topology().await.unwrap(); + // Ok(JsValue::from_serde(&topology).unwrap()) + // }) + // } pub(crate) async fn get_nym_topology(&self) -> NymTopology { - let validator_client_config = validator_client::Config::new(self.validator_server.clone()); - let validator_client = validator_client::Client::new(validator_client_config); + let validator_client_config = validator_client::Config::new( + vec![self.validator_server.clone()], + &self.mixnet_contract_address, + ); + let mut validator_client = validator_client::Client::new(validator_client_config); - match validator_client.get_active_topology().await { + let mixnodes = match validator_client.get_mix_nodes().await { Err(err) => panic!("{}", err), - Ok(topology) => { - let nym_topology: NymTopology = topology.into(); - let version = env!("CARGO_PKG_VERSION"); - nym_topology.filter_system_version(version) - } - } + Ok(mixes) => mixes, + }; + + let gateways = match validator_client.get_gateways().await { + Err(err) => panic!("{}", err), + Ok(gateways) => gateways, + }; + + let topology = nym_topology_from_bonds(mixnodes, gateways); + let version = env!("CARGO_PKG_VERSION"); + topology.filter_system_version(version) } } diff --git a/common/client-libs/validator-client-rest/Cargo.toml b/common/client-libs/validator-client-rest/Cargo.toml deleted file mode 100644 index 466499652d..0000000000 --- a/common/client-libs/validator-client-rest/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "validator-client-rest" -version = "0.1.0" -authors = ["Jędrzej Stuczyński "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -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 deleted file mode 100644 index 4cd2c4b163..0000000000 --- a/common/client-libs/validator-client-rest/src/lib.rs +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -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; - -#[derive(Debug)] -pub enum ValidatorClientError { - ReqwestClientError(reqwest::Error), - ValidatorError(String), -} - -impl std::error::Error for ValidatorClientError {} - -impl From for ValidatorClientError { - fn from(err: reqwest::Error) -> Self { - ValidatorClientError::ReqwestClientError(err) - } -} - -impl Display for ValidatorClientError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - ValidatorClientError::ReqwestClientError(err) => { - write!(f, "there was an issue with the REST request - {}", err) - } - ValidatorClientError::ValidatorError(err) => { - write!(f, "there was an issue with the validator client - {}", err) - } - } - } -} - -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 { - initial_rest_servers: Vec, - mixnet_contract_address: String, - mixnode_page_limit: Option, - gateway_page_limit: Option, -} - -impl Config { - pub fn new>( - rest_servers_available_base_urls: Vec, - mixnet_contract_address: S, - ) -> Self { - Config { - initial_rest_servers: rest_servers_available_base_urls, - mixnet_contract_address: mixnet_contract_address.into(), - mixnode_page_limit: None, - gateway_page_limit: None, - } - } - - pub fn with_mixnode_page_limit(mut self, limit: Option) -> Config { - self.mixnode_page_limit = limit; - self - } - - pub fn with_gateway_page_limit(mut self, limit: Option) -> Config { - self.gateway_page_limit = limit; - self - } -} - -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.available_validators_rest_urls[0], self.config.mixnet_contract_address - ) - } - - 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>, - { - let query_url = format!("{}/{}?encoding=base64", self.base_query_path(), query); - - let query_response: QueryResponse = self - .reqwest_client - .get(query_url) - .send() - .await? - .json() - .await?; - - match query_response { - QueryResponse::Ok(smart_res) => Ok(smart_res.result.smart), - QueryResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)), - QueryResponse::CodedError(err) => Err(ValidatorError(format!("{}", err))), - } - } - - async fn get_mix_nodes_paged( - &mut self, - start_after: Option, - ) -> Result { - let query_content_json = serde_json::to_string(&QueryRequest::GetMixNodes { - limit: self.config.mixnode_page_limit, - start_after, - }) - .expect("serde was incorrectly implemented on QueryRequest::GetMixNodes!"); - - // we need to encode our json request - let query_content = base64::encode(query_content_json); - - self.query_validators(query_content).await - } - - pub async fn get_mix_nodes(&mut self) -> Result, ValidatorClientError> { - let mut mixnodes = Vec::new(); - let mut start_after = None; - loop { - let mut paged_response = self.get_mix_nodes_paged(start_after.take()).await?; - mixnodes.append(&mut paged_response.nodes); - - if let Some(start_after_res) = paged_response.start_next_after { - start_after = Some(start_after_res) - } else { - break; - } - } - - Ok(mixnodes) - } - - async fn get_gateways_paged( - &mut self, - start_after: Option, - ) -> Result { - let query_content_json = serde_json::to_string(&QueryRequest::GetGateways { - limit: self.config.gateway_page_limit, - start_after, - }) - .expect("serde was incorrectly implemented on QueryRequest::GetGateways!"); - - // we need to encode our json request - let query_content = base64::encode(query_content_json); - - self.query_validators(query_content).await - } - - pub async fn get_gateways(&mut self) -> Result, ValidatorClientError> { - let mut gateways = Vec::new(); - let mut start_after = None; - loop { - let mut paged_response = self.get_gateways_paged(start_after.take()).await?; - gateways.append(&mut paged_response.nodes); - - if let Some(start_after_res) = paged_response.start_next_after { - start_after = Some(start_after_res) - } else { - break; - } - } - - Ok(gateways) - } -} diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 4833926e31..c55c61b94e 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -1,20 +1,15 @@ [package] name = "validator-client" version = "0.1.0" -authors = ["Jedrzej Stuczynski , David Hrycyszyn "] +authors = ["Jędrzej Stuczyński "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -log = "0.4" -reqwest = { version = "0.11", features = ["json"] } -schemars = "0.7" +base64 = "0.13" +mixnet-contract = { path = "../../../common/mixnet-contract" } serde = { version = "1.0", features = ["derive"] } - -crypto = { path = "../../crypto" } -topology = { path = "../../topology" } - -[dev-dependencies] -mockito = "0.30" -tokio = "1.4" \ No newline at end of file +serde_json = "1.0" +rand = "0.8" +reqwest = { version = "0.11", features = ["json"] } diff --git a/common/client-libs/validator-client/src/lib.rs b/common/client-libs/validator-client/src/lib.rs index 1fc2fcdc7d..4cd2c4b163 100644 --- a/common/client-libs/validator-client/src/lib.rs +++ b/common/client-libs/validator-client/src/lib.rs @@ -1,89 +1,25 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 -use crate::models::gateway::GatewayRegistrationInfo; -use crate::models::mixmining::{BatchMixStatus, MixStatus}; -use crate::models::mixnode::MixRegistrationInfo; -use crate::models::topology::Topology; -use crate::rest_requests::{ - ActiveTopologyGet, ActiveTopologyGetResponse, BatchMixStatusPost, GatewayRegisterPost, - MixRegisterPost, MixStatusPost, NodeUnregisterDelete, ReputationPatch, RestRequest, - RestRequestError, TopologyGet, TopologyGetResponse, -}; +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::fmt::{self, Display, Formatter}; +use std::collections::VecDeque; -pub mod models; -pub mod rest_requests; - -// for ease of use -type Result = std::result::Result; - -const MAX_SANE_UNEXPECTED_PRINT: usize = 100; - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase", untagged)] -pub(crate) enum ErrorResponses { - Error(ErrorResponse), - Unexpected(String), -} - -impl From for ValidatorClientError { - fn from(err: ErrorResponses) -> Self { - match err { - ErrorResponses::Error(err_message) => { - ValidatorClientError::ValidatorError(err_message.error) - } - ErrorResponses::Unexpected(received) => { - ValidatorClientError::UnexpectedResponse(received) - } - } - } -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct ErrorResponse { - error: String, -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct OkResponse { - ok: bool, -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase", untagged)] -pub(crate) enum DefaultRestResponse { - Ok(OkResponse), - Error(ErrorResponses), -} +mod models; +pub(crate) mod serde_helpers; #[derive(Debug)] pub enum ValidatorClientError { - RestRequestError(RestRequestError), ReqwestClientError(reqwest::Error), ValidatorError(String), - UnexpectedResponse(String), } -impl From for ValidatorClientError { - fn from(err: RestRequestError) -> Self { - ValidatorClientError::RestRequestError(err) - } -} +impl std::error::Error for ValidatorClientError {} impl From for ValidatorClientError { fn from(err: reqwest::Error) -> Self { @@ -92,218 +28,214 @@ impl From for ValidatorClientError { } impl Display for ValidatorClientError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - ValidatorClientError::RestRequestError(err) => { - write!(f, "could not prepare the REST request - {}", err) - } ValidatorClientError::ReqwestClientError(err) => { write!(f, "there was an issue with the REST request - {}", err) } ValidatorClientError::ValidatorError(err) => { write!(f, "there was an issue with the validator client - {}", err) } - ValidatorClientError::UnexpectedResponse(received) => { - if received.len() < MAX_SANE_UNEXPECTED_PRINT { - write!( - f, - "received data was completely unexpected. got: {}", - received - ) - } else { - write!( - f, - "received data was completely unexpected. got: {}...", - received - .chars() - .take(MAX_SANE_UNEXPECTED_PRINT) - .collect::() - ) - } - } } } } +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 { - base_url: String, + initial_rest_servers: Vec, + mixnet_contract_address: String, + mixnode_page_limit: Option, + gateway_page_limit: Option, } impl Config { - pub fn new>(base_url: S) -> Self { + pub fn new>( + rest_servers_available_base_urls: Vec, + mixnet_contract_address: S, + ) -> Self { Config { - base_url: 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, } } + + pub fn with_mixnode_page_limit(mut self, limit: Option) -> Config { + self.mixnode_page_limit = limit; + self + } + + pub fn with_gateway_page_limit(mut self, limit: Option) -> Config { + self.gateway_page_limit = limit; + self + } } 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, } } - async fn make_rest_request( - &self, - request: R, - ) -> Result { - let mut req_builder = self + 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.available_validators_rest_urls[0], self.config.mixnet_contract_address + ) + } + + 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>, + { + let query_url = format!("{}/{}?encoding=base64", self.base_query_path(), query); + + let query_response: QueryResponse = self .reqwest_client - .request(R::METHOD, request.url().clone()); + .get(query_url) + .send() + .await? + .json() + .await?; - if let Some(json_payload) = request.json_payload() { - // if applicable, attach payload - req_builder = req_builder.json(json_payload) - } - Ok(req_builder.send().await?.json().await?) - } - - pub async fn register_mix(&self, mix_registration_info: MixRegistrationInfo) -> Result<()> { - let req = MixRegisterPost::new( - &self.config.base_url, - None, - None, - Some(mix_registration_info), - )?; - match self.make_rest_request(req).await? { - DefaultRestResponse::Ok(_) => Ok(()), - DefaultRestResponse::Error(err) => Err(err.into()), + match query_response { + QueryResponse::Ok(smart_res) => Ok(smart_res.result.smart), + QueryResponse::Error(err) => Err(ValidatorClientError::ValidatorError(err.error)), + QueryResponse::CodedError(err) => Err(ValidatorError(format!("{}", err))), } } - pub async fn register_gateway( - &self, - gateway_registration_info: GatewayRegistrationInfo, - ) -> Result<()> { - let req = GatewayRegisterPost::new( - &self.config.base_url, - None, - None, - Some(gateway_registration_info), - )?; - match self.make_rest_request(req).await? { - DefaultRestResponse::Ok(ok_res) => { - if ok_res.ok { - Ok(()) - } else { - Err(ValidatorClientError::ValidatorError( - "received ok response with false".into(), - )) - } + async fn get_mix_nodes_paged( + &mut self, + start_after: Option, + ) -> Result { + let query_content_json = serde_json::to_string(&QueryRequest::GetMixNodes { + limit: self.config.mixnode_page_limit, + start_after, + }) + .expect("serde was incorrectly implemented on QueryRequest::GetMixNodes!"); + + // we need to encode our json request + let query_content = base64::encode(query_content_json); + + self.query_validators(query_content).await + } + + pub async fn get_mix_nodes(&mut self) -> Result, ValidatorClientError> { + let mut mixnodes = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self.get_mix_nodes_paged(start_after.take()).await?; + mixnodes.append(&mut paged_response.nodes); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; } - DefaultRestResponse::Error(err) => Err(err.into()), } + + Ok(mixnodes) } - pub async fn unregister_node(&self, node_id: &str) -> Result<()> { - let req = - NodeUnregisterDelete::new(&self.config.base_url, Some(vec![node_id]), None, None)?; + async fn get_gateways_paged( + &mut self, + start_after: Option, + ) -> Result { + let query_content_json = serde_json::to_string(&QueryRequest::GetGateways { + limit: self.config.gateway_page_limit, + start_after, + }) + .expect("serde was incorrectly implemented on QueryRequest::GetGateways!"); - match self.make_rest_request(req).await? { - DefaultRestResponse::Ok(ok_res) => { - if ok_res.ok { - Ok(()) - } else { - Err(ValidatorClientError::ValidatorError( - "received ok response with false".into(), - )) - } + // we need to encode our json request + let query_content = base64::encode(query_content_json); + + self.query_validators(query_content).await + } + + pub async fn get_gateways(&mut self) -> Result, ValidatorClientError> { + let mut gateways = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self.get_gateways_paged(start_after.take()).await?; + gateways.append(&mut paged_response.nodes); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; } - DefaultRestResponse::Error(err) => Err(err.into()), } - } - pub async fn set_reputation(&self, node_id: &str, new_reputation: i64) -> Result<()> { - let new_rep_string = new_reputation.to_string(); - let query_param_values = vec![&*new_rep_string]; - let query_param_keys = ReputationPatch::query_param_keys(); - - let query_params = query_param_keys - .into_iter() - .zip(query_param_values.into_iter()) - .collect(); - - let req = ReputationPatch::new( - &self.config.base_url, - Some(vec![node_id]), - Some(query_params), - None, - )?; - match self.make_rest_request(req).await? { - DefaultRestResponse::Ok(ok_res) => { - if ok_res.ok { - Ok(()) - } else { - Err(ValidatorClientError::ValidatorError( - "received ok response with false".into(), - )) - } - } - DefaultRestResponse::Error(err) => Err(err.into()), - } - } - - pub async fn get_topology(&self) -> Result { - let req = TopologyGet::new(&self.config.base_url, None, None, None)?; - match self.make_rest_request(req).await? { - TopologyGetResponse::Ok(topology) => Ok(topology), - TopologyGetResponse::Error(err) => Err(err.into()), - } - } - - pub async fn get_active_topology(&self) -> Result { - let req = ActiveTopologyGet::new(&self.config.base_url, None, None, None)?; - match self.make_rest_request(req).await? { - ActiveTopologyGetResponse::Ok(topology) => Ok(topology), - ActiveTopologyGetResponse::Error(err) => Err(err.into()), - } - } - - pub async fn post_mixmining_status(&self, status: MixStatus) -> Result<()> { - let req = MixStatusPost::new(&self.config.base_url, None, None, Some(status))?; - match self.make_rest_request(req).await? { - DefaultRestResponse::Ok(ok_res) => { - if ok_res.ok { - Ok(()) - } else { - Err(ValidatorClientError::ValidatorError( - "received ok response with false".into(), - )) - } - } - DefaultRestResponse::Error(err) => Err(err.into()), - } - } - - pub async fn post_batch_mixmining_status(&self, batch_status: BatchMixStatus) -> Result<()> { - let req = BatchMixStatusPost::new(&self.config.base_url, None, None, Some(batch_status))?; - match self.make_rest_request(req).await? { - DefaultRestResponse::Ok(ok_res) => { - if ok_res.ok { - Ok(()) - } else { - Err(ValidatorClientError::ValidatorError( - "received ok response with false".into(), - )) - } - } - DefaultRestResponse::Error(err) => Err(err.into()), - } - } -} - -#[cfg(test)] -pub(crate) fn client_test_fixture(base_url: &str) -> Client { - Client { - config: Config::new(base_url), - reqwest_client: reqwest::Client::new(), + Ok(gateways) } } diff --git a/common/client-libs/validator-client-rest/src/models.rs b/common/client-libs/validator-client/src/models.rs similarity index 100% rename from common/client-libs/validator-client-rest/src/models.rs rename to common/client-libs/validator-client/src/models.rs diff --git a/common/client-libs/validator-client/src/models/gateway.rs b/common/client-libs/validator-client/src/models/gateway.rs deleted file mode 100644 index 43d97d42c4..0000000000 --- a/common/client-libs/validator-client/src/models/gateway.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::models::node::NodeInfo; -use crypto::asymmetric::{encryption, identity}; -use serde::{Deserialize, Serialize}; -use std::convert::TryInto; -use std::io; -use std::net::{SocketAddr, ToSocketAddrs}; -use topology::gateway::GatewayConversionError; - -// used for gateways to register themselves -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct GatewayRegistrationInfo { - #[serde(flatten)] - pub(crate) node_info: NodeInfo, - pub(crate) clients_host: String, -} - -impl GatewayRegistrationInfo { - pub fn new( - mix_host: String, - clients_host: String, - identity_key: String, - sphinx_key: String, - version: String, - location: String, - incentives_address: Option, - ) -> Self { - GatewayRegistrationInfo { - node_info: NodeInfo { - mix_host, - identity_key, - sphinx_key, - version, - location, - incentives_address: incentives_address.unwrap_or_else(|| "".to_string()), - }, - clients_host, - } - } -} - -// actual entry in topology -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct RegisteredGateway { - #[serde(flatten)] - pub(crate) gateway_info: GatewayRegistrationInfo, - pub(crate) registration_time: i64, - pub(crate) reputation: i64, -} - -impl RegisteredGateway { - pub fn identity(&self) -> String { - self.gateway_info.node_info.identity_key.clone() - } - - pub fn mixnet_listener(&self) -> String { - self.gateway_info.node_info.mix_host.clone() - } - - pub fn clients_listener(&self) -> String { - self.gateway_info.clients_host.clone() - } - - pub fn version(&self) -> String { - self.gateway_info.node_info.version.clone() - } - - pub fn version_ref(&self) -> &str { - &self.gateway_info.node_info.version - } - - fn resolve_hostname(&self) -> Result { - self.gateway_info - .node_info - .mix_host - .to_socket_addrs() - .map_err(GatewayConversionError::InvalidAddress)? - .next() - .ok_or_else(|| { - GatewayConversionError::InvalidAddress(io::Error::new( - io::ErrorKind::Other, - "no valid socket address", - )) - }) - } -} - -impl TryInto for RegisteredGateway { - type Error = GatewayConversionError; - - fn try_into(self) -> Result { - Ok(topology::gateway::Node { - owner: "N/A".to_string(), - stake: 0, - mixnet_listener: self.resolve_hostname()?, - location: self.gateway_info.node_info.location, - client_listener: self.gateway_info.clients_host, - identity_key: identity::PublicKey::from_base58_string( - self.gateway_info.node_info.identity_key, - )?, - sphinx_key: encryption::PublicKey::from_base58_string( - self.gateway_info.node_info.sphinx_key, - )?, - version: self.gateway_info.node_info.version, - }) - } -} - -impl<'a> TryInto for &'a RegisteredGateway { - type Error = GatewayConversionError; - - fn try_into(self) -> Result { - Ok(topology::gateway::Node { - owner: "N/A".to_string(), - stake: 0, - mixnet_listener: self.resolve_hostname()?, - location: self.gateway_info.node_info.location.clone(), - client_listener: self.gateway_info.clients_host.clone(), - identity_key: identity::PublicKey::from_base58_string( - &self.gateway_info.node_info.identity_key, - )?, - sphinx_key: encryption::PublicKey::from_base58_string( - &self.gateway_info.node_info.sphinx_key, - )?, - version: self.gateway_info.node_info.version.clone(), - }) - } -} diff --git a/common/client-libs/validator-client/src/models/mixmining.rs b/common/client-libs/validator-client/src/models/mixmining.rs deleted file mode 100644 index 270d1bb7fd..0000000000 --- a/common/client-libs/validator-client/src/models/mixmining.rs +++ /dev/null @@ -1,19 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -/// A notification sent to the validators to let them know whether a given mix is -/// currently up or down (based on whether it's mixing packets) -pub struct MixStatus { - pub pub_key: String, - pub ip_version: String, - pub up: bool, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -/// A notification sent to the validators to let them know whether a given set of mixes is -/// currently up or down (based on whether it's mixing packets) -pub struct BatchMixStatus { - pub status: Vec, -} diff --git a/common/client-libs/validator-client/src/models/mixnode.rs b/common/client-libs/validator-client/src/models/mixnode.rs deleted file mode 100644 index 68640d03c6..0000000000 --- a/common/client-libs/validator-client/src/models/mixnode.rs +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::models::node::NodeInfo; -use crypto::asymmetric::{encryption, identity}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::convert::TryInto; -use std::io; -use std::net::{SocketAddr, ToSocketAddrs}; -use topology::mix::{MixnodeConversionError, Node}; - -// used for mixnode to register themselves -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct MixRegistrationInfo { - #[serde(flatten)] - pub(crate) node_info: NodeInfo, - pub(crate) layer: u64, -} - -impl MixRegistrationInfo { - pub fn new( - mix_host: String, - identity_key: String, - sphinx_key: String, - version: String, - location: String, - layer: u64, - incentives_address: Option, - ) -> Self { - MixRegistrationInfo { - node_info: NodeInfo { - mix_host, - identity_key, - sphinx_key, - version, - location, - incentives_address: incentives_address.unwrap_or_else(|| "".to_string()), - }, - layer, - } - } -} - -// actual entry in topology -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub struct RegisteredMix { - #[serde(flatten)] - pub(crate) mix_info: MixRegistrationInfo, - pub(crate) registration_time: i64, - pub(crate) reputation: i64, -} - -impl RegisteredMix { - pub fn identity(&self) -> String { - self.mix_info.node_info.identity_key.clone() - } - - pub fn mix_host(&self) -> String { - self.mix_info.node_info.mix_host.clone() - } - - pub fn reputation(&self) -> i64 { - self.reputation - } - - pub fn layer(&self) -> u64 { - self.mix_info.layer - } - - pub fn version(&self) -> String { - self.mix_info.node_info.version.clone() - } - - pub fn version_ref(&self) -> &str { - &self.mix_info.node_info.version - } - - pub fn incentives_address(&self) -> String { - self.mix_info.node_info.incentives_address.clone() - } - - fn resolve_hostname(&self) -> Result { - self.mix_info - .node_info - .mix_host - .to_socket_addrs() - .map_err(MixnodeConversionError::InvalidAddress)? - .next() - .ok_or_else(|| { - MixnodeConversionError::InvalidAddress(io::Error::new( - io::ErrorKind::Other, - "no valid socket address", - )) - }) - } -} - -impl TryInto for RegisteredMix { - type Error = MixnodeConversionError; - - fn try_into(self) -> Result { - Ok(topology::mix::Node { - owner: "N/A".to_string(), - stake: 0, - host: self.resolve_hostname()?, - location: self.mix_info.node_info.location, - identity_key: identity::PublicKey::from_base58_string( - self.mix_info.node_info.identity_key, - )?, - sphinx_key: encryption::PublicKey::from_base58_string( - self.mix_info.node_info.sphinx_key, - )?, - layer: self.mix_info.layer, - version: self.mix_info.node_info.version, - }) - } -} - -impl<'a> TryInto for &'a RegisteredMix { - type Error = MixnodeConversionError; - - fn try_into(self) -> Result { - Ok(topology::mix::Node { - owner: "N/A".to_string(), - stake: 0, - host: self.resolve_hostname()?, - location: self.mix_info.node_info.location.clone(), - identity_key: identity::PublicKey::from_base58_string( - &self.mix_info.node_info.identity_key, - )?, - sphinx_key: encryption::PublicKey::from_base58_string( - &self.mix_info.node_info.sphinx_key, - )?, - layer: self.mix_info.layer, - version: self.mix_info.node_info.version.clone(), - }) - } -} diff --git a/common/client-libs/validator-client/src/models/mod.rs b/common/client-libs/validator-client/src/models/mod.rs deleted file mode 100644 index f6076854b1..0000000000 --- a/common/client-libs/validator-client/src/models/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub mod gateway; -pub mod mixmining; -pub mod mixnode; -mod node; -pub mod topology; -pub mod validators; diff --git a/common/client-libs/validator-client/src/models/node.rs b/common/client-libs/validator-client/src/models/node.rs deleted file mode 100644 index 78da3ec92a..0000000000 --- a/common/client-libs/validator-client/src/models/node.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] -#[serde(rename_all = "camelCase")] -pub(crate) struct NodeInfo { - pub(crate) mix_host: String, - pub(crate) identity_key: String, - pub(crate) sphinx_key: String, - pub(crate) version: String, - pub(crate) location: String, - pub(crate) incentives_address: String, -} diff --git a/common/client-libs/validator-client/src/models/topology.rs b/common/client-libs/validator-client/src/models/topology.rs deleted file mode 100644 index f061fcc33f..0000000000 --- a/common/client-libs/validator-client/src/models/topology.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::models::gateway::RegisteredGateway; -use crate::models::mixnode::RegisteredMix; -use log::*; -use serde::{Deserialize, Serialize}; -use std::convert::TryInto; -use topology::{MixLayer, NymTopology}; - -// Topology shows us the current state of the overall Nym network -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct Topology { - pub mix_nodes: Vec, - pub gateways: Vec, -} - -// changed from `TryInto`. reason being is that we should not fail entire topology -// conversion if there's one invalid node on the network screwing around -impl From for NymTopology { - fn from(topology: Topology) -> Self { - use std::collections::HashMap; - - let mut mixes = HashMap::new(); - for mix in topology.mix_nodes.into_iter() { - let layer = mix.mix_info.layer as MixLayer; - if layer == 0 || layer > 3 { - warn!( - "{} says it's on invalid layer {}!", - mix.mix_info.node_info.identity_key, layer - ); - continue; - } - let mix_id = mix.mix_info.node_info.identity_key.clone(); - - let layer_entry = mixes.entry(layer).or_insert_with(Vec::new); - match mix.try_into() { - Ok(mix) => layer_entry.push(mix), - Err(err) => { - warn!("Mix {} is malformed - {:?}", mix_id, err); - continue; - } - } - } - - let mut gateways = Vec::with_capacity(topology.gateways.len()); - for gate in topology.gateways.into_iter() { - let gate_id = gate.gateway_info.node_info.identity_key.clone(); - match gate.try_into() { - Ok(gate) => gateways.push(gate), - Err(err) => { - warn!("Gateway {} is malformed - {:?}", gate_id, err); - continue; - } - } - } - - NymTopology::new(mixes, gateways) - } -} diff --git a/common/client-libs/validator-client/src/models/validators.rs b/common/client-libs/validator-client/src/models/validators.rs deleted file mode 100644 index 511ace0d19..0000000000 --- a/common/client-libs/validator-client/src/models/validators.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ValidatorsOutput { - pub(crate) block_height: i64, - pub(crate) validators: Vec, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub(crate) struct ValidatorOutput { - pub(crate) address: String, - pub(crate) pub_key: String, - pub(crate) proposer_priority: i64, - pub(crate) voting_power: i64, -} diff --git a/common/client-libs/validator-client/src/rest_requests/active_topology_get.rs b/common/client-libs/validator-client/src/rest_requests/active_topology_get.rs deleted file mode 100644 index 4f0281394d..0000000000 --- a/common/client-libs/validator-client/src/rest_requests/active_topology_get.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::models::topology::Topology; -use crate::rest_requests::{PathParam, QueryParam, RestRequest, RestRequestError}; -use crate::ErrorResponses; -use reqwest::{Method, Url}; -use serde::Deserialize; - -pub struct Request { - url: Url, -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase", untagged)] -pub(crate) enum Response { - Ok(Topology), - Error(ErrorResponses), -} - -impl RestRequest for Request { - const METHOD: Method = Method::GET; - const RELATIVE_PATH: &'static str = "/api/mixmining/topology/active"; - - type JsonPayload = (); - type ExpectedJsonResponse = Response; - - fn new( - base_url: &str, - _: Option>, - _: Option>, - _: Option, - ) -> Result { - let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) - .map_err(|err| RestRequestError::MalformedUrl(err.to_string()))?; - - Ok(Request { url }) - } - - fn url(&self) -> &Url { - &self.url - } -} diff --git a/common/client-libs/validator-client/src/rest_requests/gateway_register_post.rs b/common/client-libs/validator-client/src/rest_requests/gateway_register_post.rs deleted file mode 100644 index d2894f13c8..0000000000 --- a/common/client-libs/validator-client/src/rest_requests/gateway_register_post.rs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::models::gateway::GatewayRegistrationInfo; -use crate::rest_requests::{PathParam, QueryParam, RestRequest, RestRequestError}; -use crate::DefaultRestResponse; -use reqwest::{Method, Url}; - -pub struct Request { - url: Url, - payload: GatewayRegistrationInfo, -} - -impl RestRequest for Request { - const METHOD: Method = Method::POST; - const RELATIVE_PATH: &'static str = "/api/mixmining/register/gateway"; - - type JsonPayload = GatewayRegistrationInfo; - type ExpectedJsonResponse = DefaultRestResponse; - - fn new( - base_url: &str, - _: Option>, - _: Option>, - body_payload: Option, - ) -> Result { - let payload = body_payload.ok_or(RestRequestError::NoPayloadProvided)?; - let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) - .map_err(|err| RestRequestError::MalformedUrl(err.to_string()))?; - - Ok(Request { url, payload }) - } - - fn url(&self) -> &Url { - &self.url - } - - fn json_payload(&self) -> Option<&Self::JsonPayload> { - Some(&self.payload) - } -} diff --git a/common/client-libs/validator-client/src/rest_requests/mix_mining_batch_status_post.rs b/common/client-libs/validator-client/src/rest_requests/mix_mining_batch_status_post.rs deleted file mode 100644 index 78339544c9..0000000000 --- a/common/client-libs/validator-client/src/rest_requests/mix_mining_batch_status_post.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::models::mixmining::BatchMixStatus; -use crate::rest_requests::{PathParam, QueryParam, RestRequest, RestRequestError}; -use crate::DefaultRestResponse; -use reqwest::{Method, Url}; - -pub struct Request { - url: Url, - payload: BatchMixStatus, -} - -impl RestRequest for Request { - const METHOD: Method = Method::POST; - const RELATIVE_PATH: &'static str = "/api/mixmining/batch"; - type JsonPayload = BatchMixStatus; - type ExpectedJsonResponse = DefaultRestResponse; - - fn new( - base_url: &str, - _: Option>, - _: Option>, - body_payload: Option, - ) -> Result { - let payload = body_payload.ok_or(RestRequestError::NoPayloadProvided)?; - let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) - .map_err(|err| RestRequestError::MalformedUrl(err.to_string()))?; - Ok(Request { url, payload }) - } - - fn url(&self) -> &Url { - &self.url - } - - fn json_payload(&self) -> Option<&Self::JsonPayload> { - Some(&self.payload) - } -} - -#[cfg(test)] -mod batch_mix_status_post_request { - use super::*; - use crate::client_test_fixture; - use mockito::mock; - - #[cfg(test)] - mod on_a_400_status { - use super::*; - - #[tokio::test] - async fn it_returns_an_error() { - let _m = mock("POST", Request::RELATIVE_PATH) - .with_status(400) - .create(); - let client = client_test_fixture(&mockito::server_url()); - let result = client - .post_batch_mixmining_status(fixtures::new_status()) - .await; - assert!(result.is_err()); - _m.assert(); - } - } - - #[cfg(test)] - mod on_a_201 { - use super::*; - - #[tokio::test] - async fn it_returns_a_response_with_201() { - let json = r#"{ - "ok": true - }"#; - let _m = mock("POST", "/api/mixmining/batch") - .with_status(201) - .with_body(json) - .create(); - let client = client_test_fixture(&mockito::server_url()); - let result = client - .post_batch_mixmining_status(fixtures::new_status()) - .await; - assert!(result.is_ok()); - _m.assert(); - } - } - - #[cfg(test)] - mod fixtures { - use crate::models::mixmining::{BatchMixStatus, MixStatus}; - - pub fn new_status() -> BatchMixStatus { - BatchMixStatus { - status: vec![MixStatus { - pub_key: "abc".to_string(), - ip_version: "4".to_string(), - up: true, - }], - } - } - } -} diff --git a/common/client-libs/validator-client/src/rest_requests/mix_mining_status_post.rs b/common/client-libs/validator-client/src/rest_requests/mix_mining_status_post.rs deleted file mode 100644 index 23aaaed40d..0000000000 --- a/common/client-libs/validator-client/src/rest_requests/mix_mining_status_post.rs +++ /dev/null @@ -1,91 +0,0 @@ -use crate::models::mixmining::MixStatus; -use crate::rest_requests::{PathParam, QueryParam, RestRequest, RestRequestError}; -use crate::DefaultRestResponse; -use reqwest::{Method, Url}; - -pub struct Request { - url: Url, - payload: MixStatus, -} - -impl RestRequest for Request { - const METHOD: Method = Method::POST; - const RELATIVE_PATH: &'static str = "/api/mixmining"; - type JsonPayload = MixStatus; - type ExpectedJsonResponse = DefaultRestResponse; - - fn new( - base_url: &str, - _: Option>, - _: Option>, - body_payload: Option, - ) -> Result { - let payload = body_payload.ok_or(RestRequestError::NoPayloadProvided)?; - let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) - .map_err(|err| RestRequestError::MalformedUrl(err.to_string()))?; - Ok(Request { url, payload }) - } - - fn url(&self) -> &Url { - &self.url - } - - fn json_payload(&self) -> Option<&Self::JsonPayload> { - Some(&self.payload) - } -} - -#[cfg(test)] -mod mix_status_post_request { - use super::*; - use crate::client_test_fixture; - use mockito::mock; - - #[cfg(test)] - mod on_a_400_status { - use super::*; - - #[tokio::test] - async fn it_returns_an_error() { - let _m = mock("POST", Request::RELATIVE_PATH) - .with_status(400) - .create(); - let client = client_test_fixture(&mockito::server_url()); - let result = client.post_mixmining_status(fixtures::new_status()).await; - assert!(result.is_err()); - _m.assert(); - } - } - - #[cfg(test)] - mod on_a_201 { - use super::*; - #[tokio::test] - async fn it_returns_a_response_with_201() { - let json = r#"{ - "ok": true - }"#; - let _m = mock("POST", Request::RELATIVE_PATH) - .with_status(201) - .with_body(json) - .create(); - let client = client_test_fixture(&mockito::server_url()); - let result = client.post_mixmining_status(fixtures::new_status()).await; - assert!(result.is_ok()); - _m.assert(); - } - } - - #[cfg(test)] - mod fixtures { - use crate::models::mixmining::MixStatus; - - pub fn new_status() -> MixStatus { - MixStatus { - pub_key: "abc".to_string(), - ip_version: "4".to_string(), - up: true, - } - } - } -} diff --git a/common/client-libs/validator-client/src/rest_requests/mix_register_post.rs b/common/client-libs/validator-client/src/rest_requests/mix_register_post.rs deleted file mode 100644 index df30f91344..0000000000 --- a/common/client-libs/validator-client/src/rest_requests/mix_register_post.rs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::models::mixnode::MixRegistrationInfo; -use crate::rest_requests::{PathParam, QueryParam, RestRequest, RestRequestError}; -use crate::DefaultRestResponse; -use reqwest::{Method, Url}; - -pub struct Request { - url: Url, - payload: MixRegistrationInfo, -} - -impl RestRequest for Request { - const METHOD: Method = Method::POST; - const RELATIVE_PATH: &'static str = "/api/mixmining/register/mix"; - - type JsonPayload = MixRegistrationInfo; - type ExpectedJsonResponse = DefaultRestResponse; - - fn new( - base_url: &str, - _: Option>, - _: Option>, - body_payload: Option, - ) -> Result { - let payload = body_payload.ok_or(RestRequestError::NoPayloadProvided)?; - let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) - .map_err(|err| RestRequestError::MalformedUrl(err.to_string()))?; - - Ok(Request { url, payload }) - } - - fn url(&self) -> &Url { - &self.url - } - - fn json_payload(&self) -> Option<&Self::JsonPayload> { - Some(&self.payload) - } -} diff --git a/common/client-libs/validator-client/src/rest_requests/mod.rs b/common/client-libs/validator-client/src/rest_requests/mod.rs deleted file mode 100644 index a76f4b83bb..0000000000 --- a/common/client-libs/validator-client/src/rest_requests/mod.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use reqwest::{Method, Url}; -use serde::{de::DeserializeOwned, Serialize}; -use std::fmt::{self, Display, Formatter}; - -pub(crate) use active_topology_get::{ - Request as ActiveTopologyGet, Response as ActiveTopologyGetResponse, -}; -pub(crate) use gateway_register_post::Request as GatewayRegisterPost; -pub(crate) use mix_mining_batch_status_post::Request as BatchMixStatusPost; -pub(crate) use mix_mining_status_post::Request as MixStatusPost; -pub(crate) use mix_register_post::Request as MixRegisterPost; -pub(crate) use node_unregister_delete::Request as NodeUnregisterDelete; -pub(crate) use set_reputation_patch::Request as ReputationPatch; -pub(crate) use topology_get::{Request as TopologyGet, Response as TopologyGetResponse}; - -pub mod active_topology_get; -pub mod gateway_register_post; -pub mod mix_mining_batch_status_post; -pub mod mix_mining_status_post; -pub mod mix_register_post; -pub mod node_unregister_delete; -pub mod set_reputation_patch; -pub mod topology_get; - -type PathParam<'a> = &'a str; -type QueryParam<'a> = (&'a str, &'a str); - -#[derive(Debug)] -pub enum RestRequestError { - InvalidPathParams, - InvalidQueryParams, - NoPayloadProvided, - MalformedUrl(String), -} - -impl Display for RestRequestError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - RestRequestError::InvalidPathParams => write!( - f, - "invalid number of path parameters was provided or they were malformed" - ), - RestRequestError::InvalidQueryParams => write!( - f, - "invalid number of query parameters was provided or they were malformed" - ), - RestRequestError::NoPayloadProvided => { - write!(f, "no request payload was provided while it was expected") - } - RestRequestError::MalformedUrl(url) => { - write!(f, "tried to make request to malformed url ({})", url) - } - } - } -} - -pub(crate) trait RestRequest { - // 'GET', 'POST', 'DELETE', etc. - const METHOD: Method; - const RELATIVE_PATH: &'static str; - - type JsonPayload: Serialize + Sized; - type ExpectedJsonResponse: DeserializeOwned + Sized; - - fn new( - base_url: &str, - path_params: Option>, - query_params: Option>, - body_payload: Option, - ) -> Result - where - Self: Sized; - - fn url(&self) -> &Url; - - fn json_payload(&self) -> Option<&Self::JsonPayload> { - None - } - - fn query_param_keys() -> Vec<&'static str> { - Vec::new() - } -} diff --git a/common/client-libs/validator-client/src/rest_requests/node_unregister_delete.rs b/common/client-libs/validator-client/src/rest_requests/node_unregister_delete.rs deleted file mode 100644 index e0abd28d41..0000000000 --- a/common/client-libs/validator-client/src/rest_requests/node_unregister_delete.rs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::rest_requests::{PathParam, QueryParam, RestRequest, RestRequestError}; -use crate::DefaultRestResponse; -use reqwest::{Method, Url}; - -pub struct Request { - url: Url, -} - -impl RestRequest for Request { - const METHOD: Method = Method::DELETE; - const RELATIVE_PATH: &'static str = "/api/mixmining/register"; - type JsonPayload = (); - type ExpectedJsonResponse = DefaultRestResponse; - - fn new( - base_url: &str, - path_params: Option>, - _: Option>, - _: Option, - ) -> Result { - // node unregister requires single path param - the node id - let path_params = path_params.ok_or(RestRequestError::InvalidPathParams)?; - if path_params.len() != 1 { - return Err(RestRequestError::InvalidPathParams); - } - // /api/mixmining/register/{id} - let base = format!("{}{}/{}", base_url, Self::RELATIVE_PATH, path_params[0]); - - let url = - Url::parse(&base).map_err(|err| RestRequestError::MalformedUrl(err.to_string()))?; - - Ok(Request { url }) - } - - fn url(&self) -> &Url { - &self.url - } -} diff --git a/common/client-libs/validator-client/src/rest_requests/set_reputation_patch.rs b/common/client-libs/validator-client/src/rest_requests/set_reputation_patch.rs deleted file mode 100644 index b97d870859..0000000000 --- a/common/client-libs/validator-client/src/rest_requests/set_reputation_patch.rs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::rest_requests::{PathParam, QueryParam, RestRequest, RestRequestError}; -use crate::DefaultRestResponse; -use reqwest::{Method, Url}; - -pub struct Request { - url: Url, -} - -impl RestRequest for Request { - const METHOD: Method = Method::PATCH; - const RELATIVE_PATH: &'static str = "/api/mixmining/reputation"; - type JsonPayload = (); - type ExpectedJsonResponse = DefaultRestResponse; - - fn new( - base_url: &str, - path_params: Option>, - query_params: Option>, - _: Option, - ) -> Result { - // set reputation requires single path param - the node id - // and single query param - what reputation should it be set to - let path_params = path_params.ok_or(RestRequestError::InvalidPathParams)?; - if path_params.len() != 1 { - return Err(RestRequestError::InvalidPathParams); - } - - let query_params = query_params.ok_or(RestRequestError::InvalidQueryParams)?; - if query_params.len() != 1 { - return Err(RestRequestError::InvalidQueryParams); - } - - // /api/mixmining/reputation/{id} - let base = format!("{}{}/{}", base_url, Self::RELATIVE_PATH, path_params[0]); - - let url = Url::parse_with_params(&base, query_params) - .map_err(|err| RestRequestError::MalformedUrl(err.to_string()))?; - - Ok(Request { url }) - } - - fn url(&self) -> &Url { - &self.url - } - - fn query_param_keys() -> Vec<&'static str> { - vec!["reputation"] - } -} diff --git a/common/client-libs/validator-client/src/rest_requests/topology_get.rs b/common/client-libs/validator-client/src/rest_requests/topology_get.rs deleted file mode 100644 index b6bfb813c9..0000000000 --- a/common/client-libs/validator-client/src/rest_requests/topology_get.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2020 Nym Technologies SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::models::topology::Topology; -use crate::rest_requests::{PathParam, QueryParam, RestRequest, RestRequestError}; -use crate::ErrorResponses; -use reqwest::{Method, Url}; -use serde::Deserialize; - -pub struct Request { - url: Url, -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase", untagged)] -pub(crate) enum Response { - Ok(Topology), - Error(ErrorResponses), -} - -impl RestRequest for Request { - const METHOD: Method = Method::GET; - const RELATIVE_PATH: &'static str = "/api/mixmining/topology"; - - type JsonPayload = (); - type ExpectedJsonResponse = Response; - - fn new( - base_url: &str, - _: Option>, - _: Option>, - _: Option, - ) -> Result { - let url = Url::parse(&format!("{}{}", base_url, Self::RELATIVE_PATH)) - .map_err(|err| RestRequestError::MalformedUrl(err.to_string()))?; - - Ok(Request { url }) - } - - fn url(&self) -> &Url { - &self.url - } -} diff --git a/common/client-libs/validator-client-rest/src/serde_helpers.rs b/common/client-libs/validator-client/src/serde_helpers.rs similarity index 100% rename from common/client-libs/validator-client-rest/src/serde_helpers.rs rename to common/client-libs/validator-client/src/serde_helpers.rs diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml index 02c071cdef..134817f973 100644 --- a/common/mixnode-common/Cargo.toml +++ b/common/mixnode-common/Cargo.toml @@ -24,5 +24,5 @@ rand = "0.8" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.4", features = ["time", "macros", "rt", "net", "io-util"] } tokio-util = { version = "0.6", features = ["codec"] } -validator-client-rest = { path = "../client-libs/validator-client-rest" } +validator-client = { path = "../client-libs/validator-client" } version-checker = { path = "../version-checker" } diff --git a/common/mixnode-common/src/rtt_measurement/mod.rs b/common/mixnode-common/src/rtt_measurement/mod.rs index d5b5b4e2c9..0bd82d9b4d 100644 --- a/common/mixnode-common/src/rtt_measurement/mod.rs +++ b/common/mixnode-common/src/rtt_measurement/mod.rs @@ -182,7 +182,7 @@ pub struct RttMeasurer { // It only does bunch of REST queries. If we update it at some point to a more sophisticated (maybe signing) client, // then it definitely cannot be constructed here and probably will need to be passed from outside, // as mixnodes/gateways would already be using an instance of said client. - validator_client: validator_client_rest::Client, + validator_client: validator_client::Client, results: AtomicVerlocResult, } @@ -204,12 +204,10 @@ impl RttMeasurer { config.listening_address, Arc::clone(&identity), )), - validator_client: validator_client_rest::Client::new( - validator_client_rest::Config::new( - config.validator_urls.clone(), - config.mixnet_contract_address.clone(), - ), - ), + validator_client: validator_client::Client::new(validator_client::Config::new( + config.validator_urls.clone(), + config.mixnet_contract_address.clone(), + )), config, results: AtomicVerlocResult::new(), } diff --git a/common/topology/src/gateway.rs b/common/topology/src/gateway.rs index a7acfdfcf9..f358f90d49 100644 --- a/common/topology/src/gateway.rs +++ b/common/topology/src/gateway.rs @@ -26,7 +26,7 @@ use std::net::SocketAddr; pub enum GatewayConversionError { InvalidIdentityKey(identity::KeyRecoveryError), InvalidSphinxKey(encryption::KeyRecoveryError), - InvalidAddress(io::Error), + InvalidAddress(String, io::Error), InvalidStake, Other(Box), } @@ -56,11 +56,11 @@ impl Display for GatewayConversionError { "failed to convert gateway due to invalid sphinx key - {}", err ), - GatewayConversionError::InvalidAddress(err) => { + GatewayConversionError::InvalidAddress(address, err) => { write!( f, - "failed to convert gateway due to invalid address - {}", - err + "failed to convert gateway due to invalid address {} - {}", + address, err ) } GatewayConversionError::InvalidStake => { @@ -129,10 +129,9 @@ impl<'a> TryFrom<&'a GatewayBond> for Node { .unwrap_or(0), location: bond.gateway.location.clone(), client_listener: bond.gateway.clients_host.clone(), - mixnet_listener: bond - .gateway - .try_resolve_hostname() - .map_err(GatewayConversionError::InvalidAddress)?, + mixnet_listener: bond.gateway.try_resolve_hostname().map_err(|err| { + GatewayConversionError::InvalidAddress(bond.gateway.mix_host.clone(), err) + })?, 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/mix.rs b/common/topology/src/mix.rs index 4d741a9ca9..058794a176 100644 --- a/common/topology/src/mix.rs +++ b/common/topology/src/mix.rs @@ -26,7 +26,7 @@ use std::net::SocketAddr; pub enum MixnodeConversionError { InvalidIdentityKey(identity::KeyRecoveryError), InvalidSphinxKey(encryption::KeyRecoveryError), - InvalidAddress(io::Error), + InvalidAddress(String, io::Error), InvalidStake, Other(Box), } @@ -56,11 +56,11 @@ impl Display for MixnodeConversionError { "failed to convert mixnode due to invalid sphinx key - {}", err ), - MixnodeConversionError::InvalidAddress(err) => { + MixnodeConversionError::InvalidAddress(address, err) => { write!( f, - "failed to convert mixnode due to invalid address - {}", - err + "failed to convert mixnode due to invalid address {} - {}", + address, err ) } MixnodeConversionError::InvalidStake => { @@ -120,10 +120,9 @@ 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(MixnodeConversionError::InvalidAddress)?, + host: bond.mix_node.try_resolve_hostname().map_err(|err| { + MixnodeConversionError::InvalidAddress(bond.mix_node.host.clone(), err) + })?, 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/gateway/Cargo.toml b/gateway/Cargo.toml index c09a21d361..e25d8aa210 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -34,5 +34,5 @@ mixnet-client = { path = "../common/client-libs/mixnet-client" } mixnode-common = { path = "../common/mixnode-common" } nymsphinx = { path = "../common/nymsphinx" } pemstore = { path = "../common/pemstore" } -validator-client-rest = { path = "../common/client-libs/validator-client-rest" } +validator-client = { path = "../common/client-libs/validator-client" } version-checker = { path = "../common/version-checker" } diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index 73c96d3d3e..9d4793e4d4 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -130,11 +130,11 @@ impl Gateway { 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_rest::Config::new( + let validator_client_config = validator_client::Config::new( self.config.get_validator_rest_endpoints(), self.config.get_validator_mixnet_contract_address(), ); - let mut validator_client = validator_client_rest::Client::new(validator_client_config); + let mut validator_client = validator_client::Client::new(validator_client_config); let existing_gateways = match validator_client.get_gateways().await { Ok(gateways) => gateways, diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 055ae6824b..8d894ee148 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -36,5 +36,5 @@ nonexhaustive-delayqueue = { path = "../common/nonexhaustive-delayqueue" } nymsphinx = { path = "../common/nymsphinx" } pemstore = { path = "../common/pemstore" } topology = { path = "../common/topology" } -validator-client-rest = { path = "../common/client-libs/validator-client-rest" } +validator-client = { path = "../common/client-libs/validator-client" } version-checker = { path = "../common/version-checker" } diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index 8643c23ebe..a8cb910c75 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -90,9 +90,8 @@ async fn choose_layer( } } - let 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 validator_client_config = validator_client::Config::new(validator_servers, mixnet_contract); + let mut validator_client = validator_client::Client::new(validator_client_config); let mixnodes = validator_client .get_mix_nodes() diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index f42f066387..01f1cc9320 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -153,11 +153,11 @@ 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( + let validator_client_config = validator_client::Config::new( self.config.get_validator_rest_endpoints(), self.config.get_validator_mixnet_contract_address(), ); - let mut validator_client = validator_client_rest::Client::new(validator_client_config); + let mut validator_client = validator_client::Client::new(validator_client_config); let existing_nodes = match validator_client.get_mix_nodes().await { Ok(nodes) => nodes, diff --git a/network-monitor/Cargo.toml b/network-monitor/Cargo.toml index dcd8d103d8..046a3b4a40 100644 --- a/network-monitor/Cargo.toml +++ b/network-monitor/Cargo.toml @@ -17,6 +17,7 @@ log = "0.4" pin-project = "1.0" pretty_env_logger = "0.4" rand = "0.7" +reqwest = { version = "0.11", features = ["json"] } serde = "1.0" serde_json = "1.0" tokio = { version = "1.4", features = ["rt-multi-thread", "macros", "signal", "time"] } @@ -25,6 +26,7 @@ tokio = { version = "1.4", features = ["rt-multi-thread", "macros", "signal", "t crypto = { path = "../common/crypto" } gateway-client = { path = "../common/client-libs/gateway-client" } gateway-requests = { path = "../gateway/gateway-requests" } +mixnet-contract = { path = "../common/mixnet-contract" } nymsphinx = { path = "../common/nymsphinx" } topology = { path = "../common/topology" } validator-client = { path = "../common/client-libs/validator-client" } diff --git a/network-monitor/src/main.rs b/network-monitor/src/main.rs index 84c0f52506..1813ebecfa 100644 --- a/network-monitor/src/main.rs +++ b/network-monitor/src/main.rs @@ -23,32 +23,36 @@ use topology::NymTopology; mod chunker; pub(crate) mod gateways_reader; -// mod mixnet_receiver; mod monitor; -// mod monitor_old; -// mod notifications; -// mod packet_sender; -// mod run_info; +mod node_status_api; mod test_packet; mod tested_network; const V4_TOPOLOGY_ARG: &str = "v4-topology-filepath"; const V6_TOPOLOGY_ARG: &str = "v6-topology-filepath"; -const VALIDATOR_ARG: &str = "validator"; +const VALIDATORS_ARG: &str = "validators"; +const NODE_STATUS_API_ARG: &str = "node-status-api"; const DETAILED_REPORT_ARG: &str = "detailed-report"; const GATEWAY_SENDING_RATE_ARG: &str = "gateway-rate"; +const MIXNET_CONTRACT_ARG: &str = "mixnet-contract"; -const DEFAULT_VALIDATOR: &str = "http://testnet-validator1.nymtech.net:8081"; +const DEFAULT_VALIDATORS: &[&str] = &[ + // "http://testnet-finney-validator.nymtech.net:1317", + "http://testnet-finney-validator2.nymtech.net:1317", + "http://mixnet.club:1317", +]; + +const DEFAULT_NODE_STATUS_API: &str = "http://localhost:8081"; const DEFAULT_GATEWAY_SENDING_RATE: usize = 500; +const DEFAULT_MIXNET_CONTRACT: &str = "hal1k0jntykt7e4g3y88ltc60czgjuqdy4c9c6gv94"; -// TODO: use this before making PR pub(crate) const TIME_CHUNK_SIZE: Duration = Duration::from_millis(50); - pub(crate) const PENALISE_OUTDATED: bool = false; // TODO: let's see how it goes and whether those new adjusting const MAX_CONCURRENT_GATEWAY_CLIENTS: Option = Some(50); const GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500); +pub(crate) const GATEWAY_CONNECTION_TIMEOUT: Duration = Duration::from_millis(2_500); fn parse_args<'a>() -> ArgMatches<'a> { App::new("Nym Network Monitor") @@ -68,16 +72,26 @@ fn parse_args<'a>() -> ArgMatches<'a> { .required(true), ) .arg( - Arg::with_name(VALIDATOR_ARG) + Arg::with_name(VALIDATORS_ARG) .help("REST endpoint of the validator the monitor will grab nodes to test") - .long(VALIDATOR_ARG) + .long(VALIDATORS_ARG) + .takes_value(true) + ) + .arg(Arg::with_name("mixnet-contract") + .long(MIXNET_CONTRACT_ARG) + .help("Address of the validator contract managing the network") + .takes_value(true), + ) + .arg( + Arg::with_name(NODE_STATUS_API_ARG) + .help("Address of the node status api to submit results to. Most likely it's a local address") + .long(NODE_STATUS_API_ARG) .takes_value(true) ) .arg( Arg::with_name(DETAILED_REPORT_ARG) .help("specifies whether a detailed report should be printed after each run") .long(DETAILED_REPORT_ARG) - , ) .arg(Arg::with_name(GATEWAY_SENDING_RATE_ARG) .help("specifies maximum rate (in packets per second) of test packets being sent to gateway") @@ -98,7 +112,24 @@ async fn main() { let v4_topology = parse_topology_file(v4_topology_path); let v6_topology = parse_topology_file(v6_topology_path); - let validator_rest_uri = matches.value_of(VALIDATOR_ARG).unwrap_or(DEFAULT_VALIDATOR); + let validators_rest_uris_borrowed = matches + .values_of(VALIDATORS_ARG) + .map(|args| args.collect::>()) + .unwrap_or_else(|| DEFAULT_VALIDATORS.to_vec()); + + let validators_rest_uris = validators_rest_uris_borrowed + .into_iter() + .map(|uri| uri.to_string()) + .collect::>(); + + let node_status_api_uri = matches + .value_of(NODE_STATUS_API_ARG) + .unwrap_or(DEFAULT_NODE_STATUS_API); + + let mixnet_contract = matches + .value_of(MIXNET_CONTRACT_ARG) + .unwrap_or(DEFAULT_MIXNET_CONTRACT); + let detailed_report = matches.is_present(DETAILED_REPORT_ARG); let sending_rate = matches .value_of(GATEWAY_SENDING_RATE_ARG) @@ -108,7 +139,11 @@ async fn main() { check_if_up_to_date(&v4_topology, &v6_topology); setup_logging(); - println!("* validator server: {}", validator_rest_uri); + println!("* validator servers: {:?}", validators_rest_uris); + println!("* node status api server: {}", node_status_api_uri); + println!("* mixnet contract: {}", mixnet_contract); + println!("* detailed report printing: {}", detailed_report); + println!("* gateway sending rate: {} packets/s", sending_rate); // TODO: in the future I guess this should somehow change to distribute the load let tested_mix_gateway = v4_topology.gateways()[0].clone(); @@ -132,19 +167,21 @@ async fn main() { ); let tested_network = TestedNetwork::new_good(v4_topology, v6_topology); - let validator_client = new_validator_client(validator_rest_uri); + let validator_client = new_validator_client(validators_rest_uris, mixnet_contract); + let node_status_api_client = new_node_status_api_client(node_status_api_uri); let (gateway_status_update_sender, gateway_status_update_receiver) = mpsc::unbounded(); let (received_processor_sender_channel, received_processor_receiver_channel) = mpsc::unbounded(); let packet_preparer = new_packet_preparer( - Arc::clone(&validator_client), - tested_network, + validator_client, + tested_network.clone(), test_mixnode_sender, *identity_keypair.public_key(), *encryption_keypair.public_key(), ); + let packet_sender = new_packet_sender( gateway_status_update_sender, Arc::clone(&identity_keypair), @@ -165,7 +202,8 @@ async fn main() { packet_sender, received_processor, summary_producer, - validator_client, + node_status_api_client, + tested_network, ); tokio::spawn(async move { packet_receiver.run().await }); @@ -186,7 +224,7 @@ async fn wait_for_interrupt() { } fn new_packet_preparer( - validator_client: Arc, + validator_client: validator_client::Client, tested_network: TestedNetwork, test_mixnode_sender: Recipient, self_public_identity: identity::PublicKey, @@ -240,9 +278,17 @@ fn new_packet_receiver( PacketReceiver::new(gateways_status_updater, processor_packets_sender) } -fn new_validator_client(validator_rest_uri: &str) -> Arc { - let config = validator_client::Config::new(validator_rest_uri.to_string()); - Arc::new(validator_client::Client::new(config)) +fn new_validator_client( + validator_rest_uris: Vec, + mixnet_contract: &str, +) -> validator_client::Client { + let config = validator_client::Config::new(validator_rest_uris, mixnet_contract); + validator_client::Client::new(config) +} + +fn new_node_status_api_client>(base_url: S) -> node_status_api::Client { + let config = node_status_api::Config::new(base_url); + node_status_api::Client::new(config) } fn setup_logging() { diff --git a/network-monitor/src/monitor/mod.rs b/network-monitor/src/monitor/mod.rs index ff894235b4..991cc1d45d 100644 --- a/network-monitor/src/monitor/mod.rs +++ b/network-monitor/src/monitor/mod.rs @@ -1,15 +1,16 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::monitor::preparer::{PacketPreparer, PreparedPackets}; +use crate::monitor::preparer::{PacketPreparer, TestedNode}; use crate::monitor::processor::ReceivedProcessor; use crate::monitor::sender::PacketSender; -use crate::monitor::summary_producer::SummaryProducer; +use crate::monitor::summary_producer::{SummaryProducer, TestReport}; +use crate::node_status_api; +use crate::node_status_api::models::{BatchGatewayStatus, BatchMixStatus}; +use crate::test_packet::NodeType; +use crate::tested_network::TestedNetwork; use log::*; -use std::collections::HashSet; -use std::sync::Arc; -use tokio::time::{interval_at, sleep, Duration, Instant}; -use validator_client::models::mixmining::BatchMixStatus; +use tokio::time::{sleep, Duration}; pub(crate) mod preparer; pub(crate) mod processor; @@ -18,7 +19,7 @@ pub(crate) mod sender; pub(crate) mod summary_producer; const PACKET_DELIVERY_TIMEOUT: Duration = Duration::from_secs(20); -const MONITOR_RUN_INTERVAL: Duration = Duration::from_secs(60); +const MONITOR_RUN_INTERVAL: Duration = Duration::from_secs(2 * 60); pub(super) struct Monitor { nonce: u64, @@ -26,7 +27,8 @@ pub(super) struct Monitor { packet_sender: PacketSender, received_processor: ReceivedProcessor, summary_producer: SummaryProducer, - validator_client: Arc, + node_status_api_client: node_status_api::Client, + tested_network: TestedNetwork, } impl Monitor { @@ -35,7 +37,8 @@ impl Monitor { packet_sender: PacketSender, received_processor: ReceivedProcessor, summary_producer: SummaryProducer, - validator_client: Arc, + node_status_api_client: node_status_api::Client, + tested_network: TestedNetwork, ) -> Self { Monitor { nonce: 1, @@ -43,28 +46,93 @@ impl Monitor { packet_sender, received_processor, summary_producer, - validator_client, + node_status_api_client, + tested_network, } } // while it might have been cleaner to put this into a separate `Notifier` structure, // I don't see much point considering it's only a single, small, method - async fn notify_validator(&self, status: BatchMixStatus) { + async fn notify_node_status_api( + &self, + mix_status: BatchMixStatus, + gateway_status: BatchGatewayStatus, + ) { if let Err(err) = self - .validator_client - .post_batch_mixmining_status(status) + .node_status_api_client + .post_batch_mix_status(mix_status) .await { - warn!("Failed to send batch status to validator - {:?}", err) + warn!( + "Failed to send batch mix status to node status api - {:?}", + err + ) + } + + if let Err(err) = self + .node_status_api_client + .post_batch_gateway_status(gateway_status) + .await + { + warn!( + "Failed to send batch mix status to node status api - {:?}", + err + ) } } - fn all_run_gateways(&self, prepared_packets: &PreparedPackets) -> HashSet { - prepared_packets - .packets - .iter() - .map(|packets| packets.gateway_address().to_base58_string()) - .collect() + // checking it this way with a TestReport is rather suboptimal but given the fact we're only + // doing this fewer than 10 times, it's not that problematic + fn check_good_nodes_status(&self, report: &TestReport) -> bool { + for v4_mixes in self.tested_network.v4_topology().mixes().values() { + for v4_mix in v4_mixes { + let node = &TestedNode { + identity: v4_mix.identity_key.to_base58_string(), + owner: v4_mix.owner.clone(), + node_type: NodeType::Mixnode, + }; + if !report.fully_working_mixes.contains(node) { + return false; + } + } + } + + for v4_gateway in self.tested_network.v4_topology().gateways() { + let node = &TestedNode { + identity: v4_gateway.identity_key.to_base58_string(), + owner: v4_gateway.owner.clone(), + node_type: NodeType::Gateway, + }; + if !report.fully_working_gateways.contains(&node) { + return false; + } + } + + for v6_mixes in self.tested_network.v6_topology().mixes().values() { + for v6_mix in v6_mixes { + let node = &TestedNode { + identity: v6_mix.identity_key.to_base58_string(), + owner: v6_mix.owner.clone(), + node_type: NodeType::Mixnode, + }; + if !report.fully_working_mixes.contains(node) { + return false; + } + } + } + + for v6_gateway in self.tested_network.v6_topology().gateways() { + let node = &TestedNode { + identity: v6_gateway.identity_key.to_base58_string(), + owner: v6_gateway.owner.clone(), + node_type: NodeType::Gateway, + }; + if !report.fully_working_gateways.contains(&node) { + return false; + } + } + + true } async fn test_run(&mut self) { @@ -79,41 +147,46 @@ impl Monitor { return; } }; - let all_gateways = self.all_run_gateways(&prepared_packets); self.received_processor.set_new_expected(self.nonce).await; - debug!(target: "Monitor", "starting to send all the packets..."); + info!(target: "Monitor", "starting to send all the packets..."); self.packet_sender .send_packets(prepared_packets.packets) .await; - debug!(target: "Monitor", "sending is over, waiting for {:?} before checking what we received", PACKET_DELIVERY_TIMEOUT); + info!(target: "Monitor", "sending is over, waiting for {:?} before checking what we received", PACKET_DELIVERY_TIMEOUT); // give the packets some time to traverse the network sleep(PACKET_DELIVERY_TIMEOUT).await; let received = self.received_processor.return_received().await; - let batch_status = self.summary_producer.produce_summary( + let test_summary = self.summary_producer.produce_summary( prepared_packets.tested_nodes, received, prepared_packets.invalid_nodes, - all_gateways, ); - self.notify_validator(batch_status).await; + // our "good" nodes MUST be working correctly otherwise we cannot trust the results + if self.check_good_nodes_status(&test_summary.test_report) { + self.notify_node_status_api( + test_summary.batch_mix_status, + test_summary.batch_gateway_status, + ) + .await; + } else { + error!("our own 'good' nodes did not pass the check - we are not going to submit results to the node status API"); + } self.nonce += 1; } pub(crate) async fn run(&mut self) { - let mut interval = interval_at(Instant::now(), MONITOR_RUN_INTERVAL); loop { - // let run_deadline = delay_for(MONITOR_RUN_INTERVAL); - interval.tick().await; self.test_run().await; - // run_deadline.await; + info!(target: "Monitor", "Next test run will happen in {:?}", MONITOR_RUN_INTERVAL); + sleep(MONITOR_RUN_INTERVAL).await; } } } diff --git a/network-monitor/src/monitor/preparer.rs b/network-monitor/src/monitor/preparer.rs index 1cff486fa1..8905f5d8b5 100644 --- a/network-monitor/src/monitor/preparer.rs +++ b/network-monitor/src/monitor/preparer.rs @@ -3,49 +3,59 @@ use crate::chunker::Chunker; use crate::monitor::sender::GatewayPackets; -use crate::test_packet::TestPacket; +use crate::test_packet::{NodeType, TestPacket}; use crate::tested_network::TestedNetwork; use crypto::asymmetric::{encryption, identity}; use log::*; +use mixnet_contract::{GatewayBond, MixNodeBond}; use nymsphinx::addressing::clients::Recipient; use nymsphinx::forwarding::packet::MixPacket; use std::convert::TryInto; use std::fmt::{self, Display, Formatter}; -use std::sync::Arc; use topology::{gateway, mix}; -use validator_client::models::gateway::RegisteredGateway; -use validator_client::models::mixnode::RegisteredMix; -use validator_client::models::topology::Topology; use validator_client::ValidatorClientError; #[derive(Debug)] pub(super) enum PacketPreparerError { - ValidatorError(ValidatorClientError), + ValidatorClientError(ValidatorClientError), } // declared type aliases for easier code reasoning type Version = String; type Id = String; +type Owner = String; #[derive(Clone)] pub(crate) enum InvalidNode { - OutdatedMix(Id, Version), - MalformedMix(Id), - OutdatedGateway(Id, Version), - MalformedGateway(Id), + OutdatedMix(Id, Owner, Version), + MalformedMix(Id, Owner), + OutdatedGateway(Id, Owner, Version), + MalformedGateway(Id, Owner), } impl Display for InvalidNode { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - InvalidNode::OutdatedMix(id, version) => { - write!(f, "Mixnode {} (v {}) is outdated", id, version) + InvalidNode::OutdatedMix(id, owner, version) => { + write!( + f, + "Mixnode {} (v {}) owned by {} is outdated", + id, version, owner + ) } - InvalidNode::MalformedMix(id) => write!(f, "Mixnode {} is malformed", id), - InvalidNode::OutdatedGateway(id, version) => { - write!(f, "Gateway {} (v {}) is outdated", id, version) + InvalidNode::MalformedMix(id, owner) => { + write!(f, "Mixnode {} owner by {} is malformed", id, owner) + } + InvalidNode::OutdatedGateway(id, owner, version) => { + write!( + f, + "Gateway {} (v {}) owned by {} is outdated", + id, version, owner + ) + } + InvalidNode::MalformedGateway(id, owner) => { + write!(f, "Gateway {} owned by {} is malformed", id, owner) } - InvalidNode::MalformedGateway(id) => write!(f, "Gateway {} is malformed", id), } } } @@ -56,15 +66,74 @@ enum PreparedNode { Invalid(InvalidNode), } +#[derive(Eq, PartialEq, Debug, Hash, Clone)] +pub(crate) struct TestedNode { + pub(crate) identity: String, + pub(crate) owner: String, + pub(crate) node_type: NodeType, +} + +impl TestedNode { + pub(crate) fn new_mix(identity: String, owner: String) -> Self { + TestedNode { + identity, + owner, + node_type: NodeType::Mixnode, + } + } + + pub(crate) fn new_gateway(identity: String, owner: String) -> Self { + TestedNode { + identity, + owner, + node_type: NodeType::Gateway, + } + } + + pub(crate) fn from_raw_mix(identity: S1, owner: S2) -> Self + where + S1: Into, + S2: Into, + { + TestedNode { + identity: identity.into(), + owner: owner.into(), + node_type: NodeType::Mixnode, + } + } + + pub(crate) fn from_raw_gateway(identity: S1, owner: S2) -> Self + where + S1: Into, + S2: Into, + { + TestedNode { + identity: identity.into(), + owner: owner.into(), + node_type: NodeType::Gateway, + } + } + + pub(crate) fn is_gateway(&self) -> bool { + self.node_type == NodeType::Gateway + } +} + +impl Display for TestedNode { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{} (owned by {})", self.identity, self.owner) + } +} + pub(crate) struct PreparedPackets { /// All packets that are going to get sent during the test as well as the gateways through /// which they ought to be sent. pub(super) packets: Vec, - /// Vector containing list of public keys of all nodes (mixnodes and gateways) being tested. + /// Vector containing list of public keys and owners of all nodes (mixnodes and gateways) being tested. /// We do not need to specify the exact test parameters as for each of them we expect to receive /// two packets back: ipv4 and ipv6 regardless of which gateway they originate. - pub(super) tested_nodes: Vec, + pub(super) tested_nodes: Vec, /// All nodes that failed to get parsed correctly. They will be marked to the validator as being /// down on ipv4 and ipv6. @@ -73,7 +142,7 @@ pub(crate) struct PreparedPackets { pub(crate) struct PacketPreparer { chunker: Chunker, - validator_client: Arc, + validator_client: validator_client::Client, tested_network: TestedNetwork, // currently all test MIXNODE packets are sent via the same gateway @@ -86,7 +155,7 @@ pub(crate) struct PacketPreparer { impl PacketPreparer { pub(crate) fn new( - validator_client: Arc, + validator_client: validator_client::Client, tested_network: TestedNetwork, test_mixnode_sender: Recipient, self_public_identity: identity::PublicKey, @@ -102,11 +171,30 @@ impl PacketPreparer { } } - async fn get_network_topology(&self) -> Result { - self.validator_client - .get_topology() - .await - .map_err(PacketPreparerError::ValidatorError) + async fn get_network_nodes( + &mut self, + ) -> Result<(Vec, Vec), PacketPreparerError> { + info!(target: "Monitor", "Obtaining network topology..."); + + let mixnodes = match self.validator_client.get_mix_nodes().await { + Err(err) => { + error!("failed to get network mixnodes - {}", err); + return Err(PacketPreparerError::ValidatorClientError(err)); + } + Ok(mixes) => mixes, + }; + + let gateways = match self.validator_client.get_gateways().await { + Err(err) => { + error!("failed to get network gateways - {}", err); + return Err(PacketPreparerError::ValidatorClientError(err)); + } + Ok(gateways) => gateways, + }; + + info!(target: "Monitor", "Obtained network topology"); + + Ok((mixnodes, gateways)) } fn check_version_compatibility(&self, mix_version: &str) -> bool { @@ -135,76 +223,108 @@ impl PacketPreparer { } } - fn mix_into_prepared_node(&self, nonce: u64, registered_mix: &RegisteredMix) -> PreparedNode { - if !self.check_version_compatibility(registered_mix.version_ref()) { + fn mix_into_prepared_node(&self, nonce: u64, mixnode_bond: &MixNodeBond) -> PreparedNode { + if !self.check_version_compatibility(&mixnode_bond.mix_node().version) { return PreparedNode::Invalid(InvalidNode::OutdatedMix( - registered_mix.identity(), - registered_mix.version(), + mixnode_bond.mix_node().identity_key.clone(), + mixnode_bond.owner.to_string(), + mixnode_bond.mix_node().version.clone(), )); } - match TryInto::::try_into(registered_mix) { + match TryInto::::try_into(mixnode_bond) { Ok(mix) => { - let v4_packet = TestPacket::new_v4(mix.identity_key, nonce); - let v6_packet = TestPacket::new_v6(mix.identity_key, nonce); + let v4_packet = TestPacket::new_v4( + mix.identity_key, + mix.owner.clone(), + nonce, + NodeType::Mixnode, + ); + let v6_packet = TestPacket::new_v6( + mix.identity_key, + mix.owner.clone(), + nonce, + NodeType::Mixnode, + ); PreparedNode::TestedMix(mix, [v4_packet, v6_packet]) } Err(err) => { - warn!("mix {} is malformed - {:?}", registered_mix.identity(), err); - PreparedNode::Invalid(InvalidNode::MalformedMix(registered_mix.identity())) + warn!(target: "bad node", + "mix {} is malformed - {}", + mixnode_bond.mix_node().identity_key, + err + ); + PreparedNode::Invalid(InvalidNode::MalformedMix( + mixnode_bond.mix_node().identity_key.clone(), + mixnode_bond.owner.to_string(), + )) } } } - fn gateway_into_prepared_node( - &self, - nonce: u64, - registered_gateway: &RegisteredGateway, - ) -> PreparedNode { - if !self.check_version_compatibility(registered_gateway.version_ref()) { + fn gateway_into_prepared_node(&self, nonce: u64, gateway_bond: &GatewayBond) -> PreparedNode { + if !self.check_version_compatibility(&gateway_bond.gateway().version) { return PreparedNode::Invalid(InvalidNode::OutdatedGateway( - registered_gateway.identity(), - registered_gateway.version(), + gateway_bond.gateway().identity_key.clone(), + gateway_bond.owner.to_string(), + gateway_bond.gateway().version.clone(), )); } - match TryInto::::try_into(registered_gateway) { + match TryInto::::try_into(gateway_bond) { Ok(gateway) => { - let v4_packet = TestPacket::new_v4(gateway.identity_key, nonce); - let v6_packet = TestPacket::new_v6(gateway.identity_key, nonce); + let v4_packet = TestPacket::new_v4( + gateway.identity_key, + gateway.owner.clone(), + nonce, + NodeType::Gateway, + ); + let v6_packet = TestPacket::new_v6( + gateway.identity_key, + gateway.owner.clone(), + nonce, + NodeType::Gateway, + ); PreparedNode::TestedGateway(gateway, [v4_packet, v6_packet]) } Err(err) => { - warn!( - "gateway {} is malformed - {:?}", - registered_gateway.identity(), + warn!(target: "bad node", + "gateway {} is malformed - {:?}", + gateway_bond.gateway().identity_key, err ); - PreparedNode::Invalid(InvalidNode::MalformedGateway(registered_gateway.identity())) + PreparedNode::Invalid(InvalidNode::MalformedGateway( + gateway_bond.gateway().identity_key.clone(), + gateway_bond.owner.to_string(), + )) } } } - fn prepare_mixnodes(&self, nonce: u64, topology: &Topology) -> Vec { - topology - .mix_nodes + fn prepare_mixnodes(&self, nonce: u64, nodes: &[MixNodeBond]) -> Vec { + nodes .iter() .map(|mix| self.mix_into_prepared_node(nonce, mix)) .collect() } - fn prepare_gateways(&self, nonce: u64, topology: &Topology) -> Vec { - topology - .gateways + fn prepare_gateways(&self, nonce: u64, nodes: &[GatewayBond]) -> Vec { + nodes .iter() .map(|gateway| self.gateway_into_prepared_node(nonce, gateway)) .collect() } - fn tested_nodes(&self, nodes: &[PreparedNode]) -> Vec { + fn tested_nodes(&self, nodes: &[PreparedNode]) -> Vec { nodes .iter() .filter_map(|node| match node { - PreparedNode::TestedGateway(gateway, _) => Some(gateway.identity_key), - PreparedNode::TestedMix(mix, _) => Some(mix.identity_key), + PreparedNode::TestedGateway(gateway, _) => Some(TestedNode::new_gateway( + gateway.identity_key.to_base58_string(), + gateway.owner.clone(), + )), + PreparedNode::TestedMix(mix, _) => Some(TestedNode::new_mix( + mix.identity_key.to_base58_string(), + mix.owner.clone(), + )), PreparedNode::Invalid(..) => None, }) .collect() @@ -306,11 +426,11 @@ impl PacketPreparer { &mut self, nonce: u64, ) -> Result { - let topology = self.get_network_topology().await?; + let (mixnode_bonds, gateway_bonds) = self.get_network_nodes().await?; let mut invalid_nodes = Vec::new(); - let mixes = self.prepare_mixnodes(nonce, &topology); - let gateways = self.prepare_gateways(nonce, &topology); + let mixes = self.prepare_mixnodes(nonce, &mixnode_bonds); + let gateways = self.prepare_gateways(nonce, &gateway_bonds); // get the keys of all nodes that will get tested this round let tested_nodes: Vec<_> = self diff --git a/network-monitor/src/monitor/processor.rs b/network-monitor/src/monitor/processor.rs index 397d5e1368..f2e9da15ee 100644 --- a/network-monitor/src/monitor/processor.rs +++ b/network-monitor/src/monitor/processor.rs @@ -159,7 +159,7 @@ impl ReceivedProcessor { messages = inner.packets_receiver.next() => { for message in messages.expect("packet receiver has died!") { if let Err(err) = inner.on_message(message) { - warn!("failed to process received gateway message - {}", err) + warn!(target: "Monitor", "failed to process received gateway message - {}", err) } } }, diff --git a/network-monitor/src/monitor/sender.rs b/network-monitor/src/monitor/sender.rs index 9bb2e76cb0..a08663640e 100644 --- a/network-monitor/src/monitor/sender.rs +++ b/network-monitor/src/monitor/sender.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::monitor::receiver::{GatewayClientUpdate, GatewayClientUpdateSender}; -use crate::TIME_CHUNK_SIZE; +use crate::{GATEWAY_CONNECTION_TIMEOUT, TIME_CHUNK_SIZE}; use crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH}; use futures::channel::mpsc; use futures::stream::{self, FuturesUnordered, StreamExt}; @@ -195,18 +195,38 @@ impl PacketSender { packets.pub_key, &fresh_gateway_client_data, ); - if let Err(err) = new_client.authenticate_and_start().await { - warn!( - "failed to authenticate with new gateway ({}) - {}", - packets.pub_key.to_base58_string(), - err - ); - return None; - } + + // Put this in timeout in case the gateway has incorrectly set their ulimit and our connection + // gets stuck in their TCP queue and just hangs on our end but does not terminate + // (an actual bug we experienced) + match tokio::time::timeout( + GATEWAY_CONNECTION_TIMEOUT, + new_client.authenticate_and_start(), + ) + .await + { + Ok(Ok(_)) => {} + Ok(Err(err)) => { + warn!( + "failed to authenticate with new gateway ({}) - {}", + packets.pub_key.to_base58_string(), + err + ); + // we failed to create a client, can't do much here + return None; + } + Err(_) => { + warn!( + "timed out while trying to authenticate with new gateway ({})", + packets.pub_key.to_base58_string() + ); + return None; + } + }; + (new_client, Some((message_receiver, ack_receiver))) }; - // TODO: change and introduce rate limiting like in the old code if let Err(err) = Self::attempt_to_send_packets(&mut client, packets.packets, max_sending_rate).await { @@ -235,7 +255,6 @@ impl PacketSender { )) .expect("packet receiver seems to have died!") } - Some(client) } @@ -277,9 +296,7 @@ impl PacketSender { .active_gateway_clients .insert(client.gateway_identity().to_bytes(), client) { - // TODO: perhaps panic instead? getting here implies there's some serious logic - // error somewhere and our assumptions no longer hold - error!( + panic!( "we got duplicate gateway client for {}!", existing.gateway_identity().to_base58_string() ); diff --git a/network-monitor/src/monitor/summary_producer.rs b/network-monitor/src/monitor/summary_producer.rs index 41c5ea4c16..ca4a4c575e 100644 --- a/network-monitor/src/monitor/summary_producer.rs +++ b/network-monitor/src/monitor/summary_producer.rs @@ -1,13 +1,14 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::monitor::preparer::InvalidNode; -use crate::test_packet::TestPacket; +use crate::monitor::preparer::{InvalidNode, TestedNode}; +use crate::node_status_api::models::{ + BatchGatewayStatus, BatchMixStatus, GatewayStatus, MixStatus, +}; +use crate::test_packet::{NodeType, TestPacket}; use crate::PENALISE_OUTDATED; -use crypto::asymmetric::identity; use log::*; -use std::collections::{HashMap, HashSet}; -use validator_client::models::mixmining::{BatchMixStatus, MixStatus}; +use std::collections::HashMap; #[derive(Default)] struct NodeResult { @@ -16,14 +17,34 @@ struct NodeResult { } impl NodeResult { - fn into_mix_status(self, pub_key: String) -> Vec { + fn into_mix_status(self, pub_key: String, owner: String) -> Vec { let v4_status = MixStatus { + owner: owner.clone(), pub_key: pub_key.clone(), ip_version: "4".to_string(), up: self.ip_v4_compatible, }; let v6_status = MixStatus { + owner, + pub_key, + ip_version: "6".to_string(), + up: self.ip_v6_compatible, + }; + + vec![v4_status, v6_status] + } + + fn into_gateway_status(self, pub_key: String, owner: String) -> Vec { + let v4_status = GatewayStatus { + owner: owner.clone(), + pub_key: pub_key.clone(), + ip_version: "4".to_string(), + up: self.ip_v4_compatible, + }; + + let v6_status = GatewayStatus { + owner, pub_key, ip_version: "6".to_string(), up: self.ip_v6_compatible, @@ -34,21 +55,21 @@ impl NodeResult { } #[derive(Default)] -struct TestReport { - total_sent: usize, - total_received: usize, - malformed: Vec, +pub(crate) struct TestReport { + pub(crate) total_sent: usize, + pub(crate) total_received: usize, + pub(crate) malformed: Vec, // below are only populated if we're going to be printing the report - only_ipv4_compatible_mixes: Vec, // can't speak v6, but can speak v4 - only_ipv6_compatible_mixes: Vec, // can't speak v4, but can speak v6 - completely_unroutable_mixes: Vec, // can't speak either v4 or v6 - fully_working_mixes: Vec, + pub(crate) only_ipv4_compatible_mixes: Vec, // can't speak v6, but can speak v4 + pub(crate) only_ipv6_compatible_mixes: Vec, // can't speak v4, but can speak v6 + pub(crate) completely_unroutable_mixes: Vec, // can't speak either v4 or v6 + pub(crate) fully_working_mixes: Vec, - only_ipv4_compatible_gateways: Vec, // can't speak v6, but can speak v4 - only_ipv6_compatible_gateways: Vec, // can't speak v4, but can speak v6 - completely_unroutable_gateways: Vec, // can't speak either v4 or v6 - fully_working_gateways: Vec, + pub(crate) only_ipv4_compatible_gateways: Vec, // can't speak v6, but can speak v4 + pub(crate) only_ipv6_compatible_gateways: Vec, // can't speak v4, but can speak v6 + pub(crate) completely_unroutable_gateways: Vec, // can't speak either v4 or v6 + pub(crate) fully_working_gateways: Vec, } impl TestReport { @@ -107,37 +128,38 @@ impl TestReport { } } - fn parse_summary( - &mut self, - summary: &HashMap, - all_gateways: HashSet, - ) { - let is_gateway = |key: &str| all_gateways.contains(key); - + fn parse_summary(&mut self, summary: &HashMap) { for (node, result) in summary.iter() { - if is_gateway(&node) { + let owned_node = node.clone(); + if node.is_gateway() { if result.ip_v4_compatible && result.ip_v6_compatible { - self.fully_working_gateways.push(node.clone()) + self.fully_working_gateways.push(owned_node) } else if result.ip_v4_compatible { - self.only_ipv4_compatible_gateways.push(node.clone()) + self.only_ipv4_compatible_gateways.push(owned_node) } else if result.ip_v6_compatible { - self.only_ipv6_compatible_gateways.push(node.clone()) + self.only_ipv6_compatible_gateways.push(owned_node) } else { - self.completely_unroutable_gateways.push(node.clone()) + self.completely_unroutable_gateways.push(owned_node) } } else if result.ip_v4_compatible && result.ip_v6_compatible { - self.fully_working_mixes.push(node.clone()) + self.fully_working_mixes.push(owned_node) } else if result.ip_v4_compatible { - self.only_ipv4_compatible_mixes.push(node.clone()) + self.only_ipv4_compatible_mixes.push(owned_node) } else if result.ip_v6_compatible { - self.only_ipv6_compatible_mixes.push(node.clone()) + self.only_ipv6_compatible_mixes.push(owned_node) } else { - self.completely_unroutable_mixes.push(node.clone()) + self.completely_unroutable_mixes.push(owned_node) } } } } +pub(crate) struct TestSummary { + pub(crate) batch_mix_status: BatchMixStatus, + pub(crate) batch_gateway_status: BatchGatewayStatus, + pub(crate) test_report: TestReport, +} + #[derive(Default)] pub(crate) struct SummaryProducer { print_report: bool, @@ -158,20 +180,21 @@ impl SummaryProducer { pub(super) fn produce_summary( &self, - expected_nodes: Vec, + expected_nodes: Vec, received_packets: Vec, invalid_nodes: Vec, - all_gateways: HashSet, - ) -> BatchMixStatus { - let mut report = TestReport::default(); + ) -> TestSummary { + let expected_nodes_count = expected_nodes.len(); + let received_packets_count = received_packets.len(); // contains map of all (seemingly valid) nodes and whether they speak ipv4/ipv6 - let mut summary: HashMap = HashMap::new(); + let mut summary: HashMap = HashMap::new(); // update based on data we actually got - for received_status in received_packets.iter() { - let entry = summary.entry(received_status.pub_key_string()).or_default(); - if received_status.ip_version().is_v4() { + for received_status in received_packets.into_iter() { + let is_received_v4 = received_status.ip_version().is_v4(); + let entry = summary.entry(received_status.into()).or_default(); + if is_received_v4 { entry.ip_v4_compatible = true } else { entry.ip_v6_compatible = true @@ -179,37 +202,69 @@ impl SummaryProducer { } // insert entries we didn't get but were expecting - for expected in expected_nodes.iter() { - summary.entry(expected.to_base58_string()).or_default(); + for expected in expected_nodes.into_iter() { + summary.entry(expected).or_default(); } // finally insert malformed nodes for malformed in invalid_nodes.iter() { match malformed { - InvalidNode::OutdatedMix(id, _) | InvalidNode::OutdatedGateway(id, _) => { + InvalidNode::OutdatedMix(id, owner, _) + | InvalidNode::OutdatedGateway(id, owner, _) => { if PENALISE_OUTDATED { - summary.insert(id.to_string(), Default::default()); + summary.insert(TestedNode::from_raw_gateway(id, owner), Default::default()); } } - InvalidNode::MalformedMix(id) | InvalidNode::MalformedGateway(id) => { - summary.insert(id.to_string(), Default::default()); + InvalidNode::MalformedMix(id, owner) | InvalidNode::MalformedGateway(id, owner) => { + summary.insert(TestedNode::from_raw_mix(id, owner), Default::default()); } } } + let mut report = TestReport { + total_sent: expected_nodes_count * 2, // we sent two packets per node (one ipv4 and one ipv6) + total_received: received_packets_count, + malformed: invalid_nodes, + + ..Default::default() + }; + + report.parse_summary(&summary); + if self.print_report { - report.total_sent = expected_nodes.len() * 2; // we sent two packets per node (one ipv4 and one ipv6) - report.total_received = received_packets.len(); - report.malformed = invalid_nodes; - report.parse_summary(&summary, all_gateways); report.print(self.print_detailed_report); } - let status = summary + let (mixes, gateways): (Vec<_>, Vec<_>) = summary .into_iter() - .flat_map(|(key, result)| result.into_mix_status(key).into_iter()) + .partition(|(node, _)| node.node_type == NodeType::Mixnode); + + let mix_statuses = mixes + .into_iter() + .flat_map(|(node, result)| { + result + .into_mix_status(node.identity, node.owner) + .into_iter() + }) .collect(); - BatchMixStatus { status } + let gateway_statuses = gateways + .into_iter() + .flat_map(|(node, result)| { + result + .into_gateway_status(node.identity, node.owner) + .into_iter() + }) + .collect(); + + TestSummary { + batch_mix_status: BatchMixStatus { + status: mix_statuses, + }, + batch_gateway_status: BatchGatewayStatus { + status: gateway_statuses, + }, + test_report: report, + } } } diff --git a/network-monitor/src/node_status_api/client.rs b/network-monitor/src/node_status_api/client.rs new file mode 100644 index 0000000000..b7698f6c07 --- /dev/null +++ b/network-monitor/src/node_status_api/client.rs @@ -0,0 +1,110 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node_status_api::models::{BatchGatewayStatus, BatchMixStatus, DefaultRestResponse}; +use crate::node_status_api::NodeStatusApiClientError; + +pub(crate) struct Config { + base_url: String, +} + +impl Config { + pub(crate) fn new>(base_url: S) -> Self { + Config { + base_url: base_url.into(), + } + } +} + +pub(crate) struct Client { + config: Config, + reqwest_client: reqwest::Client, +} + +impl Client { + pub(crate) fn new(config: Config) -> Self { + let reqwest_client = reqwest::Client::new(); + Client { + config, + reqwest_client, + } + } + + // Potentially, down the line, this could be moved to /common/client-libs + // and additional methods could be added like GET for report data, but currently + // we have absolutely no use for that in Rust. + + pub(crate) async fn post_batch_mix_status( + &self, + batch_status: BatchMixStatus, + ) -> Result<(), NodeStatusApiClientError> { + const RELATIVE_PATH: &str = "api/status/mixnode/batch"; + + let url = format!("{}/{}", self.config.base_url, RELATIVE_PATH); + + let response = self + .reqwest_client + .post(url) + .json(&batch_status) + .send() + .await?; + + if response.status().is_success() { + let response_content: DefaultRestResponse = response.json().await?; + match response_content { + DefaultRestResponse::Ok(ok_response) => { + if ok_response.ok { + Ok(()) + } else { + Err(NodeStatusApiClientError::NodeStatusApiError( + "received an ok response with false status".into(), + )) + } + } + DefaultRestResponse::Error(err_response) => Err(err_response.into()), + } + } else { + Err(NodeStatusApiClientError::NodeStatusApiError(format!( + "received response with status {}", + response.status() + ))) + } + } + + pub(crate) async fn post_batch_gateway_status( + &self, + batch_status: BatchGatewayStatus, + ) -> Result<(), NodeStatusApiClientError> { + const RELATIVE_PATH: &str = "api/status/gateway/batch"; + + let url = format!("{}/{}", self.config.base_url, RELATIVE_PATH); + + let response = self + .reqwest_client + .post(url) + .json(&batch_status) + .send() + .await?; + + if response.status().is_success() { + let response_content: DefaultRestResponse = response.json().await?; + match response_content { + DefaultRestResponse::Ok(ok_response) => { + if ok_response.ok { + Ok(()) + } else { + Err(NodeStatusApiClientError::NodeStatusApiError( + "received an ok response with false status".into(), + )) + } + } + DefaultRestResponse::Error(err_response) => Err(err_response.into()), + } + } else { + Err(NodeStatusApiClientError::NodeStatusApiError(format!( + "received response with status {}", + response.status() + ))) + } + } +} diff --git a/network-monitor/src/node_status_api/mod.rs b/network-monitor/src/node_status_api/mod.rs new file mode 100644 index 0000000000..6570a3ebc1 --- /dev/null +++ b/network-monitor/src/node_status_api/mod.rs @@ -0,0 +1,73 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::node_status_api::models::ErrorResponses; +use std::fmt::{self, Display, Formatter}; + +mod client; +pub(crate) mod models; + +pub(crate) use client::{Client, Config}; + +const MAX_SANE_UNEXPECTED_PRINT: usize = 100; + +#[derive(Debug)] +pub enum NodeStatusApiClientError { + ReqwestClientError(reqwest::Error), + NodeStatusApiError(String), + UnexpectedResponse(String), +} + +impl From for NodeStatusApiClientError { + fn from(err: reqwest::Error) -> Self { + NodeStatusApiClientError::ReqwestClientError(err) + } +} + +impl From for NodeStatusApiClientError { + fn from(err: ErrorResponses) -> Self { + match err { + ErrorResponses::Error(err_message) => { + NodeStatusApiClientError::NodeStatusApiError(err_message.error) + } + ErrorResponses::Unexpected(received) => { + NodeStatusApiClientError::UnexpectedResponse(received.to_string()) + } + } + } +} + +impl Display for NodeStatusApiClientError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + NodeStatusApiClientError::ReqwestClientError(err) => { + write!(f, "there was an issue with the REST request - {}", err) + } + NodeStatusApiClientError::NodeStatusApiError(err) => { + write!( + f, + "there was an issue with the node status api client - {}", + err + ) + } + NodeStatusApiClientError::UnexpectedResponse(received) => { + if received.len() < MAX_SANE_UNEXPECTED_PRINT { + write!( + f, + "received data was completely unexpected. got: {}", + received + ) + } else { + write!( + f, + "received data was completely unexpected. got: {}...", + received + .chars() + .take(MAX_SANE_UNEXPECTED_PRINT) + .collect::() + ) + } + } + } + } +} diff --git a/network-monitor/src/node_status_api/models.rs b/network-monitor/src/node_status_api/models.rs new file mode 100644 index 0000000000..93e39ec470 --- /dev/null +++ b/network-monitor/src/node_status_api/models.rs @@ -0,0 +1,68 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +/// A notification sent to the validators to let them know whether a given mix is +/// currently up or down (based on whether it's mixing packets) +pub struct MixStatus { + pub pub_key: String, + pub owner: String, + pub ip_version: String, + pub up: bool, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +/// A notification sent to the validators to let them know whether a given set of mixes is +/// currently up or down (based on whether it's mixing packets) +pub struct BatchMixStatus { + pub status: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +/// A notification sent to the validators to let them know whether a given gateway is +/// currently up or down (based on whether it's mixing packets) +pub struct GatewayStatus { + pub pub_key: String, + pub owner: String, + pub ip_version: String, + pub up: bool, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +/// A notification sent to the validators to let them know whether a given set of gateways is +/// currently up or down (based on whether it's mixing packets) +pub struct BatchGatewayStatus { + pub status: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase", untagged)] +pub(crate) enum ErrorResponses { + Error(ErrorResponse), + Unexpected(serde_json::Value), +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ErrorResponse { + pub(crate) error: String, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub(crate) struct OkResponse { + pub(crate) ok: bool, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase", untagged)] +pub(crate) enum DefaultRestResponse { + Ok(OkResponse), + Error(ErrorResponses), +} diff --git a/network-monitor/src/test_packet.rs b/network-monitor/src/test_packet.rs index ebc1df18a2..08ef1bcad7 100644 --- a/network-monitor/src/test_packet.rs +++ b/network-monitor/src/test_packet.rs @@ -1,17 +1,40 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crypto::asymmetric::{encryption, identity}; +use crate::monitor::preparer::TestedNode; +use crypto::asymmetric::identity; use std::convert::{TryFrom, TryInto}; use std::fmt::{self, Display, Formatter}; use std::hash::{Hash, Hasher}; use std::mem; +use std::str::Utf8Error; + +#[repr(u8)] +#[derive(Eq, PartialEq, Debug, Hash, Clone, Copy)] +pub(crate) enum NodeType { + Mixnode = 0, + Gateway = 1, +} + +impl TryFrom for NodeType { + type Error = TestPacketError; + + fn try_from(value: u8) -> Result { + match value { + _ if value == (Self::Mixnode as u8) => Ok(Self::Mixnode), + _ if value == (Self::Gateway as u8) => Ok(Self::Gateway), + _ => Err(TestPacketError::InvalidNodeType), + } + } +} #[derive(Debug)] pub(crate) enum TestPacketError { IncompletePacket, InvalidIpVersion, + InvalidNodeType, InvalidNodeKey, + InvalidOwner(Utf8Error), } impl From for TestPacketError { @@ -20,6 +43,12 @@ impl From for TestPacketError { } } +impl From for TestPacketError { + fn from(err: Utf8Error) -> Self { + TestPacketError::InvalidOwner(err) + } +} + #[repr(u8)] #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] pub(crate) enum IpVersion { @@ -57,20 +86,23 @@ impl Display for IpVersion { } } -#[derive(Eq, Copy, Clone, Debug)] +#[derive(Eq, Clone, Debug)] pub(crate) struct TestPacket { ip_version: IpVersion, nonce: u64, pub_key: identity::PublicKey, + owner: String, + node_type: NodeType, } impl Display for TestPacket { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!( f, - "TestPacket {{ ip: {}, pub_key: {}, nonce: {} }}", + "TestPacket {{ ip: {}, pub_key: {}, owner: {}, nonce: {} }}", self.ip_version, self.pub_key.to_base58_string(), + self.owner, self.nonce ) } @@ -81,6 +113,7 @@ impl Hash for TestPacket { self.ip_version.hash(state); self.nonce.hash(state); self.pub_key.to_bytes().hash(state); + self.owner.hash(state); } } @@ -93,19 +126,33 @@ impl PartialEq for TestPacket { } impl TestPacket { - pub(crate) fn new_v4(pub_key: identity::PublicKey, nonce: u64) -> Self { + pub(crate) fn new_v4( + pub_key: identity::PublicKey, + owner: String, + nonce: u64, + node_type: NodeType, + ) -> Self { TestPacket { ip_version: IpVersion::V4, nonce, pub_key, + owner, + node_type, } } - pub(crate) fn new_v6(pub_key: identity::PublicKey, nonce: u64) -> Self { + pub(crate) fn new_v6( + pub_key: identity::PublicKey, + owner: String, + nonce: u64, + node_type: NodeType, + ) -> Self { TestPacket { ip_version: IpVersion::V6, nonce, pub_key, + owner, + node_type, } } @@ -117,17 +164,15 @@ impl TestPacket { self.ip_version } - pub(crate) fn pub_key_string(&self) -> String { - self.pub_key.to_base58_string() - } - - pub(crate) fn to_bytes(self) -> Vec { + pub(crate) fn to_bytes(&self) -> Vec { self.nonce .to_be_bytes() .iter() .cloned() + .chain(std::iter::once(self.node_type as u8)) .chain(std::iter::once(self.ip_version as u8)) .chain(self.pub_key.to_bytes().iter().cloned()) + .chain(self.owner.as_bytes().iter().cloned()) .collect() } @@ -135,19 +180,35 @@ impl TestPacket { // nonce size let n = mem::size_of::(); - if b.len() != n + 1 + encryption::PUBLIC_KEY_SIZE { + if b.len() < n + 1 + identity::PUBLIC_KEY_LENGTH { return Err(TestPacketError::IncompletePacket); } // this unwrap can't fail as we've already checked for the size let nonce = u64::from_be_bytes(b[0..n].try_into().unwrap()); - let ip_version = IpVersion::try_from(b[n])?; - let pub_key = identity::PublicKey::from_bytes(&b[n + 1..])?; + let node_type = NodeType::try_from(b[n])?; + + let ip_version = IpVersion::try_from(b[n + 1])?; + let pub_key = + identity::PublicKey::from_bytes(&b[n + 2..n + 2 + identity::PUBLIC_KEY_LENGTH])?; + let owner = std::str::from_utf8(&b[n + 2 + identity::PUBLIC_KEY_LENGTH..])?; Ok(TestPacket { + node_type, ip_version, nonce, pub_key, + owner: owner.to_owned(), }) } } + +impl From for TestedNode { + fn from(packet: TestPacket) -> Self { + TestedNode { + identity: packet.pub_key.to_base58_string(), + owner: packet.owner, + node_type: packet.node_type, + } + } +} diff --git a/network-monitor/src/tested_network/good_topology.rs b/network-monitor/src/tested_network/good_topology.rs index 47a2033534..3fa91c8e0b 100644 --- a/network-monitor/src/tested_network/good_topology.rs +++ b/network-monitor/src/tested_network/good_topology.rs @@ -1,16 +1,23 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use mixnet_contract::{GatewayBond, MixNodeBond}; +use serde::Deserialize; use std::fs; -use topology::NymTopology; -use validator_client::models::topology::Topology; +use topology::{nym_topology_from_bonds, NymTopology}; + +#[derive(Deserialize)] +struct GoodTopology { + mixnodes: Vec, + gateways: Vec, +} pub(crate) fn parse_topology_file(file_path: &str) -> NymTopology { let file_content = fs::read_to_string(file_path).expect("specified topology file does not exist"); - let validator_topology = serde_json::from_str::(&file_content) + let good_topology = serde_json::from_str::(&file_content) .expect("topology in specified file is malformed"); - let nym_topology: NymTopology = validator_topology.into(); + let nym_topology = nym_topology_from_bonds(good_topology.mixnodes, good_topology.gateways); if nym_topology.mixes().len() != 3 { panic!("topology has different than 3 number of layers") } diff --git a/network-monitor/src/tested_network/mod.rs b/network-monitor/src/tested_network/mod.rs index aca2cecdbf..85cac62a66 100644 --- a/network-monitor/src/tested_network/mod.rs +++ b/network-monitor/src/tested_network/mod.rs @@ -7,6 +7,7 @@ use topology::{gateway, mix, NymTopology}; pub(crate) mod good_topology; +#[derive(Clone)] pub(crate) struct TestedNetwork { system_version: String, good_v4_topology: NymTopology, @@ -60,4 +61,12 @@ impl TestedNetwork { good_topology.set_gateways(vec![gateway]); good_topology } + + pub(crate) fn v4_topology(&self) -> &NymTopology { + &self.good_v4_topology + } + + pub(crate) fn v6_topology(&self) -> &NymTopology { + &self.good_v6_topology + } }