Merge pull request #1170 from nymtech/feature/connection-test-nymd-api-urls-indep
wallet: connection test nymd and api urls independently
This commit is contained in:
Generated
+3
@@ -6068,10 +6068,12 @@ dependencies = [
|
||||
"base64",
|
||||
"bip39",
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"config",
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"flate2",
|
||||
"futures",
|
||||
"itertools",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
@@ -6082,6 +6084,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"ts-rs",
|
||||
"url",
|
||||
"validator-api-requests",
|
||||
|
||||
@@ -9,6 +9,7 @@ rust-version = "1.56"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.13"
|
||||
colored = "2.0"
|
||||
mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" }
|
||||
vesting-contract = { path = "../../../contracts/vesting" }
|
||||
@@ -18,6 +19,8 @@ reqwest = { version = "0.11", features = ["json"] }
|
||||
thiserror = "1"
|
||||
log = "0.4"
|
||||
url = { version = "2.2", features = ["serde"] }
|
||||
tokio = { version = "1.10", features = ["sync", "time"] }
|
||||
futures = "0.3"
|
||||
|
||||
coconut-interface = { path = "../../coconut-interface" }
|
||||
network-defaults = { path = "../../network-defaults" }
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::{NymdClient, QueryNymdClient};
|
||||
use crate::ApiClient;
|
||||
use network_defaults::all::Network;
|
||||
|
||||
use colored::Colorize;
|
||||
use core::fmt;
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::BuildHasher;
|
||||
use std::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
use url::Url;
|
||||
|
||||
const MAX_URLS_TESTED: usize = 200;
|
||||
const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2;
|
||||
|
||||
// Run connection tests for all specified nymd and api urls. These are all run concurrently.
|
||||
pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
|
||||
nymd_urls: impl Iterator<Item = (Network, Url)>,
|
||||
api_urls: impl Iterator<Item = (Network, Url)>,
|
||||
mixnet_contract_address: HashMap<Network, Option<cosmrs::AccountId>, H>,
|
||||
) -> (
|
||||
HashMap<Network, Vec<(Url, bool)>>,
|
||||
HashMap<Network, Vec<(Url, bool)>>,
|
||||
) {
|
||||
// Setup all the clients for the connection tests
|
||||
let connection_test_clients =
|
||||
setup_connection_tests(nymd_urls, api_urls, mixnet_contract_address);
|
||||
|
||||
// Run all tests concurrently
|
||||
let connection_results = futures::future::join_all(
|
||||
connection_test_clients
|
||||
.into_iter()
|
||||
.take(MAX_URLS_TESTED)
|
||||
.map(ClientForConnectionTest::run_connection_check),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Seperate and collect results into HashMaps
|
||||
(
|
||||
extract_and_collect_results_into_map(&connection_results, &UrlType::Nymd),
|
||||
extract_and_collect_results_into_map(&connection_results, &UrlType::Api),
|
||||
)
|
||||
}
|
||||
|
||||
fn setup_connection_tests<H: BuildHasher + 'static>(
|
||||
nymd_urls: impl Iterator<Item = (Network, Url)>,
|
||||
api_urls: impl Iterator<Item = (Network, Url)>,
|
||||
mixnet_contract_address: HashMap<Network, Option<cosmrs::AccountId>, H>,
|
||||
) -> impl Iterator<Item = ClientForConnectionTest> {
|
||||
let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| {
|
||||
let address = mixnet_contract_address
|
||||
.get(&network)
|
||||
.expect("No configured contract address")
|
||||
.clone();
|
||||
NymdClient::<QueryNymdClient>::connect(url.as_str(), address, None, None)
|
||||
.map(move |client| ClientForConnectionTest::Nymd(network, url, Box::new(client)))
|
||||
.ok()
|
||||
});
|
||||
|
||||
let api_connection_test_clients = api_urls.map(|(network, url)| {
|
||||
ClientForConnectionTest::Api(network, url.clone(), ApiClient::new(url))
|
||||
});
|
||||
|
||||
nymd_connection_test_clients.chain(api_connection_test_clients)
|
||||
}
|
||||
|
||||
fn extract_and_collect_results_into_map(
|
||||
connection_results: &[ConnectionResult],
|
||||
url_type: &UrlType,
|
||||
) -> HashMap<Network, Vec<(Url, bool)>> {
|
||||
connection_results
|
||||
.iter()
|
||||
.filter(|c| &c.url_type() == url_type)
|
||||
.map(|c| {
|
||||
let (network, url, result) = c.result();
|
||||
(*network, (url.clone(), *result))
|
||||
})
|
||||
.into_group_map()
|
||||
}
|
||||
|
||||
async fn test_nymd_connection(
|
||||
network: Network,
|
||||
url: &Url,
|
||||
client: &NymdClient<QueryNymdClient>,
|
||||
) -> ConnectionResult {
|
||||
let result = match timeout(
|
||||
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
|
||||
client.get_mixnet_contract_version(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Err(NymdError::TendermintError(e))) => {
|
||||
// If we get a tendermint-rpc error, we classify the node as not contactable
|
||||
log::debug!("Checking: nymd_url: {network}: {url}: failed: {}", e);
|
||||
false
|
||||
}
|
||||
Ok(Err(NymdError::AbciError(code, log))) => {
|
||||
// We accept the mixnet contract not found as ok from a connection standpoint. This happens
|
||||
// for example on a pre-launch network.
|
||||
log::debug!(
|
||||
"Checking: nymd_url: {network}: {url}: {}, but with abci error: {code}: {log}",
|
||||
"success".green()
|
||||
);
|
||||
code == 18
|
||||
}
|
||||
Ok(Err(error @ NymdError::NoContractAddressAvailable)) => {
|
||||
log::debug!(
|
||||
"Checking: nymd_url: {network}: {url}: {}: {error}",
|
||||
"failed".red()
|
||||
);
|
||||
false
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
// For any other error, we're optimistic and just try anyway.
|
||||
log::debug!(
|
||||
"Checking: nymd_url: {network}: {url}: {}, but with error: {e}",
|
||||
"success".green()
|
||||
);
|
||||
true
|
||||
}
|
||||
Ok(Ok(_)) => {
|
||||
log::debug!(
|
||||
"Checking: nymd_url: {network}: {url}: {}",
|
||||
"success".green()
|
||||
);
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!(
|
||||
"Checking: nymd_url: {network}: {url}: {}: {e}",
|
||||
"failed".red()
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
ConnectionResult::Nymd(network, url.clone(), result)
|
||||
}
|
||||
|
||||
async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) -> ConnectionResult {
|
||||
let result = match timeout(
|
||||
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
|
||||
client.get_cached_mixnodes(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(_)) => {
|
||||
log::debug!("Checking: api_url: {network}: {url}: {}", "success".green());
|
||||
true
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
log::debug!(
|
||||
"Checking: api_url: {network}: {url}: {}: {e}",
|
||||
"failed".red()
|
||||
);
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!(
|
||||
"Checking: api_url: {network}: {url}: {}: {e}",
|
||||
"failed".red()
|
||||
);
|
||||
false
|
||||
}
|
||||
};
|
||||
ConnectionResult::Api(network, url.clone(), result)
|
||||
}
|
||||
|
||||
enum ClientForConnectionTest {
|
||||
Nymd(Network, Url, Box<NymdClient<QueryNymdClient>>),
|
||||
Api(Network, Url, ApiClient),
|
||||
}
|
||||
|
||||
impl ClientForConnectionTest {
|
||||
async fn run_connection_check(self) -> ConnectionResult {
|
||||
match self {
|
||||
ClientForConnectionTest::Nymd(network, ref url, ref client) => {
|
||||
test_nymd_connection(network, url, client).await
|
||||
}
|
||||
ClientForConnectionTest::Api(network, ref url, ref client) => {
|
||||
test_api_connection(network, url, client).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum UrlType {
|
||||
Nymd,
|
||||
Api,
|
||||
}
|
||||
|
||||
impl fmt::Display for UrlType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
UrlType::Nymd => write!(f, "nymd"),
|
||||
UrlType::Api => write!(f, "api"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ConnectionResult {
|
||||
Nymd(Network, Url, bool),
|
||||
Api(Network, Url, bool),
|
||||
}
|
||||
|
||||
impl ConnectionResult {
|
||||
fn result(&self) -> (&Network, &Url, &bool) {
|
||||
match self {
|
||||
ConnectionResult::Nymd(network, url, result)
|
||||
| ConnectionResult::Api(network, url, result) => (network, url, result),
|
||||
}
|
||||
}
|
||||
|
||||
fn url_type(&self) -> UrlType {
|
||||
match self {
|
||||
ConnectionResult::Nymd(..) => UrlType::Nymd,
|
||||
ConnectionResult::Api(..) => UrlType::Api,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConnectionResult {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let (network, url, result) = self.result();
|
||||
let url_type = self.url_type();
|
||||
write!(
|
||||
f,
|
||||
"{network}: {url}: {url_type}: connection is successful: {result}"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod client;
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub mod connection_tester;
|
||||
mod error;
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub mod nymd;
|
||||
|
||||
Generated
+23
@@ -551,6 +551,17 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"lazy_static",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.1.0"
|
||||
@@ -1112,6 +1123,12 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.8"
|
||||
@@ -2749,12 +2766,15 @@ dependencies = [
|
||||
"bip39",
|
||||
"cfg-if",
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"config",
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"dirs",
|
||||
"dotenv",
|
||||
"eyre",
|
||||
"futures",
|
||||
"itertools",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"pretty_env_logger",
|
||||
@@ -5106,10 +5126,12 @@ dependencies = [
|
||||
"base64",
|
||||
"bip39",
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"config",
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"flate2",
|
||||
"futures",
|
||||
"itertools",
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
@@ -5120,6 +5142,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2 0.9.9",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"url",
|
||||
"validator-api-requests",
|
||||
"vesting-contract",
|
||||
|
||||
@@ -21,9 +21,12 @@ tauri-macros = "=1.0.0-rc.1"
|
||||
[dependencies]
|
||||
bip39 = "1.0"
|
||||
cfg-if = "1.0.0"
|
||||
colored = "2.0"
|
||||
dirs = "4.0"
|
||||
dotenv = "0.15.0"
|
||||
eyre = "0.6.5"
|
||||
futures = "0.3.15"
|
||||
itertools = "0.10"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
rand = "0.6.5"
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
use crate::{error::BackendError, network::Network as WalletNetwork};
|
||||
use config::defaults::{all::SupportedNetworks, ValidatorDetails};
|
||||
use config::NymConfig;
|
||||
use reqwest::StatusCode;
|
||||
use core::fmt;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::iter::zip;
|
||||
use std::time::Duration;
|
||||
use std::{fs, io, path::PathBuf};
|
||||
use strum::IntoEnumIterator;
|
||||
@@ -115,16 +114,18 @@ impl Config {
|
||||
.chain(self.network.validators(network))
|
||||
.cloned()
|
||||
.chain(base_validators)
|
||||
.unique()
|
||||
}
|
||||
|
||||
pub fn get_validators_with_api_endpoint(
|
||||
&self,
|
||||
network: WalletNetwork,
|
||||
) -> impl Iterator<Item = ValidatorUrlWithApiEndpoint> + '_ {
|
||||
pub fn get_nymd_urls(&self, network: WalletNetwork) -> impl Iterator<Item = Url> + '_ {
|
||||
self.get_validators(network).into_iter().map(|v| v.nymd_url)
|
||||
}
|
||||
|
||||
pub fn get_api_urls(&self, network: WalletNetwork) -> impl Iterator<Item = Url> + '_ {
|
||||
self
|
||||
.get_validators(network)
|
||||
.into_iter()
|
||||
.filter_map(|validator| ValidatorUrlWithApiEndpoint::try_from(validator).ok())
|
||||
.filter_map(|v| v.api_url)
|
||||
}
|
||||
|
||||
pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> Option<cosmrs::AccountId> {
|
||||
@@ -164,66 +165,24 @@ impl Config {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(3))
|
||||
.build()?;
|
||||
log::debug!(
|
||||
"Fetching validator urls from: {}",
|
||||
REMOTE_SOURCE_OF_VALIDATOR_URLS
|
||||
);
|
||||
let response = client
|
||||
.get(REMOTE_SOURCE_OF_VALIDATOR_URLS.to_string())
|
||||
.send()
|
||||
.await?;
|
||||
self.base.fetched_validators = serde_json::from_str(&response.text().await?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn check_validator_health(
|
||||
&self,
|
||||
network: WalletNetwork,
|
||||
) -> Result<Vec<(ValidatorUrl, StatusCode)>, BackendError> {
|
||||
// Limit the number of validators we query
|
||||
let max_validators = 200_usize;
|
||||
let validators_to_query = || self.get_validators(network).take(max_validators);
|
||||
|
||||
let validator_urls = validators_to_query().map(|v| {
|
||||
let mut health_url = v.nymd_url.clone();
|
||||
health_url.set_path("health");
|
||||
(v, health_url)
|
||||
});
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(3))
|
||||
.build()?;
|
||||
|
||||
let requests = validator_urls.map(|(_, url)| client.get(url).send());
|
||||
let responses = futures::future::join_all(requests).await;
|
||||
|
||||
let validators_responding_success =
|
||||
zip(validators_to_query(), responses).filter_map(|(v, r)| match r {
|
||||
Ok(r) if r.status().is_success() => Some((v, r.status())),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
Ok(validators_responding_success.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub async fn check_validator_health_for_all_networks(
|
||||
&self,
|
||||
) -> Result<HashMap<WalletNetwork, Vec<(ValidatorUrl, StatusCode)>>, BackendError> {
|
||||
let validator_health_requests =
|
||||
WalletNetwork::iter().map(|network| self.check_validator_health(network));
|
||||
|
||||
let responses_keyed_by_network = zip(
|
||||
WalletNetwork::iter(),
|
||||
futures::future::join_all(validator_health_requests).await,
|
||||
log::debug!(
|
||||
"Received validator urls: \n{}",
|
||||
self.base.fetched_validators
|
||||
);
|
||||
|
||||
// Iterate and collect manually to be able to return errors in the response
|
||||
let mut responses = HashMap::new();
|
||||
for (network, response) in responses_keyed_by_network {
|
||||
responses.insert(network, response?);
|
||||
}
|
||||
Ok(responses)
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct ValidatorUrl {
|
||||
pub nymd_url: Url,
|
||||
pub api_url: Option<Url>,
|
||||
@@ -243,23 +202,14 @@ impl TryFrom<ValidatorDetails> for ValidatorUrl {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ValidatorUrlWithApiEndpoint {
|
||||
pub nymd_url: Url,
|
||||
pub api_url: Url,
|
||||
}
|
||||
|
||||
impl TryFrom<ValidatorUrl> for ValidatorUrlWithApiEndpoint {
|
||||
type Error = BackendError;
|
||||
|
||||
fn try_from(validator: ValidatorUrl) -> Result<Self, Self::Error> {
|
||||
match validator.api_url {
|
||||
Some(api_url) => Ok(ValidatorUrlWithApiEndpoint {
|
||||
nymd_url: validator.nymd_url,
|
||||
api_url,
|
||||
}),
|
||||
None => Err(BackendError::NoValidatorApiUrlConfigured),
|
||||
}
|
||||
impl fmt::Display for ValidatorUrl {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let s1 = format!("nymd_url: {}", self.nymd_url);
|
||||
let s2 = self
|
||||
.api_url
|
||||
.as_ref()
|
||||
.map(|url| format!(", api_url: {}", url));
|
||||
write!(f, " {}{},", s1, s2.unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +235,27 @@ impl OptionalValidators {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for OptionalValidators {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let s1 = self
|
||||
.mainnet
|
||||
.as_ref()
|
||||
.map(|validators| format!("mainnet: [\n{}\n]", validators.iter().format("\n")))
|
||||
.unwrap_or_default();
|
||||
let s2 = self
|
||||
.sandbox
|
||||
.as_ref()
|
||||
.map(|validators| format!(",\nsandbox: [\n{}\n]", validators.iter().format("\n")))
|
||||
.unwrap_or_default();
|
||||
let s3 = self
|
||||
.qa
|
||||
.as_ref()
|
||||
.map(|validators| format!(",\nqa: [\n{}\n]", validators.iter().format("\n")))
|
||||
.unwrap_or_default();
|
||||
write!(f, "{}{}{}", s1, s2, s3)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -30,6 +30,7 @@ use crate::operations::vesting;
|
||||
use crate::state::State;
|
||||
|
||||
fn main() {
|
||||
dotenv::dotenv().ok();
|
||||
setup_logging();
|
||||
|
||||
tauri::Builder::default()
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
use crate::coin::{Coin, Denom};
|
||||
use crate::config::{Config, ValidatorUrlWithApiEndpoint};
|
||||
use crate::config::Config;
|
||||
use crate::error::BackendError;
|
||||
use crate::network::Network;
|
||||
use crate::network::Network as WalletNetwork;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::wallet_storage::{self, DEFAULT_WALLET_ACCOUNT_ID};
|
||||
|
||||
use bip39::{Language, Mnemonic};
|
||||
use config::defaults::all::Network;
|
||||
use config::defaults::COSMOS_DERIVATION_PATH;
|
||||
use cosmrs::bip32::DerivationPath;
|
||||
use itertools::Itertools;
|
||||
use rand::seq::SliceRandom;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use strum::IntoEnumIterator;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::error::NymdError;
|
||||
use validator_client::nymd::SigningNymdClient;
|
||||
use validator_client::Client;
|
||||
use url::Url;
|
||||
|
||||
use validator_client::{
|
||||
connection_tester::run_validator_connection_test, nymd::SigningNymdClient, Client,
|
||||
};
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/account.ts"))]
|
||||
@@ -112,7 +116,7 @@ pub async fn create_new_mnemonic() -> Result<String, BackendError> {
|
||||
#[tauri::command]
|
||||
pub async fn switch_network(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
network: Network,
|
||||
network: WalletNetwork,
|
||||
) -> Result<Account, BackendError> {
|
||||
let account = {
|
||||
let r_state = state.read().await;
|
||||
@@ -158,16 +162,41 @@ async fn _connect_with_mnemonic(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Account, BackendError> {
|
||||
update_validator_urls(state.clone()).await?;
|
||||
let validators = choose_validators(mnemonic.clone(), &state).await?;
|
||||
|
||||
let config = state.read().await.config();
|
||||
let clients = create_clients(&validators, &mnemonic, &config)?;
|
||||
|
||||
for network in WalletNetwork::iter() {
|
||||
log::debug!(
|
||||
"List of validators for {network}: [\n{}\n]",
|
||||
config.get_validators(network).format(",\n")
|
||||
);
|
||||
}
|
||||
|
||||
// Run connection tests on all nymd and validator-api endpoints
|
||||
let (nymd_urls, api_urls) = {
|
||||
let mixnet_contract_address = WalletNetwork::iter()
|
||||
.map(|network| (network.into(), config.get_mixnet_contract_address(network)))
|
||||
.collect::<HashMap<_, _>>();
|
||||
let nymd_urls = WalletNetwork::iter().flat_map(|network| {
|
||||
config
|
||||
.get_nymd_urls(network)
|
||||
.map(move |url| (network.into(), url))
|
||||
});
|
||||
let api_urls = WalletNetwork::iter().flat_map(|network| {
|
||||
config
|
||||
.get_api_urls(network)
|
||||
.map(move |url| (network.into(), url))
|
||||
});
|
||||
|
||||
run_validator_connection_test(nymd_urls, api_urls, mixnet_contract_address).await
|
||||
};
|
||||
|
||||
let clients = create_clients(&nymd_urls, &api_urls, &mnemonic, &config)?;
|
||||
|
||||
// Set the default account
|
||||
let default_network: Network = config::defaults::DEFAULT_NETWORK.into();
|
||||
let default_network: WalletNetwork = config::defaults::DEFAULT_NETWORK.into();
|
||||
let client_for_default_network = clients
|
||||
.iter()
|
||||
.find(|client| Network::from(client.network) == default_network);
|
||||
.find(|client| WalletNetwork::from(client.network) == default_network);
|
||||
let account_for_default_network = match client_for_default_network {
|
||||
Some(client) => Ok(Account::new(
|
||||
client.nymd.mixnet_contract_address()?.to_string(),
|
||||
@@ -181,7 +210,7 @@ async fn _connect_with_mnemonic(
|
||||
|
||||
// Register all the clients
|
||||
for client in clients {
|
||||
let network: Network = client.network.into();
|
||||
let network: WalletNetwork = client.network.into();
|
||||
let mut w_state = state.write().await;
|
||||
w_state.add_client(network, client);
|
||||
}
|
||||
@@ -189,18 +218,72 @@ async fn _connect_with_mnemonic(
|
||||
account_for_default_network
|
||||
}
|
||||
|
||||
fn select_random_responding_nymd_url(
|
||||
nymd_urls: &HashMap<Network, Vec<(Url, bool)>>,
|
||||
network: WalletNetwork,
|
||||
config: &Config,
|
||||
) -> Url {
|
||||
// We pick a randon responding nymd url, and if not, fall back on the first one in the list.
|
||||
nymd_urls
|
||||
.get(&network.into())
|
||||
.and_then(|urls| {
|
||||
let nymd_urls: Vec<_> = urls
|
||||
.iter()
|
||||
.filter_map(|(url, result)| if *result { Some(url.clone()) } else { None })
|
||||
.collect();
|
||||
nymd_urls.choose(&mut rand::thread_rng()).cloned()
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
log::debug!("No passing nymd_urls for {network}: using default");
|
||||
config
|
||||
.get_nymd_urls(network)
|
||||
.next()
|
||||
.expect("Expected at least one hardcoded nymd url")
|
||||
})
|
||||
}
|
||||
|
||||
fn select_first_responding_api_url(
|
||||
api_urls: &HashMap<Network, Vec<(Url, bool)>>,
|
||||
network: WalletNetwork,
|
||||
config: &Config,
|
||||
) -> Url {
|
||||
// We pick the first API url among the responding ones. If none exists, fall back on the first
|
||||
// one in the list.
|
||||
api_urls
|
||||
.get(&network.into())
|
||||
.and_then(|urls| {
|
||||
urls
|
||||
.iter()
|
||||
.find_map(|(url, result)| if *result { Some(url.clone()) } else { None })
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
log::debug!("No passing api_urls for {network}: using default");
|
||||
config
|
||||
.get_api_urls(network)
|
||||
.next()
|
||||
.expect("Expected at least one hardcoded api url")
|
||||
})
|
||||
}
|
||||
|
||||
fn create_clients(
|
||||
validators: &HashMap<Network, ValidatorUrlWithApiEndpoint>,
|
||||
nymd_urls: &HashMap<Network, Vec<(Url, bool)>>,
|
||||
api_urls: &HashMap<Network, Vec<(Url, bool)>>,
|
||||
mnemonic: &Mnemonic,
|
||||
config: &Config,
|
||||
) -> Result<Vec<Client<SigningNymdClient>>, BackendError> {
|
||||
let mut clients = Vec::new();
|
||||
for network in Network::iter() {
|
||||
for network in WalletNetwork::iter() {
|
||||
let nymd_url = select_random_responding_nymd_url(nymd_urls, network, config);
|
||||
let api_url = select_first_responding_api_url(api_urls, network, config);
|
||||
|
||||
log::info!("Connecting to: nymd_url: {nymd_url} for {network}");
|
||||
log::info!("Connecting to: api_url: {api_url} for {network}");
|
||||
|
||||
let client = validator_client::Client::new_signing(
|
||||
validator_client::Config::new(
|
||||
network.into(),
|
||||
validators[&network].nymd_url.clone(),
|
||||
validators[&network].api_url.clone(),
|
||||
nymd_url,
|
||||
api_url,
|
||||
config.get_mixnet_contract_address(network),
|
||||
config.get_vesting_contract_address(network),
|
||||
config.get_bandwidth_claim_contract_address(network),
|
||||
@@ -212,122 +295,6 @@ fn create_clients(
|
||||
Ok(clients)
|
||||
}
|
||||
|
||||
async fn choose_validators(
|
||||
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();
|
||||
log::info!(
|
||||
"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,
|
||||
mnemonic: Mnemonic,
|
||||
) -> Result<Option<(Network, ValidatorUrlWithApiEndpoint)>, BackendError> {
|
||||
for validator in validators {
|
||||
if let Some(responding_validator) =
|
||||
try_connect_to_validator(&validator, config, network, mnemonic.clone()).await?
|
||||
{
|
||||
// Pick the first successful one
|
||||
return Ok(Some(responding_validator));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn try_connect_to_validator(
|
||||
validator: &ValidatorUrlWithApiEndpoint,
|
||||
config: &Config,
|
||||
network: Network,
|
||||
mnemonic: Mnemonic,
|
||||
) -> Result<Option<(Network, ValidatorUrlWithApiEndpoint)>, BackendError> {
|
||||
let client = validator_client::Client::new_signing(
|
||||
validator_client::Config::new(
|
||||
network.into(),
|
||||
validator.nymd_url.clone(),
|
||||
validator.api_url.clone(),
|
||||
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 {
|
||||
log::info!(
|
||||
"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,
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn does_password_file_exist() -> Result<bool, BackendError> {
|
||||
log::info!("Checking wallet file");
|
||||
|
||||
Reference in New Issue
Block a user