Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c835ab24fd | |||
| 36fb0eba29 | |||
| 1b37e85418 | |||
| 6a93497c8f | |||
| ffbd76539a | |||
| bdcc19e86a | |||
| 7929bac685 | |||
| f590aad42c | |||
| ec23f3dcb7 | |||
| 9644eb4329 | |||
| 7a4c6e4ed4 | |||
| d5ad504104 | |||
| d684f6d7ae | |||
| f0d9703587 | |||
| c08efef8ed | |||
| 6d29774744 | |||
| af6bab7703 | |||
| 7033f92d82 | |||
| 128dfa6d81 | |||
| 0b0ec075bb | |||
| cca4d21e7c |
@@ -4,6 +4,26 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.0.11] (2023-02-28)
|
||||
|
||||
- Fix empty dealer set loop ([#3105])
|
||||
- The nym-api db.sqlite is broken when trying to run against it it in `enabled-credentials-mode true` there is an ordering issue with migrations when using the credential binary to purchase bandwidth ([#3100])
|
||||
- Feature/latency based gateway selection ([#3081])
|
||||
- Fix the credential binary to handle transactions to sleep when in non-inProgress epochs ([#3057])
|
||||
- Publish mixnet contract to crates.io ([#1919])
|
||||
- Publish vesting contract to crates.io ([#1920])
|
||||
- Feature/update checker to use master ([#3097])
|
||||
- Feature/improve binary checks ([#3094])
|
||||
|
||||
[#3105]: https://github.com/nymtech/nym/issues/3105
|
||||
[#3100]: https://github.com/nymtech/nym/issues/3100
|
||||
[#3081]: https://github.com/nymtech/nym/pull/3081
|
||||
[#3057]: https://github.com/nymtech/nym/issues/3057
|
||||
[#1919]: https://github.com/nymtech/nym/issues/1919
|
||||
[#1920]: https://github.com/nymtech/nym/issues/1920
|
||||
[#3097]: https://github.com/nymtech/nym/pull/3097
|
||||
[#3094]: https://github.com/nymtech/nym/pull/3094
|
||||
|
||||
## [v1.1.10] (2023-02-21)
|
||||
|
||||
- Verloc listener causing mixnode unexpected shutdown ([#3038])
|
||||
|
||||
Generated
+12
-10
@@ -677,7 +677,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"dashmap 5.4.0",
|
||||
@@ -705,6 +705,8 @@ dependencies = [
|
||||
"time 0.3.17",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-tungstenite 0.14.0",
|
||||
"tungstenite 0.13.0",
|
||||
"url",
|
||||
"validator-client",
|
||||
"wasm-bindgen",
|
||||
@@ -1800,7 +1802,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.1.4",
|
||||
@@ -3279,7 +3281,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.11"
|
||||
version = "1.1.12"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3389,7 +3391,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
@@ -3447,7 +3449,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
dependencies = [
|
||||
"clap 4.1.4",
|
||||
"client-core",
|
||||
@@ -3541,7 +3543,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3618,7 +3620,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.11"
|
||||
version = "1.1.12"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atty",
|
||||
@@ -3673,7 +3675,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"clap 4.1.4",
|
||||
@@ -3711,7 +3713,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"log",
|
||||
@@ -3786,7 +3788,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
dependencies = [
|
||||
"clap 4.1.4",
|
||||
"client-core",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.66"
|
||||
@@ -20,6 +20,7 @@ serde_json = "1.0.89"
|
||||
tap = "1.0.1"
|
||||
thiserror = "1.0.34"
|
||||
url = { version ="2.2", features = ["serde"] }
|
||||
tungstenite = { version = "0.13.0", default-features = false }
|
||||
tokio = { version = "1.24.1", features = ["macros"]}
|
||||
time = "0.3.17"
|
||||
|
||||
@@ -44,6 +45,9 @@ features = ["time"]
|
||||
version = "1.24.1"
|
||||
features = ["time"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
|
||||
version = "0.14"
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
version = "0.6.2"
|
||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||
@@ -65,6 +69,7 @@ features = ["futures"]
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
|
||||
path = "../../common/wasm-utils"
|
||||
features = ["websocket"]
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.time]
|
||||
version = "0.3.17"
|
||||
|
||||
@@ -304,7 +304,7 @@ where
|
||||
}
|
||||
let gateway_address = self.gateway_config.gateway_listener.clone();
|
||||
if gateway_address.is_empty() {
|
||||
return Err(ClientCoreError::GatwayAddressUnknown);
|
||||
return Err(ClientCoreError::GatewayAddressUnknown);
|
||||
}
|
||||
|
||||
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_config::defaults::NymNetworkDetails;
|
||||
use nym_config::{NymConfig, OptionalSet, DB_FILE_NAME};
|
||||
use nym_config::{NymConfig, OptionalSet, CRED_DB_FILE_NAME};
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
@@ -579,7 +579,7 @@ impl<T: NymConfig> Client<T> {
|
||||
}
|
||||
|
||||
fn default_database_path(id: &str) -> PathBuf {
|
||||
T::default_data_directory(id).join(DB_FILE_NAME)
|
||||
T::default_data_directory(id).join(CRED_DB_FILE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use gateway_client::error::GatewayClientError;
|
||||
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use nym_topology::gateway::GatewayConversionError;
|
||||
use nym_topology::NymTopologyError;
|
||||
use validator_client::ValidatorClientError;
|
||||
|
||||
@@ -53,7 +54,32 @@ pub enum ClientCoreError {
|
||||
GatewayOwnerUnknown,
|
||||
|
||||
#[error("The address of the gateway is unknown - did you run init?")]
|
||||
GatwayAddressUnknown,
|
||||
GatewayAddressUnknown,
|
||||
|
||||
#[error("The gateway is malformed: {source}")]
|
||||
MalformedGateway {
|
||||
#[from]
|
||||
source: GatewayConversionError,
|
||||
},
|
||||
|
||||
#[error("failed to establish connection to gateway: {source}")]
|
||||
GatewayConnectionFailure {
|
||||
#[from]
|
||||
source: tungstenite::Error,
|
||||
},
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[error("failed to establish gateway connection (wasm)")]
|
||||
GatewayJsConnectionFailure,
|
||||
|
||||
#[error("Gateway connection was abruptly closed")]
|
||||
GatewayConnectionAbruptlyClosed,
|
||||
|
||||
#[error("Timed out while trying to establish gateway connection")]
|
||||
GatewayConnectionTimeout,
|
||||
|
||||
#[error("No ping measurements for the gateway ({identity}) performed")]
|
||||
NoGatewayMeasurements { identity: String },
|
||||
|
||||
#[error("failed to register receiver for reconstructed mixnet messages")]
|
||||
FailedToRegisterReceiver,
|
||||
|
||||
@@ -6,52 +6,223 @@ use crate::{
|
||||
config::{persistence::key_pathfinder::ClientKeyPathfinder, Config},
|
||||
error::ClientCoreError,
|
||||
};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use gateway_client::wasm_mockups::SigningNyxdClient;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use gateway_client::GatewayClient;
|
||||
use gateway_requests::registration::handshake::SharedKeys;
|
||||
use log::{debug, info, trace, warn};
|
||||
use nym_config::NymConfig;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_topology::{filter::VersionFilterable, gateway};
|
||||
use rand::{seq::SliceRandom, thread_rng};
|
||||
use rand::{seq::SliceRandom, thread_rng, Rng};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tap::TapFallible;
|
||||
use tungstenite::Message;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::net::TcpStream;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::Instant;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::connect_async;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use validator_client::nyxd::SigningNyxdClient;
|
||||
|
||||
pub(super) async fn query_gateway_details(
|
||||
validator_servers: Vec<Url>,
|
||||
chosen_gateway_id: Option<identity::PublicKey>,
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
let nym_api = validator_servers
|
||||
.choose(&mut thread_rng())
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type WsConn = WebSocketStream<MaybeTlsStream<TcpStream>>;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use gateway_client::wasm_mockups::SigningNyxdClient;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_timer::Instant;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_utils::websocket::JSWebsocket;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
type WsConn = JSWebsocket;
|
||||
|
||||
const MEASUREMENTS: usize = 3;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
const PING_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||
|
||||
struct GatewayWithLatency {
|
||||
gateway: gateway::Node,
|
||||
latency: Duration,
|
||||
}
|
||||
|
||||
impl GatewayWithLatency {
|
||||
fn new(gateway: gateway::Node, latency: Duration) -> Self {
|
||||
GatewayWithLatency { gateway, latency }
|
||||
}
|
||||
}
|
||||
|
||||
async fn current_gateways<R: Rng>(
|
||||
rng: &mut R,
|
||||
nym_apis: Vec<Url>,
|
||||
) -> Result<Vec<gateway::Node>, ClientCoreError> {
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
let validator_client = validator_client::client::NymApiClient::new(nym_api.clone());
|
||||
let client = validator_client::client::NymApiClient::new(nym_api.clone());
|
||||
|
||||
log::trace!("Fetching list of gateways from: {}", nym_api);
|
||||
let gateways = validator_client.get_cached_gateways().await?;
|
||||
|
||||
let gateways = client.get_cached_gateways().await?;
|
||||
let valid_gateways = gateways
|
||||
.into_iter()
|
||||
.filter_map(|gateway| gateway.try_into().ok())
|
||||
.collect::<Vec<gateway::Node>>();
|
||||
|
||||
// we were always filtering by version so I'm not removing that 'feature'
|
||||
let filtered_gateways = valid_gateways.filter_by_version(env!("CARGO_PKG_VERSION"));
|
||||
Ok(filtered_gateways)
|
||||
}
|
||||
|
||||
// if we have chosen particular gateway - use it, otherwise choose a random one.
|
||||
// (remember that in active topology all gateways have at least 100 reputation so should
|
||||
// be working correctly)
|
||||
if let Some(gateway_id) = chosen_gateway_id {
|
||||
filtered_gateways
|
||||
.iter()
|
||||
.find(|gateway| gateway.identity_key == gateway_id)
|
||||
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_id.to_string()))
|
||||
.cloned()
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
|
||||
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
|
||||
Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
|
||||
Ok(Err(conn_failure)) => Err(conn_failure.into()),
|
||||
Ok(Ok((stream, _))) => Ok(stream),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
|
||||
JSWebsocket::new(endpoint).map_err(|_| ClientCoreError::GatewayJsConnectionFailure)
|
||||
}
|
||||
|
||||
async fn measure_latency(gateway: gateway::Node) -> Result<GatewayWithLatency, ClientCoreError> {
|
||||
let addr = gateway.clients_address();
|
||||
trace!(
|
||||
"establishing connection to {} ({addr})...",
|
||||
gateway.identity_key,
|
||||
);
|
||||
let mut stream = connect(&addr).await?;
|
||||
|
||||
let mut results = Vec::new();
|
||||
for _ in 0..MEASUREMENTS {
|
||||
let measurement_future = async {
|
||||
let ping_content = vec![1, 2, 3];
|
||||
let start = Instant::now();
|
||||
stream.send(Message::Ping(ping_content.clone())).await?;
|
||||
|
||||
match stream.next().await {
|
||||
Some(Ok(Message::Pong(content))) => {
|
||||
if content == ping_content {
|
||||
let elapsed = Instant::now().duration_since(start);
|
||||
trace!("current ping time: {elapsed:?}");
|
||||
results.push(elapsed);
|
||||
} else {
|
||||
warn!("received a pong message with different content? wtf.")
|
||||
}
|
||||
}
|
||||
Some(Ok(_)) => warn!("received a message that's not a pong!"),
|
||||
Some(Err(err)) => return Err(err.into()),
|
||||
None => return Err(ClientCoreError::GatewayConnectionAbruptlyClosed),
|
||||
}
|
||||
|
||||
Ok::<(), ClientCoreError>(())
|
||||
};
|
||||
|
||||
// thanks to wasm we can't use tokio::time::timeout : (
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let timeout = tokio::time::sleep(PING_TIMEOUT);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tokio::pin!(timeout);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let mut timeout = wasm_timer::Delay::new(PING_TIMEOUT);
|
||||
|
||||
tokio::select! {
|
||||
_ = &mut timeout => {
|
||||
warn!("timed out while trying to perform measurement...")
|
||||
}
|
||||
res = measurement_future => res?,
|
||||
}
|
||||
}
|
||||
|
||||
let count = results.len() as u64;
|
||||
if count == 0 {
|
||||
return Err(ClientCoreError::NoGatewayMeasurements {
|
||||
identity: gateway.identity_key.to_base58_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let sum: Duration = results.into_iter().sum();
|
||||
let avg = Duration::from_nanos(sum.as_nanos() as u64 / count);
|
||||
|
||||
Ok(GatewayWithLatency::new(gateway, avg))
|
||||
}
|
||||
|
||||
async fn choose_gateway_by_latency<R: Rng>(
|
||||
rng: &mut R,
|
||||
gateways: Vec<gateway::Node>,
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
info!("choosing gateway by latency...");
|
||||
|
||||
let mut gateways_with_latency = Vec::new();
|
||||
for gateway in gateways {
|
||||
let id = *gateway.identity();
|
||||
trace!("measuring latency to {id}...");
|
||||
let with_latency = match measure_latency(gateway).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
warn!("failed to measure {id}: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
debug!(
|
||||
"{id} ({}): {:?}",
|
||||
with_latency.gateway.location, with_latency.latency
|
||||
);
|
||||
gateways_with_latency.push(with_latency)
|
||||
}
|
||||
|
||||
let chosen = gateways_with_latency
|
||||
.choose_weighted(rng, |item| 1. / item.latency.as_secs_f32())
|
||||
.expect("invalid selection weight!");
|
||||
|
||||
info!(
|
||||
"chose gateway {} (located at {}) with average latency of {:?}",
|
||||
chosen.gateway.identity_key, chosen.gateway.location, chosen.latency
|
||||
);
|
||||
|
||||
Ok(chosen.gateway.clone())
|
||||
}
|
||||
|
||||
fn uniformly_random_gateway<R: Rng>(
|
||||
rng: &mut R,
|
||||
gateways: Vec<gateway::Node>,
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
gateways
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub(super) async fn query_gateway_details(
|
||||
validator_servers: Vec<Url>,
|
||||
chosen_gateway_id: Option<identity::PublicKey>,
|
||||
by_latency: bool,
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
let gateways = current_gateways(&mut rng, validator_servers).await?;
|
||||
|
||||
// if we set an explicit gateway, use that one and nothing else
|
||||
if let Some(explicitly_chosen) = chosen_gateway_id {
|
||||
gateways
|
||||
.into_iter()
|
||||
.find(|gateway| gateway.identity_key == explicitly_chosen)
|
||||
.ok_or_else(|| ClientCoreError::NoGatewayWithId(explicitly_chosen.to_string()))
|
||||
} else if by_latency {
|
||||
choose_gateway_by_latency(&mut rng, gateways).await
|
||||
} else {
|
||||
filtered_gateways
|
||||
.choose(&mut rand::thread_rng())
|
||||
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
|
||||
.cloned()
|
||||
uniformly_random_gateway(&mut rng, gateways)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,9 +77,11 @@ pub async fn register_with_gateway(
|
||||
key_manager: &mut KeyManager,
|
||||
nym_api_endpoints: Vec<Url>,
|
||||
chosen_gateway_id: Option<identity::PublicKey>,
|
||||
by_latency: bool,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError> {
|
||||
// Get the gateway details of the gateway we will use
|
||||
let gateway = helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id).await?;
|
||||
let gateway =
|
||||
helpers::query_gateway_details(nym_api_endpoints, chosen_gateway_id, by_latency).await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
|
||||
let our_identity = key_manager.identity_keypair();
|
||||
@@ -102,6 +104,7 @@ pub async fn setup_gateway_from_config<C, T>(
|
||||
register_gateway: bool,
|
||||
user_chosen_gateway_id: Option<identity::PublicKey>,
|
||||
config: &Config<T>,
|
||||
by_latency: bool,
|
||||
) -> Result<GatewayEndpointConfig, ClientCoreError>
|
||||
where
|
||||
C: NymConfig + ClientCoreConfigTrait,
|
||||
@@ -117,9 +120,12 @@ where
|
||||
}
|
||||
|
||||
// Else, we preceed by querying the nym-api
|
||||
let gateway =
|
||||
helpers::query_gateway_details(config.get_nym_api_endpoints(), user_chosen_gateway_id)
|
||||
.await?;
|
||||
let gateway = helpers::query_gateway_details(
|
||||
config.get_nym_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
by_latency,
|
||||
)
|
||||
.await?;
|
||||
log::debug!("Querying gateway gives: {}", gateway);
|
||||
|
||||
// If we are not registering, just return this and assume the caller has the keys already and
|
||||
|
||||
@@ -11,7 +11,7 @@ use commands::*;
|
||||
use error::Result;
|
||||
use log::*;
|
||||
use nym_bin_common::completions::fig_generate;
|
||||
use nym_config::{DATA_DIR, DB_FILE_NAME};
|
||||
use nym_config::{CRED_DB_FILE_NAME, DATA_DIR};
|
||||
use nym_network_defaults::{setup_env, NymNetworkDetails};
|
||||
use std::process::exit;
|
||||
use std::time::{Duration, SystemTime};
|
||||
@@ -51,8 +51,11 @@ async fn block_until_coconut_is_available<C: Clone + CosmWasmClient + Send + Syn
|
||||
|
||||
break;
|
||||
} else {
|
||||
// Use 20 additional seconds to avoid the exact moment of going into the final epoch state
|
||||
let secs_until_final = epoch.final_timestamp_secs() + 20 - current_timestamp_secs;
|
||||
// Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries
|
||||
let secs_until_final = epoch
|
||||
.final_timestamp_secs()
|
||||
.saturating_sub(current_timestamp_secs)
|
||||
+ 1;
|
||||
info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final);
|
||||
std::thread::sleep(Duration::from_secs(secs_until_final));
|
||||
}
|
||||
@@ -70,7 +73,10 @@ async fn main() -> Result<()> {
|
||||
|
||||
match args.command {
|
||||
Command::Run(r) => {
|
||||
let db_path = r.client_home_directory.join(DATA_DIR).join(DB_FILE_NAME);
|
||||
let db_path = r
|
||||
.client_home_directory
|
||||
.join(DATA_DIR)
|
||||
.join(CRED_DB_FILE_NAME);
|
||||
let shared_storage = credential_storage::initialise_storage(db_path).await;
|
||||
let recovery_storage = recovery_storage::RecoveryStorage::new(r.recovery_dir)?;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -25,6 +25,11 @@ pub(crate) struct Init {
|
||||
#[clap(long)]
|
||||
gateway: Option<identity::PublicKey>,
|
||||
|
||||
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
|
||||
/// uniformly.
|
||||
#[clap(long, conflicts_with = "gateway")]
|
||||
latency_based_selection: bool,
|
||||
|
||||
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
|
||||
/// potentially causing loss of access.
|
||||
#[clap(long)]
|
||||
@@ -143,6 +148,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
register_gateway,
|
||||
user_chosen_gateway_id,
|
||||
config.get_base(),
|
||||
args.latency_based_selection,
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -37,6 +37,11 @@ pub(crate) struct Init {
|
||||
#[clap(long)]
|
||||
gateway: Option<identity::PublicKey>,
|
||||
|
||||
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
|
||||
/// uniformly.
|
||||
#[clap(long, conflicts_with = "gateway")]
|
||||
latency_based_selection: bool,
|
||||
|
||||
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
|
||||
/// potentially causing loss of access.
|
||||
#[clap(long)]
|
||||
@@ -149,6 +154,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
|
||||
register_gateway,
|
||||
user_chosen_gateway_id,
|
||||
config.get_base(),
|
||||
args.latency_based_selection,
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?;
|
||||
|
||||
@@ -36,9 +36,9 @@ nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
|
||||
# at some point it might be possible to make it wasm-compatible
|
||||
# perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated)
|
||||
async-trait = { version = "0.1.51", optional = true }
|
||||
bip39 = { version = "1", features = ["rand"], optional = true }
|
||||
bip39 = { version = "2", features = ["rand"], optional = true }
|
||||
nym-config = { path = "../../config", optional = true }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
|
||||
cosmrs = { version = "0.8.0", features = ["rpc", "bip32", "cosmwasm"], optional = true}
|
||||
cw3 = { version = "0.13.4", optional = true }
|
||||
cw4 = { version = "0.13.4", optional = true }
|
||||
prost = { version = "0.10", default-features = false, optional = true }
|
||||
|
||||
@@ -11,9 +11,7 @@ use nym_api_requests::models::{
|
||||
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef};
|
||||
pub use nym_mixnet_contract_common::{mixnode::MixNodeDetails, GatewayBond, IdentityKeyRef, MixId};
|
||||
|
||||
#[cfg(feature = "nyxd-client")]
|
||||
use crate::nyxd::traits::{DkgQueryClient, MixnetQueryClient, MultisigQueryClient};
|
||||
|
||||
@@ -121,7 +121,7 @@ impl GasAdjustable for Gas {
|
||||
mod sealed {
|
||||
use cosmrs::tx::{self, Gas};
|
||||
use cosmrs::Coin as CosmosCoin;
|
||||
use cosmrs::{AccountId, Decimal as CosmosDecimal, Denom as CosmosDenom};
|
||||
use cosmrs::{AccountId, Denom as CosmosDenom};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
fn cosmos_denom_inner_getter(val: &CosmosDenom) -> String {
|
||||
@@ -138,29 +138,11 @@ mod sealed {
|
||||
}
|
||||
}
|
||||
|
||||
fn cosmos_decimal_inner_getter(val: &CosmosDecimal) -> u64 {
|
||||
// haha, this code is so disgusting. I'll make a PR on cosmrs to slightly alleviate those issues...
|
||||
// note: unwrap here is fine as the to_string is just returning a stringified u64 which, well, is a valid u64
|
||||
val.to_string().parse().unwrap()
|
||||
}
|
||||
|
||||
// at the time of writing it the current cosmrs' Decimal is extremely limited...
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "CosmosDecimal")]
|
||||
struct Decimal(#[serde(getter = "cosmos_decimal_inner_getter")] u64);
|
||||
|
||||
impl From<Decimal> for CosmosDecimal {
|
||||
fn from(val: Decimal) -> Self {
|
||||
val.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
struct Coin {
|
||||
#[serde(with = "Denom")]
|
||||
denom: CosmosDenom,
|
||||
#[serde(with = "Decimal")]
|
||||
amount: CosmosDecimal,
|
||||
amount: u128,
|
||||
}
|
||||
|
||||
impl From<Coin> for CosmosCoin {
|
||||
|
||||
@@ -42,7 +42,7 @@ pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
|
||||
pub use cosmrs::tendermint::Time as TendermintTime;
|
||||
pub use cosmrs::tx::{self, Gas};
|
||||
pub use cosmrs::Coin as CosmosCoin;
|
||||
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
|
||||
pub use cosmrs::{bip32, AccountId, Denom};
|
||||
pub use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||
pub use signing_client::Client as SigningNyxdClient;
|
||||
|
||||
@@ -25,7 +25,7 @@ toml = "0.5.6"
|
||||
url = "2.2"
|
||||
tap = "1"
|
||||
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = "0.8.0"
|
||||
cosmwasm-std = { version = "1.0.0" }
|
||||
|
||||
validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] }
|
||||
|
||||
@@ -18,7 +18,7 @@ pub mod defaults;
|
||||
|
||||
pub const CONFIG_DIR: &str = "config";
|
||||
pub const DATA_DIR: &str = "data";
|
||||
pub const DB_FILE_NAME: &str = "db.sqlite";
|
||||
pub const CRED_DB_FILE_NAME: &str = "credentials_database.db";
|
||||
|
||||
pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
fn template() -> &'static str;
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = "0.8.0"
|
||||
thiserror = "1.0"
|
||||
|
||||
# I guess temporarily until we get serde support in coconut up and running
|
||||
|
||||
@@ -20,7 +20,7 @@ url = "2.2"
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = "0.8.0"
|
||||
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"nyxd-client",
|
||||
|
||||
@@ -80,7 +80,7 @@ pub(crate) mod tests {
|
||||
use crate::epoch_state::transactions::advance_epoch_state;
|
||||
use crate::support::tests::fixtures::dealer_details_fixture;
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::GROUP_MEMBERS;
|
||||
use crate::support::tests::helpers::{add_fixture_dealer, GROUP_MEMBERS};
|
||||
use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cw4::Member;
|
||||
@@ -147,6 +147,8 @@ pub(crate) mod tests {
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
|
||||
add_fixture_dealer(deps.as_mut());
|
||||
advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
|
||||
let ret = try_add_dealer(
|
||||
|
||||
@@ -53,6 +53,7 @@ pub(crate) mod tests {
|
||||
use crate::epoch_state::transactions::advance_epoch_state;
|
||||
use crate::support::tests::fixtures::{dealer_details_fixture, dealing_bytes_fixture};
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::add_fixture_dealer;
|
||||
use coconut_dkg_common::dealer::DealerDetails;
|
||||
use coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
@@ -80,6 +81,7 @@ pub(crate) mod tests {
|
||||
.block
|
||||
.time
|
||||
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
|
||||
add_fixture_dealer(deps.as_mut());
|
||||
advance_epoch_state(deps.as_mut(), env).unwrap();
|
||||
|
||||
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
|
||||
|
||||
@@ -89,23 +89,29 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
|
||||
let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
|
||||
let next_epoch = if let Some(state) = current_epoch.state.next() {
|
||||
// We are during DKG process
|
||||
let mut new_state = state;
|
||||
if let EpochState::DealingExchange { resharing } = state {
|
||||
let current_dealers = current_dealers()
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.collect::<Result<Vec<Addr>, _>>()?;
|
||||
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
|
||||
let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3;
|
||||
THRESHOLD.save(deps.storage, &threshold)?;
|
||||
if !resharing {
|
||||
let replacement_data = InitialReplacementData {
|
||||
initial_dealers: current_dealers,
|
||||
initial_height: None,
|
||||
};
|
||||
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
|
||||
if current_dealers.is_empty() {
|
||||
// If no dealer registered yet, we just stay in the same state until there's at least one
|
||||
new_state = current_epoch.state;
|
||||
} else {
|
||||
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
|
||||
let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3;
|
||||
THRESHOLD.save(deps.storage, &threshold)?;
|
||||
if !resharing {
|
||||
let replacement_data = InitialReplacementData {
|
||||
initial_dealers: current_dealers,
|
||||
initial_height: None,
|
||||
};
|
||||
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Epoch::new(
|
||||
state,
|
||||
new_state,
|
||||
current_epoch.epoch_id,
|
||||
current_epoch.time_configuration,
|
||||
env.block.time,
|
||||
@@ -392,6 +398,14 @@ pub(crate) mod tests {
|
||||
EarlyEpochStateAdvancement(1)
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(1);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
epoch.state,
|
||||
EpochState::PublicKeySubmission { resharing: false }
|
||||
);
|
||||
|
||||
// setup dealer details
|
||||
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
|
||||
for details in all_details.iter() {
|
||||
@@ -404,7 +418,7 @@ pub(crate) mod tests {
|
||||
.may_load(&deps.storage)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
env.block.time = env.block.time.plus_seconds(1);
|
||||
env.block.time = env.block.time.plus_seconds(epoch.time_configuration.public_key_submission_time_secs);
|
||||
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
|
||||
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
|
||||
assert_eq!(
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::contract::instantiate;
|
||||
use crate::dealers::storage::current_dealers;
|
||||
use coconut_dkg_common::msg::InstantiateMsg;
|
||||
use coconut_dkg_common::types::DealerDetails;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier};
|
||||
use cosmwasm_std::{
|
||||
from_binary, to_binary, ContractResult, Empty, MemoryStorage, OwnedDeps, QuerierResult,
|
||||
SystemResult, WasmQuery,
|
||||
from_binary, to_binary, Addr, ContractResult, DepsMut, Empty, MemoryStorage, OwnedDeps,
|
||||
QuerierResult, SystemResult, WasmQuery,
|
||||
};
|
||||
use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse};
|
||||
use lazy_static::lazy_static;
|
||||
@@ -22,6 +24,22 @@ lazy_static! {
|
||||
pub static ref GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(vec![]);
|
||||
}
|
||||
|
||||
pub fn add_fixture_dealer(deps: DepsMut<'_>) {
|
||||
let owner = Addr::unchecked("owner");
|
||||
current_dealers()
|
||||
.save(
|
||||
deps.storage,
|
||||
&owner,
|
||||
&DealerDetails {
|
||||
address: owner.clone(),
|
||||
bte_public_key_with_proof: String::new(),
|
||||
announce_address: String::new(),
|
||||
assigned_index: 100,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn querier_handler(query: &WasmQuery) -> QuerierResult {
|
||||
let bin = match query {
|
||||
WasmQuery::Smart { contract_addr, msg } => {
|
||||
|
||||
@@ -91,7 +91,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::epoch_state::transactions::advance_epoch_state;
|
||||
use crate::support::tests::helpers;
|
||||
use crate::support::tests::helpers::MULTISIG_CONTRACT;
|
||||
use crate::support::tests::helpers::{add_fixture_dealer, MULTISIG_CONTRACT};
|
||||
use coconut_dkg_common::dealer::DealerDetails;
|
||||
use coconut_dkg_common::types::{EpochState, TimeConfiguration};
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
@@ -104,6 +104,7 @@ mod tests {
|
||||
let info = mock_info("requester", &[]);
|
||||
let share = "share".to_string();
|
||||
|
||||
add_fixture_dealer(deps.as_mut());
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
@@ -171,6 +172,7 @@ mod tests {
|
||||
.to_string()
|
||||
}
|
||||
);
|
||||
add_fixture_dealer(deps.as_mut());
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
@@ -247,6 +249,7 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
add_fixture_dealer(deps.as_mut());
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
@@ -292,6 +295,7 @@ mod tests {
|
||||
let share = "share".to_string();
|
||||
let multisig_info = mock_info(MULTISIG_CONTRACT, &[]);
|
||||
|
||||
add_fixture_dealer(deps.as_mut());
|
||||
env.block.time = env
|
||||
.block
|
||||
.time
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/network-explorer",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -121,4 +121,4 @@
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
GatewayBondAnnotated,
|
||||
GatewayBond,
|
||||
} from '../typeDefs/explorer-api';
|
||||
import { toPercentIntegerString } from '../utils';
|
||||
|
||||
function getFromCache(key: string) {
|
||||
const ts = Number(localStorage.getItem('ts'));
|
||||
@@ -94,9 +93,9 @@ export class Api {
|
||||
static fetchGateways = async (): Promise<GatewayBond[]> => {
|
||||
const res = await fetch(GATEWAYS_API);
|
||||
const gatewaysAnnotated: GatewayBondAnnotated[] = await res.json();
|
||||
return gatewaysAnnotated.map(({ gateway_bond, performance }) => ({
|
||||
return gatewaysAnnotated.map(({ gateway_bond, node_performance }) => ({
|
||||
...gateway_bond,
|
||||
performance: toPercentIntegerString(performance),
|
||||
node_performance,
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GatewayResponse, GatewayBond, GatewayReportResponse } from '../typeDefs/explorer-api';
|
||||
import { toPercentIntegerString } from '../utils';
|
||||
|
||||
export type GatewayRowType = {
|
||||
id: string;
|
||||
@@ -8,7 +9,7 @@ export type GatewayRowType = {
|
||||
host: string;
|
||||
location: string;
|
||||
version: string;
|
||||
performance: string;
|
||||
node_performance: string;
|
||||
};
|
||||
|
||||
export type GatewayEnrichedRowType = GatewayRowType & {
|
||||
@@ -29,7 +30,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy
|
||||
bond: gw.pledge_amount.amount || 0,
|
||||
host: gw.gateway.host || '',
|
||||
version: gw.gateway.version || '',
|
||||
performance: gw.performance,
|
||||
node_performance: toPercentIntegerString(gw.node_performance.last_24h),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -46,6 +47,6 @@ export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayRe
|
||||
mixPort: gateway.gateway.mix_port || 0,
|
||||
routingScore: `${report.most_recent}%`,
|
||||
avgUptime: `${report.last_day || report.last_hour}%`,
|
||||
performance: gateway.performance,
|
||||
node_performance: toPercentIntegerString(gateway.node_performance.most_recent),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ const CustomPagination = () => {
|
||||
color="primary"
|
||||
count={state.pagination.pageCount}
|
||||
page={state.pagination.page + 1}
|
||||
onChange={(event, value) => apiRef.current.setPage(value - 1)}
|
||||
onChange={(_, value) => apiRef.current.setPage(value - 1)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ const columns: ColumnsType[] = [
|
||||
headerAlign: 'left',
|
||||
},
|
||||
{
|
||||
field: 'routingScore',
|
||||
field: 'node_performance',
|
||||
title: 'Routing Score',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
@@ -130,13 +130,13 @@ const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: Gat
|
||||
* Guard component to handle loading and not found states
|
||||
*/
|
||||
const PageGatewayDetailGuard: FCWithChildren = () => {
|
||||
const [selectedGateway, setSelectedGateway] = React.useState<GatewayBond | undefined>();
|
||||
const [selectedGateway, setSelectedGateway] = React.useState<GatewayBond>();
|
||||
const { gateways } = useMainContext();
|
||||
const { id } = useParams<{ id: string | undefined }>();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (gateways?.data) {
|
||||
setSelectedGateway(gateways.data.find((gateway) => gateway.gateway.identity_key === id));
|
||||
setSelectedGateway(gateways.data.find((g) => g.gateway.identity_key === id));
|
||||
}
|
||||
}, [gateways, id]);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Link as RRDLink } from 'react-router-dom';
|
||||
import { Box, Button, Card, Grid, Link as MuiLink } from '@mui/material';
|
||||
import { Box, Card, Grid, Link as MuiLink } from '@mui/material';
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
|
||||
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid';
|
||||
import { SelectChangeEvent } from '@mui/material/Select';
|
||||
@@ -86,7 +86,6 @@ export const PageGateways: FCWithChildren = () => {
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
field: 'identity_key',
|
||||
headerName: 'Identity Key',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Identity Key" />,
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
width: 380,
|
||||
@@ -119,7 +118,7 @@ export const PageGateways: FCWithChildren = () => {
|
||||
<MuiLink
|
||||
sx={{ ...cellStyles }}
|
||||
component={RRDLink}
|
||||
to={`/network-components/gateway/${params.row.identityKey}`}
|
||||
to={`/network-components/gateway/${params.row.identity_key}`}
|
||||
data-testid="pledge-amount"
|
||||
>
|
||||
{unymToNym(params.value, 6)}
|
||||
@@ -127,8 +126,7 @@ export const PageGateways: FCWithChildren = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'performance',
|
||||
headerName: 'Routing Score',
|
||||
field: 'node_performance',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Routing Score" />,
|
||||
width: 150,
|
||||
headerAlign: 'left',
|
||||
@@ -137,7 +135,7 @@ export const PageGateways: FCWithChildren = () => {
|
||||
<MuiLink
|
||||
sx={{ ...cellStyles }}
|
||||
component={RRDLink}
|
||||
to={`/network-components/gateway/${params.row.identityKey}`}
|
||||
to={`/network-components/gateway/${params.row.identity_key}`}
|
||||
data-testid="pledge-amount"
|
||||
>
|
||||
{`${params.value}%`}
|
||||
@@ -154,7 +152,7 @@ export const PageGateways: FCWithChildren = () => {
|
||||
<MuiLink
|
||||
sx={{ ...cellStyles }}
|
||||
component={RRDLink}
|
||||
to={`/network-components/gateway/${params.row.identityKey}`}
|
||||
to={`/network-components/gateway/${params.row.identity_key}`}
|
||||
data-testid="host"
|
||||
>
|
||||
{params.value}
|
||||
@@ -168,9 +166,9 @@ export const PageGateways: FCWithChildren = () => {
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<Button
|
||||
<Box
|
||||
onClick={() => handleSearch(params.value as string)}
|
||||
sx={{ ...cellStyles, justifyContent: 'flex-start' }}
|
||||
sx={{ ...cellStyles, justifyContent: 'flex-start', cursor: 'pointer' }}
|
||||
data-testid="location-button"
|
||||
>
|
||||
<Tooltip text={params.value} id="gateway-location-text">
|
||||
@@ -184,7 +182,7 @@ export const PageGateways: FCWithChildren = () => {
|
||||
{params.value}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -207,7 +205,6 @@ export const PageGateways: FCWithChildren = () => {
|
||||
},
|
||||
{
|
||||
field: 'version',
|
||||
headerName: 'Version',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Version" />,
|
||||
width: 150,
|
||||
headerAlign: 'left',
|
||||
|
||||
@@ -114,6 +114,12 @@ export interface StatsResponse {
|
||||
packets_explicitly_dropped_since_last_update: number;
|
||||
}
|
||||
|
||||
export interface NodePerformance {
|
||||
most_recent: string;
|
||||
last_hour: string;
|
||||
last_24h: string;
|
||||
}
|
||||
|
||||
export type MixNodeHistoryResponse = StatsResponse;
|
||||
|
||||
export interface GatewayBond {
|
||||
@@ -122,12 +128,12 @@ export interface GatewayBond {
|
||||
total_delegation: Amount;
|
||||
owner: string;
|
||||
gateway: Gateway;
|
||||
performance: string;
|
||||
node_performance: NodePerformance;
|
||||
}
|
||||
|
||||
export interface GatewayBondAnnotated {
|
||||
gateway_bond: GatewayBond;
|
||||
performance: string;
|
||||
node_performance: NodePerformance;
|
||||
}
|
||||
|
||||
export type GatewayResponse = GatewayBond[];
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -11,7 +11,7 @@ thiserror = "1.0"
|
||||
k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
|
||||
eyre = "0.6.5"
|
||||
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = "0.8.0"
|
||||
|
||||
nym-cli-commands = { path = "../../common/commands" }
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.11"
|
||||
version = "1.1.12"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-api"
|
||||
version = "1.1.11"
|
||||
version = "1.1.12"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = "0.8.0"
|
||||
cosmwasm-std = { version = "1.0.0", default-features = false }
|
||||
getset = "0.1.1"
|
||||
schemars = { version = "0.8", features = ["preserve_order"] }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
use cosmwasm_std::{Addr, Coin, Decimal};
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::reward_params::{Performance, RewardingParams};
|
||||
@@ -93,12 +93,21 @@ pub struct MixnodeStatusResponse {
|
||||
pub status: MixnodeStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct NodePerformance {
|
||||
pub most_recent: Performance,
|
||||
pub last_hour: Performance,
|
||||
pub last_24h: Performance,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct MixNodeBondAnnotated {
|
||||
pub mixnode_details: MixNodeDetails,
|
||||
pub stake_saturation: StakeSaturation,
|
||||
pub uncapped_stake_saturation: StakeSaturation,
|
||||
// NOTE: the performance field is deprecated in favour of node_performance
|
||||
pub performance: Performance,
|
||||
pub node_performance: NodePerformance,
|
||||
pub estimated_operator_apy: Decimal,
|
||||
pub estimated_delegators_apy: Decimal,
|
||||
pub family: Option<FamilyHead>,
|
||||
@@ -112,12 +121,32 @@ impl MixNodeBondAnnotated {
|
||||
pub fn mix_id(&self) -> MixId {
|
||||
self.mixnode_details.mix_id()
|
||||
}
|
||||
|
||||
pub fn identity_key(&self) -> &str {
|
||||
self.mixnode_details.bond_information.identity()
|
||||
}
|
||||
|
||||
pub fn owner(&self) -> &Addr {
|
||||
self.mixnode_details.bond_information.owner()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct GatewayBondAnnotated {
|
||||
pub gateway_bond: GatewayBond,
|
||||
// NOTE: the performance field is deprecated in favour of node_performance
|
||||
pub performance: Performance,
|
||||
pub node_performance: NodePerformance,
|
||||
}
|
||||
|
||||
impl GatewayBondAnnotated {
|
||||
pub fn identity(&self) -> &String {
|
||||
self.gateway_bond.identity()
|
||||
}
|
||||
|
||||
pub fn owner(&self) -> &Addr {
|
||||
self.gateway_bond.owner()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -147,7 +176,17 @@ pub struct RewardEstimationResponse {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct UptimeResponse {
|
||||
pub mix_id: MixId,
|
||||
// The same as node_performance.last_24h. Legacy
|
||||
pub avg_uptime: u8,
|
||||
pub node_performance: NodePerformance,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct GatewayUptimeResponse {
|
||||
pub identity: String,
|
||||
// The same as node_performance.last_24h. Legacy
|
||||
pub avg_uptime: u8,
|
||||
pub node_performance: NodePerformance,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
|
||||
+27
-4
@@ -1,6 +1,6 @@
|
||||
use crate::node_status_api::reward_estimate::{compute_apy_from_reward, compute_reward_estimate};
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated};
|
||||
use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodePerformance};
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_mixnet_contract_common::{reward_params::Performance, Interval, MixId};
|
||||
use nym_mixnet_contract_common::{
|
||||
@@ -99,16 +99,15 @@ pub(super) async fn annotate_nodes_with_details(
|
||||
.rewarding_details
|
||||
.uncapped_bond_saturation(&interval_reward_params);
|
||||
|
||||
let rewarded_set_status = rewarded_set.get(&mixnode.mix_id()).copied();
|
||||
|
||||
// If the performance can't be obtained, because the nym-api was not started with
|
||||
// the monitoring (and hence, storage), then reward estimates will be all zero
|
||||
|
||||
let performance =
|
||||
get_mixnode_performance_from_storage(storage, mixnode.mix_id(), current_interval)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let rewarded_set_status = rewarded_set.get(&mixnode.mix_id()).copied();
|
||||
|
||||
let reward_estimate = compute_reward_estimate(
|
||||
&mixnode,
|
||||
performance,
|
||||
@@ -117,6 +116,17 @@ pub(super) async fn annotate_nodes_with_details(
|
||||
current_interval,
|
||||
);
|
||||
|
||||
let node_performance = if let Some(storage) = storage {
|
||||
storage
|
||||
.construct_mixnode_report(mixnode.mix_id())
|
||||
.await
|
||||
.map(NodePerformance::from)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.unwrap_or_default();
|
||||
|
||||
let (estimated_operator_apy, estimated_delegators_apy) =
|
||||
compute_apy_from_reward(&mixnode, reward_estimate, current_interval);
|
||||
|
||||
@@ -129,6 +139,7 @@ pub(super) async fn annotate_nodes_with_details(
|
||||
stake_saturation,
|
||||
uncapped_stake_saturation,
|
||||
performance,
|
||||
node_performance,
|
||||
estimated_operator_apy,
|
||||
estimated_delegators_apy,
|
||||
family,
|
||||
@@ -152,9 +163,21 @@ pub(crate) async fn annotate_gateways_with_details(
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let node_performance = if let Some(storage) = storage {
|
||||
storage
|
||||
.construct_gateway_report(gateway_bond.identity())
|
||||
.await
|
||||
.map(NodePerformance::from)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.unwrap_or_default();
|
||||
|
||||
annotated.push(GatewayBondAnnotated {
|
||||
gateway_bond,
|
||||
performance,
|
||||
node_performance,
|
||||
});
|
||||
}
|
||||
annotated
|
||||
|
||||
@@ -8,26 +8,112 @@ use crate::{NodeStatusCache, NymContractCache};
|
||||
use cosmwasm_std::Decimal;
|
||||
use nym_api_requests::models::{
|
||||
AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayBondAnnotated,
|
||||
InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse,
|
||||
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
|
||||
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
|
||||
GatewayUptimeResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
|
||||
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
|
||||
MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse,
|
||||
UptimeResponse,
|
||||
};
|
||||
use nym_mixnet_contract_common::reward_params::Performance;
|
||||
use nym_mixnet_contract_common::{Interval, MixId, RewardedSetNodeStatus};
|
||||
use nym_mixnet_contract_common::{MixId, RewardedSetNodeStatus};
|
||||
use rocket::http::Status;
|
||||
use rocket::State;
|
||||
|
||||
use super::reward_estimate::compute_reward_estimate;
|
||||
|
||||
pub(crate) async fn _mixnode_report(
|
||||
async fn get_gateway_bond_annotated(
|
||||
cache: &NodeStatusCache,
|
||||
identity: &str,
|
||||
) -> Result<GatewayBondAnnotated, ErrorResponse> {
|
||||
let gateways = cache.gateways_annotated().await.ok_or(ErrorResponse::new(
|
||||
"no data available",
|
||||
Status::ServiceUnavailable,
|
||||
))?;
|
||||
|
||||
gateways
|
||||
.into_inner()
|
||||
.into_iter()
|
||||
.find(|gateway| gateway.identity() == identity)
|
||||
.ok_or(ErrorResponse::new(
|
||||
"mixnode bond not found",
|
||||
Status::NotFound,
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_mixnode_bond_annotated(
|
||||
cache: &NodeStatusCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixNodeBondAnnotated, ErrorResponse> {
|
||||
let mixnodes = cache.mixnodes_annotated().await.ok_or(ErrorResponse::new(
|
||||
"no data available",
|
||||
Status::ServiceUnavailable,
|
||||
))?;
|
||||
|
||||
mixnodes
|
||||
.into_inner()
|
||||
.into_iter()
|
||||
.find(|mixnode| mixnode.mix_id() == mix_id)
|
||||
.ok_or(ErrorResponse::new(
|
||||
"mixnode bond not found",
|
||||
Status::NotFound,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn _gateway_report(
|
||||
cache: &NodeStatusCache,
|
||||
identity: &str,
|
||||
) -> Result<GatewayStatusReportResponse, ErrorResponse> {
|
||||
let gateway = get_gateway_bond_annotated(cache, identity).await?;
|
||||
|
||||
Ok(GatewayStatusReportResponse {
|
||||
identity: gateway.identity().to_owned(),
|
||||
owner: gateway.owner().to_string(),
|
||||
most_recent: gateway.node_performance.most_recent.round_to_integer(),
|
||||
last_hour: gateway.node_performance.last_hour.round_to_integer(),
|
||||
last_day: gateway.node_performance.last_24h.round_to_integer(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn _gateway_uptime_history(
|
||||
storage: &NymApiStorage,
|
||||
identity: &str,
|
||||
) -> Result<GatewayUptimeHistoryResponse, ErrorResponse> {
|
||||
storage
|
||||
.get_gateway_uptime_history(identity)
|
||||
.await
|
||||
.map(GatewayUptimeHistoryResponse::from)
|
||||
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
|
||||
}
|
||||
|
||||
pub(crate) async fn _gateway_core_status_count(
|
||||
storage: &State<NymApiStorage>,
|
||||
identity: &str,
|
||||
since: Option<i64>,
|
||||
) -> Result<GatewayCoreStatusResponse, ErrorResponse> {
|
||||
let count = storage
|
||||
.get_core_gateway_status_count(identity, since)
|
||||
.await
|
||||
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))?;
|
||||
|
||||
Ok(GatewayCoreStatusResponse {
|
||||
identity: identity.to_string(),
|
||||
count,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn _mixnode_report(
|
||||
cache: &NodeStatusCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixnodeStatusReportResponse, ErrorResponse> {
|
||||
storage
|
||||
.construct_mixnode_report(mix_id)
|
||||
.await
|
||||
.map(MixnodeStatusReportResponse::from)
|
||||
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
|
||||
let mixnode = get_mixnode_bond_annotated(cache, mix_id).await?;
|
||||
|
||||
Ok(MixnodeStatusReportResponse {
|
||||
mix_id,
|
||||
identity: mixnode.identity_key().to_owned(),
|
||||
owner: mixnode.owner().to_string(),
|
||||
most_recent: mixnode.node_performance.most_recent.round_to_integer(),
|
||||
last_hour: mixnode.node_performance.last_hour.round_to_integer(),
|
||||
last_day: mixnode.node_performance.last_24h.round_to_integer(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn _mixnode_uptime_history(
|
||||
@@ -103,21 +189,6 @@ pub(crate) async fn _get_mixnode_reward_estimation(
|
||||
}
|
||||
}
|
||||
|
||||
async fn average_mixnode_performance(
|
||||
mix_id: MixId,
|
||||
current_interval: Interval,
|
||||
storage: &NymApiStorage,
|
||||
) -> Result<Performance, ErrorResponse> {
|
||||
storage
|
||||
.get_average_mixnode_uptime_in_the_last_24hrs(
|
||||
mix_id,
|
||||
current_interval.current_epoch_end_unix_timestamp(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn _compute_mixnode_reward_estimation(
|
||||
user_reward_param: ComputeRewardEstParam,
|
||||
cache: &NodeStatusCache,
|
||||
@@ -254,21 +325,28 @@ pub(crate) async fn _get_mixnode_inclusion_probability(
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_mixnode_avg_uptime(
|
||||
cache: &NymContractCache,
|
||||
storage: &NymApiStorage,
|
||||
cache: &NodeStatusCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<UptimeResponse, ErrorResponse> {
|
||||
let current_interval = cache
|
||||
.current_interval()
|
||||
.await
|
||||
.into_inner()
|
||||
.ok_or_else(|| ErrorResponse::new("server error", Status::InternalServerError))?;
|
||||
|
||||
let performance = average_mixnode_performance(mix_id, current_interval, storage).await?;
|
||||
let mixnode = get_mixnode_bond_annotated(cache, mix_id).await?;
|
||||
|
||||
Ok(UptimeResponse {
|
||||
mix_id,
|
||||
avg_uptime: performance.round_to_integer(),
|
||||
avg_uptime: mixnode.node_performance.last_24h.round_to_integer(),
|
||||
node_performance: mixnode.node_performance,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_gateway_avg_uptime(
|
||||
cache: &NodeStatusCache,
|
||||
identity: &str,
|
||||
) -> Result<GatewayUptimeResponse, ErrorResponse> {
|
||||
let gateway = get_gateway_bond_annotated(cache, identity).await?;
|
||||
|
||||
Ok(GatewayUptimeResponse {
|
||||
identity: identity.to_string(),
|
||||
avg_uptime: gateway.node_performance.last_24h.round_to_integer(),
|
||||
node_performance: gateway.node_performance,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -288,7 +366,7 @@ pub(crate) async fn _get_mixnode_inclusion_probabilities(
|
||||
})
|
||||
} else {
|
||||
Err(ErrorResponse::new(
|
||||
"No data available".to_string(),
|
||||
"No data available",
|
||||
Status::ServiceUnavailable,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ pub(crate) fn node_status_routes(
|
||||
routes::get_mixnode_stake_saturation,
|
||||
routes::get_mixnode_inclusion_probability,
|
||||
routes::get_mixnode_avg_uptime,
|
||||
routes::get_gateway_avg_uptime,
|
||||
routes::get_mixnode_inclusion_probabilities,
|
||||
routes::get_mixnodes_detailed,
|
||||
routes::get_rewarded_set_detailed,
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::node_status_api::utils::NodeUptimes;
|
||||
use crate::storage::models::NodeStatus;
|
||||
use nym_api_requests::models::{
|
||||
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, HistoricalUptimeResponse,
|
||||
MixnodeStatusReportResponse, MixnodeUptimeHistoryResponse, RequestError,
|
||||
MixnodeStatusReportResponse, MixnodeUptimeHistoryResponse, NodePerformance, RequestError,
|
||||
};
|
||||
use nym_mixnet_contract_common::reward_params::Performance;
|
||||
use nym_mixnet_contract_common::{IdentityKey, MixId};
|
||||
@@ -179,6 +179,16 @@ impl From<MixnodeStatusReport> for MixnodeStatusReportResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MixnodeStatusReport> for NodePerformance {
|
||||
fn from(report: MixnodeStatusReport) -> Self {
|
||||
NodePerformance {
|
||||
most_recent: report.most_recent.into(),
|
||||
last_hour: report.last_hour.into(),
|
||||
last_24h: report.last_day.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
|
||||
pub struct GatewayStatusReport {
|
||||
pub(crate) identity: String,
|
||||
@@ -228,6 +238,16 @@ impl From<GatewayStatusReport> for GatewayStatusReportResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GatewayStatusReport> for NodePerformance {
|
||||
fn from(report: GatewayStatusReport) -> Self {
|
||||
NodePerformance {
|
||||
most_recent: report.most_recent.into(),
|
||||
last_hour: report.last_hour.into(),
|
||||
last_24h: report.last_day.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, JsonSchema)]
|
||||
pub struct MixnodeUptimeHistory {
|
||||
pub(crate) mix_id: MixId,
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
use super::helpers::_get_gateways_detailed;
|
||||
use super::NodeStatusCache;
|
||||
use crate::node_status_api::helpers::{
|
||||
_compute_mixnode_reward_estimation, _get_active_set_detailed, _get_mixnode_avg_uptime,
|
||||
_get_mixnode_inclusion_probabilities, _get_mixnode_inclusion_probability,
|
||||
_get_mixnode_reward_estimation, _get_mixnode_stake_saturation, _get_mixnode_status,
|
||||
_get_mixnodes_detailed, _get_rewarded_set_detailed, _mixnode_core_status_count,
|
||||
_mixnode_report, _mixnode_uptime_history,
|
||||
_compute_mixnode_reward_estimation, _gateway_core_status_count, _gateway_report,
|
||||
_gateway_uptime_history, _get_active_set_detailed, _get_gateway_avg_uptime,
|
||||
_get_mixnode_avg_uptime, _get_mixnode_inclusion_probabilities,
|
||||
_get_mixnode_inclusion_probability, _get_mixnode_reward_estimation,
|
||||
_get_mixnode_stake_saturation, _get_mixnode_status, _get_mixnodes_detailed,
|
||||
_get_rewarded_set_detailed, _mixnode_core_status_count, _mixnode_report,
|
||||
_mixnode_uptime_history,
|
||||
};
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use crate::storage::NymApiStorage;
|
||||
@@ -16,12 +18,12 @@ use crate::NymContractCache;
|
||||
use nym_api_requests::models::{
|
||||
AllInclusionProbabilitiesResponse, ComputeRewardEstParam, GatewayBondAnnotated,
|
||||
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
|
||||
InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse,
|
||||
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
|
||||
GatewayUptimeResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
|
||||
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
|
||||
MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse,
|
||||
UptimeResponse,
|
||||
};
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use rocket_okapi::openapi;
|
||||
@@ -29,15 +31,10 @@ use rocket_okapi::openapi;
|
||||
#[openapi(tag = "status")]
|
||||
#[get("/gateway/<identity>/report")]
|
||||
pub(crate) async fn gateway_report(
|
||||
storage: &State<NymApiStorage>,
|
||||
cache: &State<NodeStatusCache>,
|
||||
identity: &str,
|
||||
) -> Result<Json<GatewayStatusReportResponse>, ErrorResponse> {
|
||||
storage
|
||||
.construct_gateway_report(identity)
|
||||
.await
|
||||
.map(GatewayStatusReportResponse::from)
|
||||
.map(Json)
|
||||
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
|
||||
Ok(Json(_gateway_report(cache, identity).await?))
|
||||
}
|
||||
|
||||
#[openapi(tag = "status")]
|
||||
@@ -46,12 +43,7 @@ pub(crate) async fn gateway_uptime_history(
|
||||
storage: &State<NymApiStorage>,
|
||||
identity: &str,
|
||||
) -> Result<Json<GatewayUptimeHistoryResponse>, ErrorResponse> {
|
||||
storage
|
||||
.get_gateway_uptime_history(identity)
|
||||
.await
|
||||
.map(GatewayUptimeHistoryResponse::from)
|
||||
.map(Json)
|
||||
.map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))
|
||||
Ok(Json(_gateway_uptime_history(storage, identity).await?))
|
||||
}
|
||||
|
||||
#[openapi(tag = "status")]
|
||||
@@ -60,25 +52,19 @@ pub(crate) async fn gateway_core_status_count(
|
||||
storage: &State<NymApiStorage>,
|
||||
identity: &str,
|
||||
since: Option<i64>,
|
||||
) -> Json<GatewayCoreStatusResponse> {
|
||||
let count = storage
|
||||
.get_core_gateway_status_count(identity, since)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
Json(GatewayCoreStatusResponse {
|
||||
identity: identity.to_string(),
|
||||
count,
|
||||
})
|
||||
) -> Result<Json<GatewayCoreStatusResponse>, ErrorResponse> {
|
||||
Ok(Json(
|
||||
_gateway_core_status_count(storage, identity, since).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[openapi(tag = "status")]
|
||||
#[get("/mixnode/<mix_id>/report")]
|
||||
pub(crate) async fn mixnode_report(
|
||||
storage: &State<NymApiStorage>,
|
||||
cache: &State<NodeStatusCache>,
|
||||
mix_id: MixId,
|
||||
) -> Result<Json<MixnodeStatusReportResponse>, ErrorResponse> {
|
||||
Ok(Json(_mixnode_report(storage, mix_id).await?))
|
||||
Ok(Json(_mixnode_report(cache, mix_id).await?))
|
||||
}
|
||||
|
||||
#[openapi(tag = "status")]
|
||||
@@ -171,11 +157,19 @@ pub(crate) async fn get_mixnode_inclusion_probability(
|
||||
#[openapi(tag = "status")]
|
||||
#[get("/mixnode/<mix_id>/avg_uptime")]
|
||||
pub(crate) async fn get_mixnode_avg_uptime(
|
||||
cache: &State<NymContractCache>,
|
||||
storage: &State<NymApiStorage>,
|
||||
cache: &State<NodeStatusCache>,
|
||||
mix_id: MixId,
|
||||
) -> Result<Json<UptimeResponse>, ErrorResponse> {
|
||||
Ok(Json(_get_mixnode_avg_uptime(cache, storage, mix_id).await?))
|
||||
Ok(Json(_get_mixnode_avg_uptime(cache, mix_id).await?))
|
||||
}
|
||||
|
||||
#[openapi(tag = "status")]
|
||||
#[get("/gateway/<identity>/avg_uptime")]
|
||||
pub(crate) async fn get_gateway_avg_uptime(
|
||||
cache: &State<NodeStatusCache>,
|
||||
identity: &str,
|
||||
) -> Result<Json<GatewayUptimeResponse>, ErrorResponse> {
|
||||
Ok(Json(_get_gateway_avg_uptime(cache, identity).await?))
|
||||
}
|
||||
|
||||
#[openapi(tag = "status")]
|
||||
|
||||
@@ -26,7 +26,8 @@ const DEFAULT_GATEWAY_PING_INTERVAL: Duration = Duration::from_secs(60);
|
||||
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
|
||||
// bandwidth bridging protocol, we can come back to a smaller timeout value
|
||||
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
||||
const DEFAULT_GATEWAY_CONNECTION_TIMEOUT: Duration = Duration::from_millis(2_500);
|
||||
// This timeout value should be big enough to accommodate an initial bandwidth acquirement
|
||||
const DEFAULT_GATEWAY_CONNECTION_TIMEOUT: Duration = Duration::from_secs(2 * 60);
|
||||
|
||||
const DEFAULT_TEST_ROUTES: usize = 3;
|
||||
const DEFAULT_MINIMUM_TEST_ROUTES: usize = 1;
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.0.11] (2023-02-28)
|
||||
|
||||
- NC - add the option to manually select and use a specific Service Provider ([#2953])
|
||||
|
||||
[#2953]: https://github.com/nymtech/nym/issues/2953
|
||||
|
||||
## [v1.1.10] (2023-02-21)
|
||||
|
||||
- NC - add logs window for troubleshooting ([#2951])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/nym-connect",
|
||||
"version": "1.1.10",
|
||||
"version": "1.1.11",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -113,4 +113,4 @@
|
||||
"webpack-merge": "^5.8.0",
|
||||
"yaml-loader": "^0.8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-connect"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
description = "nym-connect"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -138,6 +138,8 @@ pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: Str
|
||||
register_gateway,
|
||||
Some(chosen_gateway_id),
|
||||
config.get_base(),
|
||||
// TODO: another instance where this setting should probably get used
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-connect",
|
||||
"version": "1.1.10"
|
||||
"version": "1.1.11"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
@@ -6,12 +6,13 @@ import { CustomTitleBar } from './CustomTitleBar';
|
||||
|
||||
export const AppWindowFrame: FCWithChildren = ({ children }) => {
|
||||
const location = useLocation();
|
||||
const { userDefinedGateway, setUserDefinedGateway } = useClientContext();
|
||||
const { userDefinedGateway, setUserDefinedGateway, userDefinedSPAddress, setUserDefinedSPAddress } =
|
||||
useClientContext();
|
||||
|
||||
// defined functions to be used when moving away from pages
|
||||
const onBack = () => {
|
||||
switch (location.pathname) {
|
||||
case '/menu/settings':
|
||||
case '/menu/settings/gateway':
|
||||
return () => {
|
||||
// when the user moves away from the settings page and the gateway is not valid
|
||||
// set isActive to false
|
||||
@@ -19,6 +20,14 @@ export const AppWindowFrame: FCWithChildren = ({ children }) => {
|
||||
setUserDefinedGateway((current) => ({ ...current, isActive: false }));
|
||||
}
|
||||
};
|
||||
case '/menu/settings/service-provider':
|
||||
return () => {
|
||||
// when the user moves away from the settings page and the sp is not valid
|
||||
// set isActive to false
|
||||
if (!userDefinedSPAddress?.address) {
|
||||
setUserDefinedSPAddress((current) => ({ ...current, isActive: false }));
|
||||
}
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@ const ArrowBackIcon = ({ onBack }: { onBack?: () => void }) => {
|
||||
return <CustomButton Icon={ArrowBack} onClick={handleBack} />;
|
||||
};
|
||||
|
||||
const getTitleIcon = (path: string) => {
|
||||
const getTitle = (path: string) => {
|
||||
if (path.includes('settings')) return 'Settings';
|
||||
if (path !== '/') {
|
||||
const title = path.split('/').slice(-1);
|
||||
return (
|
||||
@@ -61,7 +62,7 @@ export const CustomTitleBar = ({ path = '/', onBack }: { path?: string; onBack?:
|
||||
<Box data-tauri-drag-region style={customTitleBarStyles.titlebar}>
|
||||
{/* set width to keep logo centered */}
|
||||
<Box sx={{ width: '40px' }}>{path === '/' ? <MenuIcon /> : <ArrowBackIcon onBack={onBack} />}</Box>
|
||||
{getTitleIcon(path)}
|
||||
{getTitle(path)}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<CustomButton Icon={Minimize} onClick={() => appWindow.minimize()} />
|
||||
<CustomButton Icon={Close} onClick={() => appWindow.close()} />
|
||||
|
||||
@@ -4,13 +4,14 @@ import { invoke } from '@tauri-apps/api';
|
||||
import { Error } from 'src/types/error';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { useEvents } from 'src/hooks/events';
|
||||
import { UserDefinedGateway } from 'src/types/gateway';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { UserDefinedGateway, UserDefinedSPAddress } from 'src/types/service-provider';
|
||||
import { getItemFromStorage, setItemInStorage } from 'src/utils';
|
||||
import { ConnectionStatusKind, GatewayPerformance } from '../types';
|
||||
import { ConnectionStatsItem } from '../components/ConnectionStats';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
|
||||
const FORAGE_KEY = 'nym-connect-user-gateway';
|
||||
const FORAGE_GATEWAY_KEY = 'nym-connect-user-gateway';
|
||||
const FORAGE_SP_KEY = 'nym-connect-user-sp';
|
||||
|
||||
type ModeType = 'light' | 'dark';
|
||||
|
||||
@@ -25,16 +26,19 @@ export type TClientContext = {
|
||||
selectedProvider?: ServiceProvider;
|
||||
showInfoModal: boolean;
|
||||
userDefinedGateway?: UserDefinedGateway;
|
||||
userDefinedSPAddress: UserDefinedSPAddress;
|
||||
serviceProviders?: ServiceProvider[];
|
||||
setMode: (mode: ModeType) => void;
|
||||
clearError: () => void;
|
||||
setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void;
|
||||
setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void;
|
||||
setConnectedSince: (connectedSince: DateTime | undefined) => void;
|
||||
setShowInfoModal: (show: boolean) => void;
|
||||
setRandomSerivceProvider: () => void;
|
||||
setSerivceProvider: () => void;
|
||||
startConnecting: () => Promise<void>;
|
||||
startDisconnecting: () => Promise<void>;
|
||||
setUserDefinedGateway: React.Dispatch<React.SetStateAction<UserDefinedGateway>>;
|
||||
setUserDefinedSPAddress: React.Dispatch<React.SetStateAction<UserDefinedSPAddress>>;
|
||||
};
|
||||
|
||||
export const ClientContext = createContext({} as TClientContext);
|
||||
@@ -50,43 +54,39 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
const [appVersion, setAppVersion] = useState<string>();
|
||||
const [gatewayPerformance, setGatewayPerformance] = useState<GatewayPerformance>('Good');
|
||||
const [showInfoModal, setShowInfoModal] = useState(false);
|
||||
const [userDefinedGateway, setUserDefinedGateway] = useState<UserDefinedGateway>({ isActive: false, gateway: '' });
|
||||
const [userDefinedGateway, setUserDefinedGateway] = useState<UserDefinedGateway>({
|
||||
isActive: false,
|
||||
gateway: undefined,
|
||||
});
|
||||
const [userDefinedSPAddress, setUserDefinedSPAddress] = useState<UserDefinedSPAddress>({
|
||||
isActive: false,
|
||||
address: undefined,
|
||||
});
|
||||
|
||||
const getAppVersion = async () => {
|
||||
const version = await getVersion();
|
||||
return version;
|
||||
};
|
||||
|
||||
const setUserGatewayInStorage = async (gateway: UserDefinedGateway) => {
|
||||
try {
|
||||
await forage.setItem({
|
||||
key: FORAGE_KEY,
|
||||
value: gateway,
|
||||
} as any)();
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
useEffect(() => {
|
||||
setItemInStorage({ key: FORAGE_GATEWAY_KEY, value: userDefinedGateway });
|
||||
}, [userDefinedGateway]);
|
||||
|
||||
const getUserGatewayFromStorage = async (): Promise<UserDefinedGateway | undefined> => {
|
||||
try {
|
||||
const gatewayFromStorage = await forage.getItem({ key: FORAGE_KEY })();
|
||||
return gatewayFromStorage;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
useEffect(() => {
|
||||
setItemInStorage({ key: FORAGE_SP_KEY, value: userDefinedSPAddress });
|
||||
}, [userDefinedSPAddress]);
|
||||
|
||||
const initialiseApp = async () => {
|
||||
const services = await invoke('get_services');
|
||||
const AppVersion = await getAppVersion();
|
||||
const storedUserDefinedGateway = await getUserGatewayFromStorage();
|
||||
const storedUserDefinedGateway = await getItemFromStorage({ key: FORAGE_GATEWAY_KEY });
|
||||
const storedUserDefinedSP = await getItemFromStorage({ key: FORAGE_SP_KEY });
|
||||
|
||||
setAppVersion(AppVersion);
|
||||
setServiceProviders(services as ServiceProvider[]);
|
||||
|
||||
if (storedUserDefinedGateway) setUserDefinedGateway(storedUserDefinedGateway);
|
||||
if (storedUserDefinedSP) setUserDefinedSPAddress(storedUserDefinedSP);
|
||||
};
|
||||
|
||||
useEvents({
|
||||
@@ -125,27 +125,37 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
}, []);
|
||||
|
||||
const shouldUseUserGateway = !!userDefinedGateway.gateway && userDefinedGateway.isActive;
|
||||
const shouldUseUserSP = !!userDefinedSPAddress.address && userDefinedSPAddress.isActive;
|
||||
|
||||
const setServiceProvider = async (newServiceProvider: ServiceProvider) => {
|
||||
await invoke('set_gateway', {
|
||||
gateway: newServiceProvider.gateway,
|
||||
gateway: shouldUseUserGateway ? userDefinedGateway.gateway : newServiceProvider.gateway,
|
||||
});
|
||||
await invoke('set_service_provider', {
|
||||
serviceProvider: shouldUseUserSP ? userDefinedSPAddress.address : newServiceProvider.address,
|
||||
});
|
||||
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
|
||||
};
|
||||
|
||||
const getRandomSPFromList = (services: ServiceProvider[]) => {
|
||||
const randomSelection = services[Math.floor(Math.random() * services.length)];
|
||||
|
||||
if (shouldUseUserGateway) return { ...randomSelection, gateway: userDefinedGateway.gateway } as ServiceProvider;
|
||||
return randomSelection;
|
||||
};
|
||||
|
||||
const setRandomSerivceProvider = async () => {
|
||||
const buildServiceProvider = async (serviceProvider: ServiceProvider) => {
|
||||
const sp = { ...serviceProvider };
|
||||
|
||||
if (shouldUseUserGateway) sp.gateway = userDefinedGateway.gateway as string;
|
||||
if (shouldUseUserSP) sp.address = userDefinedSPAddress.address as string;
|
||||
|
||||
return sp;
|
||||
};
|
||||
|
||||
const setSerivceProvider = async () => {
|
||||
if (serviceProviders) {
|
||||
const randomServiceProvider = getRandomSPFromList(serviceProviders);
|
||||
await setServiceProvider(randomServiceProvider);
|
||||
await setUserGatewayInStorage(userDefinedGateway);
|
||||
setSelectedProvider(randomServiceProvider);
|
||||
const withUserDefinitions = await buildServiceProvider(randomServiceProvider);
|
||||
await setServiceProvider(withUserDefinitions);
|
||||
setSelectedProvider(withUserDefinitions);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
@@ -165,21 +175,25 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
showInfoModal,
|
||||
setConnectionStats,
|
||||
selectedProvider,
|
||||
serviceProviders,
|
||||
connectedSince,
|
||||
setConnectedSince,
|
||||
setRandomSerivceProvider,
|
||||
setSerivceProvider,
|
||||
startConnecting,
|
||||
startDisconnecting,
|
||||
gatewayPerformance,
|
||||
setShowInfoModal,
|
||||
userDefinedSPAddress,
|
||||
userDefinedGateway,
|
||||
setUserDefinedGateway,
|
||||
setUserDefinedSPAddress,
|
||||
}),
|
||||
[
|
||||
mode,
|
||||
appVersion,
|
||||
error,
|
||||
showInfoModal,
|
||||
serviceProviders,
|
||||
connectedSince,
|
||||
connectionStatus,
|
||||
connectionStats,
|
||||
@@ -187,6 +201,7 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => {
|
||||
gatewayPerformance,
|
||||
selectedProvider,
|
||||
userDefinedGateway,
|
||||
userDefinedSPAddress,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const mockValues: TClientContext = {
|
||||
gatewayPerformance: 'Good',
|
||||
showInfoModal: false,
|
||||
userDefinedGateway: { isActive: false, gateway: '' },
|
||||
userDefinedSPAddress: { isActive: false, address: '' },
|
||||
setShowInfoModal: () => {},
|
||||
setMode: () => {},
|
||||
clearError: () => {},
|
||||
@@ -18,8 +19,9 @@ const mockValues: TClientContext = {
|
||||
setConnectionStatus: () => {},
|
||||
startConnecting: async () => {},
|
||||
startDisconnecting: async () => {},
|
||||
setRandomSerivceProvider: () => {},
|
||||
setSerivceProvider: () => {},
|
||||
setUserDefinedGateway: () => {},
|
||||
setUserDefinedSPAddress: () => {},
|
||||
};
|
||||
|
||||
export const MockProvider: FCWithChildren<{
|
||||
|
||||
@@ -28,7 +28,7 @@ export const ConnectionPage = () => {
|
||||
// eslint-disable-next-line default-case
|
||||
switch (currentStatus) {
|
||||
case 'disconnected':
|
||||
await context.setRandomSerivceProvider();
|
||||
await context.setSerivceProvider();
|
||||
await context.startConnecting();
|
||||
context.setConnectedSince(DateTime.now());
|
||||
context.setShowInfoModal(true);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ConnectionStatusKind } from 'src/types';
|
||||
import { AppVersion } from 'src/components/AppVersion';
|
||||
|
||||
export const GatewaySettings = () => {
|
||||
const { userDefinedGateway, setUserDefinedGateway } = useClientContext();
|
||||
const { userDefinedGateway, setUserDefinedGateway, connectionStatus } = useClientContext();
|
||||
const [gatewayKey, setGatewayKey] = useState<string | undefined>(userDefinedGateway?.gateway);
|
||||
|
||||
const handleIsValidGatewayKey = (isValid: boolean) => {
|
||||
@@ -23,8 +23,6 @@ export const GatewaySettings = () => {
|
||||
setUserDefinedGateway((current) => ({ ...current, isActive: e.target.checked }));
|
||||
};
|
||||
|
||||
const { connectionStatus } = useClientContext();
|
||||
|
||||
return (
|
||||
<Box height="100%">
|
||||
<Stack justifyContent="space-between" height="100%">
|
||||
@@ -60,6 +58,7 @@ export const GatewaySettings = () => {
|
||||
onValidate={handleIsValidGatewayKey}
|
||||
sx={{ mt: 1 }}
|
||||
disabled={connectionStatus === 'connected' || !userDefinedGateway?.isActive}
|
||||
autoFocus
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import {
|
||||
Autocomplete,
|
||||
Box,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormHelperText,
|
||||
Stack,
|
||||
Switch,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { AppVersion } from 'src/components/AppVersion';
|
||||
import { ConnectionStatusKind } from 'src/types';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
|
||||
export const ServiceProviderSettings = () => {
|
||||
const { connectionStatus, serviceProviders, userDefinedSPAddress, setUserDefinedSPAddress } = useClientContext();
|
||||
|
||||
const toggleOnOff = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setUserDefinedSPAddress((current) => ({ ...current, isActive: e.target.checked }));
|
||||
};
|
||||
|
||||
const handleSelectFromList = (value: string | null) => {
|
||||
setUserDefinedSPAddress((current) => ({ ...current, address: value ?? undefined }));
|
||||
};
|
||||
|
||||
const getSPDescription = (spAddress: string) => {
|
||||
const match = serviceProviders?.find((sp) => sp.address === spAddress);
|
||||
|
||||
if (match) return match.description;
|
||||
|
||||
return 'The service provider specified is not in our known list.';
|
||||
};
|
||||
|
||||
const validateInput = (value: string) => {
|
||||
setUserDefinedSPAddress((current) => ({ ...current, address: !value.length ? undefined : value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box height="100%">
|
||||
<Stack justifyContent="space-between" height="100%">
|
||||
<Box>
|
||||
<Typography fontWeight="bold" variant="body2" mb={1}>
|
||||
Select your Service Provider
|
||||
</Typography>
|
||||
<Typography color="grey.300" variant="body2" mb={2}>
|
||||
Pick a service provider from the list or enter your own
|
||||
</Typography>
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={userDefinedSPAddress.isActive}
|
||||
onChange={toggleOnOff}
|
||||
disabled={connectionStatus === ConnectionStatusKind.connected}
|
||||
size="small"
|
||||
sx={{ ml: 1 }}
|
||||
/>
|
||||
}
|
||||
label={userDefinedSPAddress.isActive ? 'On' : 'Off'}
|
||||
/>
|
||||
{connectionStatus === ConnectionStatusKind.connected && (
|
||||
<FormHelperText sx={{ m: 0, my: 1 }}>This setting is disabled during an active connection</FormHelperText>
|
||||
)}
|
||||
{userDefinedSPAddress.isActive && serviceProviders && (
|
||||
<Autocomplete
|
||||
clearOnEscape
|
||||
disabled={connectionStatus === 'connected'}
|
||||
sx={{ mt: 1 }}
|
||||
options={serviceProviders.map((sp) => sp.address)}
|
||||
freeSolo
|
||||
value={userDefinedSPAddress.address || ''}
|
||||
onChange={(e, value) => handleSelectFromList(value)}
|
||||
size="small"
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
autoFocus
|
||||
{...params}
|
||||
placeholder="Service provider"
|
||||
onChange={(e) => validateInput(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
ListboxProps={{ style: { background: 'unset', fontSize: '14px' } }}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
{userDefinedSPAddress.address && userDefinedSPAddress.isActive && (
|
||||
<Typography sx={{ mt: 1 }}>{getSPDescription(userDefinedSPAddress.address)}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<AppVersion />
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,10 @@ import { Link, List, ListItem, ListItemButton, ListItemText, Stack } from '@mui/
|
||||
import { AppVersion } from 'src/components/AppVersion';
|
||||
import { toggleLogViewer } from 'src/utils';
|
||||
|
||||
const menuSchema = [{ title: 'Select your gateway', path: 'gateway' }];
|
||||
const menuSchema = [
|
||||
{ title: 'Select your gateway', path: 'gateway' },
|
||||
{ title: 'Select a service provider', path: 'service-provider' },
|
||||
];
|
||||
|
||||
export const SettingsMenu = () => (
|
||||
<Stack justifyContent="space-between" height="100%">
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CompatibleApps } from 'src/pages/menu/Apps';
|
||||
import { HelpGuide } from 'src/pages/menu/Guide';
|
||||
import { SettingsMenu } from 'src/pages/menu/settings';
|
||||
import { GatewaySettings } from 'src/pages/menu/settings/GatewaySettings';
|
||||
import { ServiceProviderSettings } from 'src/pages/menu/settings/ServiceProviderSettings';
|
||||
|
||||
export const AppRoutes = () => (
|
||||
<Routes>
|
||||
@@ -17,6 +18,7 @@ export const AppRoutes = () => (
|
||||
<Route path="settings">
|
||||
<Route index element={<SettingsMenu />} />
|
||||
<Route path="gateway" element={<GatewaySettings />} />
|
||||
<Route path="service-provider" element={<ServiceProviderSettings />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface UserDefinedGateway {
|
||||
isActive: boolean;
|
||||
gateway?: string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface UserDefinedGateway {
|
||||
isActive: boolean;
|
||||
gateway?: string;
|
||||
}
|
||||
|
||||
export interface UserDefinedSPAddress {
|
||||
isActive: boolean;
|
||||
address?: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface StorageKeyValue<T> {
|
||||
key: string;
|
||||
value: T;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { EventName, listen, UnlistenFn, EventCallback } from '@tauri-apps/api/event';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { StorageKeyValue } from 'src/types/storage';
|
||||
|
||||
export const useTauriEvents = <T>(event: EventName, handler: EventCallback<T>) => {
|
||||
const unlisten = useRef<UnlistenFn>();
|
||||
@@ -22,3 +24,25 @@ export const useTauriEvents = <T>(event: EventName, handler: EventCallback<T>) =
|
||||
export const toggleLogViewer = async () => {
|
||||
await invoke('help_log_toggle_window');
|
||||
};
|
||||
|
||||
export async function setItemInStorage<T>({ key, value }: StorageKeyValue<T>) {
|
||||
try {
|
||||
await forage.setItem({
|
||||
key,
|
||||
value,
|
||||
} as any)();
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const getItemFromStorage = async ({ key }: Pick<StorageKeyValue<undefined>, 'key'>) => {
|
||||
try {
|
||||
const gatewayFromStorage = await forage.getItem({ key })();
|
||||
return gatewayFromStorage;
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
@@ -118,6 +118,7 @@ pub async fn init_socks5_config(
|
||||
&mut key_manager,
|
||||
nym_api_endpoints,
|
||||
Some(chosen_gateway_id),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ strum = { version = "0.23", features = ["derive"] }
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
cosmwasm-std = "1.0.0-beta8"
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = "0.8.0"
|
||||
|
||||
nym-config = { path = "../../common/config" }
|
||||
nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
|
||||
@@ -52,7 +52,7 @@ base64 = "0.13"
|
||||
zeroize = "1.4.3"
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = "0.8.0"
|
||||
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"nyxd-client",
|
||||
|
||||
@@ -259,6 +259,8 @@ where
|
||||
&mut self.key_manager,
|
||||
self.config.nym_api_endpoints.clone(),
|
||||
user_chosen_gateway,
|
||||
// TODO: this should probably be configurable with the config
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version = "1.65"
|
||||
|
||||
@@ -24,6 +24,11 @@ pub(crate) struct Init {
|
||||
#[clap(long)]
|
||||
gateway: Option<identity::PublicKey>,
|
||||
|
||||
/// Specifies whether the new gateway should be determined based by latency as opposed to being chosen
|
||||
/// uniformly.
|
||||
#[clap(long, conflicts_with = "gateway")]
|
||||
latency_based_selection: bool,
|
||||
|
||||
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
|
||||
/// potentially causing loss of access.
|
||||
#[clap(long)]
|
||||
@@ -115,6 +120,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), NetworkRequesterError> {
|
||||
register_gateway,
|
||||
user_chosen_gateway_id,
|
||||
config.get_base(),
|
||||
args.latency_based_selection,
|
||||
)
|
||||
.await
|
||||
.map_err(|source| {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-cli"
|
||||
version = "1.1.10"
|
||||
version = "1.1.11"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export const IdentityKeyFormField: FCWithChildren<{
|
||||
size?: 'small' | 'medium';
|
||||
sx?: SxProps;
|
||||
disabled?: boolean;
|
||||
autoFocus?: boolean;
|
||||
}> = ({
|
||||
required,
|
||||
fullWidth,
|
||||
@@ -37,6 +38,7 @@ export const IdentityKeyFormField: FCWithChildren<{
|
||||
showTickOnValid = true,
|
||||
size,
|
||||
disabled,
|
||||
autoFocus,
|
||||
}) => {
|
||||
const [value, setValue] = React.useState<string | undefined>(initialValue);
|
||||
const [validationError, setValidationError] = React.useState<string | undefined>();
|
||||
@@ -106,6 +108,7 @@ export const IdentityKeyFormField: FCWithChildren<{
|
||||
InputLabelProps={{ shrink: true }}
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user