From a274edffba2e681d9937484e8b2eb248759fee90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 18 Aug 2021 14:41:00 +0100 Subject: [PATCH] Feature/nymd client integration (#736) * Calculating gas fees * Ability to set custom fees * Added extra test * Removed commented code * Moved all msg types to common contract crate * Temporarily disabling get_tx method * Finishing up nymd client API * Comment fix * Remaining fee values * Some cleanup * Removed needless borrow * Fixed imports in contract tests * Moved error types around * New ValidatorClient * Experiment with new type of defaults * Removed dead module * Dealt with unwrap * Migrated mixnode to use new validator client * Migrated gateway to use new validator client * Mixnode and gateway adjustments * More exported defaults * Clients using new validator client * Fixed mixnode upgrade * Moved default values to a new crate * Changed behaviour of validator client features * Migrated basic functions of validator api * Updated config + fixed startup * Fixed wasm client build * Integration with the explorer api * Removed tokio dev dependency * Needless borrow * Fixex wasm client build * Fixed tauri client build * Needless borrows * Fixed client upgrade print * Removed redundant comments * Made note on aggregated verification key into a doc comment * Removed mixnet contract references from verloc * Modified default validators structure * Reformatted validator-api Cargo.toml file * Removed commented code * Made the doc comment example a no-run * Fixed a upgrade print... again * Adjusted the doc example * Removed unused import --- Cargo.lock | 69 +-- Cargo.toml | 2 + clients/client-core/Cargo.toml | 1 + .../src/client/topology_control.rs | 46 +- clients/client-core/src/config/mod.rs | 87 +--- clients/native/Cargo.toml | 3 +- clients/native/src/client/config/mod.rs | 5 +- clients/native/src/client/config/template.rs | 7 +- clients/native/src/client/mod.rs | 38 +- clients/native/src/commands/init.rs | 45 +- clients/native/src/commands/mod.rs | 16 +- clients/native/src/commands/run.rs | 5 - clients/native/src/commands/upgrade.rs | 249 ++--------- clients/socks5/Cargo.toml | 2 + clients/socks5/src/client/config/mod.rs | 7 +- clients/socks5/src/client/config/template.rs | 7 +- clients/socks5/src/client/mod.rs | 43 +- clients/socks5/src/commands/init.rs | 46 +- clients/socks5/src/commands/mod.rs | 16 +- clients/socks5/src/commands/run.rs | 5 - clients/socks5/src/commands/upgrade.rs | 249 ++--------- clients/tauri-client/src-tauri/Cargo.toml | 4 +- clients/tauri-client/src-tauri/src/main.rs | 164 +++++-- clients/webassembly/Cargo.toml | 2 + clients/webassembly/src/client/mod.rs | 45 +- .../client-libs/validator-client/Cargo.toml | 15 +- .../validator-client/src/client.rs | 329 ++++++++++++++ .../client-libs/validator-client/src/error.rs | 213 +-------- .../client-libs/validator-client/src/lib.rs | 269 +----------- .../validator-client/src/models.rs | 57 --- .../src/nymd/cosmwasm_client/client.rs | 73 ++-- .../src/nymd/cosmwasm_client/helpers.rs | 18 +- .../src/nymd/cosmwasm_client/logs.rs | 11 +- .../src/nymd/cosmwasm_client/mod.rs | 6 +- .../nymd/cosmwasm_client/signing_client.rs | 70 ++- .../src/nymd/cosmwasm_client/types.rs | 29 +- .../validator-client/src/nymd/error.rs | 100 +++++ .../validator-client/src/nymd/gas_price.rs | 8 +- .../validator-client/src/nymd/mod.rs | 114 ++--- .../validator-client/src/nymd/wallet/mod.rs | 30 +- .../validator-client/src/serde_helpers.rs | 26 -- .../src/validator_api/error.rs | 2 +- .../validator-client/src/validator_api/mod.rs | 184 ++++++-- .../src/validator_api/routes.rs | 11 + common/coconut-interface/Cargo.toml | 6 - common/coconut-interface/src/error.rs | 9 +- common/coconut-interface/src/lib.rs | 175 +------- common/config/Cargo.toml | 3 + common/config/src/defaults.rs | 26 +- common/config/src/helpers.rs | 95 ---- common/config/src/lib.rs | 3 - common/credentials/Cargo.toml | 14 + common/credentials/src/bandwidth.rs | 47 ++ common/credentials/src/error.rs | 21 + common/credentials/src/lib.rs | 8 + common/credentials/src/utils.rs | 134 ++++++ common/mixnode-common/Cargo.toml | 12 +- common/mixnode-common/src/verloc/mod.rs | 70 +-- common/network-defaults/Cargo.toml | 10 + common/network-defaults/src/lib.rs | 82 ++++ contracts/mixnet/Cargo.lock | 87 ++++ explorer-api/Cargo.toml | 1 + explorer-api/src/main.rs | 2 - explorer-api/src/mix_nodes/mod.rs | 11 +- gateway/Cargo.toml | 2 + gateway/src/commands/init.rs | 6 - gateway/src/commands/mod.rs | 17 +- gateway/src/commands/run.rs | 8 +- gateway/src/commands/upgrade.rs | 266 ++---------- gateway/src/config/mod.rs | 68 +-- gateway/src/config/template.rs | 9 +- .../websocket/connection_handler.rs | 18 +- .../client_handling/websocket/listener.rs | 9 +- gateway/src/node/mod.rs | 21 +- mixnode/Cargo.toml | 9 +- mixnode/src/commands/init.rs | 6 - mixnode/src/commands/mod.rs | 17 +- mixnode/src/commands/run.rs | 8 +- mixnode/src/commands/upgrade.rs | 410 ++---------------- mixnode/src/config/mod.rs | 89 +--- mixnode/src/config/template.rs | 9 +- mixnode/src/node/mod.rs | 17 +- validator-api/Cargo.toml | 27 +- validator-api/src/cache/mod.rs | 37 +- validator-api/src/coconut/mod.rs | 8 +- validator-api/src/config/mod.rs | 64 ++- validator-api/src/config/template.rs | 18 +- validator-api/src/main.rs | 202 ++++++--- validator-api/src/network_monitor/mod.rs | 39 +- .../src/network_monitor/monitor/sender.rs | 25 +- validator-api/src/nymd_client.rs | 80 ++++ 91 files changed, 2249 insertions(+), 2784 deletions(-) create mode 100644 common/client-libs/validator-client/src/client.rs delete mode 100644 common/client-libs/validator-client/src/models.rs create mode 100644 common/client-libs/validator-client/src/nymd/error.rs delete mode 100644 common/client-libs/validator-client/src/serde_helpers.rs create mode 100644 common/client-libs/validator-client/src/validator_api/routes.rs delete mode 100644 common/config/src/helpers.rs create mode 100644 common/credentials/Cargo.toml create mode 100644 common/credentials/src/bandwidth.rs create mode 100644 common/credentials/src/error.rs create mode 100644 common/credentials/src/lib.rs create mode 100644 common/credentials/src/utils.rs create mode 100644 common/network-defaults/Cargo.toml create mode 100644 common/network-defaults/src/lib.rs create mode 100644 validator-api/src/nymd_client.rs diff --git a/Cargo.lock b/Cargo.lock index 5a2f9658fd..daf1ab435d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,12 +119,14 @@ name = "app" version = "0.1.0" dependencies = [ "coconut-interface", - "gateway-client", + "credentials", "serde", "serde_json", "tauri", "tauri-build", "tokio", + "url", + "validator-client", ] [[package]] @@ -645,6 +647,7 @@ dependencies = [ "tempfile", "tokio", "topology", + "url", "validator-client", ] @@ -693,14 +696,9 @@ name = "coconut-interface" version = "0.1.0" dependencies = [ "coconut-rs", - "crypto", - "digest 0.9.0", "getset", "serde", "sha2", - "thiserror", - "url", - "validator-client", ] [[package]] @@ -772,8 +770,10 @@ version = "0.1.0" dependencies = [ "handlebars", "humantime-serde", + "network-defaults", "serde", "toml", + "url", ] [[package]] @@ -798,26 +798,6 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" -[[package]] -name = "const_format" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f4087b5a1164f92255f8b301c88fc8627e5abf5e25b5476f84b02e4b47795d" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c36c619c422113552db4eb28cddba8faa757e33f758cc3415bd2885977b591" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - [[package]] name = "constant_time_eq" version = "0.1.5" @@ -1015,6 +995,16 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "credentials" +version = "0.1.0" +dependencies = [ + "coconut-interface", + "thiserror", + "url", + "validator-client", +] + [[package]] name = "crossbeam-channel" version = "0.5.1" @@ -1596,6 +1586,7 @@ dependencies = [ "isocountry", "log", "mixnet-contract", + "network-defaults", "okapi", "pretty_env_logger", "reqwest", @@ -2989,6 +2980,7 @@ dependencies = [ "serde", "tokio", "tokio-util", + "url", "validator-client", "version-checker", ] @@ -3077,6 +3069,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" +[[package]] +name = "network-defaults" +version = "0.1.0" +dependencies = [ + "url", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -3202,6 +3201,7 @@ dependencies = [ "client-core", "coconut-interface", "config", + "credentials", "crypto", "dirs", "dotenv", @@ -3231,6 +3231,7 @@ version = "0.11.0" dependencies = [ "coconut-interface", "console_error_panic_hook", + "credentials", "crypto", "futures", "gateway-client", @@ -3239,6 +3240,7 @@ dependencies = [ "rand 0.7.3", "serde", "topology", + "url", "validator-client", "wasm-bindgen", "wasm-bindgen-futures", @@ -3254,6 +3256,7 @@ dependencies = [ "clap", "coconut-interface", "config", + "credentials", "crypto", "dashmap", "dirs", @@ -3274,6 +3277,7 @@ dependencies = [ "tokio-stream", "tokio-tungstenite", "tokio-util", + "url", "validator-client", "version-checker", ] @@ -3306,6 +3310,7 @@ dependencies = [ "tokio-util", "toml", "topology", + "url", "validator-client", "version-checker", ] @@ -3339,6 +3344,7 @@ dependencies = [ "client-core", "coconut-interface", "config", + "credentials", "crypto", "dirs", "dotenv", @@ -3358,6 +3364,7 @@ dependencies = [ "socks5-requests", "tokio", "topology", + "url", "validator-client", "version-checker", ] @@ -3369,9 +3376,8 @@ dependencies = [ "anyhow", "clap", "coconut-interface", - "coconut-rs", "config", - "const_format", + "credentials", "crypto", "dirs", "dotenv", @@ -3394,6 +3400,7 @@ dependencies = [ "sqlx", "tokio", "topology", + "url", "validator-client", "version-checker", ] @@ -6316,23 +6323,21 @@ dependencies = [ "async-trait", "base64", "bip39", + "coconut-interface", "config", "cosmos_sdk", "cosmwasm-std", "flate2", - "fluvio-wasm-timer", - "getrandom 0.2.3", "itertools 0.10.1", "log", "mixnet-contract", + "network-defaults", "prost", - "rand 0.8.4", "reqwest", "serde", "serde_json", "sha2", "thiserror", - "tokio", "url", ] diff --git a/Cargo.toml b/Cargo.toml index c08290c6d0..0b112df4f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,11 @@ members = [ "common/client-libs/validator-client", "common/coconut-interface", "common/config", + "common/credentials", "common/crypto", "common/mixnet-contract", "common/mixnode-common", + "common/network-defaults", "common/nonexhaustive-delayqueue", "common/nymsphinx", "common/nymsphinx/acknowledgements", diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml index aadf4318fe..3d13ae8234 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -15,6 +15,7 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0", features = ["derive"] } sled = "0.34" tokio = { version = "1.4", features = ["macros"] } +url = { version ="2.2", features = ["serde"] } # internal config = { path = "../../common/config" } diff --git a/clients/client-core/src/client/topology_control.rs b/clients/client-core/src/client/topology_control.rs index d325d4cca7..ffcbcb0f50 100644 --- a/clients/client-core/src/client/topology_control.rs +++ b/clients/client-core/src/client/topology_control.rs @@ -4,6 +4,8 @@ use log::*; use nymsphinx::addressing::clients::Recipient; use nymsphinx::params::DEFAULT_NUM_MIX_HOPS; +use rand::seq::SliceRandom; +use rand::thread_rng; use std::ops::Deref; use std::sync::Arc; use std::time; @@ -12,6 +14,7 @@ use tokio::runtime::Handle; use tokio::sync::{RwLock, RwLockReadGuard}; use tokio::task::JoinHandle; use topology::{nym_topology_from_bonds, NymTopology}; +use url::Url; // I'm extremely curious why compiler NEVER complained about lack of Debug here before #[derive(Debug)] @@ -125,54 +128,61 @@ impl Default for TopologyAccessor { } pub struct TopologyRefresherConfig { - available_validators: Vec, - mixnet_contract_address: String, + validator_api_urls: Vec, refresh_rate: time::Duration, } impl TopologyRefresherConfig { - pub fn new( - available_validators: Vec, - mixnet_contract_address: String, - refresh_rate: time::Duration, - ) -> Self { + pub fn new(validator_api_urls: Vec, refresh_rate: time::Duration) -> Self { TopologyRefresherConfig { - available_validators, - mixnet_contract_address, + validator_api_urls, refresh_rate, } } } pub struct TopologyRefresher { - validator_client: validator_client::Client, + validator_client: validator_client::ApiClient, + validator_api_urls: Vec, topology_accessor: TopologyAccessor, refresh_rate: Duration, + currently_used_api: usize, was_latest_valid: bool, } impl TopologyRefresher { - pub fn new(cfg: TopologyRefresherConfig, topology_accessor: TopologyAccessor) -> Self { - 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); + pub fn new(mut cfg: TopologyRefresherConfig, topology_accessor: TopologyAccessor) -> Self { + cfg.validator_api_urls.shuffle(&mut thread_rng()); TopologyRefresher { - validator_client, + validator_client: validator_client::ApiClient::new(cfg.validator_api_urls[0].clone()), + validator_api_urls: cfg.validator_api_urls, topology_accessor, refresh_rate: cfg.refresh_rate, + currently_used_api: 0, was_latest_valid: true, } } + fn use_next_validator_api(&mut self) { + if self.validator_api_urls.len() == 1 { + warn!("There's only a single validator API available - it won't be possible to use a different one"); + return; + } + + self.currently_used_api = (self.currently_used_api + 1) % self.validator_api_urls.len(); + self.validator_client + .change_validator_api(self.validator_api_urls[self.currently_used_api].clone()) + } + async fn get_current_compatible_topology(&mut self) -> Option { // TODO: optimization for the future: // only refresh mixnodes on timer and refresh gateways only when // we have to send to a new, unknown, gateway - let mixnodes = match self.validator_client.get_cached_mix_nodes().await { + let mixnodes = match self.validator_client.get_cached_mixnodes().await { Err(err) => { error!("failed to get network mixnodes - {}", err); return None; @@ -199,6 +209,10 @@ impl TopologyRefresher { trace!("Refreshing the topology"); let new_topology = self.get_current_compatible_topology().await; + if new_topology.is_none() { + self.use_next_validator_api(); + } + if new_topology.is_none() && self.was_latest_valid { // if we failed to grab this topology, but the one before it was alright, let's assume // validator had a tiny hiccup and use the old data diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs index 993ab21742..857a57d1f0 100644 --- a/clients/client-core/src/config/mod.rs +++ b/clients/client-core/src/config/mod.rs @@ -2,11 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use config::defaults::*; -use config::{deserialize_duration, deserialize_validators, NymConfig}; +use config::NymConfig; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use std::path::PathBuf; use std::time::Duration; +use url::Url; pub mod persistence; @@ -23,22 +24,10 @@ const DEFAULT_TOPOLOGY_REFRESH_RATE: Duration = Duration::from_secs(5 * 60); // const DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT: Duration = Duration::from_millis(5_000); const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500); -// helper function to get default validators as a Vec -pub fn default_validator_rest_endpoints() -> Vec { - DEFAULT_VALIDATOR_REST_ENDPOINTS - .iter() - .map(|&endpoint| endpoint.to_string()) - .collect() -} - pub fn missing_string_value() -> String { MISSING_VALUE.to_string() } -pub fn missing_vec_string_value() -> Vec { - vec![missing_string_value()] -} - #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { @@ -122,12 +111,8 @@ impl Config { self.client.gateway_listener = gateway_listener.into(); } - pub fn set_custom_validators(&mut self, validators: Vec) { - self.client.validator_rest_urls = validators; - } - - pub fn set_mixnet_contract>(&mut self, contract_address: S) { - self.client.mixnet_contract_address = contract_address.into(); + pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec) { + self.client.validator_api_urls = validator_api_urls; } pub fn set_high_default_traffic_volume(&mut self) { @@ -176,12 +161,8 @@ impl Config { self.client.ack_key_file.clone() } - pub fn get_validator_rest_endpoints(&self) -> Vec { - self.client.validator_rest_urls.clone() - } - - pub fn get_validator_mixnet_contract_address(&self) -> String { - self.client.mixnet_contract_address.clone() + pub fn get_validator_api_endpoints(&self) -> Vec { + self.client.validator_api_urls.clone() } pub fn get_gateway_id(&self) -> String { @@ -253,17 +234,8 @@ pub struct Client { /// ID specifies the human readable ID of this particular client. id: String, - /// URL to the validator server for obtaining network topology. - #[serde( - deserialize_with = "deserialize_validators", - default = "missing_vec_string_value", - alias = "validator_rest_url" - )] - validator_rest_urls: Vec, - - /// Address of the validator contract managing the network. - #[serde(default = "missing_string_value")] - mixnet_contract_address: String, + /// Addresses to APIs running on validator from which the client gets the view of the network. + validator_api_urls: Vec, /// Path to file containing private identity key. private_identity_key_file: PathBuf, @@ -310,8 +282,7 @@ impl Default for Client { Client { version: env!("CARGO_PKG_VERSION").to_string(), id: "".to_string(), - validator_rest_urls: default_validator_rest_endpoints(), - mixnet_contract_address: DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(), + validator_api_urls: default_api_endpoints(), private_identity_key_file: Default::default(), public_identity_key_file: Default::default(), private_encryption_key_file: Default::default(), @@ -374,20 +345,14 @@ pub struct Debug { /// sent packet is going to be delayed at any given mix node. /// So for a packet going through three mix nodes, on average, it will take three times this value /// until the packet reaches its destination. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] average_packet_delay: Duration, /// The parameter of Poisson distribution determining how long, on average, /// sent acknowledgement is going to be delayed at any given mix node. /// So for an ack going through three mix nodes, on average, it will take three times this value /// until the packet reaches its destination. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] average_ack_delay: Duration, /// Value multiplied with the expected round trip time of an acknowledgement packet before @@ -398,53 +363,35 @@ pub struct Debug { /// Value added to the expected round trip time of an acknowledgement packet before /// it is assumed it was lost and retransmission of the data packet happens. /// In an ideal network with 0 latency, this value would have been 0. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] ack_wait_addition: Duration, /// The parameter of Poisson distribution determining how long, on average, /// it is going to take for another loop cover traffic message to be sent. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] loop_cover_traffic_average_delay: Duration, /// The parameter of Poisson distribution determining how long, on average, /// it is going to take another 'real traffic stream' message to be sent. /// If no real packets are available and cover traffic is enabled, /// a loop cover message is sent instead in order to preserve the rate. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] message_sending_average_delay: Duration, /// How long we're willing to wait for a response to a message sent to the gateway, /// before giving up on it. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] gateway_response_timeout: Duration, /// The uniform delay every which clients are querying the directory server /// to try to obtain a compatible network topology to send sphinx packets through. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] topology_refresh_rate: Duration, /// During topology refresh, test packets are sent through every single possible network /// path. This timeout determines waiting period until it is decided that the packet /// did not reach its destination. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] topology_resolution_timeout: Duration, } diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index 4220cda432..1ebea3c229 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -16,7 +16,7 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it # the AsyncRead, AsyncWrite, Stream, Sink, etc. traits could be used from tokio # channels should really be replaced with crossbeam due to that implementation being more efficient # and the single instance of abortable we have should really be refactored anyway -url = "2.1" # I think we should just replace it with String +url = "2.2" clap = "2.33.0" # for the command line arguments dirs = "3.0" # for determining default store directories in config @@ -32,6 +32,7 @@ tokio-tungstenite = "0.14" # websocket ## internal client-core = { path = "../client-core" } coconut-interface = { path = "../../common/coconut-interface" } +credentials = { path = "../../common/credentials" } config = { path = "../../common/config" } crypto = { path = "../../common/crypto" } gateway-client = { path = "../../common/client-libs/gateway-client" } diff --git a/clients/native/src/client/config/mod.rs b/clients/native/src/client/config/mod.rs index a0368460db..84b2184ab2 100644 --- a/clients/native/src/client/config/mod.rs +++ b/clients/native/src/client/config/mod.rs @@ -4,14 +4,13 @@ use crate::client::config::template::config_template; use client_core::config::Config as BaseConfig; pub use client_core::config::MISSING_VALUE; +use config::defaults::DEFAULT_WEBSOCKET_LISTENING_PORT; use config::NymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; mod template; -const DEFAULT_LISTENING_PORT: u16 = 1977; - #[derive(Debug, Deserialize, PartialEq, Serialize, Clone, Copy)] #[serde(deny_unknown_fields)] pub enum SocketType { @@ -117,7 +116,7 @@ impl Default for Socket { fn default() -> Self { Socket { socket_type: SocketType::WebSocket, - listening_port: DEFAULT_LISTENING_PORT, + listening_port: DEFAULT_WEBSOCKET_LISTENING_PORT, } } } diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs index ebe6fe70d7..b2ca0ec46b 100644 --- a/clients/native/src/client/config/template.rs +++ b/clients/native/src/client/config/template.rs @@ -19,16 +19,13 @@ version = '{{ client.version }}' # Human readable ID of this particular client. id = '{{ client.id }}' -# URL to the validator server for obtaining network topology. +# Addresses to APIs running on validator from which the client gets the view of the network. validator_rest_urls = [ - {{#each client.validator_rest_urls }} + {{#each client.validator_api_urls }} '{{this}}', {{/each}} ] -# Address of the validator contract managing the network. -mixnet_contract_address = '{{ client.mixnet_contract_address }}' - # Path to file containing private identity key. private_identity_key_file = '{{ client.private_identity_key_file }}' diff --git a/clients/native/src/client/mod.rs b/clients/native/src/client/mod.rs index 69a9ab7d09..2b1c28b8cf 100644 --- a/clients/native/src/client/mod.rs +++ b/clients/native/src/client/mod.rs @@ -23,6 +23,8 @@ use client_core::client::topology_control::{ }; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use coconut_interface::Credential; +use credentials::bandwidth::prepare_for_spending; +use credentials::obtain_aggregate_verification_key; use crypto::asymmetric::identity; use futures::channel::mpsc; use gateway_client::{ @@ -164,6 +166,30 @@ impl NymClient { .start(self.runtime.handle()) } + async fn prepare_credential(&self) -> Credential { + let verification_key = obtain_aggregate_verification_key( + &self.config.get_base().get_validator_api_endpoints(), + ) + .await + .expect("could not obtain aggregate verification key of validators"); + + let bandwidth_credential = credentials::bandwidth::obtain_signature( + &self.key_manager.identity_keypair().public_key().to_bytes(), + &self.config.get_base().get_validator_api_endpoints(), + ) + .await + .expect("could not obtain bandwidth credential"); + // the above would presumably be loaded from a file + + // the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff) + prepare_for_spending( + &self.key_manager.identity_keypair().public_key().to_bytes(), + &bandwidth_credential, + &verification_key, + ) + .expect("could not prepare out bandwidth credential for spending") + } + fn start_gateway_client( &mut self, mixnet_message_sender: MixnetMessageSender, @@ -182,12 +208,7 @@ impl NymClient { .expect("provided gateway id is invalid!"); self.runtime.block_on(async { - let coconut_credential = Credential::init( - self.config.get_base().get_validator_rest_endpoints(), - *self.key_manager.identity_keypair().public_key(), - ) - .await - .expect("Could not initialize coconut credential"); + let coconut_credential = self.prepare_credential().await; let mut gateway_client = GatewayClient::new( gateway_address, @@ -213,10 +234,7 @@ impl NymClient { // the current global view of topology fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) { let topology_refresher_config = TopologyRefresherConfig::new( - self.config.get_base().get_validator_rest_endpoints(), - self.config - .get_base() - .get_validator_mixnet_contract_address(), + self.config.get_base().get_validator_api_endpoints(), self.config.get_base().get_topology_refresh_rate(), ); let mut topology_refresher = diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index b3d9fe961c..2035daf78c 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -8,6 +8,8 @@ use client_core::client::key_manager::KeyManager; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use coconut_interface::Credential; use config::NymConfig; +use credentials::bandwidth::prepare_for_spending; +use credentials::obtain_aggregate_verification_key; use crypto::asymmetric::{encryption, identity}; use gateway_client::GatewayClient; use gateway_requests::registration::handshake::SharedKeys; @@ -15,10 +17,12 @@ use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::nodes::NodeIdentity; use rand::rngs::OsRng; use rand::seq::SliceRandom; +use rand::thread_rng; use std::convert::TryInto; use std::sync::Arc; use std::time::Duration; use topology::{filter::VersionFilterable, gateway}; +use url::Url; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { App::new("init") @@ -39,11 +43,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) - .arg(Arg::with_name("mixnet-contract") - .long("mixnet-contract") - .help("Address of the validator contract managing the network") - .takes_value(true), - ) .arg(Arg::with_name("disable-socket") .long("disable-socket") .help("Whether to not start the websocket") @@ -61,15 +60,29 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { ) } +// this behaviour should definitely be changed, we shouldn't +// need to get bandwidth credential for registration +async fn prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential { + let verification_key = obtain_aggregate_verification_key(validators) + .await + .expect("could not obtain aggregate verification key of validators"); + + let bandwidth_credential = credentials::bandwidth::obtain_signature(raw_identity, validators) + .await + .expect("could not obtain bandwidth credential"); + + prepare_for_spending(raw_identity, &bandwidth_credential, &verification_key) + .expect("could not prepare out bandwidth credential for spending") +} + async fn register_with_gateway( gateway: &gateway::Node, our_identity: Arc, - validator_urls: Vec, + validator_urls: Vec, ) -> SharedKeys { let timeout = Duration::from_millis(1500); - let coconut_credential = Credential::init(validator_urls, *our_identity.public_key()) - .await - .expect("Could not initialize coconut credential"); + let coconut_credential = + prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await; let mut gateway_client = GatewayClient::new_init( gateway.clients_address(), gateway.identity_key, @@ -88,12 +101,13 @@ async fn register_with_gateway( } async fn gateway_details( - validator_servers: Vec, - mixnet_contract: &str, + validator_servers: Vec, chosen_gateway_id: Option<&str>, ) -> gateway::Node { - let validator_client_config = validator_client::Config::new(validator_servers, mixnet_contract); - let validator_client = validator_client::Client::new(validator_client_config); + let validator_api = validator_servers + .choose(&mut thread_rng()) + .expect("The list of validator apis is empty"); + let validator_client = validator_client::ApiClient::new(validator_api.clone()); let gateways = validator_client.get_cached_gateways().await.unwrap(); let valid_gateways = gateways @@ -189,15 +203,14 @@ pub fn execute(matches: &ArgMatches) { let registration_fut = async { let gate_details = gateway_details( - config.get_base().get_validator_rest_endpoints(), - &config.get_base().get_validator_mixnet_contract_address(), + config.get_base().get_validator_api_endpoints(), chosen_gateway_id, ) .await; config .get_base_mut() .with_gateway_id(gate_details.identity_key.to_base58_string()); - let validator_urls = config.get_base().get_validator_rest_endpoints(); + let validator_urls = config.get_base().get_validator_api_endpoints(); let shared_keys = register_with_gateway( &gate_details, key_manager.identity_keypair(), diff --git a/clients/native/src/commands/mod.rs b/clients/native/src/commands/mod.rs index 04a25a3f83..475f14a9b6 100644 --- a/clients/native/src/commands/mod.rs +++ b/clients/native/src/commands/mod.rs @@ -3,14 +3,20 @@ use crate::client::config::{Config, SocketType}; use clap::ArgMatches; +use url::Url; pub(crate) mod init; pub(crate) mod run; pub(crate) mod upgrade; -fn parse_validators(raw: &str) -> Vec { +fn parse_validators(raw: &str) -> Vec { raw.split(',') - .map(|raw_validator| raw_validator.trim().into()) + .map(|raw_validator| { + raw_validator + .trim() + .parse() + .expect("one of the provided validator api urls is invalid") + }) .collect() } @@ -18,11 +24,7 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi if let Some(raw_validators) = matches.value_of("validators") { config .get_base_mut() - .set_custom_validators(parse_validators(raw_validators)); - } - - if let Some(contract_address) = matches.value_of("mixnet-contract") { - config.get_base_mut().set_mixnet_contract(contract_address) + .set_custom_validator_apis(parse_validators(raw_validators)); } if let Some(gateway_id) = matches.value_of("gateway") { diff --git a/clients/native/src/commands/run.rs b/clients/native/src/commands/run.rs index 96151e7c70..d08f4ea764 100644 --- a/clients/native/src/commands/run.rs +++ b/clients/native/src/commands/run.rs @@ -24,11 +24,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Comma separated list rest rest endpoints of the validators") .takes_value(true), ) - .arg(Arg::with_name("mixnet-contract") - .long("mixnet-contract") - .help("Address of the validator contract managing the network") - .takes_value(true), - ) .arg(Arg::with_name("gateway") .long("gateway") .help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened") diff --git a/clients/native/src/commands/upgrade.rs b/clients/native/src/commands/upgrade.rs index 049354b19f..c8a3bc44a5 100644 --- a/clients/native/src/commands/upgrade.rs +++ b/clients/native/src/commands/upgrade.rs @@ -3,12 +3,17 @@ use crate::client::config::{Config, MISSING_VALUE}; use clap::{App, Arg, ArgMatches}; -use client_core::config::default_validator_rest_endpoints; -use config::defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS; +use config::defaults::default_api_endpoints; use config::NymConfig; use std::fmt::Display; use std::process; -use version_checker::{parse_version, Version}; +use version_checker::Version; + +#[allow(dead_code)] +fn fail_upgrade(from_version: D1, to_version: D2) -> ! { + print_failed_upgrade(from_version, to_version); + process::exit(1) +} fn print_start_upgrade(from: D1, to: D2) { println!( @@ -31,28 +36,29 @@ fn print_successful_upgrade(from: D1, to: D2) { ); } -pub fn command_args<'a, 'b>() -> App<'a, 'b> { - App::new("upgrade").about("Try to upgrade the client") - .arg( - Arg::with_name("id") - .long("id") - .help("Id of the nym-client we want to upgrade") - .takes_value(true) - .required(true), - ) - // the rest of arguments depend on the upgrade path - .arg(Arg::with_name("current version") - .long("current-version") - .help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'") - .takes_value(true) - ) +fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! { + eprintln!( + "Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!", + config_version, package_version + ); + process::exit(1) } -fn unsupported_upgrade(config_version: Version, package_version: Version) -> ! { - eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, package_version); +fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! { + eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, current_version); process::exit(1) } +pub fn command_args<'a, 'b>() -> App<'a, 'b> { + App::new("upgrade").about("Try to upgrade the client").arg( + Arg::with_name("id") + .long("id") + .help("Id of the nym-client we want to upgrade") + .takes_value(true) + .required(true), + ) +} + fn parse_config_version(config: &Config) -> Version { let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| { eprintln!("failed to parse client version! - {:?}", err); @@ -87,190 +93,28 @@ fn parse_package_version() -> Version { version } -fn pre_090_upgrade(from: &str, mut config: Config) -> Config { - // this is not extracted to separate function as you only have to manually pass version - // if upgrading from pre090 version - let from = match from.strip_prefix('v') { - Some(stripped) => stripped, - None => from, - }; - - let from = match from.strip_prefix('V') { - Some(stripped) => stripped, - None => from, - }; - - let from_version = parse_version(from).expect("invalid version provided!"); - if from_version.major == 0 && from_version.minor < 8 { - // technically this could be implemented, but is there any point in that? - eprintln!("upgrading client from before v0.8.0 is not supported. Please run `init` with new binary instead"); - process::exit(1) - } - - if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 { - eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version"); - process::exit(1) - } - - // note: current is guaranteed to not have any `build` information suffix (nor pre-release - // information), as this was asserted at the beginning of this command) - // - // upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate - // step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0) - // this way we don't need to have all the crazy paths on how to upgrade from any version to any - // other version. We just upgrade one minor version at a time. - let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); - let to_version = if current.major == 0 && current.minor == 9 { - current - } else { - Version::new(0, 9, 0) - }; - - if config.get_base().get_validator_rest_endpoints()[0] != MISSING_VALUE { - eprintln!("existing config seems to have specified new validator rest endpoint which was only introduced in 0.9.0! Can't perform upgrade."); - print_failed_upgrade(&from_version, &to_version); - process::exit(1); - } - - print_start_upgrade(&from_version, &to_version); - - config - .get_base_mut() - .set_custom_version(to_version.to_string().as_ref()); - - println!( - "Setting validator REST endpoint to to {:?}", - default_validator_rest_endpoints() - ); - - config - .get_base_mut() - .set_custom_validators(default_validator_rest_endpoints()); - - config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&from_version, &to_version); - process::exit(1); - }); - - print_successful_upgrade(from_version, to_version); - - config -} - -/* -changes: -- introduction of mixnet contract address field -- change to default validator rest endpoint - */ -fn minor_010_upgrade( +fn minor_0_12_upgrade( mut config: Config, _matches: &ArgMatches, config_version: &Version, package_version: &Version, ) -> Config { - let to_version = if package_version.major == 0 && package_version.minor == 10 { + let to_version = if package_version.major == 0 && package_version.minor == 12 { package_version.clone() } else { - Version::new(0, 10, 0) + Version::new(0, 12, 0) }; print_start_upgrade(&config_version, &to_version); - config - .get_base_mut() - .set_custom_version(to_version.to_string().as_ref()); - - if config.get_base().get_validator_mixnet_contract_address() != MISSING_VALUE { - eprintln!("existing config seems to have specified mixnet contract address which was only introduced in 0.10.0! Can't perform upgrade."); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); - } - println!( - "Setting mixnet contract address to {}", - DEFAULT_MIXNET_CONTRACT_ADDRESS + "Setting validator API endpoints to {:?}", + default_api_endpoints() ); config .get_base_mut() - .set_mixnet_contract(DEFAULT_MIXNET_CONTRACT_ADDRESS); - - // The default validator endpoint changed - println!( - "Setting validator REST endpoint to to {:?}", - default_validator_rest_endpoints() - ); - - config - .get_base_mut() - .set_custom_validators(default_validator_rest_endpoints()); - - config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); - }); - - print_successful_upgrade(config_version, to_version); - - config -} - -// no changes but version number -fn patch_010_upgrade( - mut config: Config, - _matches: &ArgMatches, - config_version: &Version, - package_version: &Version, -) -> Config { - let to_version = package_version; - - print_start_upgrade(&config_version, &to_version); - - config - .get_base_mut() - .set_custom_version(to_version.to_string().as_ref()); - - config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); - }); - - print_successful_upgrade(config_version, to_version); - - config -} - -fn minor_011_upgrade( - mut config: Config, - _matches: &ArgMatches, - config_version: &Version, - package_version: &Version, -) -> Config { - let to_version = package_version; - - print_start_upgrade(&config_version, &to_version); - - println!( - "Setting mixnet contract address to {}", - DEFAULT_MIXNET_CONTRACT_ADDRESS - ); - - config - .get_base_mut() - .set_mixnet_contract(DEFAULT_MIXNET_CONTRACT_ADDRESS); - - // The default validator endpoint changed - println!( - "Setting validator REST endpoint to {:?}", - default_validator_rest_endpoints() - ); - - config - .get_base_mut() - .set_custom_validators(default_validator_rest_endpoints()); + .set_custom_validator_apis(default_api_endpoints()); config .get_base_mut() @@ -298,18 +142,11 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches, package_version: Version config = match config_version.major { 0 => match config_version.minor { - 9 => minor_010_upgrade(config, matches, &config_version, &Version::new(0, 10, 0)), - 10 => match config_version.patch { - 0 => { - patch_010_upgrade(config, matches, &config_version, &Version::new(0, 10, 1)) - } - _ => { - minor_011_upgrade(config, matches, &config_version, &Version::new(0, 11, 0)) - } - }, - _ => unsupported_upgrade(config_version, package_version), + 9 | 10 => outdated_upgrade(&config_version, &package_version), + 11 => minor_0_12_upgrade(config, matches, &config_version, &package_version), + _ => unsupported_upgrade(&config_version, &package_version), }, - _ => unsupported_upgrade(config_version, package_version), + _ => unsupported_upgrade(&config_version, &package_version), } } } @@ -319,22 +156,14 @@ pub fn execute(matches: &ArgMatches) { let id = matches.value_of("id").unwrap(); - let mut existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { + let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {:?}", err); process::exit(1) }); - // versions fields were added in 0.9.0 if existing_config.get_base().get_version() == MISSING_VALUE { - let self_reported_version = matches.value_of("current version").unwrap_or_else(|| { - eprintln!( - "trying to upgrade from pre v0.9.0 without providing current system version!" - ); - process::exit(1) - }); - - // upgrades up to 0.9.0 - existing_config = pre_090_upgrade(self_reported_version, existing_config); + eprintln!("the existing configuration file does not seem to contain version number."); + process::exit(1); } // here be upgrade path to 0.9.X and beyond based on version number from config diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 1326c26cc0..b494b510fb 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -20,10 +20,12 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0", features = ["derive"] } # for config serialization/deserialization snafu = "0.6" tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal"] } +url = "2.2" # internal client-core = { path = "../client-core" } coconut-interface = { path = "../../common/coconut-interface" } +credentials = { path = "../../common/credentials" } config = { path = "../../common/config" } crypto = { path = "../../common/crypto" } gateway-client = { path = "../../common/client-libs/gateway-client" } diff --git a/clients/socks5/src/client/config/mod.rs b/clients/socks5/src/client/config/mod.rs index d32a2a8b24..60b3c88ac0 100644 --- a/clients/socks5/src/client/config/mod.rs +++ b/clients/socks5/src/client/config/mod.rs @@ -4,6 +4,7 @@ use crate::client::config::template::config_template; use client_core::config::Config as BaseConfig; pub use client_core::config::MISSING_VALUE; +use config::defaults::DEFAULT_SOCKS5_LISTENING_PORT; use config::NymConfig; use nymsphinx::addressing::clients::Recipient; use serde::{Deserialize, Serialize}; @@ -11,8 +12,6 @@ use std::path::PathBuf; mod template; -const DEFAULT_LISTENING_PORT: u16 = 1080; - #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Config { @@ -103,7 +102,7 @@ pub struct Socks5 { impl Socks5 { pub fn new>(provider_mix_address: S) -> Self { Socks5 { - listening_port: DEFAULT_LISTENING_PORT, + listening_port: DEFAULT_SOCKS5_LISTENING_PORT, provider_mix_address: provider_mix_address.into(), } } @@ -112,7 +111,7 @@ impl Socks5 { impl Default for Socks5 { fn default() -> Self { Socks5 { - listening_port: DEFAULT_LISTENING_PORT, + listening_port: DEFAULT_SOCKS5_LISTENING_PORT, provider_mix_address: "".into(), } } diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs index b1412db7e0..0bdce7b650 100644 --- a/clients/socks5/src/client/config/template.rs +++ b/clients/socks5/src/client/config/template.rs @@ -19,16 +19,13 @@ version = '{{ client.version }}' # Human readable ID of this particular client. id = '{{ client.id }}' -# URL to the validator server for obtaining network topology. +# Addresses to APIs running on validator from which the client gets the view of the network. validator_rest_urls = [ - {{#each client.validator_rest_urls }} + {{#each client.validator_api_urls }} '{{this}}', {{/each}} ] -# Address of the validator contract managing the network. -mixnet_contract_address = '{{ client.mixnet_contract_address }}' - # Path to file containing private identity key. private_identity_key_file = '{{ client.private_identity_key_file }}' diff --git a/clients/socks5/src/client/mod.rs b/clients/socks5/src/client/mod.rs index c8eaa63e76..485866673a 100644 --- a/clients/socks5/src/client/mod.rs +++ b/clients/socks5/src/client/mod.rs @@ -24,6 +24,8 @@ use client_core::client::topology_control::{ }; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use coconut_interface::Credential; +use credentials::bandwidth::prepare_for_spending; +use credentials::obtain_aggregate_verification_key; use crypto::asymmetric::identity; use futures::channel::mpsc; use gateway_client::{ @@ -152,6 +154,30 @@ impl NymClient { .start(self.runtime.handle()) } + async fn prepare_credential(&self) -> Credential { + let verification_key = obtain_aggregate_verification_key( + &self.config.get_base().get_validator_api_endpoints(), + ) + .await + .expect("could not obtain aggregate verification key of validators"); + + let bandwidth_credential = credentials::bandwidth::obtain_signature( + &self.key_manager.identity_keypair().public_key().to_bytes(), + &self.config.get_base().get_validator_api_endpoints(), + ) + .await + .expect("could not obtain bandwidth credential"); + // the above would presumably be loaded from a file + + // the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff) + prepare_for_spending( + &self.key_manager.identity_keypair().public_key().to_bytes(), + &bandwidth_credential, + &verification_key, + ) + .expect("could not prepare out bandwidth credential for spending") + } + fn start_gateway_client( &mut self, mixnet_message_sender: MixnetMessageSender, @@ -170,12 +196,7 @@ impl NymClient { .expect("provided gateway id is invalid!"); self.runtime.block_on(async { - let coconut_credential = Credential::init( - self.config.get_base().get_validator_rest_endpoints(), - *self.key_manager.identity_keypair().public_key(), - ) - .await - .expect("Could not initialize coconut credential"); + let coconut_credential = self.prepare_credential().await; let mut gateway_client = GatewayClient::new( gateway_address, @@ -201,20 +222,14 @@ impl NymClient { // the current global view of topology fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) { let topology_refresher_config = TopologyRefresherConfig::new( - self.config.get_base().get_validator_rest_endpoints(), - self.config - .get_base() - .get_validator_mixnet_contract_address(), + self.config.get_base().get_validator_api_endpoints(), self.config.get_base().get_topology_refresh_rate(), ); let mut topology_refresher = TopologyRefresher::new(topology_refresher_config, topology_accessor); // before returning, block entire runtime to refresh the current network view so that any // components depending on topology would see a non-empty view - info!( - "Obtaining initial network topology from {}", - self.config.get_base().get_validator_rest_endpoints()[0] - ); + info!("Obtaining initial network topology"); self.runtime.block_on(topology_refresher.refresh()); // TODO: a slightly more graceful termination here diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index f7ef786301..99e8eff716 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -8,16 +8,19 @@ use client_core::client::key_manager::KeyManager; use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder; use coconut_interface::Credential; use config::NymConfig; +use credentials::bandwidth::prepare_for_spending; +use credentials::obtain_aggregate_verification_key; use crypto::asymmetric::{encryption, identity}; use gateway_client::GatewayClient; use gateway_requests::registration::handshake::SharedKeys; use nymsphinx::addressing::clients::Recipient; use nymsphinx::addressing::nodes::NodeIdentity; -use rand::{prelude::SliceRandom, rngs::OsRng}; +use rand::{prelude::SliceRandom, rngs::OsRng, thread_rng}; use std::convert::TryInto; use std::sync::Arc; use std::time::Duration; use topology::{filter::VersionFilterable, gateway}; +use url::Url; pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { App::new("init") @@ -44,11 +47,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) - .arg(Arg::with_name("mixnet-contract") - .long("mixnet-contract") - .help("Address of the validator contract managing the network") - .takes_value(true), - ) .arg(Arg::with_name("port") .short("p") .long("port") @@ -62,15 +60,29 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { ) } +// this behaviour should definitely be changed, we shouldn't +// need to get bandwidth credential for registration +async fn prepare_temporary_credential(validators: &[Url], raw_identity: &[u8]) -> Credential { + let verification_key = obtain_aggregate_verification_key(validators) + .await + .expect("could not obtain aggregate verification key of validators"); + + let bandwidth_credential = credentials::bandwidth::obtain_signature(raw_identity, validators) + .await + .expect("could not obtain bandwidth credential"); + + prepare_for_spending(raw_identity, &bandwidth_credential, &verification_key) + .expect("could not prepare out bandwidth credential for spending") +} + async fn register_with_gateway( gateway: &gateway::Node, our_identity: Arc, - validator_urls: Vec, + validator_urls: Vec, ) -> SharedKeys { let timeout = Duration::from_millis(1500); - let coconut_credential = Credential::init(validator_urls, *our_identity.public_key()) - .await - .expect("Could not initialize coconut credential"); + let coconut_credential = + prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await; let mut gateway_client = GatewayClient::new_init( gateway.clients_address(), gateway.identity_key, @@ -89,12 +101,13 @@ async fn register_with_gateway( } async fn gateway_details( - validator_servers: Vec, - mixnet_contract: &str, + validator_servers: Vec, chosen_gateway_id: Option<&str>, ) -> gateway::Node { - let validator_client_config = validator_client::Config::new(validator_servers, mixnet_contract); - let validator_client = validator_client::Client::new(validator_client_config); + let validator_api = validator_servers + .choose(&mut thread_rng()) + .expect("The list of validator apis is empty"); + let validator_client = validator_client::ApiClient::new(validator_api.clone()); let gateways = validator_client.get_cached_gateways().await.unwrap(); let valid_gateways = gateways @@ -191,15 +204,14 @@ pub fn execute(matches: &ArgMatches) { let registration_fut = async { let gate_details = gateway_details( - config.get_base().get_validator_rest_endpoints(), - &config.get_base().get_validator_mixnet_contract_address(), + config.get_base().get_validator_api_endpoints(), chosen_gateway_id, ) .await; config .get_base_mut() .with_gateway_id(gate_details.identity_key.to_base58_string()); - let validator_urls = config.get_base().get_validator_rest_endpoints(); + let validator_urls = config.get_base().get_validator_api_endpoints(); let shared_keys = register_with_gateway( &gate_details, key_manager.identity_keypair(), diff --git a/clients/socks5/src/commands/mod.rs b/clients/socks5/src/commands/mod.rs index eefd717e2c..7b6ff6c4b5 100644 --- a/clients/socks5/src/commands/mod.rs +++ b/clients/socks5/src/commands/mod.rs @@ -3,14 +3,20 @@ use crate::client::config::Config; use clap::ArgMatches; +use url::Url; pub(crate) mod init; pub(crate) mod run; pub(crate) mod upgrade; -fn parse_validators(raw: &str) -> Vec { +fn parse_validators(raw: &str) -> Vec { raw.split(',') - .map(|raw_validator| raw_validator.trim().into()) + .map(|raw_validator| { + raw_validator + .trim() + .parse() + .expect("one of the provided validator api urls is invalid") + }) .collect() } @@ -18,11 +24,7 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi if let Some(raw_validators) = matches.value_of("validators") { config .get_base_mut() - .set_custom_validators(parse_validators(raw_validators)); - } - - if let Some(contract_address) = matches.value_of("mixnet-contract") { - config.get_base_mut().set_mixnet_contract(contract_address) + .set_custom_validator_apis(parse_validators(raw_validators)); } if let Some(gateway_id) = matches.value_of("gateway") { diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index 173a6e4856..facd85144f 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -34,11 +34,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) - .arg(Arg::with_name("mixnet-contract") - .long("mixnet-contract") - .help("Address of the validator contract managing the network") - .takes_value(true), - ) .arg(Arg::with_name("gateway") .long("gateway") .help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened") diff --git a/clients/socks5/src/commands/upgrade.rs b/clients/socks5/src/commands/upgrade.rs index bd4cda6f71..c8a3bc44a5 100644 --- a/clients/socks5/src/commands/upgrade.rs +++ b/clients/socks5/src/commands/upgrade.rs @@ -3,12 +3,17 @@ use crate::client::config::{Config, MISSING_VALUE}; use clap::{App, Arg, ArgMatches}; -use client_core::config::default_validator_rest_endpoints; -use config::defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS; +use config::defaults::default_api_endpoints; use config::NymConfig; use std::fmt::Display; use std::process; -use version_checker::{parse_version, Version}; +use version_checker::Version; + +#[allow(dead_code)] +fn fail_upgrade(from_version: D1, to_version: D2) -> ! { + print_failed_upgrade(from_version, to_version); + process::exit(1) +} fn print_start_upgrade(from: D1, to: D2) { println!( @@ -31,28 +36,29 @@ fn print_successful_upgrade(from: D1, to: D2) { ); } -pub fn command_args<'a, 'b>() -> App<'a, 'b> { - App::new("upgrade").about("Try to upgrade the client") - .arg( - Arg::with_name("id") - .long("id") - .help("Id of the nym-socks5-client we want to upgrade") - .takes_value(true) - .required(true), - ) - // the rest of arguments depend on the upgrade path - .arg(Arg::with_name("current version") - .long("current-version") - .help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'") - .takes_value(true) - ) +fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! { + eprintln!( + "Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!", + config_version, package_version + ); + process::exit(1) } -fn unsupported_upgrade(config_version: Version, package_version: Version) -> ! { - eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, package_version); +fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! { + eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, current_version); process::exit(1) } +pub fn command_args<'a, 'b>() -> App<'a, 'b> { + App::new("upgrade").about("Try to upgrade the client").arg( + Arg::with_name("id") + .long("id") + .help("Id of the nym-client we want to upgrade") + .takes_value(true) + .required(true), + ) +} + fn parse_config_version(config: &Config) -> Version { let version = Version::parse(config.get_base().get_version()).unwrap_or_else(|err| { eprintln!("failed to parse client version! - {:?}", err); @@ -87,190 +93,28 @@ fn parse_package_version() -> Version { version } -fn pre_090_upgrade(from: &str, mut config: Config) -> Config { - // this is not extracted to separate function as you only have to manually pass version - // if upgrading from pre090 version - let from = match from.strip_prefix('v') { - Some(stripped) => stripped, - None => from, - }; - - let from = match from.strip_prefix('V') { - Some(stripped) => stripped, - None => from, - }; - - let from_version = parse_version(from).expect("invalid version provided!"); - if from_version.major == 0 && from_version.minor < 8 { - // technically this could be implemented, but is there any point in that? - eprintln!("upgrading client from before v0.8.0 is not supported. Please run `init` with new binary instead"); - process::exit(1) - } - - if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 { - eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version"); - process::exit(1) - } - - // note: current is guaranteed to not have any `build` information suffix (nor pre-release - // information), as this was asserted at the beginning of this command) - // - // upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate - // step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0) - // this way we don't need to have all the crazy paths on how to upgrade from any version to any - // other version. We just upgrade one minor version at a time. - let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); - let to_version = if current.major == 0 && current.minor == 9 { - current - } else { - Version::new(0, 9, 0) - }; - - if config.get_base().get_validator_rest_endpoints()[0] != MISSING_VALUE { - eprintln!("existing config seems to have specified new validator rest endpoint which was only introduced in 0.9.0! Can't perform upgrade."); - print_failed_upgrade(&from_version, &to_version); - process::exit(1); - } - - print_start_upgrade(&from_version, &to_version); - - config - .get_base_mut() - .set_custom_version(to_version.to_string().as_ref()); - - println!( - "Setting validator REST endpoint to {:?}", - default_validator_rest_endpoints() - ); - - config - .get_base_mut() - .set_custom_validators(default_validator_rest_endpoints()); - - config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&from_version, &to_version); - process::exit(1); - }); - - print_successful_upgrade(from_version, to_version); - - config -} - -/* -changes: -- introduction of mixnet contract address field -- change to default validator rest endpoint - */ -fn minor_010_upgrade( +fn minor_0_12_upgrade( mut config: Config, _matches: &ArgMatches, config_version: &Version, package_version: &Version, ) -> Config { - let to_version = if package_version.major == 0 && package_version.minor == 10 { + let to_version = if package_version.major == 0 && package_version.minor == 12 { package_version.clone() } else { - Version::new(0, 10, 0) + Version::new(0, 12, 0) }; print_start_upgrade(&config_version, &to_version); - config - .get_base_mut() - .set_custom_version(to_version.to_string().as_ref()); - - if config.get_base().get_validator_mixnet_contract_address() != MISSING_VALUE { - eprintln!("existing config seems to have specified mixnet contract address which was only introduced in 0.10.0! Can't perform upgrade."); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); - } - println!( - "Setting mixnet contract address to {}", - DEFAULT_MIXNET_CONTRACT_ADDRESS + "Setting validator API endpoints to {:?}", + default_api_endpoints() ); config .get_base_mut() - .set_mixnet_contract(DEFAULT_MIXNET_CONTRACT_ADDRESS); - - // The default validator endpoint changed - println!( - "Setting validator REST endpoint to to {:?}", - default_validator_rest_endpoints() - ); - - config - .get_base_mut() - .set_custom_validators(default_validator_rest_endpoints()); - - config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); - }); - - print_successful_upgrade(config_version, to_version); - - config -} - -// no changes but version number -fn patch_010_upgrade( - mut config: Config, - _matches: &ArgMatches, - config_version: &Version, - package_version: &Version, -) -> Config { - let to_version = package_version; - - print_start_upgrade(&config_version, &to_version); - - config - .get_base_mut() - .set_custom_version(to_version.to_string().as_ref()); - - config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); - }); - - print_successful_upgrade(config_version, to_version); - - config -} - -fn minor_011_upgrade( - mut config: Config, - _matches: &ArgMatches, - config_version: &Version, - package_version: &Version, -) -> Config { - let to_version = package_version; - - print_start_upgrade(&config_version, &to_version); - - println!( - "Setting mixnet contract address to {}", - DEFAULT_MIXNET_CONTRACT_ADDRESS - ); - - config - .get_base_mut() - .set_mixnet_contract(DEFAULT_MIXNET_CONTRACT_ADDRESS); - - // The default validator endpoint changed - println!( - "Setting validator REST endpoint to {:?}", - default_validator_rest_endpoints() - ); - - config - .get_base_mut() - .set_custom_validators(default_validator_rest_endpoints()); + .set_custom_validator_apis(default_api_endpoints()); config .get_base_mut() @@ -298,18 +142,11 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches, package_version: Version config = match config_version.major { 0 => match config_version.minor { - 9 => minor_010_upgrade(config, matches, &config_version, &Version::new(0, 10, 0)), - 10 => match config_version.patch { - 0 => { - patch_010_upgrade(config, matches, &config_version, &Version::new(0, 10, 1)) - } - _ => { - minor_011_upgrade(config, matches, &config_version, &Version::new(0, 11, 0)) - } - }, - _ => unsupported_upgrade(config_version, package_version), + 9 | 10 => outdated_upgrade(&config_version, &package_version), + 11 => minor_0_12_upgrade(config, matches, &config_version, &package_version), + _ => unsupported_upgrade(&config_version, &package_version), }, - _ => unsupported_upgrade(config_version, package_version), + _ => unsupported_upgrade(&config_version, &package_version), } } } @@ -319,22 +156,14 @@ pub fn execute(matches: &ArgMatches) { let id = matches.value_of("id").unwrap(); - let mut existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { + let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {:?}", err); process::exit(1) }); - // versions fields were added in 0.9.0 if existing_config.get_base().get_version() == MISSING_VALUE { - let self_reported_version = matches.value_of("current version").unwrap_or_else(|| { - eprintln!( - "trying to upgrade from pre v0.9.0 without providing current system version!" - ); - process::exit(1) - }); - - // upgrades up to 0.9.0 - existing_config = pre_090_upgrade(self_reported_version, existing_config); + eprintln!("the existing configuration file does not seem to contain version number."); + process::exit(1); } // here be upgrade path to 0.9.X and beyond based on version number from config diff --git a/clients/tauri-client/src-tauri/Cargo.toml b/clients/tauri-client/src-tauri/Cargo.toml index 5dc3d1b3de..c39fd4e600 100644 --- a/clients/tauri-client/src-tauri/Cargo.toml +++ b/clients/tauri-client/src-tauri/Cargo.toml @@ -19,9 +19,11 @@ serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.0.0-beta.4", features = [] } tokio = "1.4" +url = "2.2" coconut-interface = { path = "../../../common/coconut-interface" } -gateway-client = {path = "../../../common/client-libs/gateway-client"} +credentials = { path = "../../../common/credentials" } +validator-client = {path = "../../../common/client-libs/validator-client"} [features] default = ["custom-protocol"] diff --git a/clients/tauri-client/src-tauri/src/main.rs b/clients/tauri-client/src-tauri/src/main.rs index ab8dd07821..7c5bb06df7 100644 --- a/clients/tauri-client/src-tauri/src/main.rs +++ b/clients/tauri-client/src-tauri/src/main.rs @@ -4,26 +4,58 @@ )] use coconut_interface::{ - self, get_aggregated_verification_key, Credential, Signature, State, ValidatorAPIClient, + self, Attribute, Credential, Parameters, Signature, Theta, VerificationKey, }; +use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key}; use std::sync::Arc; use tokio::sync::RwLock; +use url::Url; + +struct State { + signatures: Vec, + n_attributes: u32, + params: Parameters, + public_attributes: Vec, + private_attributes: Vec, + aggregated_verification_key: Option, +} + +impl State { + fn init(public_attributes: Vec, private_attributes: Vec) -> State { + let n_attributes = (public_attributes.len() + private_attributes.len()) as u32; + let params = Parameters::new(n_attributes).unwrap(); + State { + signatures: Vec::new(), + n_attributes, + params, + public_attributes, + private_attributes, + aggregated_verification_key: None, + } + } +} + +fn parse_url_validators(raw: &[String]) -> Result, String> { + let mut parsed_urls = Vec::with_capacity(raw.len()); + for url in raw { + let parsed_url: Url = url + .parse() + .map_err(|err| format!("one of validator urls is malformed - {}", err))?; + parsed_urls.push(parsed_url) + } + Ok(parsed_urls) +} #[tauri::command] async fn randomise_credential( idx: usize, state: tauri::State<'_, Arc>>, ) -> Result, String> { - { - let mut state = state.write().await; - let signature = state.signatures.remove(idx); - let new = signature.randomise(&state.params); - state.signatures.insert(idx, new); - } - { - let state = state.read().await; - Ok(state.signatures.clone()) - } + let mut state = state.write().await; + let signature = state.signatures.remove(idx); + let new = signature.randomise(&state.params); + state.signatures.insert(idx, new); + Ok(state.signatures.clone()) } #[tauri::command] @@ -31,14 +63,9 @@ async fn delete_credential( idx: usize, state: tauri::State<'_, Arc>>, ) -> Result, String> { - { - let mut state = state.write().await; - let _ = state.signatures.remove(idx); - } - { - let state = state.read().await; - Ok(state.signatures.clone()) - } + let mut state = state.write().await; + state.signatures.remove(idx); + Ok(state.signatures.clone()) } #[tauri::command] @@ -49,24 +76,67 @@ async fn list_credentials( Ok(state.signatures.clone()) } +async fn get_aggregated_verification_key( + validator_urls: Vec, + state: tauri::State<'_, Arc>>, +) -> Result { + if let Some(verification_key) = &state.read().await.aggregated_verification_key { + return Ok(verification_key.clone()); + } + + let parsed_urls = parse_url_validators(&validator_urls)?; + let key = obtain_aggregate_verification_key(&parsed_urls) + .await + .map_err(|err| format!("failed to obtain aggregate verification key - {:?}", err))?; + + state + .write() + .await + .aggregated_verification_key + .replace(key.clone()); + + Ok(key) +} + +async fn prove_credential( + idx: usize, + validator_urls: Vec, + state: tauri::State<'_, Arc>>, +) -> Result { + let verification_key = get_aggregated_verification_key(validator_urls, state.clone()).await?; + let state = state.read().await; + + if let Some(signature) = state.signatures.get(idx) { + match coconut_interface::prove_credential( + &state.params, + &verification_key, + signature, + &state.private_attributes, + ) { + Ok(theta) => Ok(theta), + Err(e) => Err(format!("{}", e)), + } + } else { + Err("Got invalid Signature idx".to_string()) + } +} + #[tauri::command] async fn verify_credential( idx: usize, validator_urls: Vec, state: tauri::State<'_, Arc>>, ) -> Result { - let state = state.read().await; + // the API needs to be improved but at least it should compile (in theory) let verification_key = - get_aggregated_verification_key(validator_urls, &ValidatorAPIClient::default()) - .await - .map_err(|e| format!("{:?}", e))?; - let theta = coconut_interface::prove_credential(idx, &verification_key, &*state) - .await - .map_err(|e| format!("{:?}", e))?; + get_aggregated_verification_key(validator_urls.clone(), state.clone()).await?; + let theta = prove_credential(idx, validator_urls, state.clone()).await?; + + let state = state.read().await; let credential = Credential::new( state.n_attributes, - &theta, + theta, &state.public_attributes, state .signatures @@ -82,33 +152,31 @@ async fn get_credential( validator_urls: Vec, state: tauri::State<'_, Arc>>, ) -> Result, String> { - let signature = { - let state = state.read().await; - coconut_interface::get_aggregated_signature( - validator_urls, - &*state, - &ValidatorAPIClient::default(), - ) - .await - .map_err(|e| format!("{:?}", e))? - }; - { - let mut state = state.write().await; - state.signatures.push(signature); - } - { - let state = state.read().await; - Ok(state.signatures.clone()) - } + let guard = state.read().await; + let parsed_urls = parse_url_validators(&validator_urls)?; + + let signature = obtain_aggregate_signature( + &guard.params, + &guard.public_attributes, + &guard.private_attributes, + &parsed_urls, + ) + .await + .map_err(|err| format!("failed to obtain aggregate signature - {:?}", err))?; + + let mut state = state.write().await; + state.signatures.push(signature); + Ok(state.signatures.clone()) } fn main() { let public_attributes = vec![coconut_interface::hash_to_scalar("public_key")]; let private_attributes = vec![coconut_interface::hash_to_scalar("private_key")]; tauri::Builder::default() - .manage(Arc::new(RwLock::new( - State::init(public_attributes, private_attributes).unwrap(), - ))) + .manage(Arc::new(RwLock::new(State::init( + public_attributes, + private_attributes, + )))) .invoke_handler(tauri::generate_handler![ get_credential, randomise_credential, diff --git a/clients/webassembly/Cargo.toml b/clients/webassembly/Cargo.toml index 9e26fd4d9c..41e5226798 100644 --- a/clients/webassembly/Cargo.toml +++ b/clients/webassembly/Cargo.toml @@ -22,9 +22,11 @@ wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } wasm-bindgen-futures = "0.4" js-sys = "0.3" rand = { version = "0.7.3", features = ["wasm-bindgen"] } +url = "2.2" # internal coconut-interface = { path = "../../common/coconut-interface" } +credentials = { path = "../../common/credentials" } crypto = { path = "../../common/crypto" } nymsphinx = { path = "../../common/nymsphinx" } topology = { path = "../../common/topology" } diff --git a/clients/webassembly/src/client/mod.rs b/clients/webassembly/src/client/mod.rs index 3be63dc9f9..bda0774024 100644 --- a/clients/webassembly/src/client/mod.rs +++ b/clients/webassembly/src/client/mod.rs @@ -2,18 +2,20 @@ // SPDX-License-Identifier: Apache-2.0 use coconut_interface::Credential; +use credentials::bandwidth::prepare_for_spending; +use credentials::obtain_aggregate_verification_key; use crypto::asymmetric::{encryption, identity}; use futures::channel::mpsc; use gateway_client::GatewayClient; use nymsphinx::acknowledgements::AckKey; use nymsphinx::addressing::clients::Recipient; -use nymsphinx::params::PacketMode; use nymsphinx::preparer::MessagePreparer; use rand::rngs::OsRng; use received_processor::ReceivedMessagesProcessor; use std::sync::Arc; use std::time::Duration; use topology::{gateway, nym_topology_from_bonds, NymTopology}; +use url::Url; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::spawn_local; use wasm_utils::{console_log, console_warn}; @@ -26,8 +28,7 @@ const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500); #[wasm_bindgen] pub struct NymClient { - validator_server: String, - mixnet_contract_address: String, + validator_server: Url, // 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 @@ -52,7 +53,7 @@ pub struct NymClient { #[wasm_bindgen] impl NymClient { #[wasm_bindgen(constructor)] - pub fn new(validator_server: String, mixnet_contract_address: String) -> Self { + pub fn new(validator_server: String) -> Self { let mut rng = OsRng; // for time being generate new keys each time... let identity = identity::KeyPair::new(&mut rng); @@ -63,8 +64,9 @@ impl NymClient { identity: Arc::new(identity), encryption_keys: Arc::new(encryption_keys), ack_key: Arc::new(ack_key), - validator_server, - mixnet_contract_address, + validator_server: validator_server + .parse() + .expect("malformed validator server url provided"), message_preparer: None, // received_keys: Default::default(), topology: None, @@ -99,6 +101,22 @@ impl NymClient { self.self_recipient().to_string() } + async fn prepare_credential(validators: &[Url], identity_bytes: &[u8]) -> Credential { + let verification_key = obtain_aggregate_verification_key(validators) + .await + .expect("could not obtain aggregate verification key of validators"); + + let bandwidth_credential = + credentials::bandwidth::obtain_signature(identity_bytes, validators) + .await + .expect("could not obtain bandwidth credential"); + // the above would presumably be loaded from a file + + // the below would only be executed once we know where we want to spend it (i.e. which gateway and stuff) + prepare_for_spending(identity_bytes, &bandwidth_credential, &verification_key) + .expect("could not prepare out bandwidth credential for spending") + } + // Right now it's impossible to have async exported functions to take `&self` rather than self pub async fn initial_setup(self) -> Self { let validator_server = self.validator_server.clone(); @@ -109,10 +127,9 @@ impl NymClient { let (mixnet_messages_sender, mixnet_messages_receiver) = mpsc::unbounded(); let (ack_sender, ack_receiver) = mpsc::unbounded(); - let coconut_credential = Credential::init(vec![validator_server], identity_public_key) - .await - .expect("Could not initialize coconut credential"); - + let coconut_credential = + Self::prepare_credential(&vec![validator_server], &identity_public_key.to_bytes()) + .await; let mut gateway_client = GatewayClient::new( gateway.clients_address(), Arc::clone(&client.identity), @@ -244,13 +261,9 @@ impl NymClient { // } pub(crate) async fn get_nym_topology(&self) -> NymTopology { - 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); + let validator_client = validator_client::ApiClient::new(self.validator_server.clone()); - let mixnodes = match validator_client.get_cached_mix_nodes().await { + let mixnodes = match validator_client.get_cached_mixnodes().await { Err(err) => panic!("{}", err), Ok(mixes) => mixes, }; diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 40bcd88e1e..d811bcf5ef 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -11,12 +11,13 @@ base64 = "0.13" mixnet-contract = { path="../../../common/mixnet-contract" } serde = { version="1", features=["derive"] } serde_json = "1" -rand = "0.8" reqwest = { version="0.11", features=["json"] } thiserror = "1" log = "0.4" -url = "2" -fluvio-wasm-timer = "0.2.5" +url = { version = "2.2", features = ["serde"] } + +coconut-interface = { path = "../../coconut-interface" } +network-defaults = { path = "../../network-defaults" } # required for nymd-client # at some point it might be possible to make it wasm-compatible @@ -31,13 +32,5 @@ sha2 = { version = "0.9.5", optional = true } itertools = { version = "0.10", optional = true } cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true } -[target."cfg(target_arch = \"wasm32\")".dependencies.getrandom] -version = "0.2" -features = ["js"] - -[dev-dependencies] -tokio = {version = "1.5", features = ["full"]} - [features] -#default = ["nymd-client"] nymd-client = ["async-trait", "bip39", "config", "cosmos_sdk", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"] diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs new file mode 100644 index 0000000000..595b97c185 --- /dev/null +++ b/common/client-libs/validator-client/src/client.rs @@ -0,0 +1,329 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(feature = "nymd-client")] +use crate::nymd::{ + error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient, +}; +use crate::{validator_api, ValidatorClientError}; +use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse}; +use mixnet_contract::{GatewayBond, MixNodeBond}; +use url::Url; + +#[cfg(feature = "nymd-client")] +pub struct Config { + api_url: Url, + nymd_url: Url, + mixnet_contract_address: Option, + + mixnode_page_limit: Option, + gateway_page_limit: Option, + mixnode_delegations_page_limit: Option, + gateway_delegations_page_limit: Option, +} + +#[cfg(feature = "nymd-client")] +impl Config { + pub fn new( + nymd_url: Url, + api_url: Url, + mixnet_contract_address: Option, + ) -> Self { + Config { + nymd_url, + mixnet_contract_address, + api_url, + mixnode_page_limit: None, + gateway_page_limit: None, + mixnode_delegations_page_limit: None, + gateway_delegations_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 fn with_mixnode_delegations_page_limit(mut self, limit: Option) -> Config { + self.mixnode_delegations_page_limit = limit; + self + } + + pub fn with_gateway_delegations_page_limit(mut self, limit: Option) -> Config { + self.gateway_delegations_page_limit = limit; + self + } +} + +#[cfg(feature = "nymd-client")] +pub struct Client { + mixnet_contract_address: Option, + mnemonic: Option, + + mixnode_page_limit: Option, + gateway_page_limit: Option, + mixnode_delegations_page_limit: Option, + gateway_delegations_page_limit: Option, + + // ideally they would have been read-only, but unfortunately rust doesn't have such features + pub validator_api: validator_api::Client, + pub nymd: NymdClient, +} + +#[cfg(feature = "nymd-client")] +impl Client { + pub fn new_signing( + config: Config, + mnemonic: bip39::Mnemonic, + ) -> Result, ValidatorClientError> { + let validator_api_client = validator_api::Client::new(config.api_url.clone()); + let nymd_client = NymdClient::connect_with_mnemonic( + config.nymd_url.as_str(), + config.mixnet_contract_address.clone(), + mnemonic.clone(), + )?; + + Ok(Client { + mixnet_contract_address: config.mixnet_contract_address, + mnemonic: Some(mnemonic), + mixnode_page_limit: config.mixnode_page_limit, + gateway_page_limit: config.gateway_page_limit, + mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, + gateway_delegations_page_limit: config.gateway_delegations_page_limit, + validator_api: validator_api_client, + nymd: nymd_client, + }) + } + + pub fn change_nymd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> { + self.nymd = NymdClient::connect_with_mnemonic( + new_endpoint.as_ref(), + self.mixnet_contract_address.clone(), + self.mnemonic.clone().unwrap(), + )?; + Ok(()) + } +} + +#[cfg(feature = "nymd-client")] +impl Client { + pub fn new_query(config: Config) -> Result, ValidatorClientError> { + let validator_api_client = validator_api::Client::new(config.api_url.clone()); + let nymd_client = NymdClient::connect( + config.nymd_url.as_str(), + config + .mixnet_contract_address + .clone() + .ok_or(ValidatorClientError::NymdError( + NymdError::NoContractAddressAvailable, + ))?, + )?; + + Ok(Client { + mixnet_contract_address: config.mixnet_contract_address, + mnemonic: None, + mixnode_page_limit: config.mixnode_page_limit, + gateway_page_limit: config.gateway_page_limit, + mixnode_delegations_page_limit: config.mixnode_delegations_page_limit, + gateway_delegations_page_limit: config.gateway_delegations_page_limit, + validator_api: validator_api_client, + nymd: nymd_client, + }) + } + + pub fn change_nymd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> { + self.nymd = NymdClient::connect( + new_endpoint.as_ref(), + self.mixnet_contract_address.clone().unwrap(), + )?; + Ok(()) + } +} + +#[cfg(feature = "nymd-client")] +impl Client { + pub fn change_validator_api(&mut self, new_endpoint: Url) { + self.validator_api.change_url(new_endpoint) + } + + // use case: somebody initialised client without a contract in order to upload and initialise one + // and now they want to actually use it without making new client + pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmos_sdk::AccountId) { + self.mixnet_contract_address = Some(mixnet_contract_address) + } + + pub async fn get_cached_mixnodes(&self) -> Result, ValidatorClientError> { + Ok(self.validator_api.get_mixnodes().await?) + } + + pub async fn get_cached_gateways(&self) -> Result, ValidatorClientError> { + Ok(self.validator_api.get_gateways().await?) + } + + // basically handles paging for us + pub async fn get_all_nymd_mixnodes(&self) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut mixnodes = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .nymd + .get_mixnodes_paged(start_after.take(), self.mixnode_page_limit) + .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) + } + + pub async fn get_all_nymd_gateways(&self) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut gateways = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .nymd + .get_gateways_paged(start_after.take(), self.gateway_page_limit) + .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) + } + + pub async fn get_all_nymd_mixnode_delegations( + &self, + identity: mixnet_contract::IdentityKey, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut delegations = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .nymd + .get_mix_delegations_paged( + identity.clone(), + start_after.take(), + self.mixnode_delegations_page_limit, + ) + .await?; + delegations.append(&mut paged_response.delegations); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(delegations) + } + + pub async fn get_all_nymd_gateway_delegations( + &self, + identity: mixnet_contract::IdentityKey, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut delegations = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .nymd + .get_gateway_delegations( + identity.clone(), + start_after.take(), + self.gateway_delegations_page_limit, + ) + .await?; + delegations.append(&mut paged_response.delegations); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(delegations) + } + + pub async fn blind_sign( + &self, + request_body: &BlindSignRequestBody, + ) -> Result { + Ok(self.validator_api.blind_sign(request_body).await?) + } + + pub async fn get_coconut_verification_key( + &self, + ) -> Result { + Ok(self.validator_api.get_coconut_verification_key().await?) + } +} + +pub struct ApiClient { + pub validator_api: validator_api::Client, + // TODO: perhaps if we really need it at some (currently I don't see any reasons for it) + // we could re-implement the communication with the REST API on port 1317 +} + +impl ApiClient { + pub fn new(api_url: Url) -> Self { + let validator_api_client = validator_api::Client::new(api_url); + + ApiClient { + validator_api: validator_api_client, + } + } + + pub fn change_validator_api(&mut self, new_endpoint: Url) { + self.validator_api.change_url(new_endpoint); + } + + pub async fn get_cached_mixnodes(&self) -> Result, ValidatorClientError> { + Ok(self.validator_api.get_mixnodes().await?) + } + + pub async fn get_cached_gateways(&self) -> Result, ValidatorClientError> { + Ok(self.validator_api.get_gateways().await?) + } + + pub async fn blind_sign( + &self, + request_body: &BlindSignRequestBody, + ) -> Result { + Ok(self.validator_api.blind_sign(request_body).await?) + } + + pub async fn get_coconut_verification_key( + &self, + ) -> Result { + Ok(self.validator_api.get_coconut_verification_key().await?) + } +} diff --git a/common/client-libs/validator-client/src/error.rs b/common/client-libs/validator-client/src/error.rs index 4ae82b5599..692605bc44 100644 --- a/common/client-libs/validator-client/src/error.rs +++ b/common/client-libs/validator-client/src/error.rs @@ -1,216 +1,21 @@ -#[cfg(feature = "nymd-client")] -use crate::nymd::cosmwasm_client::types::ContractCodeId; +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use crate::validator_api; -#[cfg(feature = "nymd-client")] -use cosmos_sdk::tendermint::block; -#[cfg(feature = "nymd-client")] -use cosmos_sdk::{bip32, rpc, tx, AccountId}; -use serde::Deserialize; -use std::io; use thiserror::Error; #[derive(Error, Debug)] pub enum ValidatorClientError { - #[error("There was an issue with the REST request - {source}")] - ReqwestClientError { - #[from] - source: reqwest::Error, - }, - - #[error("There was an issue with the validator-api request - {source}")] + #[error("There was an issue with the validator api request - {source}")] ValidatorAPIError { #[from] - source: validator_api::error::ValidatorAPIClientError, + source: validator_api::error::ValidatorAPIError, }, - #[error("An IO error has occured: {source}")] - IoError { - #[from] - source: io::Error, - }, - - #[error("There was an issue with the validator client - {0}")] - ValidatorError(String), + #[error("One of the provided URLs was malformed - {0}")] + MalformedUrlProvided(#[from] url::ParseError), #[cfg(feature = "nymd-client")] - #[error("No contract address is available to perform the call")] - NoContractAddressAvailable, - - #[cfg(feature = "nymd-client")] - #[error("There was an issue with bip32 - {0}")] - Bip32Error(bip32::Error), - - #[cfg(feature = "nymd-client")] - #[error("There was an issue with bip32 - {0}")] - Bip39Error(bip39::Error), - - #[cfg(feature = "nymd-client")] - #[error("Failed to derive account address")] - AccountDerivationError, - - #[cfg(feature = "nymd-client")] - #[error("Address {0} was not found in the wallet")] - SigningAccountNotFound(AccountId), - - #[cfg(feature = "nymd-client")] - #[error("Failed to sign raw transaction")] - SigningFailure, - - #[cfg(feature = "nymd-client")] - #[error("{0} is not a valid tx hash")] - InvalidTxHash(String), - - #[cfg(feature = "nymd-client")] - #[error("There was an issue with a tendermint RPC request - {0}")] - TendermintError(rpc::Error), - - #[cfg(feature = "nymd-client")] - #[error("There was an issue when attempting to serialize data")] - SerializationError(String), - - #[cfg(feature = "nymd-client")] - #[error("There was an issue when attempting to deserialize data")] - DeserializationError(String), - - #[cfg(feature = "nymd-client")] - #[error("There was an issue when attempting to encode our protobuf data - {0}")] - ProtobufEncodingError(prost::EncodeError), - - #[cfg(feature = "nymd-client")] - #[error("There was an issue when attempting to decode our protobuf data - {0}")] - ProtobufDecodingError(prost::DecodeError), - - #[cfg(feature = "nymd-client")] - #[error("Account {0} does not exist on the chain")] - NonExistentAccountError(AccountId), - - #[cfg(feature = "nymd-client")] - #[error("There was an issue with the serialization/deserialization - {0}")] - SerdeJsonError(serde_json::Error), - - #[cfg(feature = "nymd-client")] - #[error("Account {0} is not a valid account address")] - MalformedAccountAddress(String), - - #[cfg(feature = "nymd-client")] - #[error("Account {0} has an invalid associated public key")] - InvalidPublicKey(AccountId), - - #[cfg(feature = "nymd-client")] - #[error("Queried contract (code_id: {0}) did not have any code information attached")] - NoCodeInformation(ContractCodeId), - - #[cfg(feature = "nymd-client")] - #[error("Queried contract (address: {0}) did not have any contract information attached")] - NoContractInformation(AccountId), - - #[cfg(feature = "nymd-client")] - #[error("Contract contains invalid operations in its history")] - InvalidContractHistoryOperation, - - #[cfg(feature = "nymd-client")] - #[error("Block has an invalid height (either negative or larger than i64::MAX")] - InvalidHeight, - - #[cfg(feature = "nymd-client")] - #[error("Failed to compress provided wasm code - {0}")] - WasmCompressionError(io::Error), - - #[cfg(feature = "nymd-client")] - #[error("Logs returned from the validator were malformed")] - MalformedLogString, - - #[cfg(feature = "nymd-client")] - #[error( - "Error when broadcasting tx {hash} at height {height}. Error occurred during CheckTx phase. Code: {code}; Raw log: {raw_log}" - )] - BroadcastTxErrorCheckTx { - hash: tx::Hash, - height: block::Height, - code: u32, - raw_log: String, - }, - - #[cfg(feature = "nymd-client")] - #[error( - "Error when broadcasting tx {hash} at height {height}. Error occurred during DeliverTx phase. Code: {code}; Raw log: {raw_log}" - )] - BroadcastTxErrorDeliverTx { - hash: tx::Hash, - height: block::Height, - code: u32, - raw_log: String, - }, - - #[cfg(feature = "nymd-client")] - #[error("The provided gas price is malformed")] - MalformedGasPrice, -} - -#[cfg(feature = "nymd-client")] -impl From for ValidatorClientError { - fn from(err: bip32::Error) -> Self { - ValidatorClientError::Bip32Error(err) - } -} - -#[cfg(feature = "nymd-client")] -impl From for ValidatorClientError { - fn from(err: bip39::Error) -> Self { - ValidatorClientError::Bip39Error(err) - } -} - -#[cfg(feature = "nymd-client")] -impl From for ValidatorClientError { - fn from(err: rpc::Error) -> Self { - ValidatorClientError::TendermintError(err) - } -} - -#[cfg(feature = "nymd-client")] -impl From for ValidatorClientError { - fn from(err: prost::EncodeError) -> Self { - ValidatorClientError::ProtobufEncodingError(err) - } -} - -#[cfg(feature = "nymd-client")] -impl From for ValidatorClientError { - fn from(err: prost::DecodeError) -> Self { - ValidatorClientError::ProtobufDecodingError(err) - } -} - -#[cfg(feature = "nymd-client")] -impl From for ValidatorClientError { - fn from(err: serde_json::Error) -> Self { - ValidatorClientError::SerdeJsonError(err) - } -} - -// this is the case of message like -/* -{ - "code": 12, - "message": "Not Implemented", - "details": [ - ] -} - */ -// I didn't manage to find where it exactly originates, nor what the correct types should be -// so all of those are some educated guesses - -#[derive(Error, Debug, Deserialize)] -#[error("code: {code} - {message}")] -pub(super) struct CodedError { - code: u32, - message: String, - details: Vec<(String, String)>, -} - -#[derive(Error, Deserialize, Debug)] -#[error("{error}")] -pub(super) struct SmartQueryError { - pub(super) error: String, + #[error("There was an issue with the Nymd client - {0}")] + NymdError(#[from] crate::nymd::error::NymdError), } diff --git a/common/client-libs/validator-client/src/lib.rs b/common/client-libs/validator-client/src/lib.rs index d5f512a79a..9ef577d97f 100644 --- a/common/client-libs/validator-client/src/lib.rs +++ b/common/client-libs/validator-client/src/lib.rs @@ -1,273 +1,14 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::time::Duration; - -pub use crate::error::ValidatorClientError; -use crate::models::{QueryRequest, QueryResponse}; -use log::error; -use mixnet_contract::{ - GatewayBond, IdentityKey, LayerDistribution, MixNodeBond, PagedGatewayResponse, - PagedMixnodeResponse, -}; -use rand::{seq::SliceRandom, thread_rng}; -use serde::Deserialize; -use url::Url; - +pub mod client; mod error; -mod models; #[cfg(feature = "nymd-client")] pub mod nymd; -pub(crate) mod serde_helpers; pub mod validator_api; -// Implement caching with a global hashmap that has two fields, queryresponse and as_at, there is a side process -pub struct Config { - initial_rest_servers: Vec, - mixnet_contract_address: String, - mixnode_page_limit: Option, - gateway_page_limit: Option, -} +pub use crate::client::ApiClient; +pub use crate::error::ValidatorClientError; -impl Config { - pub fn new>( - rest_servers_available_base_urls: Vec, - mixnet_contract_address: S, - ) -> Self { - let initial_rest_servers = rest_servers_available_base_urls - .iter() - .map(|base_url| Url::parse(base_url).expect("Bad validator URL")) - .collect(); - Config { - initial_rest_servers, - 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, - validator_api_client: validator_api::Client, -} - -impl Client { - pub fn new(config: Config) -> Self { - let reqwest_client = reqwest::Client::new(); - let validator_api_client = validator_api::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") - } - - Client { - config, - reqwest_client, - validator_api_client, - } - } - - pub fn available_validators_rest_urls(&self) -> Vec { - self.config.initial_rest_servers.clone() - } - - fn base_query_path(&self, url: &str) -> String { - format!( - "{}/wasm/contract/{}/smart", - url, self.config.mixnet_contract_address - ) - } - - // async fn latest_block(&self) -> Block { - // let path = format!("{}/block", self.available_validators_rest_urls[0]); - // let response = self.reqwest_client.get(path).send().await?.json().await?; - // } - - async fn query_validators( - &self, - query: String, - use_validator_api: bool, - ) -> Result - where - for<'a> T: Deserialize<'a>, - { - let mut failed = 0; - let sleep_secs = 5; - // Randomly select a validator to query, keep querying and shuffling until we get a response - let mut validator_urls = self.available_validators_rest_urls().clone(); - - // This will never exit - // JS: Shouldn't it have some sort of maximum attempts counter to return an error at some point? - loop { - validator_urls.as_mut_slice().shuffle(&mut thread_rng()); - for url in validator_urls.iter() { - let res = if use_validator_api { - Ok(self - .validator_api_client - .query_validator_api(query.clone(), url) - .await?) - } else { - self.query_validator(query.clone(), url).await - }; - match res { - Ok(res) => return Ok(res), - Err(e) => { - failed += 1; - error!("{}", e); - error!("Total failed requests {}", failed); - } - } - } - error!( - "No validators available out of {} attempted! Will try again in {} seconds. Listing all attempted:", - validator_urls.len(), sleep_secs - ); - for url in validator_urls.iter() { - error!("{}", url) - } - // Went with only wasm_timer so we can avoid features on the lib, and pulling in tokio - fluvio_wasm_timer::Delay::new(Duration::from_secs(sleep_secs)).await?; - } - } - - async fn query_validator( - &self, - query: String, - validator_url: &Url, - ) -> Result - where - for<'a> T: Deserialize<'a>, - { - let query_url = format!( - "{}/{}?encoding=base64", - self.base_query_path(validator_url.as_str()), - 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(ValidatorClientError::ValidatorError(format!("{}", err))) - } - } - } - - async fn get_mix_nodes_paged( - &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, false).await - } - - pub async fn get_mix_nodes(&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) - } - - pub async fn get_cached_mix_nodes(&self) -> Result, ValidatorClientError> { - let query_content = format!( - "{}{}", - validator_api::VALIDATOR_API_CACHE_VERSION.to_string(), - validator_api::VALIDATOR_API_MIXNODES.to_string() - ); - self.query_validators(query_content, true).await - } - - async fn get_gateways_paged( - &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, false).await - } - - pub async fn get_gateways(&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) - } - - pub async fn get_cached_gateways(&self) -> Result, ValidatorClientError> { - let query_content = format!( - "{}{}", - validator_api::VALIDATOR_API_CACHE_VERSION.to_string(), - validator_api::VALIDATOR_API_GATEWAYS.to_string() - ); - self.query_validators(query_content, true).await - } - - pub async fn get_layer_distribution(&self) -> Result { - // serialization of an empty enum can't fail... - let query_content_json = - serde_json::to_string(&QueryRequest::LayerDistribution {}).unwrap(); - - // we need to encode our json request - let query_content = base64::encode(query_content_json); - - self.query_validators(query_content, false).await - } -} +#[cfg(feature = "nymd-client")] +pub use client::{Client, Config}; diff --git a/common/client-libs/validator-client/src/models.rs b/common/client-libs/validator-client/src/models.rs deleted file mode 100644 index 93b4fbacd6..0000000000 --- a/common/client-libs/validator-client/src/models.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::error::{CodedError, SmartQueryError}; -use crate::serde_helpers::{de_i64_from_str, de_smart_query_response_from_str}; -use mixnet_contract::IdentityKey; -use serde::{Deserialize, Serialize}; - -// TODO: this is a duplicate code but it really does not feel -// like it would belong in the common crate because it's TOO contract specific... -// I'm not entirely sure what to do about it now. -#[derive(Serialize)] -#[serde(rename_all = "snake_case")] -pub(super) enum QueryRequest { - GetMixNodes { - limit: Option, - start_after: Option, - }, - GetGateways { - start_after: Option, - limit: Option, - }, - LayerDistribution {}, -} - -#[derive(Deserialize, Debug)] -#[serde(bound = "for<'a> T: Deserialize<'a>")] -pub(super) struct SmartQueryResult -where - for<'a> T: Deserialize<'a>, -{ - #[serde(deserialize_with = "de_smart_query_response_from_str")] - pub(super) smart: T, -} - -#[derive(Deserialize, Debug)] -#[serde(bound = "for<'a> T: Deserialize<'a>")] -pub(super) struct SmartQueryResponse -where - for<'a> T: Deserialize<'a>, -{ - #[serde(deserialize_with = "de_i64_from_str")] - pub(super) height: i64, - pub(super) result: SmartQueryResult, -} - -#[derive(Deserialize, Debug)] -#[serde(bound = "for<'a> T: Deserialize<'a>")] -#[serde(untagged)] -pub(super) enum QueryResponse -where - for<'a> T: Deserialize<'a>, -{ - Ok(SmartQueryResponse), - Error(SmartQueryError), - CodedError(CodedError), -} diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs index 69c9722d79..1a78c9c33c 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs @@ -6,7 +6,7 @@ use crate::nymd::cosmwasm_client::types::{ Account, Code, CodeDetails, Contract, ContractCodeHistoryEntry, ContractCodeId, SequenceResponse, }; -use crate::ValidatorClientError; +use crate::nymd::error::NymdError; use async_trait::async_trait; use cosmos_sdk::proto::cosmos::auth::v1beta1::{ BaseAccount, QueryAccountRequest, QueryAccountResponse, @@ -39,7 +39,7 @@ pub trait CosmWasmClient: rpc::Client { &self, path: Option, req: Req, - ) -> Result + ) -> Result where Req: Message, Res: Message + Default, @@ -52,19 +52,16 @@ pub trait CosmWasmClient: rpc::Client { Ok(Res::decode(res.value.as_ref())?) } - async fn get_chain_id(&self) -> Result { + async fn get_chain_id(&self) -> Result { Ok(self.status().await?.node_info.network) } - async fn get_height(&self) -> Result { + async fn get_height(&self) -> Result { Ok(self.status().await?.sync_info.latest_block_height) } // TODO: the return type should probably be changed to a non-proto, type-safe Account alternative - async fn get_account( - &self, - address: &AccountId, - ) -> Result, ValidatorClientError> { + async fn get_account(&self, address: &AccountId) -> Result, NymdError> { let path = Some("/cosmos.auth.v1beta1.Query/Account".parse().unwrap()); let req = QueryAccountRequest { @@ -85,21 +82,18 @@ pub trait CosmWasmClient: rpc::Client { .transpose() } - async fn get_sequence( - &self, - address: &AccountId, - ) -> Result { + async fn get_sequence(&self, address: &AccountId) -> Result { let base_account = self .get_account(address) .await? - .ok_or_else(|| ValidatorClientError::NonExistentAccountError(address.clone()))?; + .ok_or_else(|| NymdError::NonExistentAccountError(address.clone()))?; Ok(SequenceResponse { account_number: base_account.account_number, sequence: base_account.sequence, }) } - async fn get_block(&self, height: Option) -> Result { + async fn get_block(&self, height: Option) -> Result { match height { Some(height) => self.block(height).await.map_err(|err| err.into()), None => self.latest_block().await.map_err(|err| err.into()), @@ -110,7 +104,7 @@ pub trait CosmWasmClient: rpc::Client { &self, address: &AccountId, search_denom: Denom, - ) -> Result, ValidatorClientError> { + ) -> Result, NymdError> { let path = Some("/cosmos.bank.v1beta1.Query/Balance".parse().unwrap()); let req = QueryBalanceRequest { @@ -125,13 +119,10 @@ pub trait CosmWasmClient: rpc::Client { res.balance .map(TryFrom::try_from) .transpose() - .map_err(|_| ValidatorClientError::SerializationError("Coin".to_owned())) + .map_err(|_| NymdError::SerializationError("Coin".to_owned())) } - async fn get_all_balances( - &self, - address: &AccountId, - ) -> Result, ValidatorClientError> { + async fn get_all_balances(&self, address: &AccountId) -> Result, NymdError> { let path = Some("/cosmos.bank.v1beta1.Query/AllBalances".parse().unwrap()); let mut raw_balances = Vec::new(); @@ -159,17 +150,17 @@ pub trait CosmWasmClient: rpc::Client { .into_iter() .map(TryFrom::try_from) .collect::>() - .map_err(|_| ValidatorClientError::SerializationError("Coins".to_owned())) + .map_err(|_| NymdError::SerializationError("Coins".to_owned())) } // disabled until https://github.com/tendermint/tendermint/issues/6802 // and consequently https://github.com/informalsystems/tendermint-rs/issues/942 is resolved // - // async fn get_tx(&self, id: tx::Hash) -> Result { + // async fn get_tx(&self, id: tx::Hash) -> Result { // Ok(self.tx(id, false).await?) // } - async fn search_tx(&self, query: Query) -> Result, ValidatorClientError> { + async fn search_tx(&self, query: Query) -> Result, NymdError> { // according to https://docs.tendermint.com/master/rpc/#/Info/tx_search // the maximum entries per page is 100 and the default is 30 // so let's attempt to use the maximum @@ -204,7 +195,7 @@ pub trait CosmWasmClient: rpc::Client { async fn broadcast_tx_async( &self, tx: Transaction, - ) -> Result { + ) -> Result { Ok(rpc::Client::broadcast_tx_async(self, tx).await?) } @@ -212,7 +203,7 @@ pub trait CosmWasmClient: rpc::Client { async fn broadcast_tx_sync( &self, tx: Transaction, - ) -> Result { + ) -> Result { Ok(rpc::Client::broadcast_tx_sync(self, tx).await?) } @@ -220,11 +211,11 @@ pub trait CosmWasmClient: rpc::Client { async fn broadcast_tx_commit( &self, tx: Transaction, - ) -> Result { + ) -> Result { Ok(rpc::Client::broadcast_tx_commit(self, tx).await?) } - async fn get_codes(&self) -> Result, ValidatorClientError> { + async fn get_codes(&self) -> Result, NymdError> { let path = Some("/cosmwasm.wasm.v1beta1.Query/Codes".parse().unwrap()); let mut raw_codes = Vec::new(); @@ -251,10 +242,7 @@ pub trait CosmWasmClient: rpc::Client { .collect::>() } - async fn get_code_details( - &self, - code_id: ContractCodeId, - ) -> Result { + async fn get_code_details(&self, code_id: ContractCodeId) -> Result { let path = Some("/cosmwasm.wasm.v1beta1.Query/Code".parse().unwrap()); let req = QueryCodeRequest { code_id }; @@ -266,13 +254,10 @@ pub trait CosmWasmClient: rpc::Client { if let Some(code_info) = res.code_info { Ok(CodeDetails::new(code_info.try_into()?, res.data)) } else { - Err(ValidatorClientError::NoCodeInformation(code_id)) + Err(NymdError::NoCodeInformation(code_id)) } } - async fn get_contracts( - &self, - code_id: ContractCodeId, - ) -> Result, ValidatorClientError> { + async fn get_contracts(&self, code_id: ContractCodeId) -> Result, NymdError> { let path = Some( "/cosmwasm.wasm.v1beta1.Query/ContractsByCode" .parse() @@ -304,12 +289,10 @@ pub trait CosmWasmClient: rpc::Client { .iter() .map(|raw| raw.parse()) .collect::>() - .map_err(|_| { - ValidatorClientError::DeserializationError("Contract addresses".to_owned()) - }) + .map_err(|_| NymdError::DeserializationError("Contract addresses".to_owned())) } - async fn get_contract(&self, address: &AccountId) -> Result { + async fn get_contract(&self, address: &AccountId) -> Result { let path = Some("/cosmwasm.wasm.v1beta1.Query/ContractInfo".parse().unwrap()); let req = QueryContractInfoRequest { @@ -324,17 +307,17 @@ pub trait CosmWasmClient: rpc::Client { if let Some(contract_info) = res.contract_info { let address = response_address .parse() - .map_err(|_| ValidatorClientError::MalformedAccountAddress(response_address))?; + .map_err(|_| NymdError::MalformedAccountAddress(response_address))?; Ok(Contract::new(address, contract_info.try_into()?)) } else { - Err(ValidatorClientError::NoContractInformation(address.clone())) + Err(NymdError::NoContractInformation(address.clone())) } } async fn get_contract_code_history( &self, address: &AccountId, - ) -> Result, ValidatorClientError> { + ) -> Result, NymdError> { let path = Some( "/cosmwasm.wasm.v1beta1.Query/ContractHistory" .parse() @@ -372,7 +355,7 @@ pub trait CosmWasmClient: rpc::Client { &self, address: &AccountId, query_data: Vec, - ) -> Result, ValidatorClientError> { + ) -> Result, NymdError> { let path = Some( "/cosmwasm.wasm.v1beta1.Query/RawContractState" .parse() @@ -395,7 +378,7 @@ pub trait CosmWasmClient: rpc::Client { &self, address: &AccountId, query_msg: &M, - ) -> Result + ) -> Result where M: ?Sized + Serialize + Sync, for<'a> T: Deserialize<'a>, diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs index dac512dac1..66a736aa52 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::ValidatorClientError; +use crate::nymd::error::NymdError; use cosmos_sdk::proto::cosmos::base::query::v1beta1::PageRequest; use cosmos_sdk::rpc::endpoint::broadcast; use flate2::write::GzEncoder; @@ -9,13 +9,13 @@ use flate2::Compression; use std::io::Write; pub(crate) trait CheckResponse: Sized { - fn check_response(self) -> Result; + fn check_response(self) -> Result; } impl CheckResponse for broadcast::tx_commit::Response { - fn check_response(self) -> Result { + fn check_response(self) -> Result { if self.check_tx.code.is_err() { - return Err(ValidatorClientError::BroadcastTxErrorCheckTx { + return Err(NymdError::BroadcastTxErrorCheckTx { hash: self.hash, height: self.height, code: self.check_tx.code.value(), @@ -24,7 +24,7 @@ impl CheckResponse for broadcast::tx_commit::Response { } if self.deliver_tx.code.is_err() { - return Err(ValidatorClientError::BroadcastTxErrorDeliverTx { + return Err(NymdError::BroadcastTxErrorDeliverTx { hash: self.hash, height: self.height, code: self.deliver_tx.code.value(), @@ -36,15 +36,13 @@ impl CheckResponse for broadcast::tx_commit::Response { } } -pub(crate) fn compress_wasm_code(code: &[u8]) -> Result, ValidatorClientError> { +pub(crate) fn compress_wasm_code(code: &[u8]) -> Result, NymdError> { // using compression level 9, same as cosmjs, that optimises for size let mut encoder = GzEncoder::new(Vec::new(), Compression::best()); encoder .write_all(code) - .map_err(ValidatorClientError::WasmCompressionError)?; - encoder - .finish() - .map_err(ValidatorClientError::WasmCompressionError) + .map_err(NymdError::WasmCompressionError)?; + encoder.finish().map_err(NymdError::WasmCompressionError) } pub(crate) fn create_pagination(key: Vec) -> PageRequest { diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs index 0e1aaf3ff2..115ffa1abc 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::ValidatorClientError; +use crate::nymd::error::NymdError; use cosmos_sdk::tendermint::abci; use itertools::Itertools; use serde::Deserialize; @@ -36,18 +36,17 @@ pub(crate) fn find_attribute<'a>( } // those two functions were separated so that the internal logic could actually be tested -fn parse_raw_str_logs(raw: &str) -> Result, ValidatorClientError> { - let logs: Vec = - serde_json::from_str(raw).map_err(|_| ValidatorClientError::MalformedLogString)?; +fn parse_raw_str_logs(raw: &str) -> Result, NymdError> { + let logs: Vec = serde_json::from_str(raw).map_err(|_| NymdError::MalformedLogString)?; if logs.len() != logs.iter().unique_by(|log| log.msg_index).count() { // this check is only here because I don't yet fully understand raw log string generation and // the fact the first entry does not seem to have `msg_index` defined on it. - return Err(ValidatorClientError::MalformedLogString); + return Err(NymdError::MalformedLogString); } Ok(logs) } -pub fn parse_raw_logs(raw: abci::Log) -> Result, ValidatorClientError> { +pub fn parse_raw_logs(raw: abci::Log) -> Result, NymdError> { parse_raw_str_logs(raw.as_ref()) } diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs index 75f5a3460a..5b04e0d47d 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs @@ -1,8 +1,8 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::nymd::error::NymdError; use crate::nymd::wallet::DirectSecp256k1HdWallet; -use crate::ValidatorClientError; use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl}; use std::convert::TryInto; @@ -12,7 +12,7 @@ pub mod logs; pub mod signing_client; pub mod types; -pub fn connect(endpoint: U) -> Result +pub fn connect(endpoint: U) -> Result where U: TryInto, { @@ -23,7 +23,7 @@ where pub fn connect_with_signer( endpoint: U, signer: DirectSecp256k1HdWallet, -) -> Result +) -> Result where U: TryInto, { diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs index a320060b50..bb9a0e588b 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs @@ -5,8 +5,8 @@ use crate::nymd::cosmwasm_client::client::CosmWasmClient; use crate::nymd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse}; use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs}; use crate::nymd::cosmwasm_client::types::*; +use crate::nymd::error::NymdError; use crate::nymd::wallet::DirectSecp256k1HdWallet; -use crate::ValidatorClientError; use async_trait::async_trait; use cosmos_sdk::bank::MsgSend; use cosmos_sdk::distribution::MsgWithdrawDelegatorReward; @@ -31,7 +31,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { fee: Fee, memo: impl Into + Send + 'static, mut meta: Option, - ) -> Result { + ) -> Result { let compressed = compress_wasm_code(&wasm_code)?; let compressed_size = compressed.len(); let compressed_checksum = Sha256::digest(&compressed).to_vec(); @@ -52,7 +52,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { instantiate_permission: Default::default(), } .to_msg() - .map_err(|_| ValidatorClientError::SerializationError("MsgStoreCode".to_owned()))?; + .map_err(|_| NymdError::SerializationError("MsgStoreCode".to_owned()))?; let tx_res = self .sign_and_broadcast_commit(sender_address, vec![upload_msg], fee, memo) @@ -96,7 +96,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { fee: Fee, memo: impl Into + Send + 'static, mut options: Option, - ) -> Result + ) -> Result where M: ?Sized + Serialize + Sync, { @@ -114,9 +114,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { funds: options.map(|options| options.funds).unwrap_or_default(), } .to_msg() - .map_err(|_| { - ValidatorClientError::SerializationError("MsgInstantiateContract".to_owned()) - })?; + .map_err(|_| NymdError::SerializationError("MsgInstantiateContract".to_owned()))?; let tx_res = self .sign_and_broadcast_commit(sender_address, vec![init_msg], fee, memo) @@ -149,14 +147,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient { new_admin: &AccountId, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let change_admin_msg = cosmwasm::MsgUpdateAdmin { sender: sender_address.clone(), new_admin: new_admin.clone(), contract: contract_address.clone(), } .to_msg() - .map_err(|_| ValidatorClientError::SerializationError("MsgUpdateAdmin".to_owned()))?; + .map_err(|_| NymdError::SerializationError("MsgUpdateAdmin".to_owned()))?; let tx_res = self .sign_and_broadcast_commit(sender_address, vec![change_admin_msg], fee, memo) @@ -175,13 +173,13 @@ pub trait SigningCosmWasmClient: CosmWasmClient { contract_address: &AccountId, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let change_admin_msg = cosmwasm::MsgClearAdmin { sender: sender_address.clone(), contract: contract_address.clone(), } .to_msg() - .map_err(|_| ValidatorClientError::SerializationError("MsgClearAdmin".to_owned()))?; + .map_err(|_| NymdError::SerializationError("MsgClearAdmin".to_owned()))?; let tx_res = self .sign_and_broadcast_commit(sender_address, vec![change_admin_msg], fee, memo) @@ -202,7 +200,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { fee: Fee, msg: &M, memo: impl Into + Send + 'static, - ) -> Result + ) -> Result where M: ?Sized + Serialize + Sync, { @@ -213,7 +211,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { migrate_msg: serde_json::to_vec(msg)?, } .to_msg() - .map_err(|_| ValidatorClientError::SerializationError("MsgMigrateContract".to_owned()))?; + .map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?; let tx_res = self .sign_and_broadcast_commit(sender_address, vec![migrate_msg], fee, memo) @@ -234,7 +232,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { fee: Fee, memo: impl Into + Send + 'static, funds: Vec, - ) -> Result + ) -> Result where M: ?Sized + Serialize + Sync, { @@ -245,7 +243,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { funds, } .to_msg() - .map_err(|_| ValidatorClientError::SerializationError("MsgExecuteContract".to_owned()))?; + .map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?; let tx_res = self .sign_and_broadcast_commit(sender_address, vec![execute_msg], fee, memo) @@ -265,14 +263,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient { amount: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let send_msg = MsgSend { from_address: sender_address.clone(), to_address: recipient_address.clone(), amount, } .to_msg() - .map_err(|_| ValidatorClientError::SerializationError("MsgSend".to_owned()))?; + .map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?; self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo) .await @@ -285,14 +283,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient { amount: Coin, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let delegate_msg = MsgDelegate { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), amount: Some(amount), } .to_msg() - .map_err(|_| ValidatorClientError::SerializationError("MsgDelegate".to_owned()))?; + .map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?; self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo) .await @@ -305,14 +303,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient { amount: Coin, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let undelegate_msg = MsgUndelegate { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), amount: Some(amount), } .to_msg() - .map_err(|_| ValidatorClientError::SerializationError("MsgUndelegate".to_owned()))?; + .map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?; self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo) .await @@ -324,15 +322,13 @@ pub trait SigningCosmWasmClient: CosmWasmClient { validator_address: &AccountId, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let withdraw_msg = MsgWithdrawDelegatorReward { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), } .to_msg() - .map_err(|_| { - ValidatorClientError::SerializationError("MsgWithdrawDelegatorReward".to_owned()) - })?; + .map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?; self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo) .await @@ -345,11 +341,11 @@ pub trait SigningCosmWasmClient: CosmWasmClient { messages: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let tx_raw = self.sign(signer_address, messages, fee, memo).await?; let tx_bytes = tx_raw .to_bytes() - .map_err(|_| ValidatorClientError::SerializationError("Tx".to_owned()))?; + .map_err(|_| NymdError::SerializationError("Tx".to_owned()))?; CosmWasmClient::broadcast_tx_async(self, tx_bytes.into()).await } @@ -361,11 +357,11 @@ pub trait SigningCosmWasmClient: CosmWasmClient { messages: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let tx_raw = self.sign(signer_address, messages, fee, memo).await?; let tx_bytes = tx_raw .to_bytes() - .map_err(|_| ValidatorClientError::SerializationError("Tx".to_owned()))?; + .map_err(|_| NymdError::SerializationError("Tx".to_owned()))?; CosmWasmClient::broadcast_tx_sync(self, tx_bytes.into()).await } @@ -377,11 +373,11 @@ pub trait SigningCosmWasmClient: CosmWasmClient { messages: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { let tx_raw = self.sign(signer_address, messages, fee, memo).await?; let tx_bytes = tx_raw .to_bytes() - .map_err(|_| ValidatorClientError::SerializationError("Tx".to_owned()))?; + .map_err(|_| NymdError::SerializationError("Tx".to_owned()))?; CosmWasmClient::broadcast_tx_commit(self, tx_bytes.into()).await } @@ -393,12 +389,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient { fee: Fee, memo: impl Into + Send + 'static, signer_data: SignerData, - ) -> Result { + ) -> Result { let signer_accounts = self.signer().try_derive_accounts()?; let account_from_signer = signer_accounts .iter() .find(|account| &account.address == signer_address) - .ok_or_else(|| ValidatorClientError::SigningAccountNotFound(signer_address.clone()))?; + .ok_or_else(|| NymdError::SigningAccountNotFound(signer_address.clone()))?; // TODO: WTF HOW IS TIMEOUT_HEIGHT SUPPOSED TO GET DETERMINED? // IT DOESNT EXIST IN COSMJS!! @@ -410,7 +406,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { SignerInfo::single_direct(Some(account_from_signer.public_key), signer_data.sequence); let auth_info = signer_info.auth_info(fee); - // ideally I'd prefer to have the entire error put into the ValidatorClientError::SigningFailure + // ideally I'd prefer to have the entire error put into the NymdError::SigningFailure // but I'm super hesitant to trying to downcast the eyre::Report to cosmos_sdk::error::Error let sign_doc = SignDoc::new( &tx_body, @@ -418,7 +414,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { &signer_data.chain_id, signer_data.account_number, ) - .map_err(|_| ValidatorClientError::SigningFailure)?; + .map_err(|_| NymdError::SigningFailure)?; self.signer() .sign_direct_with_account(account_from_signer, sign_doc) @@ -430,7 +426,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { messages: Vec, fee: Fee, memo: impl Into + Send + 'static, - ) -> Result { + ) -> Result { // TODO: Future optimisation: rather than grabbing current account_number and sequence // on every sign request -> just keep them cached on the struct and increment as required let sequence_response = self.get_sequence(signer_address).await?; @@ -455,7 +451,7 @@ impl Client { pub fn connect_with_signer( endpoint: U, signer: DirectSecp256k1HdWallet, - ) -> Result + ) -> Result where U: TryInto, { diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs index af158916fa..179447001f 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs @@ -4,7 +4,7 @@ // TODO: There's a significant argument to pull those out of the package and make a PR on https://github.com/cosmos/cosmos-rust/ use crate::nymd::cosmwasm_client::logs::Log; -use crate::ValidatorClientError; +use crate::nymd::error::NymdError; use cosmos_sdk::crypto::PublicKey; use cosmos_sdk::proto::cosmos::auth::v1beta1::BaseAccount; use cosmos_sdk::proto::cosmwasm::wasm::v1beta1::{ @@ -38,19 +38,19 @@ pub struct Account { } impl TryFrom for Account { - type Error = ValidatorClientError; + type Error = NymdError; fn try_from(value: BaseAccount) -> Result { let address: AccountId = value .address .parse() - .map_err(|_| ValidatorClientError::MalformedAccountAddress(value.address.clone()))?; + .map_err(|_| NymdError::MalformedAccountAddress(value.address.clone()))?; let pubkey = value .pub_key .map(PublicKey::try_from) .transpose() - .map_err(|_| ValidatorClientError::InvalidPublicKey(address.clone()))?; + .map_err(|_| NymdError::InvalidPublicKey(address.clone()))?; Ok(Account { address, @@ -85,7 +85,7 @@ pub struct Code { } impl TryFrom for Code { - type Error = ValidatorClientError; + type Error = NymdError; fn try_from(value: CodeInfoResponse) -> Result { let CodeInfoResponse { @@ -98,7 +98,7 @@ impl TryFrom for Code { let creator = creator .parse() - .map_err(|_| ValidatorClientError::MalformedAccountAddress(creator))?; + .map_err(|_| NymdError::MalformedAccountAddress(creator))?; let source = if source.is_empty() { None @@ -144,7 +144,7 @@ pub(crate) struct ContractInfo { } impl TryFrom for ContractInfo { - type Error = ValidatorClientError; + type Error = NymdError; fn try_from(value: ProtoContractInfo) -> Result { let ProtoContractInfo { @@ -161,7 +161,7 @@ impl TryFrom for ContractInfo { Some( admin .parse() - .map_err(|_| ValidatorClientError::MalformedAccountAddress(admin))?, + .map_err(|_| NymdError::MalformedAccountAddress(admin))?, ) }; @@ -169,7 +169,7 @@ impl TryFrom for ContractInfo { code_id, creator: creator .parse() - .map_err(|_| ValidatorClientError::MalformedAccountAddress(creator))?, + .map_err(|_| NymdError::MalformedAccountAddress(creator))?, admin, label, }) @@ -219,14 +219,14 @@ pub struct ContractCodeHistoryEntry { } impl TryFrom for ContractCodeHistoryEntry { - type Error = ValidatorClientError; + type Error = NymdError; fn try_from(value: ProtoContractCodeHistoryEntry) -> Result { let operation = match ContractCodeHistoryOperationType::from_i32(value.operation) - .ok_or(ValidatorClientError::InvalidContractHistoryOperation)? + .ok_or(NymdError::InvalidContractHistoryOperation)? { ContractCodeHistoryOperationType::Unspecified => { - return Err(ValidatorClientError::InvalidContractHistoryOperation) + return Err(NymdError::InvalidContractHistoryOperation) } ContractCodeHistoryOperationType::Init => ContractCodeHistoryEntryOperation::Init, ContractCodeHistoryOperationType::Genesis => ContractCodeHistoryEntryOperation::Genesis, @@ -236,9 +236,8 @@ impl TryFrom for ContractCodeHistoryEntry { Ok(ContractCodeHistoryEntry { operation, code_id: value.code_id, - msg_json: String::from_utf8(value.msg).map_err(|_| { - ValidatorClientError::DeserializationError("Contract history msg".to_owned()) - })?, + msg_json: String::from_utf8(value.msg) + .map_err(|_| NymdError::DeserializationError("Contract history msg".to_owned()))?, }) } } diff --git a/common/client-libs/validator-client/src/nymd/error.rs b/common/client-libs/validator-client/src/nymd/error.rs new file mode 100644 index 0000000000..d4b0b15f15 --- /dev/null +++ b/common/client-libs/validator-client/src/nymd/error.rs @@ -0,0 +1,100 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::nymd::cosmwasm_client::types::ContractCodeId; +use cosmos_sdk::tendermint::block; +use cosmos_sdk::{bip32, rpc, tx, AccountId}; +use std::io; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum NymdError { + #[error("No contract address is available to perform the call")] + NoContractAddressAvailable, + + #[error("There was an issue with bip32 - {0}")] + Bip32Error(#[from] bip32::Error), + + #[error("There was an issue with bip32 - {0}")] + Bip39Error(#[from] bip39::Error), + + #[error("Failed to derive account address")] + AccountDerivationError, + + #[error("Address {0} was not found in the wallet")] + SigningAccountNotFound(AccountId), + + #[error("Failed to sign raw transaction")] + SigningFailure, + + #[error("{0} is not a valid tx hash")] + InvalidTxHash(String), + + #[error("There was an issue with a tendermint RPC request - {0}")] + TendermintError(#[from] rpc::Error), + + #[error("There was an issue when attempting to serialize data")] + SerializationError(String), + + #[error("There was an issue when attempting to deserialize data")] + DeserializationError(String), + + #[error("There was an issue when attempting to encode our protobuf data - {0}")] + ProtobufEncodingError(#[from] prost::EncodeError), + + #[error("There was an issue when attempting to decode our protobuf data - {0}")] + ProtobufDecodingError(#[from] prost::DecodeError), + + #[error("Account {0} does not exist on the chain")] + NonExistentAccountError(AccountId), + + #[error("There was an issue with the serialization/deserialization - {0}")] + SerdeJsonError(#[from] serde_json::Error), + + #[error("Account {0} is not a valid account address")] + MalformedAccountAddress(String), + + #[error("Account {0} has an invalid associated public key")] + InvalidPublicKey(AccountId), + + #[error("Queried contract (code_id: {0}) did not have any code information attached")] + NoCodeInformation(ContractCodeId), + + #[error("Queried contract (address: {0}) did not have any contract information attached")] + NoContractInformation(AccountId), + + #[error("Contract contains invalid operations in its history")] + InvalidContractHistoryOperation, + + #[error("Block has an invalid height (either negative or larger than i64::MAX")] + InvalidHeight, + + #[error("Failed to compress provided wasm code - {0}")] + WasmCompressionError(io::Error), + + #[error("Logs returned from the validator were malformed")] + MalformedLogString, + + #[error( + "Error when broadcasting tx {hash} at height {height}. Error occurred during CheckTx phase. Code: {code}; Raw log: {raw_log}" + )] + BroadcastTxErrorCheckTx { + hash: tx::Hash, + height: block::Height, + code: u32, + raw_log: String, + }, + + #[error( + "Error when broadcasting tx {hash} at height {height}. Error occurred during DeliverTx phase. Code: {code}; Raw log: {raw_log}" + )] + BroadcastTxErrorDeliverTx { + hash: tx::Hash, + height: block::Height, + code: u32, + raw_log: String, + }, + + #[error("The provided gas price is malformed")] + MalformedGasPrice, +} diff --git a/common/client-libs/validator-client/src/nymd/gas_price.rs b/common/client-libs/validator-client/src/nymd/gas_price.rs index f4eb7fd6f2..12278c9c5a 100644 --- a/common/client-libs/validator-client/src/nymd/gas_price.rs +++ b/common/client-libs/validator-client/src/nymd/gas_price.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::ValidatorClientError; +use crate::nymd::error::NymdError; use config::defaults; use cosmos_sdk::Denom; use cosmwasm_std::Decimal; @@ -19,7 +19,7 @@ pub struct GasPrice { } impl FromStr for GasPrice { - type Err = ValidatorClientError; + type Err = NymdError; fn from_str(s: &str) -> Result { let possible_amount = s @@ -29,11 +29,11 @@ impl FromStr for GasPrice { let amount_len = possible_amount.len(); let amount = possible_amount .parse() - .map_err(|_| ValidatorClientError::MalformedGasPrice)?; + .map_err(|_| NymdError::MalformedGasPrice)?; let possible_denom = s.chars().skip(amount_len).collect::(); let denom = possible_denom .parse() - .map_err(|_| ValidatorClientError::MalformedGasPrice)?; + .map_err(|_| NymdError::MalformedGasPrice)?; Ok(GasPrice { amount, denom }) } diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index abae407436..32bde42a8e 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -1,19 +1,16 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::nymd::cosmwasm_client::client::CosmWasmClient; use crate::nymd::cosmwasm_client::signing_client; -use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; use crate::nymd::cosmwasm_client::types::{ ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions, InstantiateResult, MigrateResult, UploadMeta, UploadResult, }; +use crate::nymd::error::NymdError; use crate::nymd::fee_helpers::Operation; -pub use crate::nymd::gas_price::GasPrice; use crate::nymd::wallet::DirectSecp256k1HdWallet; -use crate::ValidatorClientError; use cosmos_sdk::rpc::endpoint::broadcast; -use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl}; +use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClientUrl}; use cosmos_sdk::tx::{Fee, Gas}; use cosmos_sdk::Coin as CosmosCoin; use cosmos_sdk::{AccountId, Denom}; @@ -27,7 +24,14 @@ use serde::Serialize; use std::collections::HashMap; use std::convert::TryInto; +pub use crate::nymd::cosmwasm_client::client::CosmWasmClient; +pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; +pub use crate::nymd::gas_price::GasPrice; +pub use cosmos_sdk::rpc::HttpClient as QueryNymdClient; +pub use signing_client::Client as SigningNymdClient; + pub mod cosmwasm_client; +pub mod error; pub(crate) mod fee_helpers; pub mod gas_price; pub mod wallet; @@ -40,16 +44,16 @@ pub struct NymdClient { custom_gas_limits: HashMap, } -impl NymdClient { +impl NymdClient { pub fn connect( endpoint: U, contract_address: AccountId, - ) -> Result, ValidatorClientError> + ) -> Result, NymdError> where U: TryInto, { Ok(NymdClient { - client: HttpClient::new(endpoint)?, + client: QueryNymdClient::new(endpoint)?, contract_address: Some(contract_address), client_address: None, gas_price: Default::default(), @@ -58,13 +62,13 @@ impl NymdClient { } } -impl NymdClient { +impl NymdClient { // maybe the wallet could be made into a generic, but for now, let's just have this one implementation pub fn connect_with_signer( endpoint: U, contract_address: Option, signer: DirectSecp256k1HdWallet, - ) -> Result, ValidatorClientError> + ) -> Result, NymdError> where U: TryInto, { @@ -75,7 +79,7 @@ impl NymdClient { .collect(); Ok(NymdClient { - client: signing_client::Client::connect_with_signer(endpoint, signer)?, + client: SigningNymdClient::connect_with_signer(endpoint, signer)?, contract_address, client_address: Some(client_address), gas_price: Default::default(), @@ -87,7 +91,7 @@ impl NymdClient { endpoint: U, contract_address: Option, mnemonic: bip39::Mnemonic, - ) -> Result, ValidatorClientError> + ) -> Result, NymdError> where U: TryInto, { @@ -99,7 +103,7 @@ impl NymdClient { .collect(); Ok(NymdClient { - client: signing_client::Client::connect_with_signer(endpoint, wallet)?, + client: SigningNymdClient::connect_with_signer(endpoint, wallet)?, contract_address, client_address: Some(client_address), gas_price: Default::default(), @@ -117,14 +121,14 @@ impl NymdClient { self.custom_gas_limits.insert(operation, limit); } - pub fn contract_address(&self) -> Result<&AccountId, ValidatorClientError> { + pub fn contract_address(&self) -> Result<&AccountId, NymdError> { self.contract_address .as_ref() - .ok_or(ValidatorClientError::NoContractAddressAvailable) + .ok_or(NymdError::NoContractAddressAvailable) } // now the question is as follows: will denom always be in the format of `u{prefix}`? - pub fn denom(&self) -> Result { + pub fn denom(&self) -> Result { Ok(format!("u{}", self.contract_address()?.prefix()) .parse() .unwrap()) @@ -143,17 +147,14 @@ impl NymdClient { operation.determine_fee(&self.gas_price, gas_limit) } - pub async fn get_balance( - &self, - address: &AccountId, - ) -> Result, ValidatorClientError> + pub async fn get_balance(&self, address: &AccountId) -> Result, NymdError> where C: CosmWasmClient + Sync, { self.client.get_balance(address, self.denom()?).await } - pub async fn get_state_params(&self) -> Result + pub async fn get_state_params(&self) -> Result where C: CosmWasmClient + Sync, { @@ -163,7 +164,7 @@ impl NymdClient { .await } - pub async fn get_layer_distribution(&self) -> Result + pub async fn get_layer_distribution(&self) -> Result where C: CosmWasmClient + Sync, { @@ -174,7 +175,7 @@ impl NymdClient { } /// Checks whether there is a bonded mixnode associated with the provided client's address - pub async fn owns_mixnode(&self, address: &AccountId) -> Result + pub async fn owns_mixnode(&self, address: &AccountId) -> Result where C: CosmWasmClient + Sync, { @@ -189,7 +190,7 @@ impl NymdClient { } /// Checks whether there is a bonded gateway associated with the provided client's address - pub async fn owns_gateway(&self, address: &AccountId) -> Result + pub async fn owns_gateway(&self, address: &AccountId) -> Result where C: CosmWasmClient + Sync, { @@ -206,13 +207,14 @@ impl NymdClient { pub async fn get_mixnodes_paged( &self, start_after: Option, - ) -> Result + page_limit: Option, + ) -> Result where C: CosmWasmClient + Sync, { let request = QueryMsg::GetMixNodes { - limit: None, start_after, + limit: page_limit, }; self.client .query_contract_smart(self.contract_address()?, &request) @@ -222,13 +224,14 @@ impl NymdClient { pub async fn get_gateways_paged( &self, start_after: Option, - ) -> Result + page_limit: Option, + ) -> Result where C: CosmWasmClient + Sync, { let request = QueryMsg::GetGateways { - limit: None, start_after, + limit: page_limit, }; self.client .query_contract_smart(self.contract_address()?, &request) @@ -239,15 +242,17 @@ impl NymdClient { pub async fn get_mix_delegations_paged( &self, mix_identity: IdentityKey, - start_after: Option, - ) -> Result + // I really hate mixing cosmwasm and cosmos-sdk types here... + start_after: Option, + page_limit: Option, + ) -> Result where C: CosmWasmClient + Sync, { let request = QueryMsg::GetMixDelegations { mix_identity: mix_identity.to_owned(), - start_after: start_after.map(|addr| Addr::unchecked(addr.as_ref())), - limit: None, + start_after, + limit: page_limit, }; self.client .query_contract_smart(self.contract_address()?, &request) @@ -259,7 +264,7 @@ impl NymdClient { &self, mix_identity: IdentityKey, delegator: &AccountId, - ) -> Result + ) -> Result where C: CosmWasmClient + Sync, { @@ -276,15 +281,16 @@ impl NymdClient { pub async fn get_gateway_delegations( &self, gateway_identity: IdentityKey, - start_after: Option, - ) -> Result + start_after: Option, + page_limit: Option, + ) -> Result where C: CosmWasmClient + Sync, { let request = QueryMsg::GetGatewayDelegations { gateway_identity, - start_after: start_after.map(|addr| Addr::unchecked(addr.as_ref())), - limit: None, + start_after, + limit: page_limit, }; self.client .query_contract_smart(self.contract_address()?, &request) @@ -296,7 +302,7 @@ impl NymdClient { &self, gateway_identity: IdentityKey, delegator: &AccountId, - ) -> Result + ) -> Result where C: CosmWasmClient + Sync, { @@ -315,7 +321,7 @@ impl NymdClient { recipient: &AccountId, amount: Vec, memo: impl Into + Send + 'static, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -332,7 +338,7 @@ impl NymdClient { fee: Fee, memo: impl Into + Send + 'static, funds: Vec, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, M: ?Sized + Serialize + Sync, @@ -347,7 +353,7 @@ impl NymdClient { wasm_code: Vec, memo: impl Into + Send + 'static, meta: Option, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -364,7 +370,7 @@ impl NymdClient { label: String, memo: impl Into + Send + 'static, options: Option, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, M: ?Sized + Serialize + Sync, @@ -380,7 +386,7 @@ impl NymdClient { contract_address: &AccountId, new_admin: &AccountId, memo: impl Into + Send + 'static, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -394,7 +400,7 @@ impl NymdClient { &self, contract_address: &AccountId, memo: impl Into + Send + 'static, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -410,7 +416,7 @@ impl NymdClient { code_id: ContractCodeId, msg: &M, memo: impl Into + Send + 'static, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, M: ?Sized + Serialize + Sync, @@ -426,7 +432,7 @@ impl NymdClient { &self, mixnode: MixNode, bond: Coin, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -446,7 +452,7 @@ impl NymdClient { } /// Unbond a mixnode, removing it from the network and reclaiming staked coins - pub async fn unbond_mixnode(&self) -> Result + pub async fn unbond_mixnode(&self) -> Result where C: SigningCosmWasmClient + Sync, { @@ -470,7 +476,7 @@ impl NymdClient { &self, mix_identity: IdentityKey, amount: Coin, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -493,7 +499,7 @@ impl NymdClient { pub async fn remove_mixnode_delegation( &self, mix_identity: IdentityKey, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -517,7 +523,7 @@ impl NymdClient { &self, gateway: Gateway, bond: Coin, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -537,7 +543,7 @@ impl NymdClient { } /// Unbond a gateway, removing it from the network and reclaiming staked coins - pub async fn unbond_gateway(&self) -> Result + pub async fn unbond_gateway(&self) -> Result where C: SigningCosmWasmClient + Sync, { @@ -561,7 +567,7 @@ impl NymdClient { &self, gateway_identity: IdentityKey, amount: Coin, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -584,7 +590,7 @@ impl NymdClient { pub async fn remove_gateway_delegation( &self, gateway_identity: IdentityKey, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { @@ -606,7 +612,7 @@ impl NymdClient { pub async fn update_state_params( &self, new_params: StateParams, - ) -> Result + ) -> Result where C: SigningCosmWasmClient + Sync, { diff --git a/common/client-libs/validator-client/src/nymd/wallet/mod.rs b/common/client-libs/validator-client/src/nymd/wallet/mod.rs index ae293dd910..ef43a65f84 100644 --- a/common/client-libs/validator-client/src/nymd/wallet/mod.rs +++ b/common/client-libs/validator-client/src/nymd/wallet/mod.rs @@ -1,7 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::ValidatorClientError; +use crate::nymd::error::NymdError; use config::defaults; use cosmos_sdk::bip32::{DerivationPath, XPrv}; use cosmos_sdk::crypto::secp256k1::SigningKey; @@ -46,19 +46,16 @@ impl DirectSecp256k1HdWallet { } /// Restores a wallet from the given BIP39 mnemonic using default options. - pub fn from_mnemonic(mnemonic: bip39::Mnemonic) -> Result { + pub fn from_mnemonic(mnemonic: bip39::Mnemonic) -> Result { DirectSecp256k1HdWalletBuilder::new().build(mnemonic) } - pub fn generate(word_count: usize) -> Result { + pub fn generate(word_count: usize) -> Result { let mneomonic = bip39::Mnemonic::generate(word_count)?; Self::from_mnemonic(mneomonic) } - fn derive_keypair( - &self, - hd_path: &DerivationPath, - ) -> Result { + fn derive_keypair(&self, hd_path: &DerivationPath) -> Result { let extended_private_key = XPrv::derive_from_path(&self.seed, hd_path)?; let private_key: SigningKey = extended_private_key.into(); @@ -67,7 +64,7 @@ impl DirectSecp256k1HdWallet { Ok((private_key, public_key)) } - pub fn try_derive_accounts(&self) -> Result, ValidatorClientError> { + pub fn try_derive_accounts(&self) -> Result, NymdError> { let mut accounts = Vec::with_capacity(self.accounts.len()); for derivation_info in &self.accounts { let keypair = self.derive_keypair(&derivation_info.hd_path)?; @@ -76,7 +73,7 @@ impl DirectSecp256k1HdWallet { let address = keypair .1 .account_id(&derivation_info.prefix) - .map_err(|_| ValidatorClientError::AccountDerivationError)?; + .map_err(|_| NymdError::AccountDerivationError)?; accounts.push(AccountData { address, @@ -96,25 +93,25 @@ impl DirectSecp256k1HdWallet { &self, signer: &AccountData, sign_doc: SignDoc, - ) -> Result { - // ideally I'd prefer to have the entire error put into the ValidatorClientError::SigningFailure + ) -> Result { + // ideally I'd prefer to have the entire error put into the NymdError::SigningFailure // but I'm super hesitant to trying to downcast the eyre::Report to cosmos_sdk::error::Error sign_doc .sign(&signer.private_key) - .map_err(|_| ValidatorClientError::SigningFailure) + .map_err(|_| NymdError::SigningFailure) } pub fn sign_direct( &self, signer_address: &AccountId, sign_doc: SignDoc, - ) -> Result { + ) -> Result { // I hate deriving accounts at every sign here so much : ( let accounts = self.try_derive_accounts()?; let account = accounts .iter() .find(|account| &account.address == signer_address) - .ok_or_else(|| ValidatorClientError::SigningAccountNotFound(signer_address.clone()))?; + .ok_or_else(|| NymdError::SigningAccountNotFound(signer_address.clone()))?; self.sign_direct_with_account(account, sign_doc) } @@ -166,10 +163,7 @@ impl DirectSecp256k1HdWalletBuilder { self } - pub fn build( - self, - mnemonic: bip39::Mnemonic, - ) -> Result { + pub fn build(self, mnemonic: bip39::Mnemonic) -> Result { let seed = mnemonic.to_seed(&self.bip39_password); let prefix = self.prefix; let accounts = self diff --git a/common/client-libs/validator-client/src/serde_helpers.rs b/common/client-libs/validator-client/src/serde_helpers.rs deleted file mode 100644 index 87c1326fb0..0000000000 --- a/common/client-libs/validator-client/src/serde_helpers.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use core::str::FromStr; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Deserializer}; - -pub(super) fn de_smart_query_response_from_str<'de, D, T>(deserializer: D) -> Result -where - D: Deserializer<'de>, - T: DeserializeOwned, -{ - let s = String::deserialize(deserializer)?; - let b64_decoded = base64::decode(&s).map_err(serde::de::Error::custom)?; - - let json_string = String::from_utf8(b64_decoded).map_err(serde::de::Error::custom)?; - serde_json::from_str(&json_string).map_err(serde::de::Error::custom) -} - -pub(super) fn de_i64_from_str<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - i64::from_str(&s).map_err(serde::de::Error::custom) -} diff --git a/common/client-libs/validator-client/src/validator_api/error.rs b/common/client-libs/validator-client/src/validator_api/error.rs index 6547f31785..b95865656c 100644 --- a/common/client-libs/validator-client/src/validator_api/error.rs +++ b/common/client-libs/validator-client/src/validator_api/error.rs @@ -1,7 +1,7 @@ use thiserror::Error; #[derive(Error, Debug)] -pub enum ValidatorAPIClientError { +pub enum ValidatorAPIError { #[error("There was an issue with the REST request - {source}")] ReqwestClientError { #[from] diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/validator_api/mod.rs index e71f21ccb8..c275a0d4b5 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/validator_api/mod.rs @@ -1,77 +1,167 @@ -pub mod error; - -use crate::validator_api::error::ValidatorAPIClientError; +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 +use crate::validator_api::error::ValidatorAPIError; +use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse}; +use mixnet_contract::{GatewayBond, MixNodeBond}; use serde::{Deserialize, Serialize}; use url::Url; -pub const VALIDATOR_API_PORT: u16 = 8080; -pub const VALIDATOR_API_CACHE_VERSION: &str = "/v1"; -pub(crate) const VALIDATOR_API_MIXNODES: &str = "/mixnodes"; -pub(crate) const VALIDATOR_API_GATEWAYS: &str = "/gateways"; -pub const VALIDATOR_API_VERIFICATION_KEY: &str = "/verification_key"; -pub const VALIDATOR_API_BLIND_SIGN: &str = "/blind_sign"; +pub mod error; +pub(crate) mod routes; + +type PathSegments<'a> = &'a [&'a str]; pub struct Client { + url: Url, reqwest_client: reqwest::Client, } -impl Default for Client { - fn default() -> Self { - Client::new() - } -} - impl Client { - pub fn new() -> Self { + pub fn new(url: Url) -> Self { let reqwest_client = reqwest::Client::new(); - Self { reqwest_client } + Self { + url, + reqwest_client, + } } - pub async fn query_validator_api( - &self, - query: String, - validator_url: &Url, - ) -> Result + pub fn change_url(&mut self, new_url: Url) { + self.url = new_url + } + + async fn query_validator_api(&self, path: PathSegments<'_>) -> Result where for<'a> T: Deserialize<'a>, { - let mut validator_api_url = validator_url.clone(); - validator_api_url - .set_port(Some(VALIDATOR_API_PORT)) - .unwrap(); - let query_url = format!("{}{}", validator_api_url.as_str(), query); - Ok(self - .reqwest_client - .get(query_url) - .send() - .await? - .json() - .await?) + let url = create_api_url(&self.url, path); + Ok(self.reqwest_client.get(url).send().await?.json().await?) } - pub async fn post_validator_api( + async fn post_validator_api( &self, - query: String, - json_body: &S, - validator_url: &Url, - ) -> Result + path: PathSegments<'_>, + json_body: &B, + ) -> Result where - for<'a> D: Deserialize<'a>, - S: Serialize, + B: Serialize + ?Sized, + for<'a> T: Deserialize<'a>, { - let mut validator_api_url = validator_url.clone(); - validator_api_url - .set_port(Some(VALIDATOR_API_PORT)) - .unwrap(); - let query_url = format!("{}{}", validator_api_url.as_str(), query); + let url = create_api_url(&self.url, path); Ok(self .reqwest_client - .post(query_url) + .get(url) .json(json_body) .send() .await? .json() .await?) } + + pub async fn get_mixnodes(&self) -> Result, ValidatorAPIError> { + self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES]) + .await + } + + pub async fn get_gateways(&self) -> Result, ValidatorAPIError> { + self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS]) + .await + } + + pub async fn blind_sign( + &self, + request_body: &BlindSignRequestBody, + ) -> Result { + self.post_validator_api( + &[routes::API_VERSION, routes::COCONUT_BLIND_SIGN], + request_body, + ) + .await + } + + pub async fn get_coconut_verification_key( + &self, + ) -> Result { + self.query_validator_api(&[routes::API_VERSION, routes::COCONUT_VERIFICATION_KEY]) + .await + } +} + +// utility function that should solve the double slash problem in validator API forever. +fn create_api_url(base: &Url, segments: PathSegments<'_>) -> Url { + let mut url = base.clone(); + let mut path_segments = url + .path_segments_mut() + .expect("provided validator url does not have a base!"); + for segment in segments { + let segment = segment.strip_prefix('/').unwrap_or(segment); + let segment = segment.strip_suffix('/').unwrap_or(segment); + + path_segments.push(segment); + } + // I don't understand why compiler couldn't figure out that it's no longer used + // and can be dropped + drop(path_segments); + + url +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn creating_api_path() { + let base_url: Url = "http://foomp.com".parse().unwrap(); + + // works with 1 segment + assert_eq!( + "http://foomp.com/foo", + create_api_url(&base_url, &["foo"]).as_str() + ); + + // works with 2 segments + assert_eq!( + "http://foomp.com/foo/bar", + create_api_url(&base_url, &["foo", "bar"]).as_str() + ); + + // works with leading slash + assert_eq!( + "http://foomp.com/foo", + create_api_url(&base_url, &["/foo"]).as_str() + ); + assert_eq!( + "http://foomp.com/foo/bar", + create_api_url(&base_url, &["/foo", "bar"]).as_str() + ); + assert_eq!( + "http://foomp.com/foo/bar", + create_api_url(&base_url, &["foo", "/bar"]).as_str() + ); + + // works with trailing slash + assert_eq!( + "http://foomp.com/foo", + create_api_url(&base_url, &["foo/"]).as_str() + ); + assert_eq!( + "http://foomp.com/foo/bar", + create_api_url(&base_url, &["foo/", "bar"]).as_str() + ); + assert_eq!( + "http://foomp.com/foo/bar", + create_api_url(&base_url, &["foo", "bar/"]).as_str() + ); + + // works with both leading and trailing slash + assert_eq!( + "http://foomp.com/foo", + create_api_url(&base_url, &["/foo/"]).as_str() + ); + assert_eq!( + "http://foomp.com/foo/bar", + create_api_url(&base_url, &["/foo/", "/bar/"]).as_str() + ); + } } diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/validator_api/routes.rs new file mode 100644 index 0000000000..83fe3a1aaf --- /dev/null +++ b/common/client-libs/validator-client/src/validator_api/routes.rs @@ -0,0 +1,11 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use network_defaults::VALIDATOR_API_VERSION; + +pub const API_VERSION: &str = VALIDATOR_API_VERSION; +pub const MIXNODES: &str = "mixnodes"; +pub const GATEWAYS: &str = "gateways"; + +pub const COCONUT_BLIND_SIGN: &str = "blind_sign"; +pub const COCONUT_VERIFICATION_KEY: &str = "verification_key"; diff --git a/common/coconut-interface/Cargo.toml b/common/coconut-interface/Cargo.toml index bdb6b8f7e3..2a5ed40a53 100644 --- a/common/coconut-interface/Cargo.toml +++ b/common/coconut-interface/Cargo.toml @@ -4,15 +4,9 @@ version = "0.1.0" edition = "2018" description = "Crutch library until there is proper SerDe support for coconut structs" - [dependencies] -digest = "0.9.0" serde = { version = "1.0", features = ["derive"] } sha2 = "0.9.3" getset = "0.1.1" -thiserror = "1.0" -url = "2" coconut-rs = { git = "https://github.com/nymtech/coconut.git", branch = "0.4.0" } -crypto = { path = "../crypto" } -validator-client = { path = "../client-libs/validator-client" } diff --git a/common/coconut-interface/src/error.rs b/common/coconut-interface/src/error.rs index 3c4b6bd32a..f0632e937d 100644 --- a/common/coconut-interface/src/error.rs +++ b/common/coconut-interface/src/error.rs @@ -10,17 +10,16 @@ pub enum CoconutInterfaceError { #[from] source: url::ParseError, }, - #[error("validator api query failed: {source}")] - ValidatorAPIFailure { - #[from] - source: validator_client::validator_api::error::ValidatorAPIClientError, - }, + #[error("could not aggregate verification key: {0}")] AggregateVerificationKeyError(coconut_rs::CoconutError), + #[error("could not prove credential: {0}")] ProveCredentialError(coconut_rs::CoconutError), + #[error("got invalid signature index: {0}")] InvalidSignatureIdx(usize), + #[error("got too many total attributes(public + private): {0} received, {1} is the maximum")] TooManyTotalAttributes(usize, u32), } diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 564d96bd02..823dfb562d 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -1,21 +1,15 @@ -pub mod error; +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 -use digest::Digest; use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; -use sha2::digest::generic_array::typenum::Unsigned; -use sha2::Sha256; -use std::convert::TryFrom; -use url::Url; - -use crate::error::CoconutInterfaceError; -pub use coconut_rs::*; -pub use validator_client::validator_api::Client as ValidatorAPIClient; -use validator_client::validator_api::{ - error::ValidatorAPIClientError, VALIDATOR_API_BLIND_SIGN, VALIDATOR_API_CACHE_VERSION, - VALIDATOR_API_VERIFICATION_KEY, +use sha2::{ + digest::{generic_array::typenum::Unsigned, Digest}, + Sha256, }; +pub use coconut_rs::*; + #[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)] pub struct Credential { #[getset(get = "pub")] @@ -29,13 +23,13 @@ pub struct Credential { impl Credential { pub fn new( n_params: u32, - theta: &Theta, + theta: Theta, public_attributes: &[Attribute], signature: &Signature, ) -> Credential { Credential { n_params, - theta: theta.clone(), + theta, public_attributes: public_attributes .iter() .map(|attr| attr.to_bs58()) @@ -44,27 +38,6 @@ impl Credential { } } - pub async fn init( - validator_urls: Vec, - public_key: crypto::asymmetric::identity::PublicKey, - ) -> Result { - let public_attributes = vec![hash_to_scalar(public_key.to_bytes())]; - let private_attributes = vec![hash_to_scalar("Bandwidth: infinite (for now)")]; - let mut state = State::init(public_attributes, private_attributes)?; - let client = ValidatorAPIClient::new(); - let signature = get_aggregated_signature(validator_urls.clone(), &state, &client).await?; - - state.signatures.push(signature); - let verification_key = get_aggregated_verification_key(validator_urls, &client).await?; - let theta = prove_credential(0, &verification_key, &state).await?; - Ok(Credential::new( - state.n_attributes, - &theta, - &*state.public_attributes, - &signature, - )) - } - pub fn public_attributes(&self) -> Vec { self.public_attributes .iter() @@ -83,7 +56,7 @@ impl Credential { } } -#[derive(Getters, CopyGetters)] +#[derive(Serialize, Deserialize, Debug, Getters, CopyGetters)] pub struct VerifyCredentialBody { #[getset(get = "pub")] n_params: u32, @@ -129,13 +102,13 @@ pub struct BlindSignRequestBody { impl BlindSignRequestBody { pub fn new( - blind_sign_request: &BlindSignRequest, + blind_sign_request: BlindSignRequest, public_key: &coconut_rs::PublicKey, public_attributes: &[Attribute], total_params: u32, ) -> BlindSignRequestBody { BlindSignRequestBody { - blind_sign_request: blind_sign_request.clone(), + blind_sign_request, public_key: public_key.clone(), public_attributes: public_attributes .iter() @@ -175,130 +148,6 @@ impl VerificationKeyResponse { } } -pub struct State { - pub signatures: Vec, - pub n_attributes: u32, - pub params: Parameters, - pub public_attributes: Vec, - pub private_attributes: Vec, -} - -impl State { - pub fn init( - public_attributes: Vec, - private_attributes: Vec, - ) -> Result { - let n_attributes_usize = public_attributes.len() + private_attributes.len(); - let n_attributes = u32::try_from(n_attributes_usize).map_err(|_| { - CoconutInterfaceError::TooManyTotalAttributes(n_attributes_usize, u32::MAX) - })?; - let params = Parameters::new(n_attributes).unwrap(); - Ok(State { - signatures: Vec::new(), - n_attributes, - params, - public_attributes, - private_attributes, - }) - } -} - -async fn get_verification_key( - url: &str, - client: &ValidatorAPIClient, -) -> Result { - let parsed_url = Url::parse(url).map_err(CoconutInterfaceError::from)?; - let verification_key_response: VerificationKeyResponse = client - .query_validator_api( - format!( - "{}{}", - VALIDATOR_API_CACHE_VERSION, VALIDATOR_API_VERIFICATION_KEY - ), - &parsed_url, - ) - .await - .map_err(CoconutInterfaceError::from)?; - Ok(verification_key_response.key) -} - -pub async fn get_aggregated_verification_key( - validator_urls: Vec, - client: &ValidatorAPIClient, -) -> Result { - let mut verification_keys = Vec::new(); - let mut indices = Vec::new(); - - for (idx, url) in validator_urls.iter().enumerate() { - verification_keys.push(get_verification_key(url.as_ref(), client).await?); - indices.push((idx + 1) as u64); - } - - aggregate_verification_keys(&verification_keys, Some(&indices)) - .map_err(CoconutInterfaceError::AggregateVerificationKeyError) -} - -pub async fn get_aggregated_signature( - validator_urls: Vec, - state: &State, - client: &ValidatorAPIClient, -) -> Result { - let elgamal_keypair = coconut_rs::elgamal_keygen(&state.params); - let blind_sign_request = coconut_rs::prepare_blind_sign( - &state.params, - elgamal_keypair.public_key(), - &state.private_attributes, - &state.public_attributes, - ) - .unwrap(); - let blind_sign_request_body = BlindSignRequestBody::new( - &blind_sign_request, - elgamal_keypair.public_key(), - &state.public_attributes, - state.n_attributes, - ); - - let mut signature_shares = vec![]; - - for (idx, url) in validator_urls.iter().enumerate() { - let parsed_url = Url::parse(url).map_err(CoconutInterfaceError::from)?; - let response: Result = client - .post_validator_api( - format!( - "{}{}", - VALIDATOR_API_CACHE_VERSION, VALIDATOR_API_BLIND_SIGN - ), - &blind_sign_request_body, - &parsed_url, - ) - .await; - if let Ok(blinded_signature_response) = response { - let blinded_signature = blinded_signature_response.blinded_signature; - let unblinded_signature = blinded_signature.unblind(elgamal_keypair.private_key()); - let signature_share = SignatureShare::new(unblinded_signature, (idx + 1) as u64); - signature_shares.push(signature_share); - } - } - Ok(aggregate_signature_shares(&signature_shares).unwrap()) -} - -pub async fn prove_credential( - idx: usize, - verification_key: &VerificationKey, - state: &State, -) -> Result { - let signature = state - .signatures - .get(idx) - .ok_or(CoconutInterfaceError::InvalidSignatureIdx(idx))?; - coconut_rs::prove_credential( - &state.params, - verification_key, - signature, - &state.private_attributes, - ) - .map_err(CoconutInterfaceError::ProveCredentialError) -} - pub fn hash_to_scalar(msg: M) -> Attribute where M: AsRef<[u8]>, diff --git a/common/config/Cargo.toml b/common/config/Cargo.toml index 6557eddd14..7a18a25cd8 100644 --- a/common/config/Cargo.toml +++ b/common/config/Cargo.toml @@ -11,3 +11,6 @@ handlebars = "3.0.1" humantime-serde = "1.0" serde = { version = "1.0", features = ["derive"] } toml = "0.5.6" +url = "2.2" + +network-defaults = { path = "../network-defaults" } \ No newline at end of file diff --git a/common/config/src/defaults.rs b/common/config/src/defaults.rs index 73a76103c6..ffcdb0004f 100644 --- a/common/config/src/defaults.rs +++ b/common/config/src/defaults.rs @@ -1,25 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -pub const DEFAULT_VALIDATOR_REST_ENDPOINTS: &[&str] = &[ - "http://testnet-milhon-validator1.nymtech.net:1317", - "http://testnet-milhon-validator2.nymtech.net:1317", -]; -pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen"; -pub const NETWORK_MONITOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfdy7hgk3"; -/// Defaults Cosmos Hub/ATOM path -pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0"; -pub const BECH32_PREFIX: &str = "punk"; -pub const DENOM: &str = "upunk"; -// as set by validators in their configs -// (note that the 'amount' postfix is relevant here as the full gas price also includes denom) -pub const GAS_PRICE_AMOUNT: f64 = 0.025; - -pub const DEFAULT_MIX_LISTENING_PORT: u16 = 1789; - -// 'GATEWAY' -pub const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000; - -// 'MIXNODE' -pub const DEFAULT_VERLOC_LISTENING_PORT: u16 = 1790; -pub const DEFAULT_HTTP_API_LISTENING_PORT: u16 = 8000; +// re-export everything from the `network-defaults` to not break the existing imports +// reason for moving defaults to separate crate is that I don't want to pull in all dependencies +// like `handlebars`, `toml`, etc if I only want to grab one constant... +pub use network_defaults::*; diff --git a/common/config/src/helpers.rs b/common/config/src/helpers.rs deleted file mode 100644 index 1c659dd166..0000000000 --- a/common/config/src/helpers.rs +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use serde::de::SeqAccess; -use serde::{ - de::{self, IntoDeserializer, Visitor}, - Deserialize, Deserializer, -}; -use std::time::Duration; - -// custom function is defined to deserialize based on whether field contains a pre 0.9.0 -// u64 interpreted as milliseconds or proper duration introduced in 0.9.0 -// -// TODO: when we get to refactoring down the line, this code can just be removed -// and all Duration fields could just have #[serde(with = "humantime_serde")] instead -// reason for that is that we don't expect anyone to be upgrading from pre 0.9.0 when we have, -// for argument sake, 0.11.0 out -pub fn deserialize_duration<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - struct DurationVisitor; - - impl<'de> Visitor<'de> for DurationVisitor { - type Value = Duration; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("u64 or a duration") - } - - fn visit_i64(self, value: i64) -> Result - where - E: de::Error, - { - self.visit_u64(value as u64) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - Ok(Duration::from_millis(Deserialize::deserialize( - value.into_deserializer(), - )?)) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - humantime_serde::deserialize(value.into_deserializer()) - } - } - - deserializer.deserialize_any(DurationVisitor) -} - -// custom function is defined to deserialize based on whether field contains a single validator rest endpoint -// or an array of multiple values -pub fn deserialize_validators<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct ValidatorsVisitor; - - impl<'de> Visitor<'de> for ValidatorsVisitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("String or Vec") - } - - fn visit_str(self, value: &str) -> Result, E> - where - E: de::Error, - { - Ok(vec![value.to_string()]) - } - - fn visit_seq(self, mut seq: A) -> Result, A::Error> - where - A: SeqAccess<'de>, - { - let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(10)); - - while let Some(next) = seq.next_element()? { - vec.push(next) - } - - Ok(vec) - } - } - - deserializer.deserialize_any(ValidatorsVisitor) -} diff --git a/common/config/src/lib.rs b/common/config/src/lib.rs index 85616e9af2..3d00921eef 100644 --- a/common/config/src/lib.rs +++ b/common/config/src/lib.rs @@ -8,9 +8,6 @@ use std::path::PathBuf; use std::{fs, io}; pub mod defaults; -pub mod helpers; - -pub use helpers::{deserialize_duration, deserialize_validators}; pub trait NymConfig: Default + Serialize + DeserializeOwned { fn template() -> &'static str; diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml new file mode 100644 index 0000000000..273d099898 --- /dev/null +++ b/common/credentials/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "credentials" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +thiserror = "1.0" +url = "2.2" + +# I guess temporarily until we get serde support in coconut up and running +coconut-interface = { path = "../coconut-interface" } +validator-client = { path = "../client-libs/validator-client" } diff --git a/common/credentials/src/bandwidth.rs b/common/credentials/src/bandwidth.rs new file mode 100644 index 0000000000..66096f732b --- /dev/null +++ b/common/credentials/src/bandwidth.rs @@ -0,0 +1,47 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +// for time being assume the bandwidth credential consists of public identity of the requester +// and private (though known... just go along with it) infinite bandwidth value +// right now this has no double-spending protection, spender binding, etc +// it's the simplest possible case + +use crate::error::Error; +use crate::utils::{obtain_aggregate_signature, prepare_credential_for_spending}; +use coconut_interface::{hash_to_scalar, Credential, Parameters, Signature, VerificationKey}; +use url::Url; + +const BANDWIDTH_VALUE: &str = "Bandwidth: infinite (for now)"; + +pub const PUBLIC_ATTRIBUTES: u32 = 1; +pub const PRIVATE_ATTRIBUTES: u32 = 1; +pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES; + +// TODO: this definitely has to be moved somewhere else. It's just a temporary solution +pub async fn obtain_signature(raw_identity: &[u8], validators: &[Url]) -> Result { + let public_attributes = vec![hash_to_scalar(raw_identity)]; + let private_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE)]; + + let params = Parameters::new(TOTAL_ATTRIBUTES)?; + + obtain_aggregate_signature(¶ms, &public_attributes, &private_attributes, validators).await +} + +pub fn prepare_for_spending( + raw_identity: &[u8], + signature: &Signature, + verification_key: &VerificationKey, +) -> Result { + let public_attributes = vec![hash_to_scalar(raw_identity)]; + let private_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE)]; + + let params = Parameters::new(TOTAL_ATTRIBUTES)?; + + prepare_credential_for_spending( + ¶ms, + &public_attributes, + &private_attributes, + signature, + verification_key, + ) +} diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs new file mode 100644 index 0000000000..552d346d6d --- /dev/null +++ b/common/credentials/src/error.rs @@ -0,0 +1,21 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use coconut_interface::CoconutError; +use thiserror::Error; +use validator_client::ValidatorClientError; + +#[derive(Debug, Error)] +pub enum Error { + #[error("The detailed description is yet to be determined")] + BandwidthCredentialError, + + #[error("Could not contact any validator")] + NoValidatorsAvailable, + + #[error("Run into a coconut error - {0}")] + CoconutError(#[from] CoconutError), + + #[error("Run into a validato client error - {0}")] + ValidatorClientError(#[from] ValidatorClientError), +} diff --git a/common/credentials/src/lib.rs b/common/credentials/src/lib.rs new file mode 100644 index 0000000000..099f348079 --- /dev/null +++ b/common/credentials/src/lib.rs @@ -0,0 +1,8 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod bandwidth; +pub mod error; +mod utils; + +pub use utils::{obtain_aggregate_signature, obtain_aggregate_verification_key}; diff --git a/common/credentials/src/utils.rs b/common/credentials/src/utils.rs new file mode 100644 index 0000000000..d12ab4decc --- /dev/null +++ b/common/credentials/src/utils.rs @@ -0,0 +1,134 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::Error; +use coconut_interface::{ + aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign, prove_credential, + Attribute, BlindSignRequestBody, Credential, Parameters, Signature, SignatureShare, + VerificationKey, +}; +use url::Url; + +/// Contacts all provided validators and then aggregate their verification keys. +/// +/// # Arguments +/// +/// * `validators`: list of validators to obtain verification keys from. +/// +/// Note: list of validators must be correctly ordered by the polynomial coordinates used +/// during key generation and it is responsibility of the caller to ensure that correct +/// number of them is provided +/// +/// # Examples +/// +/// ```no_run +/// use url::{Url, ParseError}; +/// use credentials::obtain_aggregate_verification_key; +/// +/// async fn example() -> Result<(), ParseError> { +/// let validators = vec!["https://testnet-milhon-validator1.nymtech.net/api".parse()?, "https://testnet-milhon-validator2.nymtech.net/api".parse()?]; +/// let aggregated_key = obtain_aggregate_verification_key(&validators).await; +/// // deal with the obtained Result +/// Ok(()) +/// } +/// ``` +pub async fn obtain_aggregate_verification_key( + validators: &[Url], +) -> Result { + if validators.is_empty() { + return Err(Error::NoValidatorsAvailable); + } + + let mut indices = Vec::with_capacity(validators.len()); + let mut shares = Vec::with_capacity(validators.len()); + + let mut client = validator_client::ApiClient::new(validators[0].clone()); + let response = client.get_coconut_verification_key().await?; + + indices.push(0); + shares.push(response.key); + + for (id, validator_url) in validators.iter().enumerate().skip(1) { + client.change_validator_api(validator_url.clone()); + let response = client.get_coconut_verification_key().await?; + indices.push(id as u64); + shares.push(response.key); + } + + Ok(aggregate_verification_keys(&shares, Some(&indices))?) +} + +async fn obtain_partial_credential( + params: &Parameters, + public_attributes: &[Attribute], + private_attributes: &[Attribute], + client: &validator_client::ApiClient, +) -> Result { + let elgamal_keypair = coconut_interface::elgamal_keygen(params); + let blind_sign_request = prepare_blind_sign( + params, + elgamal_keypair.public_key(), + private_attributes, + public_attributes, + )?; + + let blind_sign_request_body = BlindSignRequestBody::new( + blind_sign_request, + elgamal_keypair.public_key(), + public_attributes, + (public_attributes.len() + private_attributes.len()) as u32, + ); + + let blinded_signature = client + .blind_sign(&blind_sign_request_body) + .await? + .blinded_signature; + Ok(blinded_signature.unblind(elgamal_keypair.private_key())) +} + +pub async fn obtain_aggregate_signature( + params: &Parameters, + public_attributes: &[Attribute], + private_attributes: &[Attribute], + validators: &[Url], +) -> Result { + if validators.is_empty() { + return Err(Error::NoValidatorsAvailable); + } + + let mut shares = Vec::with_capacity(validators.len()); + + let mut client = validator_client::ApiClient::new(validators[0].clone()); + let first = + obtain_partial_credential(params, public_attributes, private_attributes, &client).await?; + shares.push(SignatureShare::new(first, 0)); + + for (id, validator_url) in validators.iter().enumerate().skip(1) { + client.change_validator_api(validator_url.clone()); + let signature = + obtain_partial_credential(params, public_attributes, private_attributes, &client) + .await?; + let share = SignatureShare::new(signature, id as u64); + shares.push(share) + } + + Ok(aggregate_signature_shares(&shares)?) +} + +// TODO: better type flow +pub fn prepare_credential_for_spending( + params: &Parameters, + public_attributes: &[Attribute], + private_attributes: &[Attribute], + signature: &Signature, + verification_key: &VerificationKey, +) -> Result { + let theta = prove_credential(params, verification_key, signature, private_attributes)?; + + Ok(Credential::new( + (public_attributes.len() + private_attributes.len()) as u32, + theta, + public_attributes, + signature, + )) +} diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml index 134817f973..05edb6ec22 100644 --- a/common/mixnode-common/Cargo.toml +++ b/common/mixnode-common/Cargo.toml @@ -8,11 +8,17 @@ edition = "2018" [dependencies] bytes = "1.0" -crypto = { path = "../crypto" } dashmap = "4.0" futures = "0.3" humantime-serde = "1.0" log = "0.4" +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"] } +url = "2.2" + +crypto = { path = "../crypto" } nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" } nymsphinx-acknowledgements = { path = "../nymsphinx/acknowledgements" } nymsphinx-addressing = { path = "../nymsphinx/addressing" } @@ -20,9 +26,5 @@ nymsphinx-forwarding = { path = "../nymsphinx/forwarding" } nymsphinx-framing = { path = "../nymsphinx/framing" } nymsphinx-params = { path = "../nymsphinx/params" } nymsphinx-types = { path = "../nymsphinx/types" } -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 = { path = "../client-libs/validator-client" } version-checker = { path = "../version-checker" } diff --git a/common/mixnode-common/src/verloc/mod.rs b/common/mixnode-common/src/verloc/mod.rs index 7daa497b25..52060cb804 100644 --- a/common/mixnode-common/src/verloc/mod.rs +++ b/common/mixnode-common/src/verloc/mod.rs @@ -8,11 +8,14 @@ use crypto::asymmetric::identity; use futures::stream::FuturesUnordered; use futures::StreamExt; use log::*; +use rand::seq::SliceRandom; +use rand::thread_rng; use std::net::{SocketAddr, ToSocketAddrs}; use std::sync::Arc; use std::time::Duration; use tokio::task::JoinHandle; use tokio::time::sleep; +use url::Url; use version_checker::parse_version; pub mod error; @@ -64,11 +67,8 @@ pub struct Config { /// due to being unable to get the list of nodes. retry_timeout: Duration, - /// URLs to the validator servers for obtaining network topology. - validator_urls: Vec, - - /// Address of the validator contract managing the network. - mixnet_contract_address: String, + /// URLs to the validator apis for obtaining network topology. + validator_api_urls: Vec, } impl Config { @@ -88,54 +88,57 @@ impl ConfigBuilder { self.0.minimum_compatible_node_version = version; self } + pub fn listening_address(mut self, listening_address: SocketAddr) -> Self { self.0.listening_address = listening_address; self } + pub fn packets_per_node(mut self, packets_per_node: usize) -> Self { self.0.packets_per_node = packets_per_node; self } + pub fn packet_timeout(mut self, packet_timeout: Duration) -> Self { self.0.packet_timeout = packet_timeout; self } + pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self { self.0.connection_timeout = connection_timeout; self } + pub fn delay_between_packets(mut self, delay_between_packets: Duration) -> Self { self.0.delay_between_packets = delay_between_packets; self } + pub fn tested_nodes_batch_size(mut self, tested_nodes_batch_size: usize) -> Self { self.0.tested_nodes_batch_size = tested_nodes_batch_size; self } + pub fn testing_interval(mut self, testing_interval: Duration) -> Self { self.0.testing_interval = testing_interval; self } + pub fn retry_timeout(mut self, retry_timeout: Duration) -> Self { self.0.retry_timeout = retry_timeout; self } - pub fn validator_urls(mut self, validator_urls: Vec) -> Self { - self.0.validator_urls = validator_urls; - self - } - pub fn mixnet_contract_address>(mut self, mixnet_contract_address: S) -> Self { - self.0.mixnet_contract_address = mixnet_contract_address.into(); + + pub fn validator_api_urls(mut self, validator_api_urls: Vec) -> Self { + self.0.validator_api_urls = validator_api_urls; self } + pub fn build(self) -> Config { // panics here are fine as those are only ever constructed at the initial setup - if self.0.validator_urls.is_empty() { + if self.0.validator_api_urls.is_empty() { panic!("at least one validator endpoint must be provided") } - if self.0.mixnet_contract_address.is_empty() { - panic!("the mixnet contract address must be set") - } self.0 } } @@ -152,8 +155,7 @@ impl Default for ConfigBuilder { tested_nodes_batch_size: DEFAULT_BATCH_SIZE, testing_interval: DEFAULT_TESTING_INTERVAL, retry_timeout: DEFAULT_RETRY_TIMEOUT, - validator_urls: vec![], - mixnet_contract_address: "".to_string(), + validator_api_urls: vec![], }) } } @@ -163,16 +165,20 @@ pub struct VerlocMeasurer { packet_sender: Arc, packet_listener: Arc, + currently_used_api: usize, + // Note: this client is only fine here as it does not maintain constant connection to the validator. // 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::Client, + validator_client: validator_client::ApiClient, results: AtomicVerlocResult, } impl VerlocMeasurer { - pub fn new(config: Config, identity: Arc) -> Self { + pub fn new(mut config: Config, identity: Arc) -> Self { + config.validator_api_urls.shuffle(&mut thread_rng()); + VerlocMeasurer { packet_sender: Arc::new(PacketSender::new( Arc::clone(&identity), @@ -185,15 +191,27 @@ impl VerlocMeasurer { config.listening_address, Arc::clone(&identity), )), - validator_client: validator_client::Client::new(validator_client::Config::new( - config.validator_urls.clone(), - config.mixnet_contract_address.clone(), - )), + currently_used_api: 0, + validator_client: validator_client::ApiClient::new( + config.validator_api_urls[0].clone(), + ), config, results: AtomicVerlocResult::new(), } } + fn use_next_validator_api(&mut self) { + if self.config.validator_api_urls.len() == 1 { + warn!("There's only a single validator API available - it won't be possible to use a different one"); + return; + } + + self.currently_used_api = + (self.currently_used_api + 1) % self.config.validator_api_urls.len(); + self.validator_client + .change_validator_api(self.config.validator_api_urls[self.currently_used_api].clone()) + } + pub fn get_verloc_results_pointer(&self) -> AtomicVerlocResult { self.results.clone_data_pointer() } @@ -255,13 +273,15 @@ impl VerlocMeasurer { loop { info!(target: "verloc", "Starting verloc measurements"); // TODO: should we also measure gateways? - let all_mixes = match self.validator_client.get_cached_mix_nodes().await { + + let all_mixes = match self.validator_client.get_cached_mixnodes().await { Ok(nodes) => nodes, Err(err) => { error!( - "failed to obtain list of mixnodes from the validator - {}", + "failed to obtain list of mixnodes from the validator - {}. Going to attempt to use another validator API in the next run", err ); + self.use_next_validator_api(); sleep(self.config.retry_timeout).await; continue; } diff --git a/common/network-defaults/Cargo.toml b/common/network-defaults/Cargo.toml new file mode 100644 index 0000000000..94e5c6d126 --- /dev/null +++ b/common/network-defaults/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "network-defaults" +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] +url = "2.2" diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs new file mode 100644 index 0000000000..072f1845ff --- /dev/null +++ b/common/network-defaults/src/lib.rs @@ -0,0 +1,82 @@ +// Copyright 2020 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use url::Url; + +pub struct ValidatorDetails<'a> { + // it is assumed those values are always valid since they're being provided in our defaults file + pub nymd_url: &'a str, + // Right now api_url is optional as we are not running the api reliably on all validators + // however, later on it should be a mandatory field + pub api_url: Option<&'a str>, +} + +impl<'a> ValidatorDetails<'a> { + pub const fn new(nymd_url: &'a str, api_url: Option<&'a str>) -> Self { + ValidatorDetails { nymd_url, api_url } + } + + pub fn nymd_url(&self) -> Url { + self.nymd_url + .parse() + .expect("the provided nymd url is invalid!") + } + + pub fn api_url(&self) -> Option { + self.api_url + .map(|url| url.parse().expect("the provided api url is invalid!")) + } +} + +pub const DEFAULT_VALIDATORS: &[ValidatorDetails] = &[ + ValidatorDetails::new( + "https://testnet-milhon-validator1.nymtech.net", + Some("https://testnet-milhon-validator1.nymtech.net/api"), + ), + ValidatorDetails::new("https://testnet-milhon-validator2.nymtech.net", None), +]; + +pub fn default_nymd_endpoints() -> Vec { + DEFAULT_VALIDATORS + .iter() + .map(|validator| validator.nymd_url()) + .collect() +} + +pub fn default_api_endpoints() -> Vec { + DEFAULT_VALIDATORS + .iter() + .filter_map(|validator| validator.api_url()) + .collect() +} + +pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen"; +pub const NETWORK_MONITOR_ADDRESS: &str = "punk1v9qauwdq5terag6uvfsdytcs2d0sdmfdy7hgk3"; + +/// Defaults Cosmos Hub/ATOM path +pub const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0"; +pub const BECH32_PREFIX: &str = "punk"; +pub const DENOM: &str = "upunk"; +// as set by validators in their configs +// (note that the 'amount' postfix is relevant here as the full gas price also includes denom) +pub const GAS_PRICE_AMOUNT: f64 = 0.025; + +pub const DEFAULT_MIX_LISTENING_PORT: u16 = 1789; + +// 'GATEWAY' +pub const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000; + +// 'MIXNODE' +pub const DEFAULT_VERLOC_LISTENING_PORT: u16 = 1790; +pub const DEFAULT_HTTP_API_LISTENING_PORT: u16 = 8000; + +// 'CLIENT' +pub const DEFAULT_WEBSOCKET_LISTENING_PORT: u16 = 1977; + +// 'SOCKS5' CLIENT +pub const DEFAULT_SOCKS5_LISTENING_PORT: u16 = 1080; + +// VALIDATOR-API +pub const DEFAULT_VALIDATOR_API_PORT: u16 = 8080; + +pub const VALIDATOR_API_VERSION: &str = "v1"; diff --git a/contracts/mixnet/Cargo.lock b/contracts/mixnet/Cargo.lock index 433b409d3c..ed361dcf24 100644 --- a/contracts/mixnet/Cargo.lock +++ b/contracts/mixnet/Cargo.lock @@ -62,8 +62,10 @@ version = "0.1.0" dependencies = [ "handlebars", "humantime-serde", + "network-defaults", "serde", "toml", + "url", ] [[package]] @@ -267,6 +269,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + [[package]] name = "generic-array" version = "0.12.4" @@ -365,6 +377,17 @@ dependencies = [ "serde", ] +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "itoa" version = "0.4.7" @@ -404,6 +427,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + [[package]] name = "mixnet-contract" version = "0.1.0" @@ -428,6 +457,13 @@ dependencies = [ "thiserror", ] +[[package]] +name = "network-defaults" +version = "0.1.0" +dependencies = [ + "url", +] + [[package]] name = "opaque-debug" version = "0.2.3" @@ -440,6 +476,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + [[package]] name = "pest" version = "2.1.3" @@ -714,6 +756,21 @@ dependencies = [ "syn", ] +[[package]] +name = "tinyvec" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + [[package]] name = "toml" version = "0.5.8" @@ -747,12 +804,42 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unicode-bidi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + [[package]] name = "version_check" version = "0.9.3" diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 6487b1a393..c5906c376a 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -22,4 +22,5 @@ log = "0.4.0" pretty_env_logger = "0.4.0" mixnet-contract = { path = "../common/mixnet-contract" } +network-defaults = { path = "../common/network-defaults" } validator-client = { path = "../common/client-libs/validator-client" } diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index 3271e87fc6..9d2c586ad3 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -12,8 +12,6 @@ mod mix_nodes; mod ping; mod state; -const VALIDATOR_API: &str = "http://testnet-milhon-validator1.nymtech.net:8080"; -const CONTRACT: &str = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen"; const GEO_IP_SERVICE: &str = "https://freegeoip.app/json/"; #[tokio::main] diff --git a/explorer-api/src/mix_nodes/mod.rs b/explorer-api/src/mix_nodes/mod.rs index 31354db960..9ed89fc4d0 100644 --- a/explorer-api/src/mix_nodes/mod.rs +++ b/explorer-api/src/mix_nodes/mod.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code; use mixnet_contract::MixNodeBond; -use validator_client::Config; +use network_defaults::default_api_endpoints; pub(crate) type LocationCache = HashMap; @@ -137,7 +137,7 @@ impl ThreadsafeMixNodesResult { let location = location_cache.get(&bond.mix_node.identity_key).cloned(); // add the location, if we've located this mix node before ( bond.mix_node.identity_key.to_string(), - MixNodeBondWithLocation { bond, location }, + MixNodeBondWithLocation { location, bond }, ) }) .collect(), @@ -152,7 +152,7 @@ pub(crate) async fn retrieve_mixnodes() -> Vec { info!("About to retrieve mixnode bonds..."); - let bonds: Vec = match client.get_cached_mix_nodes().await { + let bonds: Vec = match client.get_cached_mixnodes().await { Ok(result) => result, Err(e) => { error!("Unable to retrieve mixnode bonds: {:?}", e); @@ -164,7 +164,6 @@ pub(crate) async fn retrieve_mixnodes() -> Vec { } // TODO: inject constants -fn new_validator_client() -> validator_client::Client { - let config = Config::new(vec![crate::VALIDATOR_API.to_string()], crate::CONTRACT); - validator_client::Client::new(config) +fn new_validator_client() -> validator_client::ApiClient { + validator_client::ApiClient::new(default_api_endpoints()[0].clone()) } diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 395a615c8d..5248800246 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -25,9 +25,11 @@ tokio = { version = "1.4", features = [ "rt-multi-thread", "net", "signal", "fs" tokio-util = { version = "0.6", features = [ "codec" ] } tokio-stream = { version = "0.1", features = [ "fs" ] } tokio-tungstenite = "0.14" +url = { version = "2.2", features = [ "serde" ] } # internal coconut-interface = { path = "../common/coconut-interface" } +credentials = { path = "../common/credentials" } config = { path = "../common/config" } crypto = { path = "../common/crypto" } gateway-requests = { path = "gateway-requests" } diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index 7b466dc9b1..79552d4fc6 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -61,12 +61,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) - .arg( - Arg::with_name(CONTRACT_ARG_NAME) - .long(CONTRACT_ARG_NAME) - .help("Address of the validator contract managing the network") - .takes_value(true), - ) } fn show_bonding_info(config: &Config) { diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index f258495827..3670b21e8c 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -3,6 +3,7 @@ use crate::config::Config; use clap::ArgMatches; +use url::Url; pub(crate) mod init; pub(crate) mod run; @@ -13,14 +14,18 @@ pub(crate) const HOST_ARG_NAME: &str = "host"; pub(crate) const MIX_PORT_ARG_NAME: &str = "mix-port"; pub(crate) const CLIENTS_PORT_ARG_NAME: &str = "clients-port"; pub(crate) const VALIDATORS_ARG_NAME: &str = "validators"; -pub(crate) const CONTRACT_ARG_NAME: &str = "mixnet-contract"; pub(crate) const ANNOUNCE_HOST_ARG_NAME: &str = "announce-host"; pub(crate) const INBOXES_ARG_NAME: &str = "inboxes"; pub(crate) const CLIENTS_LEDGER_ARG_NAME: &str = "clients-ledger"; -fn parse_validators(raw: &str) -> Vec { +fn parse_validators(raw: &str) -> Vec { raw.split(',') - .map(|raw_validator| raw_validator.trim().into()) + .map(|raw_validator| { + raw_validator + .trim() + .parse() + .expect("one of the provided validator api urls is invalid") + }) .collect() } @@ -61,11 +66,7 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi } if let Some(raw_validators) = matches.value_of(VALIDATORS_ARG_NAME) { - config = config.with_custom_validators(parse_validators(raw_validators)); - } - - if let Some(contract_address) = matches.value_of(CONTRACT_ARG_NAME) { - config = config.with_custom_mixnet_contract(contract_address) + config = config.with_custom_validator_apis(parse_validators(raw_validators)); } if let Some(inboxes_dir) = matches.value_of(INBOXES_ARG_NAME) { diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index 0418be413a..da03f685ed 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -64,12 +64,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) - .arg( - Arg::with_name(CONTRACT_ARG_NAME) - .long(CONTRACT_ARG_NAME) - .help("Address of the validator contract managing the network") - .takes_value(true), - ) } fn show_binding_warning(address: String) { @@ -162,7 +156,7 @@ pub fn execute(matches: &ArgMatches) { println!( "Validator servers: {:?}", - config.get_validator_rest_endpoints() + config.get_validator_api_endpoints() ); println!( diff --git a/gateway/src/commands/upgrade.rs b/gateway/src/commands/upgrade.rs index c9eb592c93..48cd7ae046 100644 --- a/gateway/src/commands/upgrade.rs +++ b/gateway/src/commands/upgrade.rs @@ -1,17 +1,16 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::commands::*; +use crate::config::{Config, MISSING_VALUE}; +use clap::{App, Arg, ArgMatches}; +use config::defaults::default_api_endpoints; +use config::NymConfig; use std::fmt::Display; use std::process; +use version_checker::Version; -use clap::{App, Arg, ArgMatches}; - -use config::defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS; -use config::NymConfig; -use version_checker::{parse_version, Version}; - -use crate::config::{default_validator_rest_endpoints, Config, MISSING_VALUE}; - +#[allow(dead_code)] fn fail_upgrade(from_version: D1, to_version: D2) -> ! { print_failed_upgrade(from_version, to_version); process::exit(1) @@ -38,38 +37,29 @@ fn print_successful_upgrade(from: D1, to: D2) { ); } -pub fn command_args<'a, 'b>() -> App<'a, 'b> { - App::new("upgrade").about("Try to upgrade the gateway") - .arg( - Arg::with_name("id") - .long("id") - .help("Id of the nym-gateway we want to upgrade") - .takes_value(true) - .required(true), - ) - // the rest of arguments depend on the upgrade path - .arg(Arg::with_name("current version") - .long("current-version") - .help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'") - .takes_value(true) - ) - .arg(Arg::with_name("listening-address") - .long("listening-address") - .help("REQUIRED FOR 0.11.0 UPGRADE. Specifies the listening address of this gateway") - .takes_value(true) - ) - .arg(Arg::with_name("announce-address") - .long("announce-address") - .help("OPTIONAL FOR 0.11.0 UPGRADE. Specifies the announce address of this gateway. If not provided, it will be set to the same value as listening address") - .takes_value(true) - ) +fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! { + eprintln!( + "Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!", + config_version, package_version + ); + process::exit(1) } -fn unsupported_upgrade(current_version: Version, config_version: Version) -> ! { +fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> ! { eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, current_version); process::exit(1) } +pub fn command_args<'a, 'b>() -> App<'a, 'b> { + App::new("upgrade").about("Try to upgrade the gateway").arg( + Arg::with_name("id") + .long("id") + .help("Id of the nym-gateway we want to upgrade") + .takes_value(true) + .required(true), + ) +} + fn parse_config_version(config: &Config) -> Version { let version = Version::parse(config.get_version()).unwrap_or_else(|err| { eprintln!("failed to parse client version! - {:?}", err); @@ -104,193 +94,33 @@ fn parse_package_version() -> Version { version } -fn pre_090_upgrade(from: &str, config: Config) -> Config { - // this is not extracted to separate function as you only have to manually pass version - // if upgrading from pre090 version - let from = match from.strip_prefix('v') { - Some(stripped) => stripped, - None => from, - }; - - let from = match from.strip_prefix('V') { - Some(stripped) => stripped, - None => from, - }; - - let from_version = parse_version(from).expect("invalid version provided!"); - if from_version.major == 0 && from_version.minor < 8 { - // technically this could be implemented, but is there any point in that? - eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead"); - process::exit(1) - } - - if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 { - eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version"); - process::exit(1) - } - - // note: current is guaranteed to not have any `build` information suffix (nor pre-release - // information), as this was asserted at the beginning of this command) - // - // upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate - // step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0) - // this way we don't need to have all the crazy paths on how to upgrade from any version to any - // other version. We just upgrade one minor version at a time. - let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); - let to_version = if current.major == 0 && current.minor == 9 { - current - } else { - Version::new(0, 9, 0) - }; - - print_start_upgrade(&from_version, &to_version); - - if config.get_validator_rest_endpoints()[0] != MISSING_VALUE { - eprintln!("existing config seems to have specified new validator rest endpoint which was only introduced in 0.9.0! Can't perform upgrade."); - print_failed_upgrade(&from_version, &to_version); - process::exit(1); - } - - println!( - "Setting validator REST endpoint to to {:?}", - default_validator_rest_endpoints() - ); - - let upgraded_config = config - .with_custom_version(to_version.to_string().as_ref()) - .with_custom_validators(default_validator_rest_endpoints()); - - upgraded_config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); - print_failed_upgrade(&from_version, &to_version); - process::exit(1); - }); - - print_successful_upgrade(from_version, to_version); - - upgraded_config -} - -fn minor_010_upgrade( +fn minor_0_12_upgrade( config: Config, _matches: &ArgMatches, config_version: &Version, package_version: &Version, ) -> Config { - let to_version = if package_version.major == 0 && package_version.minor == 10 { + let to_version = if package_version.major == 0 && package_version.minor == 12 { package_version.clone() } else { - Version::new(0, 10, 0) + Version::new(0, 12, 0) }; print_start_upgrade(&config_version, &to_version); - if config.get_validator_mixnet_contract_address() != MISSING_VALUE { - eprintln!("existing config seems to have specified mixnet contract address which was only introduced in 0.10.0! Can't perform upgrade."); - fail_upgrade(&config_version, &to_version) - } - println!( - "Setting validator REST endpoint to {:?}", - default_validator_rest_endpoints() - ); - - println!( - "Setting mixnet contract address to {}", - DEFAULT_MIXNET_CONTRACT_ADDRESS + "Setting validator API endpoints to {:?}", + default_api_endpoints() ); let upgraded_config = config .with_custom_version(to_version.to_string().as_ref()) - .with_custom_validators(default_validator_rest_endpoints()) - .with_custom_mixnet_contract(DEFAULT_MIXNET_CONTRACT_ADDRESS); + .with_custom_validator_apis(default_api_endpoints()); upgraded_config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); - fail_upgrade(&config_version, &to_version) - }); - - print_successful_upgrade(config_version, to_version); - - upgraded_config -} - -// no changes but version number -fn patch_010_upgrade( - config: Config, - _matches: &ArgMatches, - config_version: &Version, - package_version: &Version, -) -> Config { - let to_version = package_version; - - print_start_upgrade(&config_version, &to_version); - - let upgraded_config = config.with_custom_version(to_version.to_string().as_ref()); - - upgraded_config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); - fail_upgrade(&config_version, &to_version) - }); - - print_successful_upgrade(config_version, to_version); - - upgraded_config -} - -fn minor_011_upgrade( - config: Config, - matches: &ArgMatches, - config_version: &Version, - package_version: &Version, -) -> Config { - let to_version = if package_version.major == 0 && package_version.minor == 11 { - package_version.clone() - } else { - Version::new(0, 11, 0) - }; - - print_start_upgrade(&config_version, &to_version); - - // normally the proper approach would have been to keep old structs, parse file according to old structs - // and move it to the new ones - // however, considering that at this point of time we don't have many gateways and all - // are run by us, this would be an unnecessary code complication and so just providing - // addresses again during upgrade is I think a better approach. - let listening_address = matches.value_of("listening-address").unwrap_or_else(|| { - eprintln!( - "trying to upgrade to {} without providing proper listening address!", - to_version - ); - fail_upgrade(&config_version, &to_version) - }); - - let announce_address = if let Some(announce_addr) = matches.value_of("announce-address") { - announce_addr.to_string() - } else { - listening_address.to_string() - }; - - println!( - "Setting validator REST endpoint to {:?}", - default_validator_rest_endpoints() - ); - - println!( - "Setting mixnet contract address to {}", - DEFAULT_MIXNET_CONTRACT_ADDRESS - ); - - let upgraded_config = config - .with_custom_version(to_version.to_string().as_ref()) - .with_listening_address(listening_address) - .with_announce_address(announce_address) - .with_custom_validators(default_validator_rest_endpoints()) - .with_custom_mixnet_contract(DEFAULT_MIXNET_CONTRACT_ADDRESS); - - upgraded_config.save_to_file(None).unwrap_or_else(|err| { - eprintln!("failed to overwrite config file! - {:?}", err); - fail_upgrade(&config_version, &to_version) + print_failed_upgrade(&config_version, &to_version); + process::exit(1); }); print_successful_upgrade(config_version, to_version); @@ -309,18 +139,11 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches, package_version: Version config = match config_version.major { 0 => match config_version.minor { - 9 => minor_010_upgrade(config, matches, &config_version, &Version::new(0, 10, 0)), - 10 => match config_version.patch { - 0 => { - patch_010_upgrade(config, matches, &config_version, &Version::new(0, 10, 1)) - } - _ => { - minor_011_upgrade(config, matches, &config_version, &Version::new(0, 11, 0)) - } - }, - _ => unsupported_upgrade(config_version, package_version), + 9 | 10 => outdated_upgrade(&config_version, &package_version), + 11 => minor_0_12_upgrade(config, matches, &config_version, &package_version), + _ => unsupported_upgrade(&config_version, &package_version), }, - _ => unsupported_upgrade(config_version, package_version), + _ => unsupported_upgrade(&config_version, &package_version), } } } @@ -328,26 +151,17 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches, package_version: Version pub fn execute(matches: &ArgMatches) { let package_version = parse_package_version(); - let id = matches.value_of("id").unwrap(); + let id = matches.value_of(ID_ARG_NAME).unwrap(); - let mut existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { + let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {:?}", err); process::exit(1) }); - // versions fields were added in 0.9.0 if existing_config.get_version() == MISSING_VALUE { - let self_reported_version = matches.value_of("current version").unwrap_or_else(|| { - eprintln!( - "trying to upgrade from pre v0.9.0 without providing current system version!" - ); - process::exit(1) - }); - - // upgrades up to 0.9.0 - existing_config = pre_090_upgrade(self_reported_version, existing_config); + eprintln!("the existing configuration file does not seem to contain version number."); + process::exit(1); } - // here be upgrade path to 0.9.X and beyond based on version number from config do_upgrade(existing_config, matches, package_version) } diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index dc7461f517..af82b6e156 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -3,12 +3,13 @@ use crate::config::template::config_template; use config::defaults::*; -use config::{deserialize_duration, deserialize_validators, NymConfig}; +use config::NymConfig; use log::error; use serde::{Deserialize, Serialize}; use std::net::IpAddr; use std::path::PathBuf; use std::time::Duration; +use url::Url; pub mod persistence; mod template; @@ -26,22 +27,10 @@ const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 128; const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16; const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: u16 = 5; -// helper function to get default validators as a Vec -pub fn default_validator_rest_endpoints() -> Vec { - DEFAULT_VALIDATOR_REST_ENDPOINTS - .iter() - .map(|&endpoint| endpoint.to_string()) - .collect() -} - pub fn missing_string_value() -> String { MISSING_VALUE.to_string() } -pub fn missing_vec_string_value() -> Vec { - vec![missing_string_value()] -} - fn bind_all_address() -> IpAddr { "0.0.0.0".parse().unwrap() } @@ -143,13 +132,8 @@ impl Config { self } - pub fn with_custom_validators(mut self, validators: Vec) -> Self { - self.gateway.validator_rest_urls = validators; - self - } - - pub fn with_custom_mixnet_contract>(mut self, mixnet_contract: S) -> Self { - self.gateway.mixnet_contract_address = mixnet_contract.into(); + pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec) -> Self { + self.gateway.validator_api_urls = validator_api_urls; self } @@ -222,12 +206,8 @@ impl Config { self.gateway.public_sphinx_key_file.clone() } - pub fn get_validator_rest_endpoints(&self) -> Vec { - self.gateway.validator_rest_urls.clone() - } - - pub fn get_validator_mixnet_contract_address(&self) -> String { - self.gateway.mixnet_contract_address.clone() + pub fn get_validator_api_endpoints(&self) -> Vec { + self.gateway.validator_api_urls.clone() } pub fn get_listening_address(&self) -> IpAddr { @@ -324,17 +304,8 @@ pub struct Gateway { /// Path to file containing public sphinx key. public_sphinx_key_file: PathBuf, - /// Validator server to which the node will be reporting their presence data. - #[serde( - deserialize_with = "deserialize_validators", - default = "missing_vec_string_value", - alias = "validator_rest_url" - )] - validator_rest_urls: Vec, - - /// Address of the validator contract managing the network. - #[serde(default = "missing_string_value")] - mixnet_contract_address: String, + /// Addresses to APIs running on validator from which the node gets the view of the network. + validator_api_urls: Vec, /// nym_home_directory specifies absolute path to the home nym gateways directory. /// It is expected to use default value and hence .toml file should not redefine this field. @@ -372,8 +343,7 @@ impl Default for Gateway { public_identity_key_file: Default::default(), private_sphinx_key_file: Default::default(), public_sphinx_key_file: Default::default(), - validator_rest_urls: default_validator_rest_endpoints(), - mixnet_contract_address: DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(), + validator_api_urls: default_api_endpoints(), nym_root_directory: Config::default_root_directory(), } } @@ -423,35 +393,23 @@ impl Default for Logging { pub struct Debug { /// Initial value of an exponential backoff to reconnect to dropped TCP connection when /// forwarding sphinx packets. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] packet_forwarding_initial_backoff: Duration, /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when /// forwarding sphinx packets. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] packet_forwarding_maximum_backoff: Duration, /// Timeout for establishing initial connection when trying to forward a sphinx packet. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] initial_connection_timeout: Duration, /// Maximum number of packets that can be stored waiting to get sent to a particular connection. maximum_connection_buffer_size: usize, /// Delay between each subsequent presence data being sent. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] presence_sending_delay: Duration, /// Length of filenames for new client messages. diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index c4522abfdd..8a833a9ded 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -49,16 +49,13 @@ mix_port = {{ gateway.mix_port }} # (default: 9000) clients_port = {{ gateway.clients_port }} -# Validator server to which the node will be getting information about the network. -validator_rest_urls = [ - {{#each gateway.validator_rest_urls }} +# Addresses to APIs running on validator from which the node gets the view of the network. +validator_api_urls = [ + {{#each gateway.validator_api_urls }} '{{this}}', {{/each}} ] -# Address of the validator contract managing the network. -mixnet_contract_address = '{{ gateway.mixnet_contract_address }}' - ##### advanced configuration options ##### # nym_home_directory specifies absolute path to the home nym gateway directory. diff --git a/gateway/src/node/client_handling/websocket/connection_handler.rs b/gateway/src/node/client_handling/websocket/connection_handler.rs index f3a629e623..82c459fe4a 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler.rs @@ -7,7 +7,7 @@ use crate::node::client_handling::clients_handler::{ use crate::node::client_handling::websocket::message_receiver::{ MixMessageReceiver, MixMessageSender, }; -use coconut_interface::get_aggregated_verification_key; +use coconut_interface::VerificationKey; use crypto::asymmetric::identity; use futures::{ channel::{mpsc, oneshot}, @@ -30,7 +30,6 @@ use tokio_tungstenite::{ tungstenite::{protocol::Message, Error as WsError}, WebSocketStream, }; -use validator_client::validator_api::Client as ValidatorAPIClient; //// TODO: note for my future self to consider the following idea: //// split the socket connection into sink and stream @@ -58,7 +57,8 @@ pub(crate) struct Handle { outbound_mix_sender: MixForwardingSender, socket_connection: SocketStream, local_identity: Arc, - validator_urls: Vec, + + aggregated_verification_key: VerificationKey, } impl Handle @@ -73,7 +73,7 @@ where clients_handler_sender: ClientsHandlerRequestSender, outbound_mix_sender: MixForwardingSender, local_identity: Arc, - validator_urls: Vec, + aggregated_verification_key: VerificationKey, ) -> Self { Handle { rng, @@ -83,7 +83,7 @@ where outbound_mix_sender, socket_connection: SocketStream::RawTcp(conn), local_identity, - validator_urls, + aggregated_verification_key, } } @@ -114,18 +114,12 @@ where debug_assert!(self.socket_connection.is_websocket()); match &mut self.socket_connection { SocketStream::UpgradedWebSocket(ws_stream) => { - let verification_key = get_aggregated_verification_key( - self.validator_urls.clone(), - &ValidatorAPIClient::default(), - ) - .await - .unwrap(); gateway_handshake( &mut self.rng, ws_stream, self.local_identity.as_ref(), init_msg, - &verification_key, + &self.aggregated_verification_key, ) .await } diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index c6ca71f9b5..7978e77a93 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -3,6 +3,7 @@ use crate::node::client_handling::clients_handler::ClientsHandlerRequestSender; use crate::node::client_handling::websocket::connection_handler::Handle; +use coconut_interface::VerificationKey; use crypto::asymmetric::identity; use log::*; use mixnet_client::forwarder::MixForwardingSender; @@ -15,19 +16,19 @@ use tokio::task::JoinHandle; pub(crate) struct Listener { address: SocketAddr, local_identity: Arc, - validator_urls: Vec, + aggregated_verification_key: VerificationKey, } impl Listener { pub(crate) fn new( address: SocketAddr, local_identity: Arc, - validator_urls: Vec, + aggregated_verification_key: VerificationKey, ) -> Self { Listener { address, local_identity, - validator_urls, + aggregated_verification_key, } } @@ -57,7 +58,7 @@ impl Listener { clients_handler_sender.clone(), outbound_mix_sender.clone(), Arc::clone(&self.local_identity), - self.validator_urls.clone(), + self.aggregated_verification_key.clone(), ); tokio::spawn(async move { handle.start_handling().await }); } diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index be80abd12c..a67da4f33d 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -6,9 +6,13 @@ use crate::node::client_handling::clients_handler::{ClientsHandler, ClientsHandl use crate::node::client_handling::websocket; use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; use crate::node::storage::{inboxes, ClientLedger}; +use coconut_interface::VerificationKey; +use credentials::obtain_aggregate_verification_key; use crypto::asymmetric::{encryption, identity}; use log::*; use mixnet_client::forwarder::{MixForwardingSender, PacketForwarder}; +use rand::seq::SliceRandom; +use rand::thread_rng; use std::net::SocketAddr; use std::process; use std::sync::Arc; @@ -81,6 +85,7 @@ impl Gateway { &self, forwarding_channel: MixForwardingSender, clients_handler_sender: ClientsHandlerRequestSender, + verification_key: VerificationKey, ) { info!("Starting client [web]socket listener..."); @@ -92,7 +97,7 @@ impl Gateway { websocket::Listener::new( listening_address, Arc::clone(&self.identity), - self.config.get_validator_rest_endpoints(), + verification_key, ) .start(clients_handler_sender, forwarding_channel); } @@ -135,11 +140,11 @@ impl Gateway { // TODO: ask DH whether this function still makes sense in ^0.10 async fn check_if_same_ip_gateway_exists(&self) -> Option { - let validator_client_config = validator_client::Config::new( - self.config.get_validator_rest_endpoints(), - self.config.get_validator_mixnet_contract_address(), - ); - let validator_client = validator_client::Client::new(validator_client_config); + let endpoints = self.config.get_validator_api_endpoints(); + let validator_api = endpoints + .choose(&mut thread_rng()) + .expect("The list of validator apis is empty"); + let validator_client = validator_client::ApiClient::new(validator_api.clone()); let existing_gateways = match validator_client.get_cached_gateways().await { Ok(gateways) => gateways, @@ -178,11 +183,13 @@ impl Gateway { } } + let validators_verification_key = obtain_aggregate_verification_key(&self.config.get_validator_api_endpoints()).await.expect("failed to contact validators to obtain their verification keys"); + let mix_forwarding_channel = self.start_packet_forwarder(); let clients_handler_sender = self.start_clients_handler(); self.start_mix_socket_listener(clients_handler_sender.clone(), mix_forwarding_channel.clone()); - self.start_client_websocket_listener(mix_forwarding_channel, clients_handler_sender); + self.start_client_websocket_listener(mix_forwarding_channel, clients_handler_sender, validators_verification_key); info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!"); diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 3f12e93ce1..968df4a9e1 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -24,11 +24,12 @@ humantime-serde = "1.0" log = "0.4.0" pretty_env_logger = "0.4.0" rand = "0.7.3" -rocket = { version="0.5.0-rc.1", features=["json"] } -serde = { version="1.0", features=["derive"] } -tokio = { version="1.8", features=["rt-multi-thread", "net", "signal"] } -tokio-util = { version="0.6.7", features=["codec"] } +rocket = { version="0.5.0-rc.1", features = ["json"] } +serde = { version="1.0", features = ["derive"] } +tokio = { version="1.8", features = ["rt-multi-thread", "net", "signal"] } +tokio-util = { version="0.6.7", features = ["codec"] } toml = "0.5.8" +url = { version = "2.2", features = ["serde"] } ## internal config = { path="../common/config" } diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index 18d0360a5f..2011910e7c 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -56,12 +56,6 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> { .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) - .arg( - Arg::with_name(CONTRACT_ARG_NAME) - .long(CONTRACT_ARG_NAME) - .help("Address of the validator contract managing the network") - .takes_value(true), - ) } fn show_bonding_info(config: &Config) { diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index ff5d1df2d7..d86fad744b 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -3,6 +3,7 @@ use crate::config::Config; use clap::ArgMatches; +use url::Url; pub(crate) mod describe; pub(crate) mod init; @@ -16,12 +17,16 @@ pub(crate) const MIX_PORT_ARG_NAME: &str = "mix-port"; pub(crate) const VERLOC_PORT_ARG_NAME: &str = "verloc-port"; pub(crate) const HTTP_API_PORT_ARG_NAME: &str = "http-api-port"; pub(crate) const VALIDATORS_ARG_NAME: &str = "validators"; -pub(crate) const CONTRACT_ARG_NAME: &str = "mixnet-contract"; pub(crate) const ANNOUNCE_HOST_ARG_NAME: &str = "announce-host"; -fn parse_validators(raw: &str) -> Vec { +fn parse_validators(raw: &str) -> Vec { raw.split(',') - .map(|raw_validator| raw_validator.trim().into()) + .map(|raw_validator| { + raw_validator + .trim() + .parse() + .expect("one of the provided validator api urls is invalid") + }) .collect() } @@ -66,11 +71,7 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi } if let Some(raw_validators) = matches.value_of(VALIDATORS_ARG_NAME) { - config = config.with_custom_validators(parse_validators(raw_validators)); - } - - if let Some(contract_address) = matches.value_of(CONTRACT_ARG_NAME) { - config = config.with_custom_mixnet_contract(contract_address) + config = config.with_custom_validator_apis(parse_validators(raw_validators)); } if let Some(announce_host) = matches.value_of(ANNOUNCE_HOST_ARG_NAME) { diff --git a/mixnode/src/commands/run.rs b/mixnode/src/commands/run.rs index 5a8ece7f10..193751da4a 100644 --- a/mixnode/src/commands/run.rs +++ b/mixnode/src/commands/run.rs @@ -58,12 +58,6 @@ pub fn command_args<'a, 'b>() -> App<'a, 'b> { .help("Comma separated list of rest endpoints of the validators") .takes_value(true), ) - .arg( - Arg::with_name(CONTRACT_ARG_NAME) - .long(CONTRACT_ARG_NAME) - .help("Address of the validator contract managing the network") - .takes_value(true), - ) } fn show_binding_warning(address: String) { @@ -151,7 +145,7 @@ pub fn execute(matches: &ArgMatches) { println!( "Validator servers: {:?}", - config.get_validator_rest_endpoints() + config.get_validator_api_endpoints() ); println!( "Listening for incoming packets on {}", diff --git a/mixnode/src/commands/upgrade.rs b/mixnode/src/commands/upgrade.rs index 8f4afdaf5f..5f4afe8d50 100644 --- a/mixnode/src/commands/upgrade.rs +++ b/mixnode/src/commands/upgrade.rs @@ -2,25 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use crate::commands::*; -use crate::config::{ - default_validator_rest_endpoints, missing_string_value, Config, MISSING_VALUE, -}; -use crate::node::node_description::{NodeDescription, DESCRIPTION_FILE}; +use crate::config::{missing_string_value, Config}; use clap::{App, Arg, ArgMatches}; -use config::defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS; +use config::defaults::default_api_endpoints; use config::NymConfig; -use crypto::asymmetric::identity; -use serde::Deserialize; use std::fmt::Display; -use std::net::SocketAddr; -use std::path::PathBuf; -use std::str::FromStr; -use std::{fs, process}; -use version_checker::{parse_version, Version}; - -type UpgradeError = (Version, String); -const CURRENT_VERSION_ARG_NAME: &str = "current-version"; +use std::process; +use version_checker::Version; +#[allow(dead_code)] fn fail_upgrade(from_version: D1, to_version: D2) -> ! { print_failed_upgrade(from_version, to_version); process::exit(1) @@ -47,28 +37,29 @@ fn print_successful_upgrade(from: D1, to: D2) { ); } -pub fn command_args<'a, 'b>() -> App<'a, 'b> { - App::new("upgrade").about("Try to upgrade the mixnode") - .arg( - Arg::with_name(ID_ARG_NAME) - .long(ID_ARG_NAME) - .help("Id of the nym-mixnode we want to upgrade") - .takes_value(true) - .required(true), - ) - // the rest of arguments depend on the upgrade path - .arg(Arg::with_name(CURRENT_VERSION_ARG_NAME) - .long(CURRENT_VERSION_ARG_NAME) - .help("REQUIRED FOR PRE-0.9.0 UPGRADES. Specifies current version of the configuration file to help to determine a valid upgrade path. Valid formats include '0.8.1', 'v0.8.1' or 'V0.8.1'") - .takes_value(true) - ) +fn outdated_upgrade(config_version: &Version, package_version: &Version) -> ! { + eprintln!( + "Cannot perform upgrade from {} to {}. Your version is too old to perform the upgrade.!", + config_version, package_version + ); + process::exit(1) } -fn unsupported_upgrade(config_version: Version, package_version: Version) -> ! { +fn unsupported_upgrade(config_version: &Version, package_version: &Version) -> ! { eprintln!("Cannot perform upgrade from {} to {}. Please let the developers know about this issue if you expected it to work!", config_version, package_version); process::exit(1) } +pub fn command_args<'a, 'b>() -> App<'a, 'b> { + App::new("upgrade").about("Try to upgrade the mixnode").arg( + Arg::with_name(ID_ARG_NAME) + .long(ID_ARG_NAME) + .help("Id of the nym-mixnode we want to upgrade") + .takes_value(true) + .required(true), + ) +} + fn parse_config_version(config: &Config) -> Version { let version = Version::parse(config.get_version()).unwrap_or_else(|err| { eprintln!("failed to parse client version! - {:?}", err); @@ -103,276 +94,40 @@ fn parse_package_version() -> Version { version } -fn pre_090_upgrade(from: &str, config: Config) -> Config { - // note: current is guaranteed to not have any `build` information suffix (nor pre-release - // information), as this was asserted at the beginning of this command) - // - // upgrade to current (if it's a 0.9.X) or try to upgrade to 0.9.0 as an intermediate - // step in future upgrades (so, for example, we might go 0.8.0 -> 0.9.0 -> 0.10.0) - // this way we don't need to have all the crazy paths on how to upgrade from any version to any - // other version. We just upgrade one minor version at a time. - let current = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); - let to_version = if current.major == 0 && current.minor == 9 { - current +fn minor_0_12_upgrade( + config: Config, + _matches: &ArgMatches, + config_version: &Version, + package_version: &Version, +) -> Config { + let to_version = if package_version.major == 0 && package_version.minor == 12 { + package_version.clone() } else { - Version::new(0, 9, 0) + Version::new(0, 12, 0) }; - print_start_upgrade(&from, &to_version); - - // this is not extracted to separate function as you only have to manually pass version - // if upgrading from pre090 version - let from = match from.strip_prefix('v') { - Some(stripped) => stripped, - None => from, - }; - - let from = match from.strip_prefix('V') { - Some(stripped) => stripped, - None => from, - }; - - let from_version = parse_version(from).expect("invalid version provided!"); - if from_version.major == 0 && from_version.minor < 8 { - // technically this could be implemented, but is there any point in that? - eprintln!("upgrading node from before v0.8.0 is not supported. Please run `init` with new binary instead"); - fail_upgrade(&from_version, &to_version) - } - - if (from_version.major == 0 && from_version.minor >= 9) || from_version.major >= 1 { - eprintln!("self reported version is higher than 0.9.0. Those releases should have already contained version numbers in config! Make sure you provided correct version"); - fail_upgrade(&from_version, &to_version) - } - - if config.get_private_identity_key_file() != missing_string_value::() - || config.get_public_identity_key_file() != missing_string_value::() - { - eprintln!("existing config seems to have specified identity keys which were only introduced in 0.9.0! Can't perform upgrade."); - fail_upgrade(&from_version, &to_version) - } - - if config.get_validator_rest_endpoints()[0] != missing_string_value::() { - eprintln!("existing config seems to have specified new validator rest endpoint which was only introduced in 0.9.0! Can't perform upgrade."); - fail_upgrade(&from_version, &to_version) - } - - let mut upgraded_config = config - .with_custom_version(to_version.to_string().as_ref()) - .with_custom_validators(default_validator_rest_endpoints()); + print_start_upgrade(&config_version, &to_version); println!( - "Setting validator REST endpoints to {:?}", - default_validator_rest_endpoints() + "Setting validator API endpoints to {:?}", + default_api_endpoints() ); - println!("Generating new identity..."); - let mut rng = rand::rngs::OsRng; - - let identity_keys = identity::KeyPair::new(&mut rng); - upgraded_config.set_default_identity_keypair_paths(); - - if let Err(err) = pemstore::store_keypair( - &identity_keys, - &pemstore::KeyPairPath::new( - upgraded_config.get_private_identity_key_file(), - upgraded_config.get_public_identity_key_file(), - ), - ) { - eprintln!("Failed to save new identity key files! - {}", err); - process::exit(1); - } + let upgraded_config = config + .with_custom_version(to_version.to_string().as_ref()) + .with_custom_validator_apis(default_api_endpoints()); upgraded_config.save_to_file(None).unwrap_or_else(|err| { eprintln!("failed to overwrite config file! - {:?}", err); - fail_upgrade(&from_version, &to_version) + print_failed_upgrade(&config_version, &to_version); + process::exit(1); }); - print_successful_upgrade(from_version, to_version); + print_successful_upgrade(config_version, to_version); upgraded_config } -fn minor_0_10_upgrade( - config: Config, - _matches: &ArgMatches, - config_version: &Version, - package_version: &Version, -) -> Result { - let to_version = if package_version.major == 0 && package_version.minor == 10 { - package_version.clone() - } else { - Version::new(0, 10, 0) - }; - - print_start_upgrade(&config_version, &to_version); - - if config.get_validator_mixnet_contract_address() != MISSING_VALUE { - return Err((to_version, "existing config seems to have specified mixnet contract address which was only introduced in 0.10.0! Can't perform upgrade.".to_string())); - } - - println!( - "Setting validator REST endpoint to {:?}", - default_validator_rest_endpoints() - ); - - println!( - "Setting mixnet contract address to {}", - DEFAULT_MIXNET_CONTRACT_ADDRESS - ); - - let upgraded_config = config - .with_custom_version(to_version.to_string().as_ref()) - .with_custom_validators(default_validator_rest_endpoints()) - .with_custom_mixnet_contract(DEFAULT_MIXNET_CONTRACT_ADDRESS); - - upgraded_config.save_to_file(None).map_err(|err| { - ( - to_version.clone(), - format!("failed to overwrite config file! - {:?}", err), - ) - })?; - - print_successful_upgrade(config_version, to_version); - - Ok(upgraded_config) -} - -fn patch_0_10_1_upgrade( - config: Config, - _matches: &ArgMatches, - config_version: &Version, - package_version: &Version, -) -> Result { - // welp, stuff like ports are mostly hardcoded and not part of the config so all is changes is just the version - // number - let to_version = package_version; - - print_start_upgrade(&config_version, &to_version); - - let upgraded_config = config.with_custom_version(to_version.to_string().as_ref()); - - upgraded_config.save_to_file(None).map_err(|err| { - ( - to_version.clone(), - format!("failed to overwrite config file! - {:?}", err), - ) - })?; - - print_successful_upgrade(config_version, to_version); - - Ok(upgraded_config) -} - -fn minor_0_11_upgrade( - config: Config, - _matches: &ArgMatches, - config_version: &Version, - package_version: &Version, -) -> Result { - let to_version = if package_version.major == 0 && package_version.minor == 11 { - package_version.clone() - } else { - Version::new(0, 11, 0) - }; - - let id = config.get_id(); - let config_path = Config::default_config_directory(Some(&id)); - - #[derive(Deserialize)] - struct OldNodeDescription { - name: String, - description: String, - link: String, - } - - print_start_upgrade(&config_version, &to_version); - - // description action - let description_file_path: PathBuf = [config_path.to_str().unwrap(), DESCRIPTION_FILE] - .iter() - .collect(); - // If the description file already exists, upgrade it - let new_description = if description_file_path.is_file() { - let description_content = fs::read_to_string(description_file_path).map_err(|err| { - ( - to_version.clone(), - format!("failed to read description file! - {:?}", err), - ) - })?; - let old_description: OldNodeDescription = - toml::from_str(&description_content).map_err(|err| { - ( - to_version.clone(), - format!("failed to deserialize description content! - {:?}", err), - ) - })?; - Some(NodeDescription { - name: old_description.name, - description: old_description.description, - link: old_description.link, - ..Default::default() - }) - } else { - None - }; - - let current_annnounce_addr = config.get_announce_address(); - // try to parse it as socket address directly - let (announce_address, custom_mix_port) = match SocketAddr::from_str(¤t_annnounce_addr) { - Ok(addr) => (addr.ip().to_string(), addr.port()), - Err(_) => { - let announce_split = current_annnounce_addr.split(':').collect::>(); - if announce_split.len() != 2 { - return Err(( - to_version, - "failed to correctly parse current announce host".to_string(), - )); - } - ( - announce_split[0].to_string(), - announce_split[1].parse().unwrap(), - ) - } - }; - - println!( - "Setting validator REST endpoint to {:?}", - default_validator_rest_endpoints() - ); - - println!( - "Setting mixnet contract address to {}", - DEFAULT_MIXNET_CONTRACT_ADDRESS - ); - - let upgraded_config = config - .with_custom_version(to_version.to_string().as_ref()) - .with_announce_address(announce_address) - .with_mix_port(custom_mix_port) - .with_custom_validators(default_validator_rest_endpoints()) - .with_custom_mixnet_contract(DEFAULT_MIXNET_CONTRACT_ADDRESS); - - if let Some(new_description) = new_description { - NodeDescription::save_to_file(&new_description, config_path).map_err(|err| { - ( - to_version.clone(), - format!("failed to overwrite description file! - {:?}", err), - ) - })?; - } - - upgraded_config.save_to_file(None).map_err(|err| { - ( - to_version.clone(), - format!("failed to overwrite config file! - {:?}", err), - ) - })?; - - print_successful_upgrade(config_version, to_version); - - Ok(upgraded_config) -} - fn do_upgrade(mut config: Config, matches: &ArgMatches, package_version: Version) { loop { let config_version = parse_config_version(&config); @@ -384,30 +139,12 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches, package_version: Version config = match config_version.major { 0 => match config_version.minor { - 9 => minor_0_10_upgrade(config, matches, &config_version, &Version::new(0, 10, 0)), - 10 => match config_version.patch { - 0 => patch_0_10_1_upgrade( - config, - matches, - &config_version, - &Version::new(0, 10, 1), - ), - _ => minor_0_11_upgrade( - config, - matches, - &config_version, - &Version::new(0, 11, 0), - ), - }, - _ => unsupported_upgrade(config_version, package_version), + 9 | 10 => outdated_upgrade(&config_version, &package_version), + 11 => minor_0_12_upgrade(config, matches, &config_version, &package_version), + _ => unsupported_upgrade(&config_version, &package_version), }, - _ => unsupported_upgrade(config_version, package_version), + _ => unsupported_upgrade(&config_version, &package_version), } - .unwrap_or_else(|(to_version, err)| { - eprintln!("{:?}", err); - print_failed_upgrade(&config_version, &to_version); - process::exit(1); - }); } } @@ -416,68 +153,15 @@ pub fn execute(matches: &ArgMatches) { let id = matches.value_of(ID_ARG_NAME).unwrap(); - let mut existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { + let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {:?}", err); process::exit(1) }); - // versions fields were added in 0.9.0 if existing_config.get_version() == missing_string_value::() { - let self_reported_version = - matches - .value_of(CURRENT_VERSION_ARG_NAME) - .unwrap_or_else(|| { - eprintln!( - "trying to upgrade from pre v0.9.0 without providing current system version!" - ); - process::exit(1) - }); - - // upgrades up to 0.9.0 - existing_config = pre_090_upgrade(self_reported_version, existing_config); + eprintln!("the existing configuration file does not seem to contain version number."); + process::exit(1); } - // here be upgrade path to 0.9.X and beyond based on version number from config do_upgrade(existing_config, matches, package_version) } - -#[cfg(test)] -mod upgrade_tests { - use super::*; - use serial_test::serial; - - #[test] - #[serial] - fn test_0_10_2_upgrade() { - let config = Config::default() - .with_id("-42") - .with_announce_address("127.0.0.1:1234") - .with_custom_version("0.10.1"); - let matches = ArgMatches::default(); - let old_version = Version::new(0, 10, 1); - let new_version = Version::new(0, 10, 2); - let new_config = minor_0_11_upgrade(config, &matches, &old_version, &new_version).unwrap(); - assert_eq!(new_config.get_version(), "0.11.0"); - } - - #[test] - #[serial] - fn test_0_10_2_upgrade_error() { - let config = Config::default() - .with_id("-42") - .with_announce_address("127.0.0.1:1234") - .with_custom_version("0.10.1"); - let matches = ArgMatches::default(); - let old_version = Version::new(0, 10, 1); - let new_version = Version::new(0, 10, 2); - config.save_to_file(None).unwrap(); - let config_file = config.get_config_file_save_location(); - let initial_perms = fs::metadata(config_file.clone()).unwrap().permissions(); - let mut new_perms = initial_perms.clone(); - new_perms.set_readonly(true); - fs::set_permissions(config_file.clone(), new_perms).unwrap(); - let ret = minor_0_11_upgrade(config, &matches, &old_version, &new_version); - fs::set_permissions(config_file, initial_perms).unwrap(); - assert!(ret.is_err()); - } -} diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 78da10801f..d23c04165f 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -3,12 +3,13 @@ use crate::config::template::config_template; use config::defaults::*; -use config::{deserialize_duration, deserialize_validators, NymConfig}; +use config::NymConfig; use serde::{Deserialize, Deserializer, Serialize}; use std::net::{IpAddr, SocketAddr}; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; +use url::Url; pub mod persistence; mod template; @@ -32,22 +33,10 @@ const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_milli const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500); const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 128; -// helper function to get default validators as a Vec -pub fn default_validator_rest_endpoints() -> Vec { - DEFAULT_VALIDATOR_REST_ENDPOINTS - .iter() - .map(|&endpoint| endpoint.to_string()) - .collect() -} - pub fn missing_string_value>() -> T { MISSING_VALUE.to_string().into() } -pub fn missing_vec_string_value() -> Vec { - vec![missing_string_value()] -} - fn bind_all_address() -> IpAddr { "0.0.0.0".parse().unwrap() } @@ -159,13 +148,8 @@ impl Config { self } - pub fn with_custom_validators(mut self, validators: Vec) -> Self { - self.mixnode.validator_rest_urls = validators; - self - } - - pub fn with_custom_mixnet_contract>(mut self, mixnet_contract: S) -> Self { - self.mixnode.mixnet_contract_address = mixnet_contract.into(); + pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec) -> Self { + self.mixnode.validator_api_urls = validator_api_urls; self } @@ -233,12 +217,8 @@ impl Config { self.mixnode.public_sphinx_key_file.clone() } - pub fn get_validator_rest_endpoints(&self) -> Vec { - self.mixnode.validator_rest_urls.clone() - } - - pub fn get_validator_mixnet_contract_address(&self) -> String { - self.mixnode.mixnet_contract_address.clone() + pub fn get_validator_api_endpoints(&self) -> Vec { + self.mixnode.validator_api_urls.clone() } pub fn get_node_stats_logging_delay(&self) -> Duration { @@ -289,13 +269,10 @@ impl Config { &self.mixnode.version } - pub fn get_id(&self) -> String { - self.mixnode.id.clone() - } - pub fn get_measurement_packets_per_node(&self) -> usize { self.verloc.packets_per_node } + pub fn get_measurement_packet_timeout(&self) -> Duration { self.verloc.packet_timeout } @@ -307,23 +284,18 @@ impl Config { pub fn get_measurement_delay_between_packets(&self) -> Duration { self.verloc.delay_between_packets } + pub fn get_measurement_tested_nodes_batch_size(&self) -> usize { self.verloc.tested_nodes_batch_size } + pub fn get_measurement_testing_interval(&self) -> Duration { self.verloc.testing_interval } + pub fn get_measurement_retry_timeout(&self) -> Duration { self.verloc.retry_timeout } - - // upgrade-specific - pub(crate) fn set_default_identity_keypair_paths(&mut self) { - self.mixnode.private_identity_key_file = - self::MixNode::default_private_identity_key_file(&self.mixnode.id); - self.mixnode.public_identity_key_file = - self::MixNode::default_public_identity_key_file(&self.mixnode.id); - } } #[derive(Debug, Deserialize, PartialEq, Serialize)] @@ -373,17 +345,8 @@ pub struct MixNode { /// Path to file containing public sphinx key. public_sphinx_key_file: PathBuf, - /// Validator server from which the node gets the view on the network. - #[serde( - deserialize_with = "deserialize_validators", - default = "missing_vec_string_value", - alias = "validator_rest_url" - )] - validator_rest_urls: Vec, - - /// Address of the validator contract managing the network. - #[serde(default = "missing_string_value")] - mixnet_contract_address: String, + /// Addresses to APIs running on validator from which the node gets the view of the network. + validator_api_urls: Vec, /// nym_home_directory specifies absolute path to the home nym MixNodes directory. /// It is expected to use default value and hence .toml file should not redefine this field. @@ -422,8 +385,7 @@ impl Default for MixNode { public_identity_key_file: Default::default(), private_sphinx_key_file: Default::default(), public_sphinx_key_file: Default::default(), - validator_rest_urls: default_validator_rest_endpoints(), - mixnet_contract_address: DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(), + validator_api_urls: default_api_endpoints(), nym_root_directory: Config::default_root_directory(), } } @@ -483,40 +445,25 @@ impl Default for Verloc { #[serde(default)] pub struct Debug { /// Delay between each subsequent node statistics being logged to the console - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] node_stats_logging_delay: Duration, /// Delay between each subsequent node statistics being updated - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] node_stats_updating_delay: Duration, /// Initial value of an exponential backoff to reconnect to dropped TCP connection when /// forwarding sphinx packets. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] packet_forwarding_initial_backoff: Duration, /// Maximum value of an exponential backoff to reconnect to dropped TCP connection when /// forwarding sphinx packets. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] packet_forwarding_maximum_backoff: Duration, /// Timeout for establishing initial connection when trying to forward a sphinx packet. - #[serde( - deserialize_with = "deserialize_duration", - serialize_with = "humantime_serde::serialize" - )] + #[serde(with = "humantime_serde")] initial_connection_timeout: Duration, /// Maximum number of packets that can be stored waiting to get sent to a particular connection. diff --git a/mixnode/src/config/template.rs b/mixnode/src/config/template.rs index b3b69f056d..c0a12c2798 100644 --- a/mixnode/src/config/template.rs +++ b/mixnode/src/config/template.rs @@ -53,16 +53,13 @@ verloc_port = {{ mixnode.verloc_port }} # (default: 8000) http_api_port = {{ mixnode.http_api_port }} -# Validator server to which the node will be getting information about the network. -validator_rest_urls = [ - {{#each mixnode.validator_rest_urls }} +# Addresses to APIs running on validator from which the node gets the view of the network. +validator_api_urls = [ + {{#each mixnode.validator_api_urls }} '{{this}}', {{/each}} ] -# Address of the validator contract managing the network. -mixnet_contract_address = '{{ mixnode.mixnet_contract_address }}' - ##### advanced configuration options ##### # Absolute path to the home Nym Clients directory. diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 2a58c14b63..d2e1df4c96 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -17,6 +17,8 @@ use crate::node::packet_delayforwarder::{DelayForwarder, PacketDelayForwardSende use crypto::asymmetric::{encryption, identity}; use log::{error, info, warn}; use mixnode_common::verloc::{self, AtomicVerlocResult, VerlocMeasurer}; +use rand::seq::SliceRandom; +use rand::thread_rng; use std::net::SocketAddr; use std::process; use std::sync::Arc; @@ -164,8 +166,7 @@ impl MixNode { .tested_nodes_batch_size(self.config.get_measurement_tested_nodes_batch_size()) .testing_interval(self.config.get_measurement_testing_interval()) .retry_timeout(self.config.get_measurement_retry_timeout()) - .validator_urls(self.config.get_validator_rest_endpoints()) - .mixnet_contract_address(self.config.get_validator_mixnet_contract_address()) + .validator_api_urls(self.config.get_validator_api_endpoints()) .build(); let mut verloc_measurer = VerlocMeasurer::new(config, Arc::clone(&self.identity_keypair)); @@ -176,13 +177,13 @@ 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::Config::new( - self.config.get_validator_rest_endpoints(), - self.config.get_validator_mixnet_contract_address(), - ); - let validator_client = validator_client::Client::new(validator_client_config); + let endpoints = self.config.get_validator_api_endpoints(); + let validator_api = endpoints + .choose(&mut thread_rng()) + .expect("The list of validator apis is empty"); + let validator_client = validator_client::ApiClient::new(validator_api.clone()); - let existing_nodes = match validator_client.get_cached_mix_nodes().await { + let existing_nodes = match validator_client.get_cached_mixnodes().await { Ok(nodes) => nodes, Err(err) => { error!("failed to grab initial network mixnodes - {}\n Please try to startup again in few minutes", err); diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index 69e8456325..a69d25e179 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -27,20 +27,16 @@ reqwest = { version = "0.11", features = ["json"] } rocket = { version = "0.5.0-rc.1", features = ["json"] } serde = "1.0" serde_json = "1.0" -tokio = { version = "1.4", features = [ - "rt-multi-thread", - "macros", - "signal", - "time", -] } -rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" } +tokio = { version = "1.4", features = ["rt-multi-thread", "macros", "signal", "time"] } +rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "dfd3662c49e2f6fc37df35091cb94d82f7fb5915" } +url = "2.2" + + anyhow = "1" getset = "0.1.1" -const_format = "0.2.15" -rocket_sync_db_pools = {version = "0.1.0-rc.1", default-features = false} -sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"]} -coconut-rs = { git = "https://github.com/nymtech/coconut.git", branch = "0.4.0" } +rocket_sync_db_pools = { version = "0.1.0-rc.1", default-features = false } +sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"] } ## internal config = { path = "../common/config" } @@ -49,13 +45,12 @@ gateway-client = { path = "../common/client-libs/gateway-client" } mixnet-contract = { path = "../common/mixnet-contract" } nymsphinx = { path = "../common/nymsphinx" } topology = { path = "../common/topology" } -validator-client = { path = "../common/client-libs/validator-client" } +validator-client = { path = "../common/client-libs/validator-client", features = ["nymd-client"] } version-checker = { path = "../common/version-checker" } coconut-interface = { path = "../common/coconut-interface" } +credentials = { path = "../common/credentials" } -[dev-dependencies] - [build-dependencies] -tokio = { version="1.4", features=["rt-multi-thread", "macros"] } -sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]} +tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] } +sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } diff --git a/validator-api/src/cache/mod.rs b/validator-api/src/cache/mod.rs index cd1d8d33b3..093bec70e5 100644 --- a/validator-api/src/cache/mod.rs +++ b/validator-api/src/cache/mod.rs @@ -1,7 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::nymd_client::Client; use anyhow::Result; +use config::defaults::VALIDATOR_API_VERSION; use mixnet_contract::{GatewayBond, MixNodeBond}; use rocket::fairing::AdHoc; use serde::Serialize; @@ -10,13 +12,12 @@ use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::sync::RwLock; use tokio::time; -use validator_client::validator_api::VALIDATOR_API_CACHE_VERSION; -use validator_client::Client; +use validator_client::nymd::CosmWasmClient; pub(crate) mod routes; -pub struct ValidatorCacheRefresher { - validator_client: Client, +pub struct ValidatorCacheRefresher { + nymd_client: Client, cache: ValidatorCache, caching_interval: Duration, } @@ -35,7 +36,6 @@ struct ValidatorCacheInner { #[derive(Default, Serialize, Clone)] pub struct Cache { value: T, - #[allow(dead_code)] as_at: u64, } @@ -53,27 +53,26 @@ impl Cache { } } -impl ValidatorCacheRefresher { +impl ValidatorCacheRefresher { pub(crate) fn new( - validators_rest_uris: Vec, - mixnet_contract: String, + nymd_client: Client, caching_interval: Duration, cache: ValidatorCache, ) -> Self { - let config = validator_client::Config::new(validators_rest_uris, mixnet_contract); - let validator_client = validator_client::Client::new(config); - ValidatorCacheRefresher { - validator_client, + nymd_client, cache, caching_interval, } } - async fn refresh_cache(&self) -> Result<()> { + async fn refresh_cache(&self) -> Result<()> + where + C: CosmWasmClient + Sync, + { let (mixnodes, gateways) = tokio::try_join!( - self.validator_client.get_mix_nodes(), - self.validator_client.get_gateways() + self.nymd_client.get_mixnodes(), + self.nymd_client.get_gateways() )?; info!( @@ -87,7 +86,10 @@ impl ValidatorCacheRefresher { Ok(()) } - pub(crate) async fn run(&self) { + pub(crate) async fn run(&self) + where + C: CosmWasmClient + Sync, + { let mut interval = time::interval(self.caching_interval); loop { interval.tick().await; @@ -113,7 +115,8 @@ impl ValidatorCache { pub fn stage() -> AdHoc { AdHoc::on_ignite("Validator Cache Stage", |rocket| async { rocket.manage(Self::new()).mount( - VALIDATOR_API_CACHE_VERSION, + // this format! is so ugly... + format!("/{}", VALIDATOR_API_VERSION), routes![routes::get_mixnodes, routes::get_gateways], ) }) diff --git a/validator-api/src/coconut/mod.rs b/validator-api/src/coconut/mod.rs index af233f79a3..ace0c62851 100644 --- a/validator-api/src/coconut/mod.rs +++ b/validator-api/src/coconut/mod.rs @@ -1,12 +1,15 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use coconut_interface::{ elgamal::PublicKey, Attribute, BlindSignRequest, BlindSignRequestBody, BlindedSignature, BlindedSignatureResponse, KeyPair, Parameters, VerificationKeyResponse, }; +use config::defaults::VALIDATOR_API_VERSION; use getset::{CopyGetters, Getters}; use rocket::fairing::AdHoc; use rocket::serde::json::Json; use rocket::State; -use validator_client::validator_api::VALIDATOR_API_CACHE_VERSION; #[derive(Getters, CopyGetters, Debug)] pub(crate) struct InternalSignRequest { @@ -39,7 +42,8 @@ impl InternalSignRequest { pub fn stage(key_pair: KeyPair) -> AdHoc { AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async { rocket.manage(key_pair).mount( - VALIDATOR_API_CACHE_VERSION, + // this format! is so ugly... + format!("/{}", VALIDATOR_API_VERSION), routes![post_blind_sign, get_verification_key], ) }) diff --git a/validator-api/src/config/mod.rs b/validator-api/src/config/mod.rs index 87415f33c3..3de2c395eb 100644 --- a/validator-api/src/config/mod.rs +++ b/validator-api/src/config/mod.rs @@ -3,22 +3,16 @@ use crate::config::template::config_template; use coconut_interface::{Base58, KeyPair}; -use config::defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS; +use config::defaults::{default_api_endpoints, DEFAULT_MIXNET_CONTRACT_ADDRESS}; use config::NymConfig; -use const_format::formatcp; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use std::time::Duration; +use url::Url; mod template; -pub const DEFAULT_VALIDATOR_HOST: &str = "localhost"; -const DEFAULT_VALIDATOR_PORT: &str = "1317"; -const DEFAULT_VALIDATOR_REST_ENDPOINTS: &[&str] = &[formatcp!( - "http://{}:{}", - DEFAULT_VALIDATOR_HOST, - DEFAULT_VALIDATOR_PORT, -)]; +const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; const DEFAULT_GATEWAY_SENDING_RATE: usize = 500; const DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS: usize = 50; @@ -74,12 +68,14 @@ impl NymConfig for Config { #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(deny_unknown_fields)] pub struct Base { - // TODO: this will probably be changed very soon to point only to a single endpoint, - // that will be a local address - validator_rest_urls: Vec, + local_validator: Url, /// Address of the validator contract managing the network mixnet_contract_address: String, + + /// Mnemonic (currently of the network monitor) used for rewarding + mnemonic: String, + // Avoid breaking derives for now keypair_bs58: String, } @@ -87,11 +83,11 @@ pub struct Base { impl Default for Base { fn default() -> Self { Base { - validator_rest_urls: DEFAULT_VALIDATOR_REST_ENDPOINTS - .iter() - .map(|&endpoint| endpoint.to_string()) - .collect(), + local_validator: DEFAULT_LOCAL_VALIDATOR + .parse() + .expect("default local validator is malformed!"), mixnet_contract_address: DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(), + mnemonic: String::default(), keypair_bs58: String::default(), } } @@ -103,6 +99,11 @@ pub struct NetworkMonitor { /// Specifies whether network monitoring service is enabled in this process. enabled: bool, + /// Specifies list of all validators on the network issuing coconut credentials. + /// A special care must be taken to ensure they are in correct order. + /// The list must also contain THIS validator that is running the test + all_validator_apis: Vec, + /// Specifies whether a detailed report should be printed after each run print_detailed_report: bool, @@ -158,6 +159,7 @@ impl Default for NetworkMonitor { fn default() -> Self { NetworkMonitor { enabled: false, + all_validator_apis: default_api_endpoints(), print_detailed_report: false, good_v4_topology_file: Self::default_good_v4_topology_file(), good_v6_topology_file: Self::default_good_v6_topology_file(), @@ -237,8 +239,8 @@ impl Config { self } - pub fn with_custom_validators(mut self, validators: Vec) -> Self { - self.base.validator_rest_urls = validators; + pub fn with_custom_nymd_validator(mut self, validator: Url) -> Self { + self.base.local_validator = validator; self } @@ -247,8 +249,18 @@ impl Config { self } - pub fn with_keypair(mut self, keypair_bs58: String) -> Self { - self.base.keypair_bs58 = keypair_bs58; + pub fn with_mnemonic>(mut self, mnemonic: S) -> Self { + self.base.mnemonic = mnemonic.into(); + self + } + + pub fn with_keypair>(mut self, keypair_bs58: S) -> Self { + self.base.keypair_bs58 = keypair_bs58.into(); + self + } + + pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec) -> Self { + self.network_monitor.all_validator_apis = validator_api_urls; self } @@ -268,14 +280,18 @@ impl Config { self.network_monitor.good_v6_topology_file.clone() } - pub fn get_validators_urls(&self) -> Vec { - self.base.validator_rest_urls.clone() + pub fn get_nymd_validator_url(&self) -> Url { + self.base.local_validator.clone() } pub fn get_mixnet_contract_address(&self) -> String { self.base.mixnet_contract_address.clone() } + pub fn get_mnemonic(&self) -> String { + self.base.mnemonic.clone() + } + pub fn get_network_monitor_run_interval(&self) -> Duration { self.network_monitor.run_interval } @@ -311,4 +327,8 @@ impl Config { pub fn get_node_status_api_database_path(&self) -> PathBuf { self.node_status_api.database_path.clone() } + + pub fn get_all_validator_api_endpoints(&self) -> Vec { + self.network_monitor.all_validator_apis.clone() + } } diff --git a/validator-api/src/config/template.rs b/validator-api/src/config/template.rs index 201f6625ba..3b190f91f2 100644 --- a/validator-api/src/config/template.rs +++ b/validator-api/src/config/template.rs @@ -11,15 +11,14 @@ pub(crate) fn config_template() -> &'static str { [base] # Validator server to which the API will be getting information about the network. -validator_rest_urls = [ - {{#each base.validator_rest_urls }} - '{{this}}', - {{/each}} -] +local_validator = '{{ base.local_validator }}' # Address of the validator contract managing the network. mixnet_contract_address = '{{ base.mixnet_contract_address }}' +# Mnemonic (currently of the network monitor) used for rewarding +mnemonic = '{{ base.mnemonic }}' + ##### network monitor config options ##### [network_monitor] @@ -27,6 +26,15 @@ mixnet_contract_address = '{{ base.mixnet_contract_address }}' # Specifies whether network monitoring service is enabled in this process. enabled = {{ network_monitor.enabled }} +# Specifies list of all validators on the network issuing coconut credentials. +# A special care must be taken to ensure they are in correct order. +# The list must also contain THIS validator that is running the test +all_validator_apis = [ + {{#each network_monitor.all_validator_apis }} + '{{this}}', + {{/each}} +] + # Specifies whether a detailed report should be printed after each run print_detailed_report = {{ network_monitor.print_detailed_report }} diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index c1ee7b5a45..b3601de13f 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -6,38 +6,54 @@ extern crate rocket; use crate::cache::ValidatorCacheRefresher; use crate::config::Config; -use crate::network_monitor::new_monitor_runnables; use crate::network_monitor::tested_network::good_topology::parse_topology_file; +use crate::network_monitor::{new_monitor_runnables, NetworkMonitorRunnables}; +use crate::nymd_client::Client; use crate::storage::NodeStatusStorage; -use ::config::NymConfig; +use ::config::{defaults::DEFAULT_VALIDATOR_API_PORT, NymConfig}; use anyhow::Result; use cache::ValidatorCache; use clap::{App, Arg, ArgMatches}; use coconut::InternalSignRequest; use log::info; use rocket::http::Method; +use rocket::{Ignite, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; use std::process; -use validator_client::validator_api::VALIDATOR_API_PORT; +use url::Url; pub(crate) mod cache; mod coconut; pub(crate) mod config; mod network_monitor; mod node_status_api; +pub(crate) mod nymd_client; pub(crate) mod storage; const MONITORING_ENABLED: &str = "enable-monitor"; const V4_TOPOLOGY_ARG: &str = "v4-topology-filepath"; const V6_TOPOLOGY_ARG: &str = "v6-topology-filepath"; -const VALIDATORS_ARG: &str = "validators"; +const API_VALIDATORS_ARG: &str = "api-validators"; const DETAILED_REPORT_ARG: &str = "detailed-report"; const MIXNET_CONTRACT_ARG: &str = "mixnet-contract"; +const MNEMONIC_ARG: &str = "mnemonic"; const WRITE_CONFIG_ARG: &str = "save-config"; const KEYPAIR_ARG: &str = "keypair"; +const NYMD_VALIDATOR_ARG: &str = "nymd-validator"; pub(crate) const PENALISE_OUTDATED: bool = false; +fn parse_validators(raw: &str) -> Vec { + raw.split(',') + .map(|raw_validator| { + raw_validator + .trim() + .parse() + .expect("one of the provided validator api urls is invalid") + }) + .collect() +} + fn parse_args<'a>() -> ArgMatches<'a> { App::new("Nym Network Monitor") .author("Nymtech") @@ -58,16 +74,21 @@ fn parse_args<'a>() -> ArgMatches<'a> { .takes_value(true) ) .arg( - Arg::with_name(VALIDATORS_ARG) - .help("REST endpoint of the validator the monitor will grab nodes to test") - .long(VALIDATORS_ARG) + Arg::with_name(NYMD_VALIDATOR_ARG) + .help("Endpoint to nymd part of the validator from which the monitor will grab nodes to test") + .long(NYMD_VALIDATOR_ARG) .takes_value(true) ) - .arg(Arg::with_name("mixnet-contract") + .arg(Arg::with_name(MIXNET_CONTRACT_ARG) .long(MIXNET_CONTRACT_ARG) .help("Address of the validator contract managing the network") .takes_value(true), ) + .arg(Arg::with_name(MNEMONIC_ARG) + .long(MNEMONIC_ARG) + .help("Mnemonic of the network monitor used for rewarding operators") + .takes_value(true), + ) .arg( Arg::with_name(DETAILED_REPORT_ARG) .help("specifies whether a detailed report should be printed after each run") @@ -117,12 +138,6 @@ fn setup_logging() { } fn override_config(mut config: Config, matches: &ArgMatches) -> Config { - fn parse_validators(raw: &str) -> Vec { - raw.split(',') - .map(|raw_validator| raw_validator.trim().into()) - .collect() - } - if matches.is_present(MONITORING_ENABLED) { config = config.enabled_network_monitor(true) } @@ -135,14 +150,29 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config { config = config.with_v6_good_topology(v6_topology_path) } - if let Some(raw_validators) = matches.value_of(VALIDATORS_ARG) { - config = config.with_custom_validators(parse_validators(raw_validators)); + if let Some(raw_validators) = matches.value_of(API_VALIDATORS_ARG) { + config = config.with_custom_validator_apis(parse_validators(raw_validators)); + } + + if let Some(raw_validator) = matches.value_of(NYMD_VALIDATOR_ARG) { + let parsed = match raw_validator.parse() { + Err(err) => { + error!("Passed validator argument is invalid - {}", err); + process::exit(1) + } + Ok(url) => url, + }; + config = config.with_custom_nymd_validator(parsed); } if let Some(mixnet_contract) = matches.value_of(MIXNET_CONTRACT_ARG) { config = config.with_custom_mixnet_contract(mixnet_contract) } + if let Some(mnemonic) = matches.value_of(MNEMONIC_ARG) { + config = config.with_mnemonic(mnemonic) + } + if matches.is_present(DETAILED_REPORT_ARG) { config = config.detailed_network_monitor_report(true) } @@ -184,6 +214,59 @@ fn setup_cors() -> Result { Ok(cors) } +async fn setup_network_monitor( + config: &Config, + rocket: &Rocket, +) -> Option { + if !config.get_network_monitor_enabled() { + return None; + } + + // get instances of managed states + let node_status_storage = rocket.state::().unwrap().clone(); + let validator_cache = rocket.state::().unwrap().clone(); + + let v4_topology = parse_topology_file(config.get_v4_good_topology_file()); + let v6_topology = parse_topology_file(config.get_v6_good_topology_file()); + network_monitor::check_if_up_to_date(&v4_topology, &v6_topology); + + Some( + new_monitor_runnables( + config, + v4_topology, + v6_topology, + node_status_storage, + validator_cache, + ) + .await, + ) +} + +async fn setup_rocket(config: &Config) -> Result> { + // let's build our rocket! + let rocket_config = rocket::config::Config { + // TODO: probably the port should be configurable? + port: DEFAULT_VALIDATOR_API_PORT, + ..Default::default() + }; + let rocket = rocket::custom(rocket_config) + .attach(setup_cors()?) + .attach(ValidatorCache::stage()) + .attach(InternalSignRequest::stage(config.keypair())); + + // see if we should start up network monitor and if so, attach the node status api + if config.get_network_monitor_enabled() { + Ok(rocket + .attach(node_status_api::stage( + config.get_node_status_api_database_path(), + )) + .ignite() + .await?) + } else { + Ok(rocket.ignite().await?) + } +} + #[tokio::main] async fn main() -> Result<()> { setup_logging(); @@ -209,67 +292,50 @@ async fn main() -> Result<()> { let config = override_config(config, &matches); // let's build our rocket! - let rocket_config = rocket::config::Config { - port: VALIDATOR_API_PORT, - ..Default::default() - }; - let rocket = rocket::custom(rocket_config) - .attach(setup_cors()?) - .attach(ValidatorCache::stage()) - .attach(InternalSignRequest::stage(config.keypair())); - - // see if we should start up network monitor and ignite our rocket - let rocket = if config.get_network_monitor_enabled() { - // don't start our node-status api if we're not running the monitor - we can't get - // report data otherwise - let rocket = rocket - .attach(node_status_api::stage( - config.get_node_status_api_database_path(), - )) - .ignite() - .await?; - - info!("Network monitor starting..."); - - // get instances of managed states - let node_status_storage = rocket.state::().unwrap().clone(); - let validator_cache = rocket.state::().unwrap().clone(); - - let v4_topology = parse_topology_file(config.get_v4_good_topology_file()); - let v6_topology = parse_topology_file(config.get_v6_good_topology_file()); - network_monitor::check_if_up_to_date(&v4_topology, &v6_topology); - - let network_monitor_runnables = new_monitor_runnables( - &config, - v4_topology, - v6_topology, - node_status_storage, - validator_cache, - ); - network_monitor_runnables.spawn_tasks(); - - rocket - } else { - info!("Network monitoring is disabled."); - rocket.ignite().await? - }; + let rocket = setup_rocket(&config).await?; + let monitor_runnables = setup_network_monitor(&config, &rocket).await; let validator_cache = rocket.state::().unwrap().clone(); - let validator_cache_refresher = ValidatorCacheRefresher::new( - config.get_validators_urls(), - config.get_mixnet_contract_address(), - config.get_caching_interval(), - validator_cache, - ); + // if network monitor is disabled, we're not going to be sending any rewarding hence + // we're not starting signing client + if config.get_network_monitor_enabled() { + let nymd_client = Client::new_signing(&config); + let validator_cache_refresher = ValidatorCacheRefresher::new( + nymd_client, + config.get_caching_interval(), + validator_cache, + ); - // spawn our cacher - tokio::spawn(async move { validator_cache_refresher.run().await }); + // spawn our cacher + tokio::spawn(async move { validator_cache_refresher.run().await }); + } else { + let nymd_client = Client::new_query(&config); + let validator_cache_refresher = ValidatorCacheRefresher::new( + nymd_client, + config.get_caching_interval(), + validator_cache, + ); + + // spawn our cacher + tokio::spawn(async move { validator_cache_refresher.run().await }); + } + + if let Some(runnables) = monitor_runnables { + info!("Starting network monitor..."); + // spawn network monitor! + runnables.spawn_tasks(); + } else { + info!("Network monitoring is disabled."); + } // and launch the rocket + let shutdown_handle = rocket.shutdown(); + tokio::spawn(rocket.launch()); wait_for_interrupt().await; + shutdown_handle.notify(); Ok(()) } diff --git a/validator-api/src/network_monitor/mod.rs b/validator-api/src/network_monitor/mod.rs index 3a289ced15..f59bb5367c 100644 --- a/validator-api/src/network_monitor/mod.rs +++ b/validator-api/src/network_monitor/mod.rs @@ -15,8 +15,12 @@ use crate::network_monitor::monitor::summary_producer::SummaryProducer; use crate::network_monitor::monitor::Monitor; use crate::network_monitor::tested_network::TestedNetwork; use crate::storage::NodeStatusStorage; +use coconut_interface::Credential; +use credentials::bandwidth::prepare_for_spending; +use credentials::obtain_aggregate_verification_key; use crypto::asymmetric::{encryption, identity}; use futures::channel::mpsc; +use log::info; use nymsphinx::addressing::clients::Recipient; use std::sync::Arc; use topology::NymTopology; @@ -44,7 +48,7 @@ impl NetworkMonitorRunnables { } } -pub(crate) fn new_monitor_runnables( +pub(crate) async fn new_monitor_runnables( config: &Config, v4_topology: NymTopology, v6_topology: NymTopology, @@ -86,10 +90,14 @@ pub(crate) fn new_monitor_runnables( *encryption_keypair.public_key(), ); + let bandwidth_credential = + TEMPORARY_obtain_bandwidth_credential(config, identity_keypair.public_key()).await; + let packet_sender = new_packet_sender( config, gateway_status_update_sender, Arc::clone(&identity_keypair), + bandwidth_credential, config.get_gateway_sending_rate(), ); @@ -135,15 +143,44 @@ fn new_packet_preparer( ) } +// SECURITY: +// this implies we are re-using the same credential for all gateways all the time (which unfortunately is true!) +#[allow(non_snake_case)] +async fn TEMPORARY_obtain_bandwidth_credential( + config: &Config, + identity: &identity::PublicKey, +) -> Credential { + info!("Trying to obtain bandwidth credential..."); + let validators = config.get_all_validator_api_endpoints(); + + let verification_key = obtain_aggregate_verification_key(&validators) + .await + .expect("could not obtain aggregate verification key of ALL validators"); + + let bandwidth_credential = + credentials::bandwidth::obtain_signature(&identity.to_bytes(), &validators) + .await + .expect("failed to obtain bandwidth credential!"); + + prepare_for_spending( + &identity.to_bytes(), + &bandwidth_credential, + &verification_key, + ) + .expect("failed to prepare bandwidth credential for spending!") +} + fn new_packet_sender( config: &Config, gateways_status_updater: GatewayClientUpdateSender, local_identity: Arc, + bandwidth_credential: Credential, max_sending_rate: usize, ) -> PacketSender { PacketSender::new( gateways_status_updater, local_identity, + bandwidth_credential, config.get_gateway_response_timeout(), config.get_gateway_connection_timeout(), config.get_max_concurrent_gateway_clients(), diff --git a/validator-api/src/network_monitor/monitor/sender.rs b/validator-api/src/network_monitor/monitor/sender.rs index 4e0c660c23..34b568b226 100644 --- a/validator-api/src/network_monitor/monitor/sender.rs +++ b/validator-api/src/network_monitor/monitor/sender.rs @@ -1,7 +1,6 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::DEFAULT_VALIDATOR_HOST; use crate::network_monitor::monitor::receiver::{GatewayClientUpdate, GatewayClientUpdateSender}; use coconut_interface::Credential; use crypto::asymmetric::identity::{self, PUBLIC_KEY_LENGTH}; @@ -22,7 +21,6 @@ use std::sync::Arc; use std::task::Poll; use std::time::Duration; use tokio::time::Instant; -use validator_client::validator_api::VALIDATOR_API_PORT; const TIME_CHUNK_SIZE: Duration = Duration::from_millis(50); @@ -65,6 +63,15 @@ struct FreshGatewayClientData { gateways_status_updater: GatewayClientUpdateSender, local_identity: Arc, gateway_response_timeout: Duration, + + // I guess in the future this struct will require aggregated verification key and.... + // ... something for obtaining actual credential + + // TODO: + // SECURITY: + // since currently we have no double spending protection, just to get things running + // we're re-using the same credential for all gateways all the time. THIS IS VERY BAD!! + bandwidth_credential: Credential, } pub(crate) struct PacketSender { @@ -84,6 +91,7 @@ impl PacketSender { pub(crate) fn new( gateways_status_updater: GatewayClientUpdateSender, local_identity: Arc, + bandwidth_credential: Credential, gateway_response_timeout: Duration, gateway_connection_timeout: Duration, max_concurrent_clients: usize, @@ -95,6 +103,7 @@ impl PacketSender { gateways_status_updater, local_identity, gateway_response_timeout, + bandwidth_credential, }), gateway_connection_timeout, max_concurrent_clients, @@ -114,16 +123,6 @@ impl PacketSender { // use old shared keys let (message_sender, message_receiver) = mpsc::unbounded(); - let coconut_credential = Credential::init( - vec![format!( - "http://{}:{}", - DEFAULT_VALIDATOR_HOST, VALIDATOR_API_PORT - )], - identity, - ) - .await - .expect("Could not initialize coconut credential"); - // currently we do not care about acks at all, but we must keep the channel alive // so that the gateway client would not crash let (ack_sender, ack_receiver) = mpsc::unbounded(); @@ -136,7 +135,7 @@ impl PacketSender { message_sender, ack_sender, fresh_gateway_client_data.gateway_response_timeout, - coconut_credential, + fresh_gateway_client_data.bandwidth_credential.clone(), ), (message_receiver, ack_receiver), ) diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs new file mode 100644 index 0000000000..4d5561c20e --- /dev/null +++ b/validator-api/src/nymd_client.rs @@ -0,0 +1,80 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::Config; +use config::defaults::DEFAULT_VALIDATOR_API_PORT; +use mixnet_contract::{GatewayBond, MixNodeBond}; +use std::sync::Arc; +use tokio::sync::RwLock; +use validator_client::nymd::{CosmWasmClient, QueryNymdClient, SigningNymdClient}; +use validator_client::ValidatorClientError; + +#[derive(Clone)] +pub(crate) struct Client(Arc>>); + +impl Client { + pub(crate) fn new_query(config: &Config) -> Self { + // the api address is irrelevant here as **WE ARE THE API** + let api_url = format!("http://localhost:{}", DEFAULT_VALIDATOR_API_PORT) + .parse() + .unwrap(); + let nymd_url = config.get_nymd_validator_url(); + + let mixnet_contract = config + .get_mixnet_contract_address() + .parse() + .expect("the mixnet contract address is invalid!"); + + let client_config = validator_client::Config::new(nymd_url, api_url, Some(mixnet_contract)); + let inner = + validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"); + + Client(Arc::new(RwLock::new(inner))) + } +} + +impl Client { + pub(crate) fn new_signing(config: &Config) -> Self { + // the api address is irrelevant here as **WE ARE THE API** + let api_url = format!("http://localhost:{}", DEFAULT_VALIDATOR_API_PORT) + .parse() + .unwrap(); + let nymd_url = config.get_nymd_validator_url(); + + let mixnet_contract = config + .get_mixnet_contract_address() + .parse() + .expect("the mixnet contract address is invalid!"); + let mnemonic = config + .get_mnemonic() + .parse() + .expect("the mnemonic is invalid!"); + + let client_config = validator_client::Config::new(nymd_url, api_url, Some(mixnet_contract)); + let inner = validator_client::Client::new_signing(client_config, mnemonic) + .expect("Failed to connect to nymd!"); + + Client(Arc::new(RwLock::new(inner))) + } +} + +impl Client { + pub(crate) async fn get_mixnodes(&self) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + self.0.read().await.get_all_nymd_mixnodes().await + } + + pub(crate) async fn get_gateways(&self) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + self.0.read().await.get_all_nymd_gateways().await + } + + #[allow(dead_code)] + pub(crate) async fn some_rewarding_stuff_here(&self) { + todo!() + } +}