Compare commits

...

1 Commits

Author SHA1 Message Date
Jon Häggblad eecd685c77 wallet: simplify connection test and set nymd_url and api_url independently 2022-03-24 15:58:06 +01:00
5 changed files with 53 additions and 124 deletions
+1
View File
@@ -2851,6 +2851,7 @@ dependencies = [
"dirs", "dirs",
"eyre", "eyre",
"futures", "futures",
"itertools",
"log", "log",
"mixnet-contract-common", "mixnet-contract-common",
"pretty_env_logger", "pretty_env_logger",
+1
View File
@@ -23,6 +23,7 @@ bip39 = "1.0"
dirs = "4.0" dirs = "4.0"
eyre = "0.6.5" eyre = "0.6.5"
futures = "0.3.15" futures = "0.3.15"
itertools = "0.10"
log = "0.4" log = "0.4"
pretty_env_logger = "0.4" pretty_env_logger = "0.4"
rand = "0.6.5" rand = "0.6.5"
+18 -5
View File
@@ -4,6 +4,7 @@
use crate::{error::BackendError, network::Network as WalletNetwork}; use crate::{error::BackendError, network::Network as WalletNetwork};
use config::defaults::{all::SupportedNetworks, ValidatorDetails}; use config::defaults::{all::SupportedNetworks, ValidatorDetails};
use config::NymConfig; use config::NymConfig;
use itertools::Itertools;
use reqwest::StatusCode; use reqwest::StatusCode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
@@ -115,8 +116,10 @@ impl Config {
.chain(self.network.validators(network)) .chain(self.network.validators(network))
.cloned() .cloned()
.chain(base_validators) .chain(base_validators)
.unique()
} }
#[allow(unused)]
pub fn get_validators_with_api_endpoint( pub fn get_validators_with_api_endpoint(
&self, &self,
network: WalletNetwork, network: WalletNetwork,
@@ -164,6 +167,10 @@ impl Config {
let client = reqwest::Client::builder() let client = reqwest::Client::builder()
.timeout(Duration::from_secs(3)) .timeout(Duration::from_secs(3))
.build()?; .build()?;
log::debug!(
"Fetching validator urls from: {}",
REMOTE_SOURCE_OF_VALIDATOR_URLS
);
let response = client let response = client
.get(REMOTE_SOURCE_OF_VALIDATOR_URLS.to_string()) .get(REMOTE_SOURCE_OF_VALIDATOR_URLS.to_string())
.send() .send()
@@ -183,6 +190,7 @@ impl Config {
let validator_urls = validators_to_query().map(|v| { let validator_urls = validators_to_query().map(|v| {
let mut health_url = v.nymd_url.clone(); let mut health_url = v.nymd_url.clone();
health_url.set_path("health"); health_url.set_path("health");
log::debug!("Checking health of: {health_url}");
(v, health_url) (v, health_url)
}); });
@@ -193,13 +201,18 @@ impl Config {
let requests = validator_urls.map(|(_, url)| client.get(url).send()); let requests = validator_urls.map(|(_, url)| client.get(url).send());
let responses = futures::future::join_all(requests).await; let responses = futures::future::join_all(requests).await;
let validators_responding_success = let validators_responding_success = zip(validators_to_query(), responses)
zip(validators_to_query(), responses).filter_map(|(v, r)| match r { .filter_map(|(v, r)| match r {
Ok(r) if r.status().is_success() => Some((v, r.status())), Ok(r) if r.status().is_success() => Some((v, r.status())),
_ => None, _ => None,
}); })
.collect::<Vec<_>>();
Ok(validators_responding_success.collect::<Vec<_>>()) for validator in &validators_responding_success {
log::debug!("Returned success: {}", validator.0.nymd_url);
}
Ok(validators_responding_success)
} }
#[allow(unused)] #[allow(unused)]
@@ -223,7 +236,7 @@ impl Config {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[derive(Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq)]
pub struct ValidatorUrl { pub struct ValidatorUrl {
pub nymd_url: Url, pub nymd_url: Url,
pub api_url: Option<Url>, pub api_url: Option<Url>,
@@ -1,20 +1,21 @@
use crate::coin::{Coin, Denom}; use crate::coin::{Coin, Denom};
use crate::config::{Config, ValidatorUrlWithApiEndpoint}; use crate::config::{Config, ValidatorUrl};
use crate::error::BackendError; use crate::error::BackendError;
use crate::network::Network; use crate::network::Network;
use crate::nymd_client; use crate::nymd_client;
use crate::state::State; use crate::state::State;
use bip39::{Language, Mnemonic}; use bip39::{Language, Mnemonic};
use rand::seq::SliceRandom;
use reqwest::StatusCode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryInto; use std::convert::TryInto;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use validator_client::nymd::error::NymdError; use url::Url;
use validator_client::nymd::SigningNymdClient; use validator_client::nymd::SigningNymdClient;
use validator_client::Client; use validator_client::Client;
@@ -101,7 +102,7 @@ pub async fn create_new_account(
} }
#[tauri::command] #[tauri::command]
pub async fn create_new_mnemonic() -> Result<String, BackendError> { pub fn create_new_mnemonic() -> Result<String, BackendError> {
let rand_mnemonic = random_mnemonic(); let rand_mnemonic = random_mnemonic();
Ok(rand_mnemonic.to_string()) Ok(rand_mnemonic.to_string())
} }
@@ -155,9 +156,9 @@ async fn _connect_with_mnemonic(
state: tauri::State<'_, Arc<RwLock<State>>>, state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Account, BackendError> { ) -> Result<Account, BackendError> {
update_validator_urls(state.clone()).await?; update_validator_urls(state.clone()).await?;
let validators = choose_validators(mnemonic.clone(), &state).await?;
let config = state.read().await.config(); let config = state.read().await.config().clone();
let validators = config.check_validator_health_for_all_networks().await?;
let clients = create_clients(&validators, &mnemonic, &config)?; let clients = create_clients(&validators, &mnemonic, &config)?;
// Set the default account // Set the default account
@@ -187,17 +188,22 @@ async fn _connect_with_mnemonic(
} }
fn create_clients( fn create_clients(
validators: &HashMap<Network, ValidatorUrlWithApiEndpoint>, validators: &HashMap<Network, Vec<(ValidatorUrl, StatusCode)>>,
mnemonic: &Mnemonic, mnemonic: &Mnemonic,
config: &Config, config: &Config,
) -> Result<Vec<Client<SigningNymdClient>>, BackendError> { ) -> Result<Vec<Client<SigningNymdClient>>, BackendError> {
let mut clients = Vec::new(); let mut clients = Vec::new();
for network in Network::iter() { for network in Network::iter() {
let nymd_url = select_validator_nymd_url(network, validators)?;
let api_url = select_validator_api_url(network, validators)?;
log::info!("{network}: nymd_url: connecting to {nymd_url}");
log::info!("{network}: api_url: connecting to {api_url}");
let client = validator_client::Client::new_signing( let client = validator_client::Client::new_signing(
validator_client::Config::new( validator_client::Config::new(
network.into(), network.into(),
validators[&network].nymd_url.clone(), nymd_url,
validators[&network].api_url.clone(), api_url,
config.get_mixnet_contract_address(network), config.get_mixnet_contract_address(network),
config.get_vesting_contract_address(network), config.get_vesting_contract_address(network),
config.get_bandwidth_claim_contract_address(network), config.get_bandwidth_claim_contract_address(network),
@@ -209,116 +215,24 @@ fn create_clients(
Ok(clients) Ok(clients)
} }
async fn choose_validators( fn select_validator_nymd_url(
mnemonic: Mnemonic,
state: &tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<HashMap<Network, ValidatorUrlWithApiEndpoint>, BackendError> {
let config = state.read().await.config();
// Try to connect to validators on all networks
let mut validators = select_responding_validators(&config, &mnemonic).await?;
// If for a network we didn't manage to connect to any validators, just go ahead and try with the
// first in the list
for network in Network::iter() {
validators.entry(network).or_insert_with(|| {
let default_validator = config
.get_validators_with_api_endpoint(network)
.next()
// We always have at least one hardcoded default validator
.unwrap();
println!(
"Using default for {network}: {}, {}",
default_validator.nymd_url, default_validator.api_url,
);
default_validator
});
}
Ok(validators)
}
// For each network, try the list of available validators one by one and use the first responding
// one.
async fn select_responding_validators(
config: &Config,
mnemonic: &Mnemonic,
) -> Result<HashMap<Network, ValidatorUrlWithApiEndpoint>, BackendError> {
use tokio::time::timeout;
let validators = futures::future::join_all(Network::iter().map(|network| {
timeout(
Duration::from_millis(3000),
try_connect_to_validators(
config.get_validators_with_api_endpoint(network),
config,
network,
mnemonic.clone(),
),
)
}))
.await;
// Drop networks that failed the global timeout
let validators = validators.into_iter().filter_map(Result::ok);
// Rewrap to return any errors during client creation
let validators = validators.collect::<Result<Vec<_>, _>>()?;
// Filter out networks where we exhausted all listed validators
let validators = validators.into_iter().flatten();
Ok(validators.collect::<HashMap<_, _>>())
}
async fn try_connect_to_validators(
validators: impl Iterator<Item = ValidatorUrlWithApiEndpoint>,
config: &Config,
network: Network, network: Network,
mnemonic: Mnemonic, validators: &HashMap<Network, Vec<(ValidatorUrl, StatusCode)>>,
) -> Result<Option<(Network, ValidatorUrlWithApiEndpoint)>, BackendError> { ) -> Result<Url, BackendError> {
for validator in validators { // For the nymd url we pick one at random
if let Some(responding_validator) = validators[&network]
try_connect_to_validator(&validator, config, network, mnemonic.clone()).await? .choose(&mut rand::thread_rng())
{ .map(|v| v.0.nymd_url.clone())
// Pick the first successful one .ok_or(BackendError::NoNymdValidatorConfigured)
return Ok(Some(responding_validator));
}
}
Ok(None)
} }
async fn try_connect_to_validator( fn select_validator_api_url(
validator: &ValidatorUrlWithApiEndpoint,
config: &Config,
network: Network, network: Network,
mnemonic: Mnemonic, validators: &HashMap<Network, Vec<(ValidatorUrl, StatusCode)>>,
) -> Result<Option<(Network, ValidatorUrlWithApiEndpoint)>, BackendError> { ) -> Result<Url, BackendError> {
let client = validator_client::Client::new_signing( // For the validator api we always just pick the first one
validator_client::Config::new( validators[&network]
network.into(), .get(0)
validator.nymd_url.clone(), .and_then(|v| v.0.api_url.clone())
validator.api_url.clone(), .ok_or(BackendError::NoValidatorApiUrlConfigured)
config.get_mixnet_contract_address(network),
config.get_vesting_contract_address(network),
config.get_bandwidth_claim_contract_address(network),
),
mnemonic,
)?;
if is_validator_connection_ok(&client).await {
println!(
"Connection ok for {network}: {}, {}",
validator.nymd_url, validator.api_url
);
Ok(Some((network, validator.clone())))
} else {
Ok(None)
}
}
// The criteria used to determina if a validator endpoint is to be used
async fn is_validator_connection_ok(client: &Client<SigningNymdClient>) -> bool {
match client.get_mixnet_contract_version().await {
Err(NymdError::TendermintError(_)) => false,
Err(_) | Ok(_) => true,
}
} }
+2 -2
View File
@@ -28,8 +28,8 @@ impl State {
.ok_or(BackendError::ClientNotInitialized) .ok_or(BackendError::ClientNotInitialized)
} }
pub fn config(&self) -> Config { pub fn config(&self) -> &Config {
self.config.clone() &self.config
} }
pub fn add_client(&mut self, network: Network, client: Client<SigningNymdClient>) { pub fn add_client(&mut self, network: Network, client: Client<SigningNymdClient>) {