Compare commits

..

17 Commits

Author SHA1 Message Date
farbanas 36fb0eba29 Update versions as part of release/v1.1.11 2023-02-28 13:53:14 +01:00
farbanas 1b37e85418 Update changelogs as part of release/v1.1.11 2023-02-28 13:43:32 +01:00
Bogdan-Ștefan Neacşu 6a93497c8f Extend public key submission in case no dealer registered (#3106) 2023-02-28 12:58:01 +01:00
Tommy Verrall ffbd76539a Merge pull request #3108 from nymtech/feature/nym-connect-select-sp
Feature/nym connect select sp
2023-02-27 17:48:31 +02:00
fmtabbara bdcc19e86a PR update 2023-02-27 15:33:54 +00:00
fmtabbara 7929bac685 turn off user defined SP address is input it empty 2023-02-27 14:20:16 +00:00
Jon Häggblad f590aad42c nym-api: uptime rework (#3053)
* nym-api: cache updates as node performance

* nym-api: update get mixnode avg_uptime endpoint

* nym-api: mixnode report to use cached data

* nym-api: annotate gateway bond with node performance

* nym-api: gateway report to use cached data

* wip

* Add get_gateway_avg_uptime

* Add comment

* update NR gateways to include node_performance on frontend

* use node_performance values on frontend

* fixup select gateway from list

* fix up lint errors

---------

Co-authored-by: fmtabbara <fmtabbara@hotmail.co.uk>
2023-02-27 12:40:00 +01:00
fmtabbara ec23f3dcb7 disable input when connected
fix lint errors
2023-02-27 11:28:38 +00:00
fmtabbara 9644eb4329 pick service provider 2023-02-27 11:28:38 +00:00
fmtabbara 7a4c6e4ed4 storage type
update types
2023-02-27 11:28:36 +00:00
fmtabbara d5ad504104 reuseable storage functions 2023-02-27 11:28:36 +00:00
Tommy Verrall d684f6d7ae Merge pull request #3099 from nymtech/feature/nym-connect-select-sp
NymConnect Select a service provider
2023-02-27 12:26:00 +02:00
Bogdan-Ștefan Neacşu f0d9703587 Feature/fix db name collision (#3103)
* Fix db naming collision

* Increase gateway timeout to accommodate bandwidth spending time
2023-02-24 16:52:56 +02:00
fmtabbara c08efef8ed add autofocus to IdentityKeyComponent 2023-02-23 22:58:59 +00:00
fmtabbara 6d29774744 style tweaks 2023-02-23 22:57:21 +00:00
fmtabbara af6bab7703 fix lint error 2023-02-23 17:40:39 +00:00
fmtabbara 7033f92d82 add UI for picking service provider 2023-02-23 17:33:03 +00:00
50 changed files with 580 additions and 252 deletions
+20
View File
@@ -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])
+1 -1
View File
@@ -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"
+2 -2
View File
@@ -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)
}
}
+5 -2
View File
@@ -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};
@@ -73,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 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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,6 @@ use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRES
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::STATE;
use crate::verification_key_shares::storage::vk_shares;
use coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData};
use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage};
@@ -159,16 +158,9 @@ pub(crate) fn try_surpassed_threshold(
check_epoch_state(deps.storage, EpochState::InProgress)?;
let threshold = THRESHOLD.load(deps.storage)?;
let dealers = vk_shares()
.range(deps.storage, None, None, Order::Ascending)
.flatten()
.filter_map(|(_, share)| {
if share.verified {
Some(share.owner)
} else {
None
}
});
let dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.flatten();
if dealers_still_active(&deps.as_ref(), dealers)? < threshold as usize {
reset_epoch_state(deps.storage)?;
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
@@ -188,7 +180,7 @@ pub(crate) fn try_surpassed_threshold(
pub(crate) mod tests {
use super::*;
use crate::error::ContractError::EarlyEpochStateAdvancement;
use crate::support::tests::fixtures::{dealer_details_fixture, vk_share_fixture};
use crate::support::tests::fixtures::dealer_details_fixture;
use crate::support::tests::helpers::{init_contract, GROUP_MEMBERS};
use coconut_dkg_common::types::{
ContractSafeBytes, DealerDetails, EpochState, TimeConfiguration,
@@ -686,12 +678,6 @@ pub(crate) mod tests {
.save(deps.as_mut().storage, &details.address, details)
.unwrap();
}
let all_shares: [_; 3] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, share.epoch_id), share)
.unwrap();
}
for times in [
time_configuration.public_key_submission_time_secs,
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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"
]
}
}
}
+2 -3
View File
@@ -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,
}));
};
+4 -3
View File
@@ -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)}
/>
);
};
+3 -3
View File
@@ -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]);
+8 -11
View File
@@ -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',
+8 -2
View File
@@ -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
View File
@@ -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>",
+1 -1
View File
@@ -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
View File
@@ -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>",
+40 -1
View File
@@ -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)]
+49 -58
View File
@@ -99,66 +99,57 @@ impl<R: RngCore + Clone> DkgController<R> {
return;
}
if let Err(err) = self.state.is_consistent(epoch.state).await {
debug!("Epoch state is corrupted - {err}. Awaiting for a DKG restart.");
} else {
let ret = match epoch.state {
EpochState::PublicKeySubmission { resharing } => {
public_key_submission(&self.dkg_client, &mut self.state, resharing)
.await
}
EpochState::DealingExchange { resharing } => {
dealing_exchange(
&self.dkg_client,
&mut self.state,
self.rng.clone(),
resharing,
)
error!("Epoch state is corrupted - {err}, the process should be terminated");
return;
}
let ret = match epoch.state {
EpochState::PublicKeySubmission { resharing } => {
public_key_submission(&self.dkg_client, &mut self.state, resharing).await
}
EpochState::DealingExchange { resharing } => {
dealing_exchange(
&self.dkg_client,
&mut self.state,
self.rng.clone(),
resharing,
)
.await
}
EpochState::VerificationKeySubmission { resharing } => {
let keypair_path = nym_pemstore::KeyPairPath::new(
self.secret_key_path.clone(),
self.verification_key_path.clone(),
);
verification_key_submission(
&self.dkg_client,
&mut self.state,
&keypair_path,
resharing,
)
.await
}
EpochState::VerificationKeyValidation { resharing } => {
verification_key_validation(&self.dkg_client, &mut self.state, resharing)
.await
}
EpochState::VerificationKeySubmission { resharing } => {
let keypair_path = nym_pemstore::KeyPairPath::new(
self.secret_key_path.clone(),
self.verification_key_path.clone(),
);
verification_key_submission(
&self.dkg_client,
&mut self.state,
&keypair_path,
resharing,
)
}
EpochState::VerificationKeyFinalization { resharing } => {
verification_key_finalization(&self.dkg_client, &mut self.state, resharing)
.await
}
EpochState::VerificationKeyValidation { resharing } => {
verification_key_validation(
&self.dkg_client,
&mut self.state,
resharing,
)
.await
}
EpochState::VerificationKeyFinalization { resharing } => {
verification_key_finalization(
&self.dkg_client,
&mut self.state,
resharing,
)
.await
}
// Just wait, in case we need to redo dkg at some point
EpochState::InProgress => {
self.state.set_was_in_progress();
Ok(())
}
};
if let Err(err) = ret {
warn!("Could not handle this iteration for the epoch state: {err}");
} else if epoch.state != EpochState::InProgress {
let persistent_state = PersistentState::from(&self.state);
if let Err(err) =
persistent_state.save_to_file(self.state.persistent_state_path())
{
warn!("Could not backup the state for this iteration: {err}");
}
}
// Just wait, in case we need to redo dkg at some point
EpochState::InProgress => {
self.state.set_was_in_progress();
Ok(())
}
};
if let Err(err) = ret {
warn!("Could not handle this iteration for the epoch state: {err}");
} else if epoch.state != EpochState::InProgress {
let persistent_state = PersistentState::from(&self.state);
if let Err(err) =
persistent_state.save_to_file(self.state.persistent_state_path())
{
warn!("Could not backup the state for this iteration: {err}");
}
}
if let Ok(current_timestamp) =
+1 -1
View File
@@ -133,7 +133,7 @@ impl ConsistentState for State {
fn proposal_id_value(&self) -> Result<u64, CoconutError> {
self.proposal_id.ok_or(CoconutError::UnrecoverableState {
reason: String::from("Proposal id should have been set"),
reason: String::from("Proposal id should have benn set"),
})
}
}
+1 -1
View File
@@ -91,7 +91,7 @@ pub enum CoconutError {
#[error("Failed to recover assigned node index: {reason}")]
NodeIndexRecoveryError { reason: String },
#[error("Unrecoverable state: {reason}")]
#[error("Unrecoverable state: {reason}. Process should be restarted")]
UnrecoverableState { reason: String },
#[error("DKG has not finished yet in order to derive the coconut key")]
+27 -4
View File
@@ -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
+115 -37
View File
@@ -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,
))
}
+1
View File
@@ -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,
+21 -1
View File
@@ -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,
+31 -37
View File
@@ -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")]
+2 -1
View File
@@ -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;
+6
View File
@@ -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])
+2 -2
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-connect"
version = "1.1.10"
version = "1.1.11"
description = "nym-connect"
authors = ["Nym Technologies SA"]
license = ""
@@ -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()} />
+50 -35
View File
@@ -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%">
+2
View File
@@ -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>
-4
View File
@@ -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;
}
+4
View File
@@ -0,0 +1,4 @@
export interface StorageKeyValue<T> {
key: string;
value: T;
}
+24
View File
@@ -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;
};
@@ -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"
@@ -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 -1
View File
@@ -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}
/>
);
};