wallet: try validators one by one if available (#1143)
This commit is contained in:
@@ -92,7 +92,7 @@ impl Config {
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
pub struct Client<C> {
|
||||
network: network_defaults::all::Network,
|
||||
pub network: network_defaults::all::Network,
|
||||
mixnet_contract_address: Option<cosmrs::AccountId>,
|
||||
vesting_contract_address: Option<cosmrs::AccountId>,
|
||||
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
|
||||
|
||||
@@ -21,7 +21,7 @@ where
|
||||
}
|
||||
|
||||
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: GasPrice,
|
||||
|
||||
@@ -641,7 +641,7 @@ pub struct Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
endpoint: U,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: GasPrice,
|
||||
|
||||
@@ -83,7 +83,7 @@ impl NymdClient<QueryNymdClient> {
|
||||
|
||||
impl NymdClient<SigningNymdClient> {
|
||||
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
|
||||
pub fn connect_with_signer<U>(
|
||||
pub fn connect_with_signer<U: Clone>(
|
||||
network: config::defaults::all::Network,
|
||||
endpoint: U,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
@@ -114,7 +114,7 @@ impl NymdClient<SigningNymdClient> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn connect_with_mnemonic<U>(
|
||||
pub fn connect_with_mnemonic<U: Clone>(
|
||||
network: config::defaults::all::Network,
|
||||
endpoint: U,
|
||||
mixnet_contract_address: Option<AccountId>,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, fmt, ops::Deref, str::FromStr};
|
||||
use std::{collections::HashMap, fmt, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
DefaultNetworkDetails, ValidatorDetails, MAINNET_DEFAULTS, QA_DEFAULTS, SANDBOX_DEFAULTS,
|
||||
@@ -98,7 +98,7 @@ pub struct NetworkDetails {
|
||||
}
|
||||
|
||||
impl From<&DefaultNetworkDetails<'_>> for NetworkDetails {
|
||||
fn from(details: &DefaultNetworkDetails) -> Self {
|
||||
fn from(details: &DefaultNetworkDetails<'_>) -> Self {
|
||||
NetworkDetails {
|
||||
bech32_prefix: details.bech32_prefix.into(),
|
||||
denom: details.denom.into(),
|
||||
@@ -118,20 +118,12 @@ pub struct SupportedNetworks {
|
||||
|
||||
impl SupportedNetworks {
|
||||
pub fn new(support: Vec<Network>) -> Self {
|
||||
let mut networks = HashMap::new();
|
||||
|
||||
for network in support {
|
||||
match network {
|
||||
Network::MAINNET => {
|
||||
networks.insert(Network::MAINNET, MAINNET_DEFAULTS.deref().into())
|
||||
}
|
||||
Network::SANDBOX => {
|
||||
networks.insert(Network::SANDBOX, SANDBOX_DEFAULTS.deref().into())
|
||||
}
|
||||
Network::QA => networks.insert(Network::QA, QA_DEFAULTS.deref().into()),
|
||||
};
|
||||
SupportedNetworks {
|
||||
networks: support
|
||||
.into_iter()
|
||||
.map(|n| (n, n.details().into()))
|
||||
.collect(),
|
||||
}
|
||||
SupportedNetworks { networks }
|
||||
}
|
||||
|
||||
pub fn bech32_prefix(&self, network: Network) -> Option<&str> {
|
||||
|
||||
Generated
+1
@@ -2816,6 +2816,7 @@ dependencies = [
|
||||
"credentials",
|
||||
"dirs",
|
||||
"eyre",
|
||||
"futures",
|
||||
"mixnet-contract-common",
|
||||
"rand 0.6.5",
|
||||
"serde",
|
||||
|
||||
@@ -22,6 +22,7 @@ tauri-macros = "=1.0.0-rc.1"
|
||||
bip39 = "1.0"
|
||||
dirs = "4.0"
|
||||
eyre = "0.6.5"
|
||||
futures = "0.3.15"
|
||||
rand = "0.6.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -7,7 +7,6 @@ use config::NymConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs, io, path::PathBuf};
|
||||
use strum::IntoEnumIterator;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -87,27 +86,14 @@ impl NymConfig for Config {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn get_nymd_validator_url(&self, network: WalletNetwork) -> Option<Url> {
|
||||
// TODO: for now we pick the first one
|
||||
pub fn get_validators(
|
||||
&self,
|
||||
network: WalletNetwork,
|
||||
) -> impl Iterator<Item = &ValidatorDetails> + '_ {
|
||||
self
|
||||
.network
|
||||
.validators(network)
|
||||
.chain(self.base.networks.validators(network.into()))
|
||||
.map(|x| x.nymd_url())
|
||||
.next()
|
||||
}
|
||||
|
||||
pub fn get_validator_api_url(&self, network: WalletNetwork) -> Option<Url> {
|
||||
// TODO: for now we pick the first one
|
||||
// NOTE: we are explictly picking the API URL for the first configured validator, not the first
|
||||
// API URL out of all validators.
|
||||
self
|
||||
.network
|
||||
.validators(network)
|
||||
.chain(self.base.networks.validators(network.into()))
|
||||
.map(|x| x.api_url())
|
||||
.next()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> Option<cosmrs::AccountId> {
|
||||
@@ -223,12 +209,17 @@ api_url = 'https://bar/api'
|
||||
let config = test_config();
|
||||
|
||||
let nymd_url = config
|
||||
.get_nymd_validator_url(WalletNetwork::MAINNET)
|
||||
.get_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.map(ValidatorDetails::nymd_url)
|
||||
.unwrap();
|
||||
assert_eq!(nymd_url.to_string(), "https://foo/".to_string());
|
||||
|
||||
// The first entry is missing an API URL
|
||||
let api_url = config.get_validator_api_url(WalletNetwork::MAINNET);
|
||||
let api_url = config
|
||||
.get_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.and_then(ValidatorDetails::api_url);
|
||||
assert_eq!(api_url, None);
|
||||
}
|
||||
|
||||
@@ -237,7 +228,9 @@ api_url = 'https://bar/api'
|
||||
let config = Config::default();
|
||||
|
||||
let nymd_url = config
|
||||
.get_nymd_validator_url(WalletNetwork::MAINNET)
|
||||
.get_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.map(ValidatorDetails::nymd_url)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
nymd_url.to_string(),
|
||||
@@ -245,7 +238,9 @@ api_url = 'https://bar/api'
|
||||
);
|
||||
|
||||
let api_url = config
|
||||
.get_validator_api_url(WalletNetwork::MAINNET)
|
||||
.get_validators(WalletNetwork::MAINNET)
|
||||
.next()
|
||||
.and_then(ValidatorDetails::api_url)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
api_url.to_string(),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
use validator_client::nymd::error::NymdError;
|
||||
use validator_client::validator_api::error::ValidatorAPIError;
|
||||
use validator_client::{nymd::error::NymdError, ValidatorClientError};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BackendError {
|
||||
@@ -51,6 +51,11 @@ pub enum BackendError {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
MalformedUrlProvided {
|
||||
#[from]
|
||||
source: url::ParseError,
|
||||
},
|
||||
#[error("failed to encrypt the given data with the provided password")]
|
||||
EncryptionError,
|
||||
#[error("failed to decrypt the given data with the provided password")]
|
||||
@@ -81,3 +86,13 @@ impl Serialize for BackendError {
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValidatorClientError> for BackendError {
|
||||
fn from(e: ValidatorClientError) -> Self {
|
||||
match e {
|
||||
ValidatorClientError::ValidatorAPIError { source } => source.into(),
|
||||
ValidatorClientError::MalformedUrlProvided(e) => e.into(),
|
||||
ValidatorClientError::NymdError(e) => e.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
|
||||
use cosmrs::Denom;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use strum::EnumIter;
|
||||
|
||||
use crate::error::BackendError;
|
||||
use config::defaults::all::Network as ConfigNetwork;
|
||||
use config::defaults::{mainnet, qa, sandbox};
|
||||
|
||||
@@ -38,25 +37,28 @@ impl Default for Network {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::from_over_into)]
|
||||
impl Into<ConfigNetwork> for Network {
|
||||
fn into(self) -> ConfigNetwork {
|
||||
match self {
|
||||
impl fmt::Display for Network {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigNetwork> for Network {
|
||||
fn from(network: ConfigNetwork) -> Self {
|
||||
match network {
|
||||
ConfigNetwork::QA => Network::QA,
|
||||
ConfigNetwork::SANDBOX => Network::SANDBOX,
|
||||
ConfigNetwork::MAINNET => Network::MAINNET,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Network> for ConfigNetwork {
|
||||
fn from(network: Network) -> Self {
|
||||
match network {
|
||||
Network::QA => ConfigNetwork::QA,
|
||||
Network::SANDBOX => ConfigNetwork::SANDBOX,
|
||||
Network::MAINNET => ConfigNetwork::MAINNET,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ConfigNetwork> for Network {
|
||||
type Error = BackendError;
|
||||
|
||||
fn try_from(value: ConfigNetwork) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
ConfigNetwork::QA => Ok(Network::QA),
|
||||
ConfigNetwork::SANDBOX => Ok(Network::SANDBOX),
|
||||
ConfigNetwork::MAINNET => Ok(Network::MAINNET),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
use crate::coin::{Coin, Denom};
|
||||
use crate::config::Config;
|
||||
use crate::error::BackendError;
|
||||
use crate::network::Network;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
|
||||
use bip39::{Language, Mnemonic};
|
||||
use config::defaults::ValidatorDetails;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use strum::IntoEnumIterator;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::error::NymdError;
|
||||
use validator_client::nymd::SigningNymdClient;
|
||||
use validator_client::Client;
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/account.ts"))]
|
||||
@@ -132,46 +138,141 @@ async fn _connect_with_mnemonic(
|
||||
mnemonic: Mnemonic,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Account, BackendError> {
|
||||
let default_network = Network::try_from(config::defaults::DEFAULT_NETWORK)?;
|
||||
let mut default_account = None;
|
||||
let default_network: Network = config::defaults::DEFAULT_NETWORK.into();
|
||||
let config = state.read().await.config();
|
||||
|
||||
// Try to connect to validators on all networks
|
||||
let mut validators = select_validators(&config, &mnemonic).await?;
|
||||
|
||||
// If 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() {
|
||||
let client = {
|
||||
let config = state.read().await.config();
|
||||
let nymd_url = config
|
||||
.get_nymd_validator_url(network)
|
||||
.ok_or(BackendError::NoNymdValidatorConfigured)?;
|
||||
let api_url = config
|
||||
.get_validator_api_url(network)
|
||||
.ok_or(BackendError::NoValidatorApiUrlConfigured)?;
|
||||
match validator_client::Client::new_signing(
|
||||
validator_client::Config::new(
|
||||
network.into(),
|
||||
nymd_url,
|
||||
api_url,
|
||||
config.get_mixnet_contract_address(network),
|
||||
config.get_vesting_contract_address(network),
|
||||
config.get_bandwidth_claim_contract_address(network),
|
||||
),
|
||||
mnemonic.clone(),
|
||||
) {
|
||||
Ok(client) => client,
|
||||
Err(e) => panic!("{}", e),
|
||||
}
|
||||
};
|
||||
validators
|
||||
.entry(network)
|
||||
// We always have at least one hardcoded defalt validator
|
||||
.or_insert_with(|| config.get_validators(network).next().unwrap().clone());
|
||||
}
|
||||
|
||||
if network == default_network {
|
||||
default_account = Some(Account::new(
|
||||
client.nymd.mixnet_contract_address()?.to_string(),
|
||||
client.nymd.address().to_string(),
|
||||
network.denom().try_into()?,
|
||||
));
|
||||
}
|
||||
// Now we are ready to create the clients that we will use
|
||||
let mut clients = Vec::new();
|
||||
for network in Network::iter() {
|
||||
let client = validator_client::Client::new_signing(
|
||||
validator_client::Config::new(
|
||||
network.into(),
|
||||
validators[&network].nymd_url(),
|
||||
validators[&network]
|
||||
.api_url()
|
||||
.ok_or(BackendError::NoValidatorApiUrlConfigured)?,
|
||||
config.get_mixnet_contract_address(network),
|
||||
config.get_vesting_contract_address(network),
|
||||
config.get_bandwidth_claim_contract_address(network),
|
||||
),
|
||||
mnemonic.clone(),
|
||||
)?;
|
||||
clients.push(client);
|
||||
}
|
||||
|
||||
// Set the default account
|
||||
let client_for_default_network = clients
|
||||
.iter()
|
||||
.find(|client| Network::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(),
|
||||
client.nymd.address().to_string(),
|
||||
default_network.denom().try_into()?,
|
||||
)),
|
||||
None => Err(BackendError::NetworkNotSupported(
|
||||
config::defaults::DEFAULT_NETWORK,
|
||||
)),
|
||||
};
|
||||
|
||||
// Register all the clients
|
||||
for client in clients {
|
||||
let network: Network = client.network.into();
|
||||
let mut w_state = state.write().await;
|
||||
w_state.add_client(network, client);
|
||||
}
|
||||
|
||||
default_account.ok_or(BackendError::NetworkNotSupported(
|
||||
config::defaults::DEFAULT_NETWORK,
|
||||
))
|
||||
account_for_default_network
|
||||
}
|
||||
|
||||
// For each network, try the list of available validators one by one and use the first responding
|
||||
// one.
|
||||
async fn select_validators(
|
||||
config: &Config,
|
||||
mnemonic: &Mnemonic,
|
||||
) -> Result<HashMap<Network, ValidatorDetails>, BackendError> {
|
||||
let validators = futures::future::join_all(Network::iter().map(|network| {
|
||||
try_connect_to_validators(
|
||||
config.get_validators(network),
|
||||
config,
|
||||
network,
|
||||
mnemonic.clone(),
|
||||
)
|
||||
}))
|
||||
.await;
|
||||
|
||||
// Collect for the purpose of returning any errors encountered
|
||||
let validators = validators.into_iter().collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Filter out networks we were not able to connect to
|
||||
Ok(validators.into_iter().flatten().collect::<HashMap<_, _>>())
|
||||
}
|
||||
|
||||
async fn try_connect_to_validators(
|
||||
validators: impl Iterator<Item = &ValidatorDetails>,
|
||||
config: &Config,
|
||||
network: Network,
|
||||
mnemonic: Mnemonic,
|
||||
) -> Result<Option<(Network, ValidatorDetails)>, 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: &ValidatorDetails,
|
||||
config: &Config,
|
||||
network: Network,
|
||||
mnemonic: Mnemonic,
|
||||
) -> Result<Option<(Network, ValidatorDetails)>, BackendError> {
|
||||
let nymd_url = validator.nymd_url();
|
||||
let api_url = match validator.api_url() {
|
||||
Some(url) => url,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let client = validator_client::Client::new_signing(
|
||||
validator_client::Config::new(
|
||||
network.into(),
|
||||
nymd_url.clone(),
|
||||
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 {
|
||||
println!("Connection ok for {network}: {nymd_url}, {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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user