Compare commits

..

44 Commits

Author SHA1 Message Date
Jon Häggblad 6d16c4becb Fix rust 2022-09-21 14:56:36 +02:00
fmtabbara 9361e9bec2 merge develop 2022-09-21 13:18:46 +01:00
fmtabbara 6c5803e93b remove unused route 2022-09-21 12:32:26 +01:00
fmtabbara 527d1fb93c update validation for rewards playground 2022-09-21 09:49:10 +01:00
fmtabbara 4ed3d2bfc2 run make file 2022-09-20 13:15:55 +01:00
fmtabbara b5c1a1b076 add initial rewards calculation 2022-09-20 13:15:39 +01:00
Jon Häggblad c6a7f4e0ef validator-api-client and wallet: compute mixnode reward estimation 2022-09-20 12:02:56 +02:00
fmtabbara b9dddd32ca update calc button style 2022-09-20 09:29:32 +01:00
fmtabbara 19173c9168 apy playground ui 2022-09-16 12:33:56 +01:00
fmtabbara b557b8d915 update cargo lock 2022-09-16 11:02:31 +01:00
fmtabbara b676a899b9 Merge branch 'develop' into feature/test-my-node 2022-09-15 11:03:32 +01:00
fmtabbara fcfabb7ce1 add print to pdf package 2022-09-12 10:37:09 +01:00
fmtabbara bb0cb06066 add print to pdf functionality for node test results 2022-09-12 10:36:45 +01:00
fmtabbara 1fdbb56895 add top margin to app bar 2022-09-09 12:50:54 +01:00
fmtabbara 7b271a3993 remove placeholder nav item 2022-09-09 12:13:39 +01:00
fmtabbara b8bc78cdb9 merge node settings branch 2022-09-09 12:09:25 +01:00
fmtabbara e9133c4a75 add stories for test my node 2022-09-08 16:19:30 +01:00
fmtabbara cd5332f26f adjust layout for overiew page 2022-09-08 12:31:02 +01:00
fmtabbara eb84fe7da3 use svg for node path 2022-09-08 12:06:19 +01:00
fmtabbara a95ba05e78 initial ui for test my node 2022-09-07 22:43:04 +01:00
Gala 99ceabb0b0 using the wallet Tab component 2022-09-06 13:18:43 +02:00
Gala 25df7bcd4d Merge branch 'develop' into 348-bonding-settings 2022-09-02 09:39:21 +02:00
Gala 1cdca7bec3 unbond modal verification step 2022-09-01 16:57:48 +02:00
Gala c809c7733d logic refactor 2022-09-01 15:06:23 +02:00
Gala 7b53003edb wip verification modal 2022-08-31 18:49:47 +02:00
Gala 831d9d2bf8 update alert text 2022-08-31 18:20:40 +02:00
Gala cb7c51ba12 remove node settings modal trigger 2022-08-31 17:37:00 +02:00
Gala 0310f0a8a9 some refactor 2022-08-31 17:23:53 +02:00
Gala bb79d08f6d dynamic values and remove hard coded code 2022-08-31 16:58:53 +02:00
Gala 414c86b500 fix button width 2022-08-31 12:13:40 +02:00
Gala 4304ffcf3c adding notification span 2022-08-31 11:44:23 +02:00
Gala 309b23e18a adding confirmation modals 2022-08-31 10:55:13 +02:00
Gala 52703583f0 adding validation to parameters settings 2022-08-31 10:10:37 +02:00
Gala 6473ef13c6 validate info fields 2022-08-30 18:51:39 +02:00
Gala 9a45f15ba4 wip 2022-08-30 16:49:07 +02:00
Gala 746795b7ce mook bonded node 2022-08-30 12:49:50 +02:00
Gala 8b81247044 Merge branch 'develop' into 348-bonding-settings 2022-08-30 11:08:19 +02:00
Gala c6cd787950 adding unbonding modal 2022-08-19 18:06:04 +02:00
Gala f9ab20b10f more styling in node
settings page
2022-08-18 17:27:28 +02:00
Gala acffd496ed nav styles 2022-08-18 17:07:59 +02:00
Gala 466ac1a1e0 settings general page 2022-08-18 16:39:05 +02:00
Gala d53adcd17e nodesettings page and logic to browse 2022-08-17 18:55:58 +02:00
Gala 36e82e831f Merge branch 'develop' into 348-bonding-settings 2022-08-17 13:55:06 +02:00
Gala cbe0115f01 wip 2022-08-17 11:10:10 +02:00
218 changed files with 3838 additions and 5087 deletions
-11
View File
@@ -2,12 +2,6 @@ name: Publish Nym binaries
on:
workflow_dispatch:
inputs:
add_tokio_unstable:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
release:
types: [created]
@@ -31,11 +25,6 @@ jobs:
with:
script: |
core.setFailed('Release tag did not start with nym-binaries-...')
- name: Sets env vars for tokio if set in manual dispatch inputs
run: |
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
-5
View File
@@ -11,10 +11,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611))
- common/ledger: new library for communicating with a Ledger device ([#1640])
### Fixed
- validator-api, mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
### Changed
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
@@ -27,7 +23,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1585]: https://github.com/nymtech/nym/pull/1585
[#1591]: https://github.com/nymtech/nym/pull/1591
[#1640]: https://github.com/nymtech/nym/pull/1640
[#1645]: https://github.com/nymtech/nym/pull/1645
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
Generated
+1 -40
View File
@@ -681,15 +681,6 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "completions"
version = "0.1.0"
dependencies = [
"clap 3.2.8",
"clap_complete",
"clap_complete_fig",
]
[[package]]
name = "config"
version = "0.1.0"
@@ -928,7 +919,6 @@ dependencies = [
"cfg-if 0.1.10",
"clap 3.2.8",
"coconut-interface",
"completions",
"config",
"credential-storage",
"credentials",
@@ -1669,7 +1659,6 @@ dependencies = [
"isocountry",
"itertools",
"log",
"maxminddb",
"mixnet-contract-common",
"network-defaults",
"okapi",
@@ -2643,15 +2632,6 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c"
[[package]]
name = "ipnetwork"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4088d739b183546b239688ddbc79891831df421773df95e236daf7867866d355"
dependencies = [
"serde",
]
[[package]]
name = "ipnetwork"
version = "0.20.0"
@@ -2925,18 +2905,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "maxminddb"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe2ba61113f9f7a9f0e87c519682d39c43a6f3f79c2cc42c3ba3dda83b1fa334"
dependencies = [
"ipnetwork 0.18.0",
"log",
"memchr",
"serde",
]
[[package]]
name = "maybe-uninit"
version = "2.0.0"
@@ -3268,7 +3236,6 @@ dependencies = [
"clap 3.2.8",
"client-core",
"coconut-interface",
"completions",
"config",
"credential-storage",
"credentials",
@@ -3308,7 +3275,6 @@ dependencies = [
"clap 3.2.8",
"coconut-interface",
"colored",
"completions",
"config",
"credentials",
"crypto",
@@ -3351,7 +3317,6 @@ dependencies = [
"bs58",
"clap 3.2.8",
"colored",
"completions",
"config",
"crypto",
"cupid",
@@ -3390,10 +3355,9 @@ version = "1.0.2"
dependencies = [
"async-trait",
"clap 3.2.8",
"completions",
"dirs",
"futures",
"ipnetwork 0.20.0",
"ipnetwork",
"log",
"network-defaults",
"nymsphinx",
@@ -3436,7 +3400,6 @@ dependencies = [
"clap 3.2.8",
"client-core",
"coconut-interface",
"completions",
"config",
"credential-storage",
"credentials",
@@ -3557,9 +3520,7 @@ dependencies = [
"config",
"cosmrs",
"cosmwasm-std",
"hex-literal",
"mixnet-contract-common",
"network-defaults",
"nym-types",
"serde",
"serde_json",
-1
View File
@@ -63,7 +63,6 @@ members = [
"common/topology",
"common/types",
"common/wasm-utils",
"common/completions",
"explorer-api",
"gateway",
"gateway/gateway-requests",
+1 -2
View File
@@ -9,7 +9,7 @@ edition = "2021"
async-trait = "0.1.52"
bip39 = "1.0.1"
cfg-if = "0.1"
clap = { version = "3.2", features = ["cargo", "derive"] }
clap = { version = "3.0.10", features = ["cargo", "derive"] }
pickledb = "0.4.1"
rand = "0.7.3"
serde = { version = "1.0", features = ["derive"] }
@@ -19,7 +19,6 @@ tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal", "m
coconut-interface = { path = "../../common/coconut-interface" }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
-7
View File
@@ -3,7 +3,6 @@
use async_trait::async_trait;
use clap::{Args, Subcommand};
use completions::ArgShell;
use pickledb::PickleDb;
use rand::rngs::OsRng;
use std::str::FromStr;
@@ -29,12 +28,6 @@ pub(crate) enum Commands {
ListDeposits(ListDeposits),
/// Get a credential for a given deposit
GetCredential(GetCredential),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
#[async_trait]
-6
View File
@@ -12,8 +12,6 @@ cfg_if::cfg_if! {
use commands::{Commands, Execute};
use error::Result;
use network_defaults::setup_env;
use clap::CommandFactory;
use completions::fig_generate;
use clap::Parser;
use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
@@ -54,14 +52,10 @@ cfg_if::cfg_if! {
),
};
let bin_name = "nym-credential-client";
match &args.command {
Commands::Deposit(m) => m.execute(&mut db, shared_storage).await?,
Commands::ListDeposits(m) => m.execute(&mut db, shared_storage).await?,
Commands::GetCredential(m) => m.execute(&mut db, shared_storage).await?,
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name)
}
Ok(())
+1 -2
View File
@@ -20,7 +20,7 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it
# and the single instance of abortable we have should really be refactored anyway
url = "2.2"
clap = { version = "3.2", features = ["cargo", "derive"] }
clap = { version = "3.2.8", features = ["cargo", "derive"] }
dirs = "4.0"
log = "0.4" # self explanatory
pretty_env_logger = "0.4" # for formatting log messages
@@ -34,7 +34,6 @@ tokio-tungstenite = "0.14" # websocket
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
-12
View File
@@ -2,9 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
pub(crate) mod init;
pub(crate) mod run;
@@ -64,12 +62,6 @@ pub(crate) enum Commands {
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -84,14 +76,10 @@ pub(crate) struct OverrideConfig {
}
pub(crate) async fn execute(args: &Cli) {
let bin_name = "nym-native-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
}
+1 -2
View File
@@ -11,7 +11,7 @@ name = "nym_socks5"
path = "src/lib.rs"
[dependencies]
clap = { version = "3.2", features = ["cargo", "derive"] }
clap = { version = "3.2.8", features = ["cargo", "derive"] }
dirs = "4.0"
futures = "0.3"
log = "0.4"
@@ -27,7 +27,6 @@ url = "2.2"
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
-12
View File
@@ -2,9 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
use config::parse_validators;
pub mod init;
@@ -65,12 +63,6 @@ pub(crate) enum Commands {
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -84,14 +76,10 @@ pub(crate) struct OverrideConfig {
}
pub(crate) async fn execute(args: &Cli) {
let bin_name = "nym-socks5-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
}
+2 -2
View File
@@ -34,6 +34,6 @@
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"@nymproject/nym-client-wasm": "1.0.0"
"@nymproject/nym-client-wasm": "file:../pkg"
}
}
}
@@ -11,9 +11,11 @@ use validator_api_requests::coconut::{
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
DeprecatedRewardEstimationResponse, DeprecatedUptimeResponse, GatewayCoreStatusResponse,
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
};
use validator_api_requests::Deprecated;
#[cfg(feature = "nymd-client")]
use crate::nymd::traits::MixnetQueryClient;
@@ -704,4 +706,69 @@ impl ApiClient {
.verify_bandwidth_credential(request_body)
.await?)
}
// =================================================
// DEPRECATED ROUTES
// TO REMOVE ONCE OTHER PARTS OF THE SYSTEM MIGRATED
// =================================================
pub async fn deprecated_get_mixnode_core_status_count_by_identity(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<Deprecated<MixnodeCoreStatusResponse>, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_core_status_count_by_identity(identity, since)
.await?)
}
pub async fn deprecated_get_mixnode_status_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<Deprecated<MixnodeStatusResponse>, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_status_by_identity(identity)
.await?)
}
pub async fn deprecated_get_mixnode_reward_estimation_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<DeprecatedRewardEstimationResponse, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_reward_estimation_by_identity(identity)
.await?)
}
pub async fn deprecated_get_mixnode_stake_saturation_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<Deprecated<StakeSaturationResponse>, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_stake_saturation_by_identity(identity)
.await?)
}
pub async fn deprecated_get_mixnode_avg_uptime_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<DeprecatedUptimeResponse, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_avg_uptime_by_identity(identity)
.await?)
}
pub async fn deprecated_get_mixnode_avg_uptimes_by_identity(
&self,
) -> Result<Vec<DeprecatedUptimeResponse>, ValidatorClientError> {
Ok(self
.validator_api
.deprecated_get_mixnode_avg_uptimes_by_identity()
.await?)
}
}
@@ -1,12 +1,12 @@
use crate::nymd::error::NymdError;
use crate::nymd::{Config as ClientConfig, NymdClient, QueryNymdClient};
use crate::ApiClient;
use network_defaults::all::Network;
use crate::nymd::traits::MixnetQueryClient;
use colored::Colorize;
use core::fmt;
use itertools::Itertools;
use network_defaults::NymNetworkDetails;
use std::collections::HashMap;
use std::hash::BuildHasher;
use std::time::Duration;
@@ -18,12 +18,12 @@ const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2;
// Run connection tests for all specified nymd and api urls. These are all run concurrently.
pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
mixnet_contract_address: HashMap<NymNetworkDetails, cosmrs::AccountId, H>,
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, cosmrs::AccountId, H>,
) -> (
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
) {
// Setup all the clients for the connection tests
let connection_test_clients =
@@ -46,16 +46,16 @@ pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
}
fn setup_connection_tests<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
mixnet_contract_address: HashMap<NymNetworkDetails, cosmrs::AccountId, H>,
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, cosmrs::AccountId, H>,
) -> impl Iterator<Item = ClientForConnectionTest> {
let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| {
let address = mixnet_contract_address
.get(&network)
.expect("No configured contract address")
.clone();
let config = ClientConfig::try_from_nym_network_details(&network)
let config = ClientConfig::try_from_nym_network_details(&network.details())
.expect("failed to create valid nymd client config");
if let Ok(mut client) = NymdClient::<QueryNymdClient>::connect(config, url.as_str()) {
@@ -81,7 +81,7 @@ fn setup_connection_tests<H: BuildHasher + 'static>(
fn extract_and_collect_results_into_map(
connection_results: &[ConnectionResult],
url_type: &UrlType,
) -> HashMap<NymNetworkDetails, Vec<(Url, bool)>> {
) -> HashMap<Network, Vec<(Url, bool)>> {
connection_results
.iter()
.filter(|c| &c.url_type() == url_type)
@@ -93,7 +93,7 @@ fn extract_and_collect_results_into_map(
}
async fn test_nymd_connection(
network: NymNetworkDetails,
network: Network,
url: &Url,
client: &NymdClient<QueryNymdClient>,
) -> ConnectionResult {
@@ -105,47 +105,56 @@ async fn test_nymd_connection(
{
Ok(Err(NymdError::TendermintError(e))) => {
// If we get a tendermint-rpc error, we classify the node as not contactable
log::debug!("Checking: nymd_url: {url}: {}: {}", "failed".red(), e);
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {}",
"failed".red(),
e
);
false
}
Ok(Err(NymdError::AbciError(code, log))) => {
// We accept the mixnet contract not found as ok from a connection standpoint. This happens
// for example on a pre-launch network.
log::debug!(
"Checking: nymd_url: {url}: {}, but with abci error: {code}: {log}",
"Checking: nymd_url: {network}: {url}: {}, but with abci error: {code}: {log}",
"success".green()
);
code == 18
}
Ok(Err(error @ NymdError::NoContractAddressAvailable)) => {
log::debug!("Checking: nymd_url: {url}: {}: {error}", "failed".red());
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {error}",
"failed".red()
);
false
}
Ok(Err(e)) => {
// For any other error, we're optimistic and just try anyway.
log::debug!(
"Checking: nymd_url: {url}: {}, but with error: {e}",
"Checking: nymd_url: {network}: {url}: {}, but with error: {e}",
"success".green()
);
true
}
Ok(Ok(_)) => {
log::debug!("Checking: nymd_url: {url}: {}", "success".green());
log::debug!(
"Checking: nymd_url: {network}: {url}: {}",
"success".green()
);
true
}
Err(e) => {
log::debug!("Checking: nymd_url: {url}: {}: {e}", "failed".red());
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {e}",
"failed".red()
);
false
}
};
ConnectionResult::Nymd(network, url.clone(), result)
}
async fn test_api_connection(
network: NymNetworkDetails,
url: &Url,
client: &ApiClient,
) -> ConnectionResult {
async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) -> ConnectionResult {
let result = match timeout(
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
client.get_cached_mixnodes(),
@@ -153,15 +162,21 @@ async fn test_api_connection(
.await
{
Ok(Ok(_)) => {
log::debug!("Checking: api_url: {url}: {}", "success".green());
log::debug!("Checking: api_url: {network}: {url}: {}", "success".green());
true
}
Ok(Err(e)) => {
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
false
}
Err(e) => {
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
false
}
};
@@ -169,8 +184,8 @@ async fn test_api_connection(
}
enum ClientForConnectionTest {
Nymd(NymNetworkDetails, Url, Box<NymdClient<QueryNymdClient>>),
Api(NymNetworkDetails, Url, ApiClient),
Nymd(Network, Url, Box<NymdClient<QueryNymdClient>>),
Api(Network, Url, ApiClient),
}
impl ClientForConnectionTest {
@@ -203,12 +218,12 @@ impl fmt::Display for UrlType {
#[derive(Debug)]
enum ConnectionResult {
Nymd(NymNetworkDetails, Url, bool),
Api(NymNetworkDetails, Url, bool),
Nymd(Network, Url, bool),
Api(Network, Url, bool),
}
impl ConnectionResult {
fn result(&self) -> (&NymNetworkDetails, &Url, &bool) {
fn result(&self) -> (&Network, &Url, &bool) {
match self {
ConnectionResult::Nymd(network, url, result)
| ConnectionResult::Api(network, url, result) => (network, url, result),
@@ -225,8 +240,11 @@ impl ConnectionResult {
impl fmt::Display for ConnectionResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (_network, url, result) = self.result();
let (network, url, result) = self.result();
let url_type = self.url_type();
write!(f, "{url}: {url_type}: connection is successful: {result}")
write!(
f,
"{network}: {url}: {url_type}: connection is successful: {result}"
)
}
}
@@ -149,7 +149,7 @@ impl NymdClient<SigningNymdClient> {
// maybe the wallet could be made into a generic, but for now, let's just have this one implementation
pub fn connect_with_signer<U: Clone>(
config: Config,
network: config::defaults::NymNetworkDetails,
network: config::defaults::all::Network,
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: Option<GasPrice>,
@@ -157,7 +157,7 @@ impl NymdClient<SigningNymdClient> {
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let denom = network.chain_details.mix_denom.base;
let denom = network.base_mix_denom();
let client_address = signer
.try_derive_accounts()?
.into_iter()
@@ -207,9 +207,8 @@ impl DirectSecp256k1HdWalletBuilder {
#[cfg(test)]
mod tests {
use network_defaults::NymNetworkDetails;
use super::*;
use network_defaults::all::Network::*;
#[test]
fn generating_account_addresses() {
@@ -219,9 +218,7 @@ mod tests {
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
];
let prefix = NymNetworkDetails::new_mainnet()
.chain_details
.bech32_account_prefix;
let prefix = MAINNET.bech32_prefix();
let addrs = vec![
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
@@ -12,10 +12,12 @@ use validator_api_requests::coconut::{
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
ComputeRewardEstParam, DeprecatedRewardEstimationResponse, DeprecatedUptimeResponse,
GatewayCoreStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
use validator_api_requests::Deprecated;
pub mod error;
pub mod routes;
@@ -248,6 +250,25 @@ impl Client {
.await
}
pub async fn compute_mixnode_reward_estimation(
&self,
identity: IdentityKeyRef<'_>,
request_body: &ComputeRewardEstParam,
) -> Result<RewardEstimationResponse, ValidatorAPIError> {
self.post_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::COMPUTE_REWARD_ESTIMATION,
],
NO_PARAMS,
request_body,
)
.await
}
pub async fn get_mixnode_stake_saturation(
&self,
mix_id: NodeId,
@@ -377,6 +398,151 @@ impl Client {
)
.await
}
// =================================================
// DEPRECATED ROUTES
// TO REMOVE ONCE OTHER PARTS OF THE SYSTEM MIGRATED
// =================================================
pub async fn deprecated_get_mixnode_core_status_count_by_identity(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<Deprecated<MixnodeCoreStatusResponse>, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
CORE_STATUS_COUNT,
],
&[(SINCE_ARG, since.to_string())],
)
.await
} else {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
CORE_STATUS_COUNT,
],
NO_PARAMS,
)
.await
}
}
pub async fn deprecated_get_mixnode_status_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<Deprecated<MixnodeStatusResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
routes::STATUS,
],
NO_PARAMS,
)
.await
}
pub async fn deprecated_get_mixnode_reward_estimation_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<DeprecatedRewardEstimationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
routes::REWARD_ESTIMATION,
],
NO_PARAMS,
)
.await
}
pub async fn deprecated_get_mixnode_stake_saturation_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<Deprecated<StakeSaturationResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
routes::STAKE_SATURATION,
],
NO_PARAMS,
)
.await
}
pub async fn deprecated_get_mixnode_inclusion_probability_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<Deprecated<InclusionProbabilityResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
routes::INCLUSION_CHANCE,
],
NO_PARAMS,
)
.await
}
pub async fn deprecated_get_mixnode_avg_uptime_by_identity(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<DeprecatedUptimeResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
routes::DEPRECATED,
identity,
routes::AVG_UPTIME,
],
NO_PARAMS,
)
.await
}
pub async fn deprecated_get_mixnode_avg_uptimes_by_identity(
&self,
) -> Result<Vec<DeprecatedUptimeResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODES,
routes::DEPRECATED,
routes::AVG_UPTIME,
],
NO_PARAMS,
)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
@@ -6,6 +6,7 @@ use network_defaults::VALIDATOR_API_VERSION;
pub const API_VERSION: &str = VALIDATOR_API_VERSION;
pub const MIXNODES: &str = "mixnodes";
pub const GATEWAYS: &str = "gateways";
pub const DEPRECATED: &str = "deprecated";
pub const DETAILED: &str = "detailed";
pub const ACTIVE: &str = "active";
@@ -29,6 +30,7 @@ pub const SINCE_ARG: &str = "since";
pub const STATUS: &str = "status";
pub const REWARD_ESTIMATION: &str = "reward-estimation";
pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation";
pub const AVG_UPTIME: &str = "avg_uptime";
pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
@@ -63,7 +63,7 @@ async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientW
for delegation in delegations {
table.add_row(vec![
to_iso_timestamp(delegation.height as u32, client).await,
delegation.mix_id.to_string(),
delegation.node_id.to_string(),
pretty_cosmwasm_coin(&delegation.amount),
delegation
.proxy
-11
View File
@@ -1,11 +0,0 @@
[package]
name = "completions"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "3.2", features = ["derive"] }
clap_complete = "3.2"
clap_complete_fig = "3.2"
-58
View File
@@ -1,58 +0,0 @@
use clap::builder::Command;
use clap::clap_derive::ArgEnum;
use clap::Args;
use clap_complete::generator::generate;
use clap_complete::Shell as ClapShell;
use std::io;
pub fn fig_generate(command: &mut Command, name: &str) {
clap_complete::generate(
clap_complete_fig::Fig,
command,
name,
&mut std::io::stdout(),
)
}
#[derive(ArgEnum, Copy, Clone)]
pub enum Shell {
Bash,
Elvish,
Fish,
PowerShell,
Zsh,
}
#[derive(Args, Copy, Clone)]
pub struct ArgShell {
#[clap(arg_enum, value_name = "SHELL")]
shell: Shell,
}
impl ArgShell {
pub fn generate(&self, command: &mut Command, name: &str) {
self.shell.generate(command, name)
}
}
impl Shell {
pub fn generate(&self, command: &mut Command, name: &str) {
match &self {
Self::Bash => {
generate(ClapShell::Bash, command, name, &mut io::stdout());
}
Self::Elvish => {
generate(ClapShell::Elvish, command, name, &mut io::stdout());
}
Self::Fish => {
generate(ClapShell::Fish, command, name, &mut io::stdout());
}
Self::PowerShell => {
generate(ClapShell::PowerShell, command, name, &mut io::stdout());
}
Self::Zsh => {
generate(ClapShell::Zsh, command, name, &mut io::stdout());
}
}
}
}
@@ -34,13 +34,12 @@ pub struct Delegation {
pub owner: Addr,
/// Id of the MixNode that this delegation was performed against.
#[serde(alias = "node_id")]
pub mix_id: NodeId,
pub node_id: NodeId,
// Note to UI/UX devs: there's absolutely no point in displaying this value to the users,
// it would serve them no purpose. It's only used for calculating rewards
/// Value of the "unit delegation" associated with the mixnode at the time of delegation.
#[serde(alias = "crr")]
#[serde(rename = "crr")]
pub cumulative_reward_ratio: Decimal,
/// Original delegation amount. Note that it is never mutated as delegation accumulates rewards.
@@ -56,7 +55,7 @@ pub struct Delegation {
impl Delegation {
pub fn new(
owner: Addr,
mix_id: NodeId,
node_id: NodeId,
cumulative_reward_ratio: Decimal,
amount: Coin,
height: u64,
@@ -64,7 +63,7 @@ impl Delegation {
) -> Self {
Delegation {
owner,
mix_id,
node_id,
cumulative_reward_ratio,
amount,
height,
@@ -73,20 +72,20 @@ impl Delegation {
}
pub fn generate_storage_key(
mix_id: NodeId,
node_id: NodeId,
owner_address: &Addr,
proxy: Option<&Addr>,
) -> StorageKey {
(mix_id, generate_owner_storage_subkey(owner_address, proxy))
(node_id, generate_owner_storage_subkey(owner_address, proxy))
}
// this function might seem a bit redundant, but I'd rather explicitly keep it around in case
// some types change in the future
pub fn generate_storage_key_with_subkey(
mix_id: NodeId,
node_id: NodeId,
owner_proxy_subkey: OwnerProxySubKey,
) -> StorageKey {
(mix_id, owner_proxy_subkey)
(node_id, owner_proxy_subkey)
}
pub fn dec_amount(&self) -> Decimal {
@@ -100,7 +99,7 @@ impl Delegation {
}
pub fn storage_key(&self) -> StorageKey {
Self::generate_storage_key(self.mix_id, &self.owner, self.proxy.as_ref())
Self::generate_storage_key(self.node_id, &self.owner, self.proxy.as_ref())
}
}
@@ -31,8 +31,8 @@ pub enum MixnetContractError {
#[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")]
InsufficientDelegation { received: Coin, minimum: Coin },
#[error("Mixnode ({mix_id}) does not exist")]
MixNodeBondNotFound { mix_id: NodeId },
#[error("Mixnode ({id}) does not exist")]
MixNodeBondNotFound { id: NodeId },
#[error("{owner} does not seem to own any mixnodes")]
NoAssociatedMixNodeBond { owner: Addr },
@@ -83,23 +83,23 @@ pub enum MixnetContractError {
epoch_end: i64,
},
#[error("Mixnode {mix_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")]
#[error("Mixnode {node_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")]
MixnodeAlreadyRewarded {
mix_id: NodeId,
node_id: NodeId,
absolute_epoch_id: u32,
},
#[error("Mixnode {mix_id} hasn't been selected to the rewarding set in this epoch ({absolute_epoch_id})")]
#[error("Mixnode {node_id} hasn't been selected to the rewarding set in this epoch ({absolute_epoch_id})")]
MixnodeNotInRewardedSet {
mix_id: NodeId,
node_id: NodeId,
absolute_epoch_id: u32,
},
#[error("Mixnode {mix_id} is currently in the process of unbonding")]
MixnodeIsUnbonding { mix_id: NodeId },
#[error("Mixnode {node_id} is currently in the process of unbonding")]
MixnodeIsUnbonding { node_id: NodeId },
#[error("Mixnode {mix_id} has already unbonded")]
MixnodeHasUnbonded { mix_id: NodeId },
#[error("Mixnode {node_id} has already unbonded")]
MixnodeHasUnbonded { node_id: NodeId },
#[error("The contract has ended up in a state that was deemed impossible: {comment}")]
InconsistentState { comment: String },
@@ -134,6 +134,6 @@ pub enum MixnetContractError {
#[error("Received unexpected value for the rewarded set. Got: {received}, expected at most: {expected}")]
UnexpectedRewardedSetSize { received: u32, expected: u32 },
#[error("Mixnode {mix_id} appears multiple times in the provided rewarded set update!")]
DuplicateRewardedSetNode { mix_id: NodeId },
#[error("Mixnode {node_id} appears multiple times in the provided rewarded set update!")]
DuplicateRewardedSetNode { node_id: NodeId },
}
@@ -6,9 +6,7 @@ use crate::reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate};
use crate::rewarding::RewardDistribution;
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer, NodeId};
pub use contracts_common::events::*;
use cosmwasm_std::{Addr, Coin, Decimal, Event};
pub const EVENT_VERSION_PREFIX: &str = "v2_";
use cosmwasm_std::{Addr, Coin, Event};
pub enum MixnetEventType {
MixnodeBonding,
@@ -49,7 +47,7 @@ impl From<MixnetEventType> for String {
impl ToString for MixnetEventType {
fn to_string(&self) -> String {
let event_name = match self {
match self {
MixnetEventType::MixnodeBonding => "mixnode_bonding",
MixnetEventType::GatewayBonding => "gateway_bonding",
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
@@ -80,9 +78,8 @@ impl ToString for MixnetEventType {
MixnetEventType::PendingIntervalConfigUpdate => "pending_interval_config_update",
MixnetEventType::IntervalConfigUpdate => "interval_config_update",
MixnetEventType::DelegationOnUnbonding => "delegation_on_unbonding_node",
};
format!("{}{}", EVENT_VERSION_PREFIX, event_name)
}
.into()
}
}
@@ -99,7 +96,7 @@ pub const DELEGATION_TARGET_KEY: &str = "delegation_target";
pub const DELEGATION_HEIGHT_KEY: &str = "delegation_latest_block_height";
// bonding/unbonding
pub const MIX_ID_KEY: &str = "mix_id";
pub const NODE_ID_KEY: &str = "node_id";
pub const NODE_IDENTITY_KEY: &str = "identity";
pub const ASSIGNED_LAYER_KEY: &str = "assigned_layer";
@@ -128,8 +125,6 @@ pub const DELEGATES_REWARD_KEY: &str = "delegates_reward";
pub const APPROXIMATE_TIME_LEFT_SECS_KEY: &str = "approximate_time_left_secs";
pub const INTERVAL_REWARDING_PARAMS_UPDATE_KEY: &str = "interval_rewarding_params_update";
pub const UPDATED_INTERVAL_REWARDING_PARAMS_KEY: &str = "updated_interval_rewarding_params";
pub const PRIOR_DELEGATES_KEY: &str = "prior_delegates";
pub const PRIOR_UNIT_DELEGATION_KEY: &str = "prior_unit_delegation";
pub const DISTRIBUTED_DELEGATION_REWARDS_KEY: &str = "distributed_delegation_rewards";
pub const FURTHER_DELEGATIONS_TO_REWARD_KEY: &str = "further_delegations";
@@ -202,7 +197,7 @@ pub fn new_withdraw_operator_reward_event(
.add_attribute(OWNER_KEY, owner.as_str())
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, mix_id.to_string())
}
pub fn new_withdraw_delegator_reward_event(
@@ -269,7 +264,7 @@ pub fn new_undelegation_event(delegator: &Addr, proxy: &Option<Addr>, mix_id: No
Event::new(MixnetEventType::Undelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, mix_id.to_string())
}
pub fn new_pending_undelegation_event(
@@ -280,7 +275,7 @@ pub fn new_pending_undelegation_event(
Event::new(MixnetEventType::PendingUndelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, mix_id.to_string())
}
pub fn new_gateway_bonding_event(
@@ -314,12 +309,12 @@ pub fn new_mixnode_bonding_event(
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
node_id: NodeId,
assigned_layer: Layer,
) -> Event {
// coin implements Display trait and we use that implementation here
Event::new(MixnetEventType::MixnodeBonding)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
@@ -327,55 +322,55 @@ pub fn new_mixnode_bonding_event(
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_unbonding_event(mix_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding).add_attribute(MIX_ID_KEY, mix_id.to_string())
pub fn new_mixnode_unbonding_event(node_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding).add_attribute(NODE_ID_KEY, node_id.to_string())
}
pub fn new_pending_mixnode_unbonding_event(
owner: &Addr,
proxy: &Option<Addr>,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
node_id: NodeId,
) -> Event {
Event::new(MixnetEventType::PendingMixnodeUnbonding)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
}
pub fn new_mixnode_config_update_event(
mix_id: NodeId,
node_id: NodeId,
owner: &Addr,
proxy: &Option<Addr>,
update: &MixNodeConfigUpdate,
) -> Event {
Event::new(MixnetEventType::MixnodeConfigUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json())
}
pub fn new_mixnode_pending_cost_params_update_event(
mix_id: NodeId,
node_id: NodeId,
owner: &Addr,
proxy: &Option<Addr>,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::PendingMixnodeCostParamsUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
pub fn new_mixnode_cost_params_update_event(
mix_id: NodeId,
node_id: NodeId,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::MixnodeCostParamsUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
@@ -431,41 +426,41 @@ pub fn new_settings_update_event(
event
}
pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event {
pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NO_REWARD_REASON_KEY, BOND_NOT_FOUND_VALUE)
}
pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event {
pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NO_REWARD_REASON_KEY, ZERO_PERFORMANCE_VALUE)
}
pub fn new_mix_rewarding_event(
interval: Interval,
mix_id: NodeId,
node_id: NodeId,
reward_distribution: RewardDistribution,
prior_delegates: Decimal,
prior_unit_delegation: Decimal,
) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
// TODO: to calculate Timmy's (delegator) reward at this time
// emit:
// - unit delegation BEFORE rewarding (to determine Timmy's state before rewarding happened)
// - total delegation BEFORE rewarding
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(PRIOR_DELEGATES_KEY, prior_delegates.to_string())
.add_attribute(PRIOR_UNIT_DELEGATION_KEY, prior_unit_delegation.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(
OPERATOR_REWARD_KEY,
reward_distribution.operator.to_string(),
@@ -78,34 +78,34 @@ impl MixNodeDetails {
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeRewarding {
/// Information provided by the operator that influence the cost function.
#[serde(alias = "cp")]
#[serde(rename = "cp")]
pub cost_params: MixNodeCostParams,
/// Total pledge and compounded reward earned by the node operator.
#[serde(alias = "op")]
#[serde(rename = "op")]
pub operator: Decimal,
/// Total delegation and compounded reward earned by all node delegators.
#[serde(alias = "dg")]
#[serde(rename = "dg")]
pub delegates: Decimal,
/// Cumulative reward earned by the "unit delegation" since the block 0.
#[serde(alias = "tur")]
#[serde(rename = "tur")]
pub total_unit_reward: Decimal,
/// Value of the theoretical "unit delegation" that has delegated to this mixnode at block 0.
#[serde(alias = "ud")]
#[serde(rename = "ud")]
pub unit_delegation: Decimal,
/// Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt
/// to reward it multiple times in the same epoch.
#[serde(alias = "le")]
#[serde(rename = "le")]
pub last_rewarded_epoch: EpochId,
// technically we don't need that field to determine reward magnitude or anything
// but it saves on extra queries to determine if we're removing the final delegation
// (so that we could zero the field correctly)
#[serde(alias = "uqd")]
#[serde(rename = "uqd")]
pub unique_delegations: u32,
}
+251
View File
@@ -0,0 +1,251 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{
DefaultNetworkDetails, DenomDetailsOwned, NymNetworkDetails, ValidatorDetails,
MAINNET_DEFAULTS, QA_DEFAULTS, SANDBOX_DEFAULTS,
};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt, str::FromStr};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum NetworkDefaultsError {
#[error("The provided network was invalid")]
MalformedNetworkProvided(String),
}
// the reason for allowing it is that this is just a temporary solution
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum Network {
QA,
SANDBOX,
MAINNET,
CUSTOM { details: NymNetworkDetails },
}
impl Network {
pub fn new_custom(details: NymNetworkDetails) -> Self {
Network::CUSTOM { details }
}
pub fn details(&self) -> NymNetworkDetails {
match self {
Self::QA => (&*QA_DEFAULTS).into(),
Self::SANDBOX => (&*SANDBOX_DEFAULTS).into(),
Self::MAINNET => (&*MAINNET_DEFAULTS).into(),
// I dislike the clone here, but for compatibility reasons we cannot define other networks with `NymNetworkDetails` directly yet
Self::CUSTOM { details } => details.clone(),
}
}
pub fn bech32_prefix(&self) -> String {
self.details().chain_details.bech32_account_prefix
}
pub fn mix_denom(&self) -> DenomDetailsOwned {
self.details().chain_details.mix_denom
}
pub fn stake_denom(&self) -> DenomDetailsOwned {
self.details().chain_details.stake_denom
}
pub fn base_mix_denom(&self) -> String {
self.details().chain_details.mix_denom.base
}
pub fn base_stake_denom(&self) -> String {
self.details().chain_details.stake_denom.base
}
pub fn mixnet_contract_address(&self) -> Option<String> {
self.details().contracts.mixnet_contract_address
}
pub fn vesting_contract_address(&self) -> Option<String> {
self.details().contracts.vesting_contract_address
}
pub fn bandwidth_claim_contract_address(&self) -> Option<String> {
self.details().contracts.bandwidth_claim_contract_address
}
pub fn coconut_bandwidth_contract_address(&self) -> Option<String> {
self.details().contracts.coconut_bandwidth_contract_address
}
pub fn multisig_contract_address(&self) -> Option<String> {
self.details().contracts.multisig_contract_address
}
pub fn validators(&self) -> Vec<ValidatorDetails> {
self.details().endpoints
}
// only used in mixnet contract tests, but I don't want to be messing with that code now
pub fn rewarding_validator_address(&self) -> &str {
match self {
Network::QA => crate::qa::REWARDING_VALIDATOR_ADDRESS,
Network::SANDBOX => crate::sandbox::REWARDING_VALIDATOR_ADDRESS,
Network::MAINNET => crate::mainnet::REWARDING_VALIDATOR_ADDRESS,
Network::CUSTOM { .. } => {
panic!("rewarding validator address is unavailable for a custom network")
}
}
}
// this should be handled differently, but I don't want to break compatibility
pub fn statistics_service_url(&self) -> &str {
match self {
Network::MAINNET => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
Network::SANDBOX => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
Network::QA => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
Network::CUSTOM { .. } => {
panic!("statistics service url is unavailable for a custom network")
}
}
}
}
impl FromStr for Network {
type Err = NetworkDefaultsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"qa" => Ok(Network::QA),
"sandbox" => Ok(Network::SANDBOX),
"mainnet" => Ok(Network::MAINNET),
_ => Err(NetworkDefaultsError::MalformedNetworkProvided(
s.to_string(),
)),
}
}
}
impl fmt::Display for Network {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Network::QA => f.write_str("QA"),
Network::SANDBOX => f.write_str("Sandbox"),
Network::MAINNET => f.write_str("Mainnet"),
Network::CUSTOM { .. } => f.write_str("Custom"),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct NetworkDetails {
bech32_prefix: String,
mix_denom: DenomDetailsOwned,
stake_denom: DenomDetailsOwned,
mixnet_contract_address: String,
vesting_contract_address: String,
bandwidth_claim_contract_address: String,
statistics_service_url: String,
validators: Vec<ValidatorDetails>,
}
impl From<&DefaultNetworkDetails> for NetworkDetails {
fn from(details: &DefaultNetworkDetails) -> Self {
NetworkDetails {
bech32_prefix: details.bech32_prefix.into(),
mix_denom: details.mix_denom.into(),
stake_denom: details.stake_denom.into(),
mixnet_contract_address: details.mixnet_contract_address.into(),
vesting_contract_address: details.vesting_contract_address.into(),
bandwidth_claim_contract_address: details.bandwidth_claim_contract_address.into(),
statistics_service_url: details.statistics_service_url.into(),
validators: details.validators.clone(),
}
}
}
// this also has to exist for compatibility reasons since I don't want to be touching the wallet now
impl From<NymNetworkDetails> for NetworkDetails {
fn from(details: NymNetworkDetails) -> Self {
NetworkDetails {
bech32_prefix: details.chain_details.bech32_account_prefix,
mix_denom: details.chain_details.mix_denom,
stake_denom: details.chain_details.stake_denom,
mixnet_contract_address: details
.contracts
.mixnet_contract_address
.unwrap_or_default(),
vesting_contract_address: details
.contracts
.vesting_contract_address
.unwrap_or_default(),
bandwidth_claim_contract_address: details
.contracts
.bandwidth_claim_contract_address
.unwrap_or_default(),
statistics_service_url: "".to_string(),
validators: details.endpoints,
}
}
}
impl NetworkDetails {
pub fn base_mix_denom(&self) -> &str {
&self.mix_denom.base
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct SupportedNetworks {
networks: HashMap<Network, NetworkDetails>,
}
impl SupportedNetworks {
pub fn new(support: Vec<Network>) -> Self {
SupportedNetworks {
networks: support
.into_iter()
.map(|n| {
let details = n.details().into();
(n, details)
})
.collect(),
}
}
pub fn bech32_prefix(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.bech32_prefix.as_str())
}
pub fn base_mix_denom(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.base_mix_denom())
}
pub fn mixnet_contract_address(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.mixnet_contract_address.as_str())
}
pub fn vesting_contract_address(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.vesting_contract_address.as_str())
}
pub fn bandwidth_claim_contract_address(&self, network: Network) -> Option<&str> {
self.networks
.get(&network)
.map(|network_details| network_details.bandwidth_claim_contract_address.as_str())
}
pub fn validators(&self, network: Network) -> impl Iterator<Item = &ValidatorDetails> {
self.networks
.get(&network)
.map(|network_details| &network_details.validators)
.into_iter()
.flatten()
}
}
+100 -41
View File
@@ -1,11 +1,15 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::{env::var, ops::Not, path::PathBuf};
use std::{env::var, path::PathBuf};
use url::Url;
pub mod all;
pub mod mainnet;
pub mod qa;
pub mod sandbox;
pub mod var_names;
pub const ETH_CONTRACT_ADDRESS: [u8; 20] = mainnet::_ETH_CONTRACT_ADDRESS;
@@ -99,99 +103,159 @@ impl NymNetworkDetails {
}
pub fn new_mainnet() -> Self {
fn parse_optional_str(raw: &str) -> Option<String> {
raw.is_empty().not().then(|| raw.into())
}
// Consider caching this process (lazy static)
NymNetworkDetails {
chain_details: ChainDetails {
bech32_account_prefix: mainnet::BECH32_PREFIX.into(),
mix_denom: mainnet::MIX_DENOM.into(),
stake_denom: mainnet::STAKE_DENOM.into(),
},
endpoints: mainnet::validators(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(mainnet::MIXNET_CONTRACT_ADDRESS),
vesting_contract_address: parse_optional_str(mainnet::VESTING_CONTRACT_ADDRESS),
bandwidth_claim_contract_address: parse_optional_str(
mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
),
coconut_bandwidth_contract_address: parse_optional_str(
mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
),
multisig_contract_address: parse_optional_str(mainnet::MULTISIG_CONTRACT_ADDRESS),
},
}
(&*MAINNET_DEFAULTS).into()
}
#[must_use]
pub fn with_bech32_account_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
self.chain_details.bech32_account_prefix = prefix.into();
self
}
#[must_use]
pub fn with_mix_denom(mut self, mix_denom: DenomDetailsOwned) -> Self {
self.chain_details.mix_denom = mix_denom;
self
}
#[must_use]
pub fn with_stake_denom(mut self, stake_denom: DenomDetailsOwned) -> Self {
self.chain_details.stake_denom = stake_denom;
self
}
#[must_use]
pub fn with_base_mix_denom<S: Into<String>>(mut self, base_mix_denom: S) -> Self {
self.chain_details.mix_denom = DenomDetailsOwned::base_only(base_mix_denom.into());
self
}
#[must_use]
pub fn with_base_stake_denom<S: Into<String>>(mut self, base_stake_denom: S) -> Self {
self.chain_details.stake_denom = DenomDetailsOwned::base_only(base_stake_denom.into());
self
}
#[must_use]
pub fn with_validator_endpoint(mut self, endpoint: ValidatorDetails) -> Self {
self.endpoints.push(endpoint);
self
}
#[must_use]
pub fn with_mixnet_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.mixnet_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_vesting_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.vesting_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_bandwidth_claim_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.bandwidth_claim_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_coconut_bandwidth_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.coconut_bandwidth_contract_address = contract.map(Into::into);
self
}
#[must_use]
pub fn with_multisig_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.multisig_contract_address = contract.map(Into::into);
self
}
}
// This conversion only exists for convenience reasons until
// we can completely phase out `DefaultNetworkDetails`
impl<'a> From<&'a DefaultNetworkDetails> for NymNetworkDetails {
fn from(details: &'a DefaultNetworkDetails) -> Self {
fn parse_optional_str(raw: &str) -> Option<String> {
if raw.is_empty() {
None
} else {
Some(raw.into())
}
}
NymNetworkDetails {
chain_details: ChainDetails {
bech32_account_prefix: details.bech32_prefix.into(),
mix_denom: details.mix_denom.into(),
stake_denom: details.stake_denom.into(),
},
endpoints: details.validators.clone(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(details.mixnet_contract_address),
vesting_contract_address: parse_optional_str(details.vesting_contract_address),
bandwidth_claim_contract_address: parse_optional_str(
details.bandwidth_claim_contract_address,
),
coconut_bandwidth_contract_address: parse_optional_str(
details.coconut_bandwidth_contract_address,
),
multisig_contract_address: parse_optional_str(details.multisig_contract_address),
},
}
}
}
// Since these are lazily constructed, we can afford to switch some of them to stronger types in the
// future. If we do this, and also get rid of the references we could potentially unify with
// `NetworkDetails`.
pub struct DefaultNetworkDetails {
bech32_prefix: &'static str,
mix_denom: DenomDetails,
stake_denom: DenomDetails,
mixnet_contract_address: &'static str,
vesting_contract_address: &'static str,
bandwidth_claim_contract_address: &'static str,
coconut_bandwidth_contract_address: &'static str,
multisig_contract_address: &'static str,
#[allow(dead_code)]
rewarding_validator_address: &'static str,
statistics_service_url: &'static str,
validators: Vec<ValidatorDetails>,
}
static MAINNET_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetworkDetails {
bech32_prefix: mainnet::BECH32_PREFIX,
mix_denom: mainnet::MIX_DENOM,
stake_denom: mainnet::STAKE_DENOM,
mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: mainnet::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: mainnet::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: mainnet::validators(),
});
static SANDBOX_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetworkDetails {
bech32_prefix: sandbox::BECH32_PREFIX,
mix_denom: sandbox::MIX_DENOM,
stake_denom: sandbox::STAKE_DENOM,
mixnet_contract_address: sandbox::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: sandbox::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: sandbox::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: sandbox::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: sandbox::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: sandbox::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: sandbox::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: sandbox::validators(),
});
static QA_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetworkDetails {
bech32_prefix: qa::BECH32_PREFIX,
mix_denom: qa::MIX_DENOM,
stake_denom: qa::STAKE_DENOM,
mixnet_contract_address: qa::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: qa::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: qa::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: qa::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: qa::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: qa::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: qa::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: qa::validators(),
});
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct DenomDetails {
pub base: &'static str,
@@ -285,17 +349,12 @@ pub fn setup_env(config_env_file: Option<PathBuf>) {
.expect("Invalid path to environment configuration file");
} else {
// if nothing is set, the use mainnet defaults
// if the user has not set `CONFIGURED`, then even if they set any of the env variables,
// overwrite them
crate::mainnet::export_to_env();
}
}
Err(_) => crate::mainnet::export_to_env(),
_ => {}
}
// if we haven't explicitly defined any of the constants, fallback to defaults
crate::mainnet::export_to_env_if_not_set()
}
// Name of the event triggered by the eth contract. If the event name is changed,
+17 -82
View File
@@ -31,107 +31,42 @@ pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(NYMD_VALIDATOR, Some(API_VALIDATOR))]
}
const DEFAULT_SUFFIX: &str = "_MAINNET_DEFAULT";
fn set_var_to_default(var: &str, value: &str) {
std::env::set_var(var, value);
std::env::set_var(format!("{}{}", var, DEFAULT_SUFFIX), "1")
}
fn set_var_conditionally_to_default(var: &str, value: &str) {
if std::env::var(var).is_err() {
set_var_to_default(var, value)
}
}
pub fn uses_default(var: &str) -> bool {
std::env::var(format!("{}{}", var, DEFAULT_SUFFIX)).is_ok()
}
pub fn read_var_if_not_default(var: &str) -> Option<String> {
if uses_default(var) {
None
} else {
std::env::var(var).ok()
}
}
pub fn export_to_env() {
set_var_to_default(var_names::CONFIGURED, "true");
set_var_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX);
set_var_to_default(var_names::MIX_DENOM, MIX_DENOM.base);
set_var_to_default(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
set_var_to_default(var_names::STAKE_DENOM, STAKE_DENOM.base);
set_var_to_default(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
set_var_to_default(
std::env::set_var(var_names::CONFIGURED, "true");
std::env::set_var(var_names::BECH32_PREFIX, BECH32_PREFIX);
std::env::set_var(var_names::MIX_DENOM, MIX_DENOM.base);
std::env::set_var(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
std::env::set_var(var_names::STAKE_DENOM, STAKE_DENOM.base);
std::env::set_var(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
std::env::set_var(
var_names::DENOMS_EXPONENT,
&STAKE_DENOM.display_exponent.to_string(),
STAKE_DENOM.display_exponent.to_string(),
);
set_var_to_default(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
set_var_to_default(
std::env::set_var(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
std::env::set_var(
var_names::VESTING_CONTRACT_ADDRESS,
VESTING_CONTRACT_ADDRESS,
);
set_var_to_default(
std::env::set_var(
var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
);
set_var_to_default(
std::env::set_var(
var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
);
set_var_to_default(
std::env::set_var(
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
set_var_to_default(
std::env::set_var(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
);
set_var_to_default(
std::env::set_var(
var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS,
STATISTICS_SERVICE_DOMAIN_ADDRESS,
);
set_var_to_default(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
set_var_to_default(var_names::API_VALIDATOR, API_VALIDATOR);
}
pub fn export_to_env_if_not_set() {
set_var_conditionally_to_default(var_names::CONFIGURED, "true");
set_var_conditionally_to_default(var_names::BECH32_PREFIX, BECH32_PREFIX);
set_var_conditionally_to_default(var_names::MIX_DENOM, MIX_DENOM.base);
set_var_conditionally_to_default(var_names::MIX_DENOM_DISPLAY, MIX_DENOM.display);
set_var_conditionally_to_default(var_names::STAKE_DENOM, STAKE_DENOM.base);
set_var_conditionally_to_default(var_names::STAKE_DENOM_DISPLAY, STAKE_DENOM.display);
set_var_conditionally_to_default(
var_names::DENOMS_EXPONENT,
&STAKE_DENOM.display_exponent.to_string(),
);
set_var_conditionally_to_default(var_names::MIXNET_CONTRACT_ADDRESS, MIXNET_CONTRACT_ADDRESS);
set_var_conditionally_to_default(
var_names::VESTING_CONTRACT_ADDRESS,
VESTING_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::MULTISIG_CONTRACT_ADDRESS,
MULTISIG_CONTRACT_ADDRESS,
);
set_var_conditionally_to_default(
var_names::REWARDING_VALIDATOR_ADDRESS,
REWARDING_VALIDATOR_ADDRESS,
);
set_var_conditionally_to_default(
var_names::STATISTICS_SERVICE_DOMAIN_ADDRESS,
STATISTICS_SERVICE_DOMAIN_ADDRESS,
);
set_var_conditionally_to_default(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
set_var_conditionally_to_default(var_names::API_VALIDATOR, API_VALIDATOR);
std::env::set_var(var_names::NYMD_VALIDATOR, NYMD_VALIDATOR);
std::env::set_var(var_names::API_VALIDATOR, API_VALIDATOR);
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{DenomDetails, ValidatorDetails};
pub(crate) const BECH32_PREFIX: &str = "n";
pub const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://qa-validator.nymtech.net",
Some("https://qa-validator-api.nymtech.net/api"),
)]
}
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{DenomDetails, ValidatorDetails};
pub(crate) const BECH32_PREFIX: &str = "nymt";
pub const MIX_DENOM: DenomDetails = DenomDetails::new("unymt", "nymt", 6);
pub const STAKE_DENOM: DenomDetails = DenomDetails::new("unyxt", "nyxt", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("E8883BAeF3869e14E4823F46662e81D4F7d2A81F");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "nymt1jh0s6qu6tuw9ut438836mmn7f3f2wencrnmdj4";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://sandbox-validator.nymtech.net",
Some("https://sandbox-validator.nymtech.net/api"),
)]
}
+11 -39
View File
@@ -1,5 +1,6 @@
use crate::error::TypesError;
use config::defaults::{DenomDetails, DenomDetailsOwned, NymNetworkDetails};
use config::defaults::all::Network;
use config::defaults::{DenomDetails, DenomDetailsOwned};
use cosmwasm_std::Fraction;
use cosmwasm_std::{Decimal, Uint128};
use schemars::JsonSchema;
@@ -55,16 +56,10 @@ pub type Denom = String;
pub struct RegisteredCoins(HashMap<Denom, CoinMetadata>);
impl RegisteredCoins {
pub fn default_denoms(network: &NymNetworkDetails) -> Self {
pub fn default_denoms(network: Network) -> Self {
let mut network_coins = HashMap::new();
network_coins.insert(
network.chain_details.mix_denom.base.clone(),
network.chain_details.mix_denom.clone().into(),
);
network_coins.insert(
network.chain_details.stake_denom.base.clone(),
network.chain_details.stake_denom.clone().into(),
);
network_coins.insert(network.mix_denom().base, network.mix_denom().into());
network_coins.insert(network.stake_denom().base, network.stake_denom().into());
RegisteredCoins(network_coins)
}
@@ -518,7 +513,7 @@ mod test {
#[test]
fn converting_to_display() {
let reg = RegisteredCoins::default_denoms(&NymNetworkDetails::new_mainnet());
let reg = RegisteredCoins::default_denoms(Network::MAINNET);
let values = vec![
(1u128, "0.000001"),
(10u128, "0.00001"),
@@ -532,29 +527,16 @@ mod test {
];
for (raw, expected) in values {
let coin = Coin::new(
raw,
NymNetworkDetails::new_mainnet()
.chain_details
.mix_denom
.base
.clone(),
);
let coin = Coin::new(raw, Network::MAINNET.mix_denom().base);
let display = reg.attempt_convert_to_display_dec_coin(coin).unwrap();
assert_eq!(
NymNetworkDetails::new_mainnet()
.chain_details
.mix_denom
.display,
display.denom
);
assert_eq!(Network::MAINNET.mix_denom().display, display.denom);
assert_eq!(expected, display.amount.to_string());
}
}
#[test]
fn converting_to_base() {
let reg = RegisteredCoins::default_denoms(&NymNetworkDetails::new_mainnet());
let reg = RegisteredCoins::default_denoms(Network::MAINNET);
let values = vec![
(1u128, "0.000001"),
(10u128, "0.00001"),
@@ -569,21 +551,11 @@ mod test {
for (expected, raw_display) in values {
let coin = DecCoin {
denom: NymNetworkDetails::new_mainnet()
.chain_details
.mix_denom
.display
.clone(),
denom: Network::MAINNET.mix_denom().display,
amount: raw_display.parse().unwrap(),
};
let base = reg.attempt_convert_to_base_coin(coin).unwrap();
assert_eq!(
NymNetworkDetails::new_mainnet()
.chain_details
.mix_denom
.base,
base.denom
);
assert_eq!(Network::MAINNET.mix_denom().base, base.denom);
assert_eq!(expected, base.amount);
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ impl Delegation {
) -> Result<Self, TypesError> {
Ok(Delegation {
owner: delegation.owner.to_string(),
mix_id: delegation.mix_id,
mix_id: delegation.node_id,
amount: reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?,
height: delegation.height,
proxy: delegation.proxy.map(|d| d.to_string()),
-2
View File
@@ -72,8 +72,6 @@ pub enum TypesError {
UnknownCoinDenom(String),
#[error("Provided event is not a delegation event")]
NotADelegationEvent,
#[error("Unknown network - {0}")]
UnknownNetwork(String),
}
impl Serialize for TypesError {
-3
View File
@@ -3,12 +3,10 @@
### Added
- vesting-contract: added queries for delegation timestamps and paged query for all vesting delegations in the contract ([#1569])
- all binaries: added shell completion and [Fig](fig.io) spec generation ([#1638])
### Changed
- mixnet-contract: compounding delegator rewards now happens instantaneously as opposed to having to wait for the current epoch to finish ([#1571])
- network-requester: updated CLI to use `clap` macros ([#1638])
### Fixed
@@ -20,7 +18,6 @@
[#1569]: https://github.com/nymtech/nym/pull/1569
[#1569]: https://github.com/nymtech/nym/pull/1571
[#1613]: https://github.com/nymtech/nym/pull/1613
[#1638]: https://github.com/nymtech/nym/pull/1638
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
+1 -1
View File
@@ -15,7 +15,7 @@ pub(crate) fn undelegate(
) -> Result<Coin, MixnetContractError> {
let tokens = mix_rewarding.undelegate(&delegation)?;
rewards_storage::MIXNODE_REWARDING.save(store, delegation.mix_id, &mix_rewarding)?;
rewards_storage::MIXNODE_REWARDING.save(store, delegation.node_id, &mix_rewarding)?;
storage::delegations().replace(store, delegation.storage_key(), None, Some(&delegation))?;
Ok(tokens)
+13 -13
View File
@@ -74,7 +74,7 @@ pub(crate) fn query_delegator_delegations_paged(
let start_next_after = delegations
.last()
.map(|del| (del.mix_id, del.proxy_storage_key()));
.map(|del| (del.node_id, del.proxy_storage_key()));
Ok(PagedDelegatorDelegationsResponse::new(
delegations,
@@ -284,19 +284,19 @@ mod tests {
let res1 = query_mixnode_delegations_paged(test.deps(), mix_id1, None, None).unwrap();
assert_eq!(res1.delegations.len(), 10);
assert!(res1.delegations.into_iter().all(|d| d.mix_id == mix_id1));
assert!(res1.delegations.into_iter().all(|d| d.node_id == mix_id1));
let res2 = query_mixnode_delegations_paged(test.deps(), mix_id2, None, None).unwrap();
assert_eq!(res2.delegations.len(), 14);
assert!(res2.delegations.into_iter().all(|d| d.mix_id == mix_id2));
assert!(res2.delegations.into_iter().all(|d| d.node_id == mix_id2));
let res3 = query_mixnode_delegations_paged(test.deps(), mix_id3, None, None).unwrap();
assert_eq!(res3.delegations.len(), 10);
assert!(res3.delegations.into_iter().all(|d| d.mix_id == mix_id3));
assert!(res3.delegations.into_iter().all(|d| d.node_id == mix_id3));
let res4 = query_mixnode_delegations_paged(test.deps(), mix_id4, None, None).unwrap();
assert_eq!(res4.delegations.len(), 10);
assert!(res4.delegations.into_iter().all(|d| d.mix_id == mix_id4));
assert!(res4.delegations.into_iter().all(|d| d.node_id == mix_id4));
}
}
@@ -610,7 +610,7 @@ mod tests {
assert_eq!(1, page1.delegations.len());
assert!(
page1.delegations[0].owner.as_str() == delegator1
&& page1.delegations[0].mix_id == mix_id1
&& page1.delegations[0].node_id == mix_id1
);
test.add_immediate_delegation(delegator1, 1000u32, mix_id2);
@@ -621,11 +621,11 @@ mod tests {
assert_eq!(2, page1.delegations.len());
assert!(
page1.delegations[0].owner.as_str() == delegator1
&& page1.delegations[0].mix_id == mix_id1
&& page1.delegations[0].node_id == mix_id1
);
assert!(
page1.delegations[1].owner.as_str() == delegator1
&& page1.delegations[1].mix_id == mix_id2
&& page1.delegations[1].node_id == mix_id2
);
test.add_immediate_delegation(delegator2, 1000u32, mix_id1);
@@ -636,11 +636,11 @@ mod tests {
assert_eq!(2, another_page1.delegations.len());
assert!(
another_page1.delegations[0].owner.as_str() == delegator1
&& another_page1.delegations[0].mix_id == mix_id1
&& another_page1.delegations[0].node_id == mix_id1
);
assert!(
another_page1.delegations[1].owner.as_str() == delegator2
&& another_page1.delegations[1].mix_id == mix_id1
&& another_page1.delegations[1].node_id == mix_id1
);
// retrieving the next page should start after the last key on this page
@@ -652,7 +652,7 @@ mod tests {
assert_eq!(1, page2.delegations.len());
assert!(
page2.delegations[0].owner.as_str() == delegator1
&& page2.delegations[0].mix_id == mix_id2
&& page2.delegations[0].node_id == mix_id2
);
// save another one
@@ -665,11 +665,11 @@ mod tests {
assert_eq!(2, page2.delegations.len());
assert!(
page2.delegations[0].owner.as_str() == delegator1
&& page2.delegations[0].mix_id == mix_id2
&& page2.delegations[0].node_id == mix_id2
);
assert!(
page2.delegations[1].owner.as_str() == delegator2
&& page2.delegations[1].mix_id == mix_id2
&& page2.delegations[1].node_id == mix_id2
);
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ pub(crate) fn delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, Delega
DELEGATION_OWNER_IDX_NAMESPACE,
),
mixnode: MultiIndex::new(
|d| d.mix_id,
|d| d.node_id,
DELEGATION_PK_NAMESPACE,
DELEGATION_MIXNODE_IDX_NAMESPACE,
),
@@ -49,9 +49,9 @@ pub(crate) fn _try_delegate_to_mixnode(
// check if the target node actually exists and is still bonded
match mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)? {
None => return Err(MixnetContractError::MixNodeBondNotFound { mix_id }),
None => return Err(MixnetContractError::MixNodeBondNotFound { id: mix_id }),
Some(bond) if bond.is_unbonding => {
return Err(MixnetContractError::MixnodeIsUnbonding { mix_id })
return Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id })
}
_ => (),
}
@@ -144,7 +144,7 @@ mod tests {
let res = try_delegate_to_mixnode(test.deps_mut(), sender, 42);
assert_eq!(
res,
Err(MixnetContractError::MixNodeBondNotFound { mix_id: 42 })
Err(MixnetContractError::MixNodeBondNotFound { id: 42 })
)
}
@@ -240,7 +240,7 @@ mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding {
mix_id: mix_id_unbonding
node_id: mix_id_unbonding
})
);
@@ -248,7 +248,7 @@ mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixNodeBondNotFound {
mix_id: mix_id_unbonded
id: mix_id_unbonded
})
);
@@ -256,7 +256,7 @@ mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixNodeBondNotFound {
mix_id: mix_id_unbonded_leftover
id: mix_id_unbonded_leftover
})
);
}
@@ -180,7 +180,7 @@ fn update_rewarded_set(
let mut tmp_set = BTreeSet::new();
for node_id in &new_rewarded_set {
if !tmp_set.insert(node_id) {
return Err(MixnetContractError::DuplicateRewardedSetNode { mix_id: *node_id });
return Err(MixnetContractError::DuplicateRewardedSetNode { node_id: *node_id });
}
}
@@ -1015,7 +1015,7 @@ mod tests {
.unwrap_err();
assert_eq!(
err,
MixnetContractError::DuplicateRewardedSetNode { mix_id: 1 }
MixnetContractError::DuplicateRewardedSetNode { node_id: 1 }
);
let nodes_with_duplicate = vec![1, 2, 3, 5, 4, 5];
let err = update_rewarded_set(
@@ -1026,7 +1026,7 @@ mod tests {
.unwrap_err();
assert_eq!(
err,
MixnetContractError::DuplicateRewardedSetNode { mix_id: 5 }
MixnetContractError::DuplicateRewardedSetNode { node_id: 5 }
);
}
+2 -2
View File
@@ -28,9 +28,9 @@ pub(crate) fn must_get_mixnode_bond_by_owner(
pub(crate) fn get_mixnode_details_by_id(
store: &dyn Storage,
mix_id: NodeId,
node_id: NodeId,
) -> StdResult<Option<MixNodeDetails>> {
if let Some(bond_information) = storage::mixnode_bonds().may_load(store, mix_id)? {
if let Some(bond_information) = storage::mixnode_bonds().may_load(store, node_id)? {
// if bond exists, rewarding details MUST also exist
let rewarding_details =
rewards_storage::MIXNODE_REWARDING.load(store, bond_information.id)?;
+13 -4
View File
@@ -453,7 +453,10 @@ pub mod tests {
// but fails if repeated (since the node is already in the "unbonding" state)(
let res = try_remove_mixnode(deps.as_mut(), info);
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id })
)
}
#[test]
@@ -519,7 +522,10 @@ pub mod tests {
// but we cannot perform any updates whilst the mixnode is already unbonding
try_remove_mixnode(deps.as_mut(), info.clone()).unwrap();
let res = try_update_mixnode_config(deps.as_mut(), info, update);
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id })
)
}
#[test]
@@ -580,7 +586,7 @@ pub mod tests {
assert_eq!(1, event.0);
assert_eq!(
PendingIntervalEventData::ChangeMixCostParams {
mix_id,
mix_id: mix_id,
new_costs: update.clone()
},
event.1
@@ -598,7 +604,10 @@ pub mod tests {
// but we cannot perform any updates whilst the mixnode is already unbonding
try_remove_mixnode(deps.as_mut(), info.clone()).unwrap();
let res = try_update_mixnode_cost_params(deps.as_mut(), info, update);
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id })
)
}
#[test]
+1 -1
View File
@@ -57,7 +57,7 @@ pub(crate) fn withdraw_delegator_reward(
delegation: Delegation,
mut mix_rewarding: MixNodeRewarding,
) -> Result<Coin, MixnetContractError> {
let mix_id = delegation.mix_id;
let mix_id = delegation.node_id;
let mut updated_delegation = delegation.clone();
let reward = mix_rewarding.withdraw_delegator_reward(&mut updated_delegation)?;
+22 -24
View File
@@ -33,7 +33,7 @@ pub(crate) fn try_reward_mixnode(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mix_id: NodeId,
node_id: NodeId,
node_performance: Performance,
) -> Result<Response, MixnetContractError> {
ensure_is_authorized(info.sender, deps.storage)?;
@@ -50,34 +50,34 @@ pub(crate) fn try_reward_mixnode(
// there's a chance of this failing to load the details if the mixnode unbonded before rewards
// were distributed and all of its delegators are also gone
let mut mix_rewarding = match storage::MIXNODE_REWARDING.may_load(deps.storage, mix_id)? {
let mut mix_rewarding = match storage::MIXNODE_REWARDING.may_load(deps.storage, node_id)? {
Some(mix_rewarding) if mix_rewarding.still_bonded() => mix_rewarding,
// don't fail if the node has unbonded as we don't want to fail the underlying transaction
_ => {
return Ok(Response::new()
.add_event(new_not_found_mix_operator_rewarding_event(interval, mix_id)));
return Ok(
Response::new().add_event(new_not_found_mix_operator_rewarding_event(
interval, node_id,
)),
);
}
};
let prior_delegates = mix_rewarding.delegates;
let prior_unit_delegation = mix_rewarding.unit_delegation;
// check if this node has already been rewarded for the current epoch.
// unlike the previous check, this one should be a hard error since this cannot be
// influenced by users actions
let absolute_epoch_id = interval.current_epoch_absolute_id();
if absolute_epoch_id == mix_rewarding.last_rewarded_epoch {
return Err(MixnetContractError::MixnodeAlreadyRewarded {
mix_id,
node_id,
absolute_epoch_id,
});
}
// again a hard error since the rewarding validator should have known not to reward this node
let node_status = interval_storage::REWARDED_SET
.load(deps.storage, mix_id)
.load(deps.storage, node_id)
.map_err(|_| MixnetContractError::MixnodeNotInRewardedSet {
mix_id,
node_id,
absolute_epoch_id,
})?;
@@ -85,10 +85,10 @@ pub(crate) fn try_reward_mixnode(
// however, we still need to update last_rewarded_epoch field
if node_performance.is_zero() {
mix_rewarding.last_rewarded_epoch = absolute_epoch_id;
storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &mix_rewarding)?;
storage::MIXNODE_REWARDING.save(deps.storage, node_id, &mix_rewarding)?;
return Ok(
Response::new().add_event(new_zero_uptime_mix_operator_rewarding_event(
interval, mix_id,
interval, node_id,
)),
);
}
@@ -106,15 +106,13 @@ pub(crate) fn try_reward_mixnode(
mix_rewarding.distribute_rewards(reward_distribution, absolute_epoch_id);
// persist changes happened to the storage
storage::MIXNODE_REWARDING.save(deps.storage, mix_id, &mix_rewarding)?;
storage::MIXNODE_REWARDING.save(deps.storage, node_id, &mix_rewarding)?;
storage::reward_accounting(deps.storage, node_reward)?;
Ok(Response::new().add_event(new_mix_rewarding_event(
interval,
mix_id,
node_id,
reward_distribution,
prior_delegates,
prior_unit_delegation,
)))
}
@@ -230,9 +228,9 @@ pub(crate) fn _try_withdraw_delegator_reward(
// (in that case the expected path of getting your tokens back is via undelegation)
match mixnodes_storage::mixnode_bonds().may_load(deps.storage, mix_id)? {
Some(mix_bond) if mix_bond.is_unbonding => {
return Err(MixnetContractError::MixnodeIsUnbonding { mix_id })
return Err(MixnetContractError::MixnodeIsUnbonding { node_id: mix_id })
}
None => return Err(MixnetContractError::MixnodeHasUnbonded { mix_id }),
None => return Err(MixnetContractError::MixnodeHasUnbonded { node_id: mix_id }),
_ => (),
};
@@ -525,7 +523,7 @@ pub mod tests {
assert!(res_standby.is_ok());
assert!(matches!(
res_inactive,
Err(MixnetContractError::MixnodeNotInRewardedSet { mix_id, .. }) if mix_id == inactive_mix_id
Err(MixnetContractError::MixnodeNotInRewardedSet { node_id, .. }) if node_id == inactive_mix_id
));
}
@@ -554,7 +552,7 @@ pub mod tests {
let res = try_reward_mixnode(test.deps_mut(), env, sender.clone(), mix_id, performance);
assert!(matches!(
res,
Err(MixnetContractError::MixnodeAlreadyRewarded { mix_id, .. }) if mix_id == mix_id
Err(MixnetContractError::MixnodeAlreadyRewarded { node_id, .. }) if node_id == mix_id
));
// in the following epoch we're good again
@@ -603,7 +601,7 @@ pub mod tests {
);
assert!(matches!(
res,
Err(MixnetContractError::MixnodeAlreadyRewarded { mix_id, .. }) if mix_id == mix_id
Err(MixnetContractError::MixnodeAlreadyRewarded { node_id, .. }) if node_id == mix_id
));
// but in the next epoch, as always, we're good again
@@ -914,7 +912,7 @@ pub mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding {
mix_id: mix_id_unbonding
node_id: mix_id_unbonding
})
);
@@ -923,7 +921,7 @@ pub mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixnodeHasUnbonded {
mix_id: mix_id_unbonded_leftover
node_id: mix_id_unbonded_leftover
})
);
}
@@ -1195,7 +1193,7 @@ pub mod tests {
assert_eq!(
res,
Err(MixnetContractError::MixnodeIsUnbonding {
mix_id: mix_id_unbonding
node_id: mix_id_unbonding
})
);
+1 -1
View File
@@ -152,7 +152,7 @@ pub(crate) fn ensure_proxy_match(
pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractError> {
if bond.is_unbonding {
return Err(MixnetContractError::MixnodeIsUnbonding { mix_id: bond.id });
return Err(MixnetContractError::MixnodeIsUnbonding { node_id: bond.id });
}
Ok(())
}
+1 -2
View File
@@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
chrono = { version = "0.4.19", features = ["serde"] }
clap = { version = "3.2", features = ["cargo", "derive"] }
clap = { version = "3.2.8", features = ["cargo", "derive"] }
humantime-serde = "1.0"
isocountry = "0.3.2"
itertools = "0.10.3"
@@ -23,7 +23,6 @@ serde = "1.0.126"
serde_json = "1.0.66"
thiserror = "1.0.29"
tokio = {version = "1.19.1", features = ["full"] }
maxminddb = "0.23.0"
mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" }
network-defaults = { path = "../common/network-defaults" }
+1 -7
View File
@@ -11,16 +11,10 @@ Features:
## Running
Supply the environment variable `GEOIP_DATABASE_PATH` with a path
that points to a GeoIP2 database file in binary format.
Supply the environment variable `GEO_IP_SERVICE_API_KEY` with a key from https://app.ipbase.com/.
Run as a service and reverse proxy with `nginx` to add `https` with Lets Encrypt.
Setup nginx to inject the request IP to the header `X-Real-IP`.
Use https://github.com/maxmind/geoipupdate to automatically
provide and update the GeoIP2 database file.
# TODO / Known Issues
## TODO
-26
View File
@@ -1,26 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::geo_ip::location::Location;
use crate::state::ExplorerApiStateContext;
use rocket::response::status;
use rocket::serde::json::Json;
use rocket::{Route, State};
use rocket_okapi::okapi::openapi3::OpenApi;
use rocket_okapi::settings::OpenApiSettings;
pub fn nym_terms_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![settings: terms]
}
#[openapi(tag = "terms")]
#[get("/")]
pub(crate) async fn terms(
_state: &State<ExplorerApiStateContext>,
location: Location,
) -> Result<Json<String>, status::Forbidden<String>> {
if location.iso_alpha2 == "US" {
return Err(status::Forbidden(Some("US government sucks".to_string())));
}
Ok(Json("Nym Terms & Conditions: Welcome".to_string()))
}
-1
View File
@@ -1 +0,0 @@
pub(crate) mod http;
@@ -1,10 +1,12 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mix_nodes::location::Location;
use crate::mix_nodes::location::{GeoLocation, Location};
use crate::state::ExplorerApiStateContext;
use log::{info, warn};
use reqwest::Error as ReqwestError;
use task::ShutdownListener;
use thiserror::Error;
pub(crate) struct GeoLocateTask {
state: ExplorerApiStateContext,
@@ -17,6 +19,13 @@ impl GeoLocateTask {
}
pub(crate) fn start(mut self) {
if ::std::env::var("GEO_IP_SERVICE_API_KEY").is_err() {
error!(
"Env var GEO_IP_SERVICE_API_KEY is not set. Geolocation tasks will not be started."
);
return;
}
info!("Spawning mix node locator task runner...");
tokio::spawn(async move {
let mut interval_timer = tokio::time::interval(std::time::Duration::from_millis(50));
@@ -44,8 +53,6 @@ impl GeoLocateTask {
.await
.unwrap_or_default();
let geo_ip = self.state.inner.geo_ip.0.clone();
for (i, cache_item) in mixnode_bonds.values().enumerate() {
if self
.state
@@ -58,50 +65,99 @@ impl GeoLocateTask {
continue;
}
match geo_ip.query(&cache_item.mix_node().host) {
Ok(opt) => match opt {
Some(location) => {
let location = Location::new(location);
// the mix node has not been located or is the cache time has expired
match locate(&cache_item.mix_node().host).await {
Ok(geo_location) => {
let location = Location::new(geo_location);
trace!(
"{} mix nodes already located. Ip {} is located in {:#?}",
i,
cache_item.mix_node().host,
location.three_letter_iso_country_code,
trace!(
"{} mix nodes already located. Ip {} is located in {:#?}",
i,
cache_item.mix_node().host,
location.three_letter_iso_country_code,
);
if i > 0 && (i % 100) == 0 {
info!(
"Located {} mixnodes...",
i + 1,
);
if i > 0 && (i % 100) == 0 {
info!("Located {} mixnodes...", i + 1,);
}
self.state
.inner
.mixnodes
.set_location(cache_item.mix_id(), Some(location))
.await;
// one node has been located, so return out of the loop
return;
}
None => {
warn!("❌ Location for {} not found.", cache_item.mix_node().host);
self.state
.inner
.mixnodes
.set_location(cache_item.mix_id(), Some(location))
.await;
// one node has been located, so return out of the loop
return;
}
Err(e) => match e {
LocateError::ReqwestError(e) => warn!(
"❌ Oh no! Location for {} failed {}",
cache_item.mix_node().host, e
),
LocateError::NotFound(e) => {
warn!(
"❌ Location for {} not found. Response body: {}",
cache_item.mix_node().host, e
);
self.state
.inner
.mixnodes
.set_location(cache_item.mix_id(), None)
.await;
}
},
LocateError::RateLimited(e) => warn!(
"❌ Oh no, we've been rate limited! Location for {} failed. Response body: {}",
cache_item.mix_node().host, e
),
},
Err(e) => {
warn!(
"❌ Oh no! Location for {} failed. Error: {:#?}",
cache_item.mix_node().host,
e
);
}
};
}
}
trace!("All mix nodes located");
}
}
#[derive(Debug, Error)]
enum LocateError {
#[error("Oops, we have made too many requests and are being rate limited. Request body: {0}")]
RateLimited(String),
#[error("Geolocation not found. Request body: {0}")]
NotFound(String),
#[error(transparent)]
ReqwestError(#[from] ReqwestError),
}
async fn locate(ip: &str) -> Result<GeoLocation, LocateError> {
let api_key = ::std::env::var("GEO_IP_SERVICE_API_KEY")
.expect("Env var GEO_IP_SERVICE_API_KEY is not set");
let uri = format!("{}/?apikey={}&ip={}", crate::GEO_IP_SERVICE, api_key, ip);
match reqwest::get(uri.clone()).await {
Ok(response) => {
if response.status() == 429 {
return Err(LocateError::RateLimited(
response
.text()
.await
.unwrap_or_else(|_| "(the response body is empty)".to_string()),
));
}
if response.status() == 404 {
return Err(LocateError::NotFound(
response
.text()
.await
.unwrap_or_else(|_| "(the response body is empty)".to_string()),
));
}
let location = response.json::<GeoLocation>().await?;
Ok(location)
}
Err(e) => Err(LocateError::ReqwestError(e)),
}
}
Binary file not shown.
-112
View File
@@ -1,112 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use isocountry::CountryCode;
use log::warn;
use maxminddb::{geoip2::Country, MaxMindDBError, Reader};
use std::{net::IpAddr, str::FromStr, sync::Arc};
const DEFAULT_DATABASE_PATH: &str = "./src/geo_ip/GeoLite2-Country.mmdb";
#[derive(Debug)]
pub enum GeoIpError {
NoValidIP,
InternalError,
}
// The current State implementation does not allow to fail on state
// creation, ie. returning Result<>. To avoid to use unwrap family,
// as a workaround, wrap the state inside an Option<>
// If Reader::open_readfile fails for some reason db will will be set to None
// and an error will be logged.
pub(crate) struct GeoIp {
pub(crate) db: Option<Reader<Vec<u8>>>,
}
#[derive(Clone)]
pub(crate) struct ThreadsafeGeoIp(pub Arc<GeoIp>);
pub(crate) struct Location {
/// two-letter country code (ISO 3166-1 alpha-2)
pub(crate) iso_alpha2: String,
/// three-letter country code (ISO 3166-1 alpha-3)
pub(crate) iso_alpha3: String,
/// English country short name (ISO 3166-1)
pub(crate) name: String,
}
impl GeoIp {
pub fn new() -> Self {
let db_path = std::env::var("GEOIP_DATABASE_PATH").unwrap_or_else(|e| {
warn!(
"Env variable GEOIP_DATABASE_PATH is not set: {} - Fallback to {}",
e, DEFAULT_DATABASE_PATH
);
DEFAULT_DATABASE_PATH.to_string()
});
let reader = Reader::open_readfile(&db_path)
.map_err(|e| {
error!("Fail to open GeoLite2 database file {}: {}", db_path, e);
})
.ok();
GeoIp { db: reader }
}
pub fn query(&self, address: &str) -> Result<Option<Location>, GeoIpError> {
let ip: IpAddr = FromStr::from_str(address).map_err(|e| {
error!("Fail to create IpAddr from {}: {}", &address, e);
GeoIpError::NoValidIP
})?;
let result = self
.db
.as_ref()
.ok_or_else(|| {
error!("No registered GeoIP database");
GeoIpError::InternalError
})?
.lookup::<Country>(ip);
match &result {
Ok(v) => Ok(Some(
Location::try_from(v).map_err(|_| GeoIpError::InternalError)?,
)),
Err(e) => match e {
MaxMindDBError::AddressNotFoundError(_) => Ok(None),
_ => Err(GeoIpError::InternalError),
},
}
}
}
impl<'a> TryFrom<&Country<'a>> for Location {
type Error = String;
fn try_from(country: &Country) -> Result<Self, Self::Error> {
let data = country.country.as_ref().ok_or_else(|| {
warn!("No Country data found");
"No Country data found"
})?;
let iso_alpha2 = String::from(data.iso_code.ok_or_else(|| {
warn!("No iso alpha-2 code found in Country data {:#?}", data);
"No iso alpha-2 code found in Country data"
})?);
let iso_codes = CountryCode::for_alpha2(&iso_alpha2).map_err(|e| {
let message = format!(
"Fail to get iso codes from iso alpha-2 country code {}: {}",
&iso_alpha2, e
);
warn!("{}", &message);
message
})?;
Ok(Location {
iso_alpha2,
iso_alpha3: String::from(iso_codes.alpha3()),
name: String::from(iso_codes.name()),
})
}
}
impl ThreadsafeGeoIp {
pub fn new() -> Self {
ThreadsafeGeoIp(Arc::new(GeoIp::new()))
}
}
-1
View File
@@ -1 +0,0 @@
pub(crate) mod location;
-76
View File
@@ -1,76 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::geo_ip::location::{GeoIpError, Location};
use crate::state::ExplorerApiStateContext;
use rocket::http::Status;
use rocket::request::FromRequest;
use rocket::request::Outcome;
use rocket::Request;
use rocket_okapi::gen::OpenApiGenerator;
use rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
const IP_HEADER: &str = "X-Real-IP";
#[derive(Debug)]
pub enum LocationError {
NoIP,
LocationNotFound,
InternalError,
}
fn find_location(request: &Request<'_>) -> Result<Location, (Status, LocationError)> {
let ip = request
.headers()
.get_one(IP_HEADER)
.map(|f| f.to_string())
.ok_or_else(|| {
error!("Header not found, {}", IP_HEADER);
(Status::Forbidden, LocationError::NoIP)
})?;
let geo_ip = &request
.rocket()
.state::<ExplorerApiStateContext>()
.ok_or((Status::InternalServerError, LocationError::InternalError))? // should never fail
.inner
.geo_ip;
let location = geo_ip
.0
.clone()
.query(&ip)
.map_err(|e| match e {
GeoIpError::NoValidIP => (Status::Forbidden, LocationError::NoIP),
GeoIpError::InternalError => {
(Status::InternalServerError, LocationError::InternalError)
}
})?
.ok_or_else(|| {
warn!("Fail to find a matching location for {}", ip);
(Status::Forbidden, LocationError::LocationNotFound)
})?;
Ok(location)
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Location {
type Error = LocationError;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
match find_location(request) {
Ok(loc) => Outcome::Success(loc),
Err(e) => Outcome::Failure(e),
}
}
}
impl<'a> OpenApiFromRequest<'a> for Location {
fn from_request_input(
_gen: &mut OpenApiGenerator,
_name: String,
_required: bool,
) -> rocket_okapi::Result<RequestHeaderInput> {
Ok(RequestHeaderInput::None)
}
}
-1
View File
@@ -1 +0,0 @@
pub(crate) mod location;
-2
View File
@@ -5,7 +5,6 @@ use rocket::{Build, Request, Rocket};
use rocket_cors::{AllowedHeaders, AllowedOrigins};
use rocket_okapi::swagger_ui::make_swagger_ui;
use crate::buy_terms::http::nym_terms_make_default_routes;
use crate::country_statistics::http::country_statistics_make_default_routes;
use crate::gateways::http::gateways_make_default_routes;
use crate::http::swagger::get_docs;
@@ -57,7 +56,6 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
"/overview" => overview_make_default_routes(&openapi_settings),
"/ping" => ping_make_default_routes(&openapi_settings),
"/validators" => validators_make_default_routes(&openapi_settings),
"/terms" => nym_terms_make_default_routes(&openapi_settings),
};
building_rocket
+1 -3
View File
@@ -8,14 +8,11 @@ use log::info;
use network_defaults::setup_env;
use task::ShutdownNotifier;
mod buy_terms;
pub(crate) mod cache;
mod client;
pub(crate) mod commands;
mod country_statistics;
mod gateways;
mod geo_ip;
mod guards;
mod helpers;
mod http;
mod mix_node;
@@ -26,6 +23,7 @@ mod state;
mod tasks;
mod validators;
const GEO_IP_SERVICE: &str = "https://api.ipbase.com/json";
const COUNTRY_DATA_REFRESH_INTERVAL: u64 = 60 * 15; // every 15 minutes
#[tokio::main]
+133
View File
@@ -8,6 +8,7 @@ use crate::mix_node::econ_stats::retrieve_mixnode_econ_stats;
use crate::mix_node::models::{
EconomicDynamicsStats, NodeDescription, NodeStats, PrettyDetailedMixNodeBond, SummedDelegations,
};
use crate::mix_nodes;
use crate::state::ExplorerApiStateContext;
use mixnet_contract_common::{Delegation, NodeId};
use reqwest::Error as ReqwestError;
@@ -26,6 +27,15 @@ pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>,
get_description,
get_stats,
get_economic_dynamics_stats,
// =================================================
// TO REMOVE ONCE OTHER PARTS OF THE SYSTEM MIGRATED
// =================================================
deprecated_get_delegations_by_identity,
deprecated_get_delegations_summed_by_identity,
deprecated_get_by_identity,
deprecated_get_description_by_identity,
deprecated_get_stats_by_identity,
deprecated_get_economic_dynamics_stats_by_identity,
]
}
@@ -194,3 +204,126 @@ pub(crate) async fn get_economic_dynamics_stats(
}
}
}
// =================================================
// TO REMOVE ONCE OTHER PARTS OF THE SYSTEM MIGRATED
// =================================================
#[openapi(tag = "mix_nodes")]
#[get("/deprecated/<pubkey>")]
pub(crate) async fn deprecated_get_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Result<Json<PrettyDetailedMixNodeBond>, NotFound<String>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return Err(NotFound("Mixnode not found".into()));
}
};
get_by_id(mix_id, state).await
}
#[openapi(tag = "mix_node")]
#[get("/deprecated/<pubkey>/delegations")]
pub(crate) async fn deprecated_get_delegations_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<Delegation>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return Json(Vec::new());
}
};
get_delegations(mix_id, state).await
}
#[openapi(tag = "mix_node")]
#[get("/deprecated/<pubkey>/delegations/summed")]
pub(crate) async fn deprecated_get_delegations_summed_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<SummedDelegations>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return Json(Vec::new());
}
};
get_delegations_summed(mix_id, state).await
}
#[openapi(tag = "mix_node")]
#[get("/deprecated/<pubkey>/description")]
pub(crate) async fn deprecated_get_description_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Option<Json<NodeDescription>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return None;
}
};
get_description(mix_id, state).await
}
#[openapi(tag = "mix_node")]
#[get("/deprecated/<pubkey>/stats")]
pub(crate) async fn deprecated_get_stats_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Option<Json<NodeStats>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return None;
}
};
get_stats(mix_id, state).await
}
#[openapi(tag = "mix_node")]
#[get("/deprecated/<pubkey>/economic-dynamics-stats")]
pub(crate) async fn deprecated_get_economic_dynamics_stats_by_identity(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Option<Json<EconomicDynamicsStats>> {
let mix_id = match mix_nodes::helpers::best_effort_pubkey_to_mix_id(state, pubkey).await {
Some(mix_id) => mix_id,
None => {
warn!(
"there doesn't seem to exist a mixnode with identity {}",
pubkey
);
return None;
}
};
get_economic_dynamics_stats(mix_id, state).await
}
+2 -2
View File
@@ -173,10 +173,10 @@ fn get_common_owner(delegations: &[Delegation]) -> Option<Addr> {
}
fn get_common_mix_id(delegations: &[Delegation]) -> Option<NodeId> {
let mix_id = delegations.iter().next()?.mix_id;
let mix_id = delegations.iter().next()?.node_id;
if delegations
.iter()
.any(|delegation| delegation.mix_id != mix_id)
.any(|delegation| delegation.node_id != mix_id)
{
log::warn!("Unexpected different node identities when summing delegations");
return None;
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::state::ExplorerApiStateContext;
use mixnet_contract_common::NodeId;
pub(crate) async fn best_effort_pubkey_to_mix_id(
state: &ExplorerApiStateContext,
pub_key: &str,
) -> Option<NodeId> {
state
.inner
.get_mix_node_by_pubkey(pub_key)
.await
.map(|node| node.mix_id())
}
+10 -5
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::geo_ip::location;
use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code;
use mixnet_contract_common::NodeId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@@ -36,14 +36,19 @@ pub(crate) struct Location {
pub(crate) two_letter_iso_country_code: String,
pub(crate) three_letter_iso_country_code: String,
pub(crate) country_name: String,
pub(crate) lat: f32,
pub(crate) lng: f32,
}
impl Location {
pub(crate) fn new(location: location::Location) -> Self {
pub(crate) fn new(geo_location: GeoLocation) -> Self {
let three_letter_iso_country_code = map_2_letter_to_3_letter_country_code(&geo_location);
Location {
country_name: location.name,
two_letter_iso_country_code: location.iso_alpha2,
three_letter_iso_country_code: location.iso_alpha3,
country_name: geo_location.country_name,
two_letter_iso_country_code: geo_location.country_code,
three_letter_iso_country_code,
lat: geo_location.latitude,
lng: geo_location.longitude,
}
}
}
+2
View File
@@ -3,9 +3,11 @@
use std::time::Duration;
pub(crate) mod helpers;
pub(crate) mod http;
pub(crate) mod location;
pub(crate) mod models;
pub(crate) mod utils;
pub(crate) const CACHE_REFRESH_RATE: Duration = Duration::from_secs(30);
pub(crate) const CACHE_ENTRY_TTL: Duration = Duration::from_secs(60);
-4
View File
@@ -7,7 +7,6 @@ use mixnet_contract_common::{IdentityKeyRef, NodeId};
use serde::{Deserialize, Serialize};
use crate::client::ThreadsafeValidatorClient;
use crate::geo_ip::location::ThreadsafeGeoIp;
use validator_client::models::MixNodeBondAnnotated;
use crate::country_statistics::country_nodes_distribution::{
@@ -31,7 +30,6 @@ pub struct ExplorerApiState {
pub(crate) mixnodes: ThreadsafeMixNodesCache,
pub(crate) ping: ThreadsafePingCache,
pub(crate) validators: ThreadsafeValidatorCache,
pub(crate) geo_ip: ThreadsafeGeoIp,
// TODO: discuss with @MS whether this is an appropriate spot for it
pub(crate) validator_client: ThreadsafeValidatorClient,
@@ -89,7 +87,6 @@ impl ExplorerApiStateContext {
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
geo_ip: ThreadsafeGeoIp::new(),
}
} else {
warn!(
@@ -105,7 +102,6 @@ impl ExplorerApiStateContext {
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
geo_ip: ThreadsafeGeoIp::new(),
}
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
// master APIs
export const API_BASE_URL = process.env.EXPLORER_API_URL;
export const VALIDATOR_API_BASE_URL = process.env.VALIDATOR_API_URL;
export const VALIDATOR_BASE_URL = process.env.VALIDATOR_URL;
export const { VALIDATOR_URL } = process.env;
export const BIG_DIPPER = process.env.BIG_DIPPER_URL;
// specific API routes
@@ -10,7 +10,7 @@ export const MIXNODE_PING = `${API_BASE_URL}/ping`;
export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`;
export const MIXNODE_API = `${API_BASE_URL}/mix-node`;
export const GATEWAYS_API = `${VALIDATOR_API_BASE_URL}/api/v1/gateways`;
export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`;
export const VALIDATORS_API = `${VALIDATOR_URL}/validators`;
export const BLOCK_API = `${VALIDATOR_API_BASE_URL}/block`;
export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`;
export const UPTIME_STORY_API = `${VALIDATOR_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this.
+7 -16
View File
@@ -60,7 +60,6 @@ export const Filters = () => {
const [showFilters, setShowFilters] = useState(false);
const [isFiltered, setIsFiltered] = useState(false);
const [filters, setFilters] = React.useState<TFilters>();
const [upperSaturationValue, setUpperSaturationValue] = React.useState<number>(100);
const baseFilters = useRef<TFilters>();
const prevFilters = useRef<TFilters>();
@@ -68,10 +67,11 @@ export const Filters = () => {
const handleToggleShowFilters = () => setShowFilters(!showFilters);
const initialiseFilters = useCallback(async () => {
let upperSaturationValue;
const allMixnodes = await Api.fetchMixnodes();
if (allMixnodes) {
setUpperSaturationValue(Math.round(Math.max(...allMixnodes.map((m) => m.stake_saturation)) * 100 + 1));
const initFilters = generateFilterSchema();
upperSaturationValue = Math.round(Math.max(...allMixnodes.map((m) => m.stake_saturation)) * 100 + 1);
const initFilters = generateFilterSchema(upperSaturationValue);
baseFilters.current = initFilters;
prevFilters.current = initFilters;
setFilters(initFilters);
@@ -79,18 +79,8 @@ export const Filters = () => {
}, []);
const handleOnChange = (id: EnumFilterKey, newValue: number[]) => {
if (id === 'stakeSaturation' && newValue[1] === 100) {
newValue.splice(1, 1, upperSaturationValue);
}
setFilters((ftrs) => {
if (ftrs)
return {
...ftrs,
[id]: {
...ftrs[id],
value: newValue,
},
};
if (ftrs) return { ...ftrs, [id]: { ...ftrs[id], value: newValue } };
return undefined;
});
};
@@ -152,10 +142,11 @@ export const Filters = () => {
</Snackbar>
<Button
size="large"
variant="contained"
variant="text"
color="inherit"
endIcon={<Tune />}
onClick={handleToggleShowFilters}
sx={{ textTransform: 'none', width: isMobile ? '100%' : 'inherit' }}
sx={{ textTransform: 'none' }}
>
Advanced filters
</Button>
@@ -1,6 +1,6 @@
import { EnumFilterKey, TFilters } from '../../typeDefs/filters';
export const generateFilterSchema = () => ({
export const generateFilterSchema = (upperSaturationValue?: number) => ({
profitMargin: {
label: 'Profit margin (%)',
id: EnumFilterKey.profitMargin,
@@ -25,12 +25,13 @@ export const generateFilterSchema = () => ({
stakeSaturation: {
label: 'Stake saturation (%)',
id: EnumFilterKey.stakeSaturation,
value: [0, 100],
value: [0, upperSaturationValue || 100],
isSmooth: true,
marks: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100].map((value) => ({
marks: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, upperSaturationValue || 100].map((value) => ({
value: value < 100 ? value : 100,
label: value < 100 ? value : '>100',
})),
max: 100,
tooltipInfo: "Select nodes with <100% saturation. Any additional stake above 100% saturation won't get rewards",
},
routingScore: {
@@ -5,16 +5,10 @@ import { EconomicsInfoRowWithIndex } from './types';
const selectionChance = (economicDynamicsStats: ApiState<MixNodeEconomicDynamicsStatsResponse> | undefined) => {
const inclusionProbability = economicDynamicsStats?.data?.active_set_inclusion_probability;
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
switch (inclusionProbability) {
case 'VeryLow':
return 'Very Low';
case 'VeryHigh':
return 'Very High';
case 'High':
case 'Good':
case 'Low':
case 'Moderate':
return inclusionProbability;
default:
return '-';
@@ -19,16 +19,12 @@ const textColour = (value: EconomicsRowsType, field: string, theme: Theme) => {
return theme.palette.warning.main;
}
if (field === 'selectionChance') {
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
switch (fieldValue) {
case 'High':
case 'VeryHigh':
return theme.palette.nym.networkExplorer.selectionChance.overModerate;
case 'Good':
case 'Moderate':
return theme.palette.nym.networkExplorer.selectionChance.moderate;
case 'Low':
case 'VeryLow':
return theme.palette.nym.networkExplorer.selectionChance.underModerate;
default:
return theme.palette.nym.wallet.fee;
@@ -46,7 +46,7 @@ export const MixNodeStatusDropdown: React.FC<MixNodeStatusDropdownProps> = ({ st
}
}}
sx={{
width: isMobile ? '50%' : 200,
width: isMobile ? 'auto' : 200,
...sx,
}}
>
+28 -39
View File
@@ -30,40 +30,39 @@ export const TableToolbar: React.FC<TableToolBarProps> = ({
width: '100%',
marginBottom: 2,
display: 'flex',
flexDirection: isMobile ? 'column' : 'row',
flexDirection: isMobile ? 'column-reverse' : 'row',
justifyContent: 'space-between',
}}
>
<Box sx={{ display: 'flex', flexDirection: isMobile ? 'column-reverse' : 'row', alignItems: 'middle' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
{childrenBefore}
<Select
labelId="simple-select-label"
id="simple-select"
value={pageSize}
onChange={onChangePageSize}
sx={{
width: isMobile ? '50%' : 200,
marginRight: isMobile ? 0 : 2,
}}
>
<MenuItem value={10} data-testid="ten">
10
</MenuItem>
<MenuItem value={30} data-testid="thirty">
30
</MenuItem>
<MenuItem value={50} data-testid="fifty">
50
</MenuItem>
<MenuItem value={100} data-testid="hundred">
100
</MenuItem>
</Select>
</Box>
<Box sx={{ display: 'flex', alignItems: 'middle' }}>
{childrenBefore}
<Select
labelId="simple-select-label"
id="simple-select"
value={pageSize}
onChange={onChangePageSize}
sx={{
width: isMobile ? 100 : 200,
}}
>
<MenuItem value={10} data-testid="ten">
10
</MenuItem>
<MenuItem value={30} data-testid="thirty">
30
</MenuItem>
<MenuItem value={50} data-testid="fifty">
50
</MenuItem>
<MenuItem value={100} data-testid="hundred">
100
</MenuItem>
</Select>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<TextField
sx={{
width: isMobile ? '100%' : 200,
width: isMobile ? '100%' : 350,
marginBottom: isMobile ? 2 : 0,
}}
value={searchTerm}
@@ -71,16 +70,6 @@ export const TableToolbar: React.FC<TableToolBarProps> = ({
placeholder="search"
onChange={(event) => onChangeSearch(event.target.value)}
/>
</Box>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'end',
gap: 1,
marginTop: isMobile ? 2 : 0,
}}
>
{withFilters && <Filters />}
{childrenAfter}
</Box>
+1 -2
View File
@@ -215,8 +215,7 @@ export type UptimeStoryResponse = {
export type MixNodeEconomicDynamicsStatsResponse = {
stake_saturation: number;
// TODO: when v2 will be deployed, remove cases: VeryHigh, Moderate and VeryLow
active_set_inclusion_probability: 'High' | 'Good' | 'Low' | 'VeryLow' | 'Moderate' | 'VeryHigh';
active_set_inclusion_probability: 'High' | 'Good' | 'Low';
reserve_set_inclusion_probability: 'High' | 'Good' | 'Low';
estimated_total_node_reward: number;
estimated_operator_reward: number;
+1 -2
View File
@@ -19,7 +19,7 @@ anyhow = "1.0.53"
async-trait = { version = "0.1.51" }
bip39 = "1.0.1"
bs58 = "0.4.0"
clap = { version = "3.2", features = ["cargo", "derive"] }
clap = { version = "3.0.10", features = ["cargo", "derive"] }
colored = "2.0"
dashmap = "4.0"
dirs = "4.0"
@@ -55,7 +55,6 @@ coconut-interface = { path = "../common/coconut-interface", optional = true }
credentials = { path = "../common/credentials" }
config = { path = "../common/config" }
crypto = { path = "../common/crypto" }
completions = { path = "../common/completions" }
gateway-requests = { path = "gateway-requests" }
mixnet-client = { path = "../common/client-libs/mixnet-client" }
mixnode-common = { path = "../common/mixnode-common" }
+11 -26
View File
@@ -4,13 +4,10 @@
use std::{process, str::FromStr};
use crate::{config::Config, Cli};
use clap::CommandFactory;
use clap::Subcommand;
use colored::Colorize;
use completions::{fig_generate, ArgShell};
use config::parse_validators;
use crypto::bech32_address_validation;
use network_defaults::mainnet::read_var_if_not_default;
use network_defaults::var_names::{
API_VALIDATOR, BECH32_PREFIX, CONFIGURED, NYMD_VALIDATOR, STATISTICS_SERVICE_DOMAIN_ADDRESS,
};
@@ -37,12 +34,6 @@ pub(crate) enum Commands {
/// Try to upgrade the gateway
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -64,16 +55,12 @@ pub(crate) struct OverrideConfig {
}
pub(crate) async fn execute(args: Cli) {
let bin_name = "nym-gateway";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::NodeDetails(m) => node_details::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Sign(m) => sign::execute(m),
Commands::Upgrade(m) => upgrade::execute(m).await,
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name),
}
}
@@ -110,29 +97,27 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
.expect("the provided statistics service url is invalid!"),
);
} else if std::env::var(CONFIGURED).is_ok() {
if let Some(raw_url) = read_var_if_not_default(STATISTICS_SERVICE_DOMAIN_ADDRESS) {
config = config.with_custom_statistics_service_url(
raw_url
.parse()
.expect("the provided statistics service url is invalid"),
);
}
let raw_url = std::env::var(STATISTICS_SERVICE_DOMAIN_ADDRESS)
.expect("statistics service url not set");
config = config.with_custom_statistics_service_url(
raw_url
.parse()
.expect("the provided statistics service url is invalid"),
)
}
if let Some(raw_validators) = args.validator_apis {
config = config.with_custom_validator_apis(parse_validators(&raw_validators));
} else if std::env::var(CONFIGURED).is_ok() {
if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) {
config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators))
}
let raw_validators = std::env::var(API_VALIDATOR).expect("api validator not set");
config = config.with_custom_validator_apis(parse_validators(&raw_validators))
}
if let Some(ref raw_validators) = args.validators {
config = config.with_custom_validator_nymd(parse_validators(raw_validators));
} else if std::env::var(CONFIGURED).is_ok() {
if let Some(raw_validators) = read_var_if_not_default(NYMD_VALIDATOR) {
config = config.with_custom_validator_nymd(::config::parse_validators(&raw_validators))
}
let raw_validators = std::env::var(NYMD_VALIDATOR).expect("nymd validator not set");
config = config.with_custom_validator_nymd(parse_validators(&raw_validators))
}
if let Some(wallet_address) = args.wallet_address {
+1 -2
View File
@@ -18,7 +18,7 @@ rust-version = "1.58.1"
[dependencies]
anyhow = "1.0.40"
bs58 = "0.4.0"
clap = { version = "3.2", features = ["cargo", "derive"] }
clap = { version = "3.0.10", features = ["cargo", "derive"] }
colored = "2.0"
cupid = "0.6.1"
dirs = "4.0"
@@ -40,7 +40,6 @@ url = { version = "2.2", features = ["serde"] }
## internal
config = { path="../common/config" }
crypto = { path="../common/crypto" }
completions = { path="../common/completions" }
mixnet-client = { path="../common/client-libs/mixnet-client" }
mixnode-common = { path="../common/mixnode-common" }
nonexhaustive-delayqueue = { path="../common/nonexhaustive-delayqueue" }
+2 -16
View File
@@ -4,11 +4,8 @@
use std::process;
use crate::{config::Config, Cli};
use clap::CommandFactory;
use clap::Subcommand;
use colored::Colorize;
use completions::{fig_generate, ArgShell};
use config::defaults::mainnet::read_var_if_not_default;
use config::{
defaults::var_names::{API_VALIDATOR, BECH32_PREFIX, CONFIGURED},
parse_validators,
@@ -41,12 +38,6 @@ pub(crate) enum Commands {
/// Show details of this mixnode
NodeDetails(node_details::NodeDetails),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -62,8 +53,6 @@ struct OverrideConfig {
}
pub(crate) async fn execute(args: Cli) {
let bin_name = "nym-mixnode";
match &args.command {
Commands::Describe(m) => describe::execute(m),
Commands::Init(m) => init::execute(m),
@@ -71,8 +60,6 @@ pub(crate) async fn execute(args: Cli) {
Commands::Sign(m) => sign::execute(m),
Commands::Upgrade(m) => upgrade::execute(m),
Commands::NodeDetails(m) => node_details::execute(m),
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name),
}
}
@@ -98,9 +85,8 @@ fn override_config(mut config: Config, args: OverrideConfig) -> Config {
if let Some(ref raw_validators) = args.validators {
config = config.with_custom_validator_apis(parse_validators(raw_validators));
} else if std::env::var(CONFIGURED).is_ok() {
if let Some(raw_validators) = read_var_if_not_default(API_VALIDATOR) {
config = config.with_custom_validator_apis(::config::parse_validators(&raw_validators))
}
let raw_validators = std::env::var(API_VALIDATOR).expect("api validator not set");
config = config.with_custom_validator_apis(parse_validators(&raw_validators))
}
if let Some(ref announce_host) = args.announce_host {
-29
View File
@@ -571,25 +571,6 @@ dependencies = [
"textwrap",
]
[[package]]
name = "clap_complete"
version = "3.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8"
dependencies = [
"clap",
]
[[package]]
name = "clap_complete_fig"
version = "3.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed37b4c0c1214673eba6ad8ea31666626bf72be98ffb323067d973c48b4964b9"
dependencies = [
"clap",
"clap_complete",
]
[[package]]
name = "clap_derive"
version = "3.2.18"
@@ -727,15 +708,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "completions"
version = "0.1.0"
dependencies = [
"clap",
"clap_complete",
"clap_complete_fig",
]
[[package]]
name = "config"
version = "0.1.0"
@@ -3375,7 +3347,6 @@ version = "1.0.2"
dependencies = [
"clap",
"client-core",
"completions",
"config",
"credential-storage",
"crypto",
-2
View File
@@ -2785,9 +2785,7 @@ dependencies = [
"config",
"cosmrs",
"cosmwasm-std",
"hex-literal",
"mixnet-contract-common",
"network-defaults",
"nym-types",
"serde",
"serde_json",
@@ -11,7 +11,7 @@ anyhow = "1.0"
argon2 = "0.4"
base64 = "0.13"
bip39 = "1.0"
clap = { version = "3.2", features = ["derive"] }
clap = { version = "3.2.20", features = ["derive"] }
log = "0.4"
pretty_env_logger = "0.4"
serde_json = "1.0.0"
-2
View File
@@ -5,7 +5,6 @@ edition = "2021"
rust-version = "1.58"
[dependencies]
hex-literal = "0.3.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
strum = { version = "0.23", features = ["derive"] }
@@ -15,7 +14,6 @@ cosmwasm-std = "1.0.0-beta8"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
config = { path = "../../common/config" }
network-defaults = { path = "../../common/network-defaults" }
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
validator-client = { path = "../../common/client-libs/validator-client", features = [
"nymd-client",
+19 -136
View File
@@ -1,10 +1,11 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use config::defaults::{mainnet, DenomDetails, NymNetworkDetails};
use nym_types::{currency::DecCoin, error::TypesError};
use config::defaults::all::Network as ConfigNetwork;
use config::defaults::{mainnet, qa, sandbox, DenomDetails};
use nym_types::currency::DecCoin;
use serde::{Deserialize, Serialize};
use std::{fmt, ops::Not, str::FromStr};
use std::fmt;
use strum::EnumIter;
#[allow(clippy::upper_case_acronyms)]
@@ -66,141 +67,23 @@ impl fmt::Display for Network {
}
}
impl From<Network> for NymNetworkDetails {
impl From<ConfigNetwork> for Network {
fn from(network: ConfigNetwork) -> Self {
match network {
ConfigNetwork::QA => Network::QA,
ConfigNetwork::SANDBOX => Network::SANDBOX,
ConfigNetwork::MAINNET => Network::MAINNET,
ConfigNetwork::CUSTOM { .. } => panic!("custom network is not supported"),
}
}
}
impl From<Network> for ConfigNetwork {
fn from(network: Network) -> Self {
match network {
Network::QA => qa::network_details(),
Network::SANDBOX => sandbox::network_details(),
Network::MAINNET => NymNetworkDetails::new_mainnet(),
}
}
}
impl FromStr for Network {
type Err = TypesError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"qa" => Ok(Network::QA),
"sandbox" => Ok(Network::SANDBOX),
"mainnet" => Ok(Network::MAINNET),
_ => Err(TypesError::UnknownNetwork(s.to_string())),
}
}
}
fn parse_optional_str(raw: &str) -> Option<String> {
raw.is_empty().not().then(|| raw.into())
}
mod sandbox {
use network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDetails};
use super::parse_optional_str;
pub(crate) const BECH32_PREFIX: &str = "nymt";
pub(crate) const MIX_DENOM: DenomDetails = DenomDetails::new("unymt", "nymt", 6);
pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyxt", "nyxt", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str =
"nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("E8883BAeF3869e14E4823F46662e81D4F7d2A81F");
//pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str =
//"nymt1jh0s6qu6tuw9ut438836mmn7f3f2wencrnmdj4";
//pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://sandbox-validator.nymtech.net",
Some("https://sandbox-validator.nymtech.net/api"),
)]
}
pub(crate) fn network_details() -> network_defaults::NymNetworkDetails {
network_defaults::NymNetworkDetails {
chain_details: ChainDetails {
bech32_account_prefix: BECH32_PREFIX.to_string(),
mix_denom: MIX_DENOM.into(),
stake_denom: STAKE_DENOM.into(),
},
endpoints: validators(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(MIXNET_CONTRACT_ADDRESS),
vesting_contract_address: parse_optional_str(VESTING_CONTRACT_ADDRESS),
bandwidth_claim_contract_address: parse_optional_str(
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
),
coconut_bandwidth_contract_address: parse_optional_str(
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
),
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
},
}
}
}
mod qa {
use network_defaults::{ChainDetails, DenomDetails, NymContracts, ValidatorDetails};
use super::parse_optional_str;
pub(crate) const BECH32_PREFIX: &str = "n";
pub(crate) const MIX_DENOM: DenomDetails = DenomDetails::new("unym", "nym", 6);
pub(crate) const STAKE_DENOM: DenomDetails = DenomDetails::new("unyx", "nyx", 6);
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n1rjzps6qrmdqmf0xz4cn4x4rcmqeqzq6hnzqg4wcvd0r2lyasdq5sepn5s8";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
//pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq";
//pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://qa-validator.nymtech.net",
Some("https://qa-validator-api.nymtech.net/api"),
)]
}
pub(crate) fn network_details() -> network_defaults::NymNetworkDetails {
network_defaults::NymNetworkDetails {
chain_details: ChainDetails {
bech32_account_prefix: BECH32_PREFIX.to_string(),
mix_denom: MIX_DENOM.into(),
stake_denom: STAKE_DENOM.into(),
},
endpoints: validators(),
contracts: NymContracts {
mixnet_contract_address: parse_optional_str(MIXNET_CONTRACT_ADDRESS),
vesting_contract_address: parse_optional_str(VESTING_CONTRACT_ADDRESS),
bandwidth_claim_contract_address: parse_optional_str(
BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
),
coconut_bandwidth_contract_address: parse_optional_str(
COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
),
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
},
Network::QA => ConfigNetwork::QA,
Network::SANDBOX => ConfigNetwork::SANDBOX,
Network::MAINNET => ConfigNetwork::MAINNET,
}
}
}
+1
View File
@@ -46,6 +46,7 @@
"react-error-boundary": "^3.1.3",
"react-hook-form": "^7.14.2",
"react-router-dom": "6",
"react-to-print": "^2.14.7",
"semver": "^6.3.0",
"string-to-color": "^2.2.2",
"use-clipboard-copy": "^0.2.0",
+7 -90
View File
@@ -13,7 +13,8 @@ use strum::IntoEnumIterator;
use url::Url;
use validator_client::nymd::AccountId as CosmosAccountId;
use config::defaults::{DenomDetailsOwned, NymNetworkDetails, ValidatorDetails};
use config::defaults::all::Network;
use config::defaults::{all::SupportedNetworks, ValidatorDetails};
use nym_wallet_types::network::Network as WalletNetwork;
use nym_wallet_types::network_config;
@@ -137,7 +138,7 @@ impl Config {
// One file per network
for (network, config) in &self.networks {
let network = match WalletNetwork::from_str(network) {
let network = match Network::from_str(network).map(Into::into) {
Ok(network) => network,
Err(err) => {
log::warn!("Unexpected name for network configuration, not saving: {err}");
@@ -197,7 +198,7 @@ impl Config {
&self,
network: WalletNetwork,
) -> impl Iterator<Item = ValidatorConfigEntry> + '_ {
self.base.networks.validators(&network).map(|v| {
self.base.networks.validators(network.into()).map(|v| {
v.clone()
.try_into()
.expect("The hardcoded validators are assumed to be valid urls")
@@ -217,7 +218,7 @@ impl Config {
pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> CosmosAccountId {
self.base
.networks
.mixnet_contract_address(&network)
.mixnet_contract_address(network.into())
.expect("No mixnet contract address found in config")
.parse()
.expect("Wrong format for mixnet contract address")
@@ -226,7 +227,7 @@ impl Config {
pub fn get_vesting_contract_address(&self, network: WalletNetwork) -> CosmosAccountId {
self.base
.networks
.vesting_contract_address(&network)
.vesting_contract_address(network.into())
.expect("No vesting contract address found in config")
.parse()
.expect("Wrong format for vesting contract address")
@@ -235,7 +236,7 @@ impl Config {
pub fn get_bandwidth_claim_contract_address(&self, network: WalletNetwork) -> CosmosAccountId {
self.base
.networks
.bandwidth_claim_contract_address(&network)
.bandwidth_claim_contract_address(network.into())
.expect("No bandwidth claim contract address found in config")
.parse()
.expect("Wrong format for bandwidth claim contract address")
@@ -420,90 +421,6 @@ impl fmt::Display for OptionalValidators {
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
struct SupportedNetworks {
networks: HashMap<WalletNetwork, NetworkDetails>,
}
impl SupportedNetworks {
fn new(support: Vec<WalletNetwork>) -> Self {
SupportedNetworks {
networks: support
.into_iter()
.map(|n| {
let details = NetworkDetails::from(NymNetworkDetails::from(n));
(n, details)
})
.collect(),
}
}
fn mixnet_contract_address(&self, network: &WalletNetwork) -> Option<&str> {
self.networks
.get(network)
.map(|network_details| network_details.mixnet_contract_address.as_str())
}
fn vesting_contract_address(&self, network: &WalletNetwork) -> Option<&str> {
self.networks
.get(network)
.map(|network_details| network_details.vesting_contract_address.as_str())
}
fn bandwidth_claim_contract_address(&self, network: &WalletNetwork) -> Option<&str> {
self.networks
.get(network)
.map(|network_details| network_details.bandwidth_claim_contract_address.as_str())
}
fn validators(&self, network: &WalletNetwork) -> impl Iterator<Item = &ValidatorDetails> {
self.networks
.get(network)
.map(|network_details| &network_details.validators)
.into_iter()
.flatten()
}
}
// Simplified variant of NymNetworkDetails for serialization to config file
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
struct NetworkDetails {
bech32_prefix: String,
mix_denom: DenomDetailsOwned,
stake_denom: DenomDetailsOwned,
mixnet_contract_address: String,
vesting_contract_address: String,
bandwidth_claim_contract_address: String,
statistics_service_url: String,
validators: Vec<ValidatorDetails>,
}
// Possibly a bit naff, but WalletNetwork is converted into the more general NymNetworkDetails, which here
// is converted to the format specific for serialization to config
impl From<NymNetworkDetails> for NetworkDetails {
fn from(details: NymNetworkDetails) -> Self {
NetworkDetails {
bech32_prefix: details.chain_details.bech32_account_prefix,
mix_denom: details.chain_details.mix_denom,
stake_denom: details.chain_details.stake_denom,
mixnet_contract_address: details
.contracts
.mixnet_contract_address
.unwrap_or_default(),
vesting_contract_address: details
.contracts
.vesting_contract_address
.unwrap_or_default(),
bandwidth_claim_contract_address: details
.contracts
.bandwidth_claim_contract_address
.unwrap_or_default(),
statistics_service_url: "".to_string(),
validators: details.endpoints,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
+1
View File
@@ -97,6 +97,7 @@ fn main() {
validator_api::status::mixnode_core_node_status,
validator_api::status::mixnode_inclusion_probability,
validator_api::status::mixnode_reward_estimation,
validator_api::status::compute_mixnode_reward_estimation,
validator_api::status::mixnode_stake_saturation,
validator_api::status::mixnode_status,
vesting::rewards::vesting_claim_delegator_reward,
@@ -4,7 +4,8 @@ use crate::network_config;
use crate::state::{WalletAccountIds, WalletState};
use crate::wallet_storage::{self, DEFAULT_LOGIN_ID};
use bip39::{Language, Mnemonic};
use config::defaults::{NymNetworkDetails, COSMOS_DERIVATION_PATH};
use config::defaults::all::Network;
use config::defaults::COSMOS_DERIVATION_PATH;
use cosmrs::bip32::DerivationPath;
use itertools::Itertools;
use nym_types::account::{Account, AccountEntry, Balance};
@@ -174,8 +175,8 @@ async fn run_connection_test(
untested_api_urls: HashMap<WalletNetwork, Vec<Url>>,
config: &Config,
) -> (
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
) {
let mixnet_contract_address = WalletNetwork::iter()
.map(|network| (network.into(), config.get_mixnet_contract_address(network)))
@@ -198,8 +199,8 @@ async fn run_connection_test(
}
fn create_clients(
nymd_urls: &HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
api_urls: &HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
nymd_urls: &HashMap<Network, Vec<(Url, bool)>>,
api_urls: &HashMap<Network, Vec<(Url, bool)>>,
default_nymd_urls: &HashMap<WalletNetwork, Url>,
default_api_urls: &HashMap<WalletNetwork, Url>,
config: &Config,
@@ -238,8 +239,8 @@ fn create_clients(
log::info!("Connecting to: nymd_url: {nymd_url} for {network}");
log::info!("Connecting to: api_url: {api_url} for {network}");
let network_details = NymNetworkDetails::from(network)
.clone()
let network_details = Network::from(network)
.details()
.with_mixnet_contract(Some(config.get_mixnet_contract_address(network).as_ref()))
.with_vesting_contract(Some(config.get_vesting_contract_address(network).as_ref()))
.with_bandwidth_claim_contract(Some(
@@ -259,7 +260,7 @@ fn create_clients(
}
fn select_random_responding_url(
urls: &HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
urls: &HashMap<Network, Vec<(Url, bool)>>,
network: WalletNetwork,
) -> Option<Url> {
urls.get(&network.into()).and_then(|urls| {
@@ -272,7 +273,7 @@ fn select_random_responding_url(
}
fn select_first_responding_url(
urls: &HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
urls: &HashMap<Network, Vec<(Url, bool)>>,
network: WalletNetwork,
//config: &Config,
) -> Option<Url> {
@@ -425,8 +426,8 @@ pub async fn add_account_for_password(
let address = {
let state = state.read().await;
let network: NymNetworkDetails = state.current_network().into();
derive_address(mnemonic, &network.chain_details.bech32_account_prefix)?.to_string()
let network: Network = state.current_network().into();
derive_address(mnemonic, &network.bech32_prefix())?.to_string()
};
// Re-read all the acccounts from the wallet to reset the state, rather than updating it
@@ -465,14 +466,10 @@ async fn set_state_with_all_accounts(
let mnemonic = account.mnemonic();
let addresses: HashMap<WalletNetwork, cosmrs::AccountId> = WalletNetwork::iter()
.map(|network| {
let config_network: NymNetworkDetails = network.into();
let config_network: Network = network.into();
(
network,
derive_address(
mnemonic.clone(),
&config_network.chain_details.bech32_account_prefix,
)
.unwrap(),
derive_address(mnemonic.clone(), &config_network.bech32_prefix()).unwrap(),
)
})
.collect();
@@ -4,10 +4,11 @@
use crate::api_client;
use crate::error::BackendError;
use crate::state::WalletState;
use mixnet_contract_common::{IdentityKeyRef, NodeId};
use mixnet_contract_common::{reward_params::Performance, IdentityKeyRef, NodeId};
use validator_client::models::{
GatewayCoreStatusResponse, InclusionProbabilityResponse, MixnodeCoreStatusResponse,
MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse,
ComputeRewardEstParam, GatewayCoreStatusResponse, InclusionProbabilityResponse,
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
};
#[tauri::command]
@@ -50,6 +51,26 @@ pub async fn mixnode_reward_estimation(
.await?)
}
#[tauri::command]
pub async fn compute_mixnode_reward_estimation(
identity: &str,
performance: Option<Performance>,
active_in_rewarded_set: Option<bool>,
pledge_amount: Option<u64>,
total_delegation: Option<u64>,
state: tauri::State<'_, WalletState>,
) -> Result<RewardEstimationResponse, BackendError> {
let request_body = ComputeRewardEstParam {
performance,
active_in_rewarded_set,
pledge_amount,
total_delegation,
};
Ok(api_client!(state)
.compute_mixnode_reward_estimation(identity, &request_body)
.await?)
}
#[tauri::command]
pub async fn mixnode_stake_saturation(
mix_id: NodeId,
+1 -3
View File
@@ -1,7 +1,6 @@
use crate::config;
use crate::error::BackendError;
use crate::simulate::SimulateResult;
use ::config::defaults::NymNetworkDetails;
use cosmwasm_std::Decimal;
use itertools::Itertools;
use log::warn;
@@ -216,9 +215,8 @@ impl WalletStateInner {
}
pub fn register_default_denoms(&mut self, network: Network) {
let details = NymNetworkDetails::from(network);
self.registered_coins
.insert(network, RegisteredCoins::default_denoms(&details));
.insert(network, RegisteredCoins::default_denoms(network.into()));
}
pub fn set_network(&mut self, network: Network) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

@@ -1,7 +1,6 @@
import React from 'react';
import { Alert, Box, Paper, Dialog, DialogContent, DialogTitle, IconButton, Stack, Typography } from '@mui/material';
import { Close } from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
const passwordCreationSteps = [
'Log out',
@@ -11,46 +10,34 @@ const passwordCreationSteps = [
'Now you can create multiple accounts',
];
export const MultiAccountHowTo = ({ show, handleClose }: { show: boolean; handleClose: () => void }) => {
const theme = useTheme();
return (
<Dialog
open={show}
onClose={handleClose}
fullWidth
PaperProps={{
style: { border: `1px solid ${theme.palette.nym.nymWallet.modal.border}` },
}}
>
<Paper>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Multi accounts</Typography>
<IconButton onClick={handleClose}>
<Close />
</IconButton>
</Box>
<Typography variant="body1" sx={{ color: (theme) => theme.palette.nym.text.muted }}>
How to set up multiple accounts
</Typography>
</DialogTitle>
<DialogContent>
<Stack spacing={2}>
<Alert
severity="warning"
icon={false}
sx={(t) => (t.palette.mode === 'dark' ? { bgcolor: (theme) => theme.palette.background.paper } : {})}
>
<Typography>In order to create multiple accounts your wallet needs a password.</Typography>
<Typography>Follow steps below to create password.</Typography>
</Alert>
<Typography>How to create a password for your account</Typography>
{passwordCreationSteps.map((step, index) => (
<Typography key={step}>{`${index + 1}. ${step}`}</Typography>
))}
</Stack>
</DialogContent>
</Paper>
</Dialog>
);
};
export const MultiAccountHowTo = ({ show, handleClose }: { show: boolean; handleClose: () => void }) => (
<Dialog open={show} onClose={handleClose} fullWidth PaperComponent={Paper} PaperProps={{ elevation: 0 }}>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Multi accounts</Typography>
<IconButton onClick={handleClose}>
<Close />
</IconButton>
</Box>
<Typography variant="body1" sx={{ color: (theme) => theme.palette.nym.text.muted }}>
How to set up multiple accounts
</Typography>
</DialogTitle>
<DialogContent>
<Stack spacing={2}>
<Alert
severity="warning"
icon={false}
sx={(t) => (t.palette.mode === 'dark' ? { bgcolor: (theme) => theme.palette.background.paper } : {})}
>
<Typography>In order to create multiple accounts your wallet needs a password.</Typography>
<Typography>Follow steps below to create password.</Typography>
</Alert>
<Typography>How to create a password for your account</Typography>
{passwordCreationSteps.map((step, index) => (
<Typography key={step}>{`${index + 1}. ${step}`}</Typography>
))}
</Stack>
</DialogContent>
</Dialog>
);
@@ -12,7 +12,6 @@ import {
Divider,
} from '@mui/material';
import { Add, ArrowDownwardSharp, Close } from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
import { AccountsContext } from 'src/context';
import { AccountItem } from '../AccountItem';
import { ConfirmPasswordModal } from './ConfirmPasswordModal';
@@ -22,8 +21,6 @@ export const AccountsModal = () => {
useContext(AccountsContext);
const [accountToSwitchTo, setAccountToSwitchTo] = useState<string>();
const theme = useTheme();
const handleClose = () => {
setDialogToDisplay(undefined);
setError(undefined);
@@ -50,51 +47,48 @@ export const AccountsModal = () => {
open={dialogToDisplay === 'Accounts'}
onClose={handleClose}
fullWidth
PaperProps={{
style: { border: `1px solid ${theme.palette.nym.nymWallet.modal.border}` },
}}
PaperComponent={Paper}
PaperProps={{ elevation: 0 }}
>
<Paper>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Accounts</Typography>
<IconButton onClick={handleClose}>
<Close />
</IconButton>
</Box>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
Switch between accounts
</Typography>
</DialogTitle>
<DialogContent sx={{ padding: 0 }}>
{accounts?.map(({ id, address }) => (
<AccountItem
name={id}
address={address}
key={address}
onSelectAccount={() => {
if (selectedAccount?.id !== id) {
setAccountToSwitchTo(id);
}
}}
/>
))}
</DialogContent>
<Divider variant="middle" sx={{ mt: 3 }} />
<DialogActions sx={{ p: 3 }}>
<Button startIcon={<ArrowDownwardSharp />} onClick={() => setDialogToDisplay('Import')}>
Import account
</Button>
<Button
disableElevation
variant="contained"
startIcon={<Add fontSize="small" />}
onClick={() => setDialogToDisplay('Add')}
>
Add new account
</Button>
</DialogActions>
</Paper>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Accounts</Typography>
<IconButton onClick={handleClose}>
<Close />
</IconButton>
</Box>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
Switch between accounts
</Typography>
</DialogTitle>
<DialogContent sx={{ padding: 0 }}>
{accounts?.map(({ id, address }) => (
<AccountItem
name={id}
address={address}
key={address}
onSelectAccount={() => {
if (selectedAccount?.id !== id) {
setAccountToSwitchTo(id);
}
}}
/>
))}
</DialogContent>
<Divider variant="middle" sx={{ mt: 3 }} />
<DialogActions sx={{ p: 3 }}>
<Button startIcon={<ArrowDownwardSharp />} onClick={() => setDialogToDisplay('Import')}>
Import account
</Button>
<Button
disableElevation
variant="contained"
startIcon={<Add fontSize="small" />}
onClick={() => setDialogToDisplay('Add')}
>
Add new account
</Button>
</DialogActions>
</Dialog>
);
};
@@ -1,6 +1,6 @@
import React, { useContext } from 'react';
import { Paper, Dialog, DialogTitle, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { Box, Paper, Dialog, DialogTitle, IconButton, Typography } from '@mui/material';
import { ArrowBack } from '@mui/icons-material';
import { ConfirmPassword } from 'src/components/ConfirmPassword';
import { AccountsContext } from 'src/context';
@@ -14,32 +14,28 @@ export const ConfirmPasswordModal = ({
onConfirm: (password: string) => Promise<void>;
}) => {
const { isLoading, error } = useContext(AccountsContext);
const theme = useTheme();
return (
<Dialog
open={Boolean(accountName)}
onClose={onClose}
fullWidth
PaperProps={{
style: { border: `1px solid ${theme.palette.nym.nymWallet.modal.border}` },
}}
PaperComponent={Paper}
PaperProps={{ elevation: 0 }}
>
<Paper>
<DialogTitle>
<Typography variant="h6">Switch account</Typography>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
Confirm password
</Typography>
</DialogTitle>
<ConfirmPassword
onConfirm={onConfirm}
error={error}
isLoading={isLoading}
buttonTitle="Switch account"
onCancel={onClose}
/>
</Paper>
<DialogTitle>
<Typography variant="h6">Switch account</Typography>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
Confirm password
</Typography>
</DialogTitle>
<ConfirmPassword
onConfirm={onConfirm}
error={error}
isLoading={isLoading}
buttonTitle="Switch account"
onCancel={onClose}
/>
</Dialog>
);
};
@@ -12,7 +12,6 @@ import {
Typography,
} from '@mui/material';
import { Close } from '@mui/icons-material';
import { useTheme } from '@mui/material/styles';
import { AccountsContext } from 'src/context';
export const EditAccountModal = () => {
@@ -20,8 +19,6 @@ export const EditAccountModal = () => {
const { accountToEdit, dialogToDisplay, setDialogToDisplay, handleEditAccount } = useContext(AccountsContext);
const theme = useTheme();
useEffect(() => {
setAccountName(accountToEdit ? accountToEdit?.id : '');
}, [accountToEdit]);
@@ -31,51 +28,48 @@ export const EditAccountModal = () => {
open={dialogToDisplay === 'Edit'}
onClose={() => setDialogToDisplay('Accounts')}
fullWidth
PaperProps={{
style: { border: `1px solid ${theme.palette.nym.nymWallet.modal.border}` },
}}
PaperComponent={Paper}
PaperProps={{ elevation: 0 }}
>
<Paper>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Edit account name</Typography>
<IconButton onClick={() => setDialogToDisplay('Accounts')}>
<Close />
</IconButton>
</Box>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
New wallet address
</Typography>
</DialogTitle>
<DialogContent sx={{ p: 0 }}>
<Box sx={{ px: 3, mt: 1 }}>
<TextField
label="Account name"
fullWidth
value={accountName}
onChange={(e) => setAccountName(e.target.value)}
autoFocus
/>
</Box>
</DialogContent>
<DialogActions sx={{ p: 3 }}>
<Button
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">Edit account name</Typography>
<IconButton onClick={() => setDialogToDisplay('Accounts')}>
<Close />
</IconButton>
</Box>
<Typography fontSize="small" sx={{ color: 'grey.600' }}>
New wallet address
</Typography>
</DialogTitle>
<DialogContent sx={{ p: 0 }}>
<Box sx={{ px: 3, mt: 1 }}>
<TextField
label="Account name"
fullWidth
disableElevation
variant="contained"
size="large"
onClick={() => {
if (accountToEdit) {
handleEditAccount({ ...accountToEdit, id: accountName });
setDialogToDisplay('Accounts');
}
}}
disabled={!accountName?.length}
>
Edit
</Button>
</DialogActions>
</Paper>
value={accountName}
onChange={(e) => setAccountName(e.target.value)}
autoFocus
/>
</Box>
</DialogContent>
<DialogActions sx={{ p: 3 }}>
<Button
fullWidth
disableElevation
variant="contained"
size="large"
onClick={() => {
if (accountToEdit) {
handleEditAccount({ ...accountToEdit, id: accountName });
setDialogToDisplay('Accounts');
}
}}
disabled={!accountName?.length}
>
Edit
</Button>
</DialogActions>
</Dialog>
);
};
+1 -1
View File
@@ -15,7 +15,7 @@ export const AppBar = () => {
const navigate = useNavigate();
return (
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent', backgroundImage: 'none' }}>
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent', backgroundImage: 'none', mt: 3 }}>
<Toolbar disableGutters>
<Grid container justifyContent="space-between" alignItems="center" flexWrap="nowrap">
<Grid item container alignItems="center" spacing={1}>
+2 -2
View File
@@ -14,11 +14,11 @@ export const Bond = ({
<Box
sx={{
display: 'flex',
alignItems: 'flex-end',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Typography variant="body2">Bond a mixnode or a gateway</Typography>
<Typography>Bond a mixnode or a gateway</Typography>
<Box
sx={{
display: 'flex',
@@ -1,4 +1,5 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Button, Stack, Typography } from '@mui/material';
import { Link } from '@nymproject/react/link/Link';
import { TBondedMixnode, urls } from 'src/context';
@@ -55,8 +56,11 @@ export const BondedMixnode = ({
network?: Network;
onActionSelect: (action: TBondedMixnodeActions) => void;
}) => {
const navigate = useNavigate();
const { name, stake, bond, stakeSaturation, profitMargin, operatorRewards, delegators, status, identityKey } =
mixnode;
const cells: Cell[] = [
{
cell: `${stake.amount} ${stake.denom}`,
@@ -117,7 +121,7 @@ export const BondedMixnode = ({
<Button
variant="text"
color="secondary"
onClick={() => onActionSelect('nodeSettings')}
onClick={() => navigate('/bonding/node-settings')}
startIcon={<NodeIcon />}
>
Settings
@@ -157,7 +157,7 @@ const AmountFormData = ({
return (
<Stack gap={3}>
<Box display="flex" gap={3} justifyContent="center">
<Box display="flex" gap={3} justifyContent="center" sx={{ mt: 2 }}>
{hasVestingTokens && <TokenPoolSelector disabled={false} onSelect={(pool) => setValue('tokenPool', pool)} />}
<CurrencyFormField
required
@@ -205,7 +205,7 @@ export const BondMixnodeForm = ({
<>
{step === 1 && (
<>
<Box sx={{ mb: 3 }}>
<Box sx={{ mb: 2 }}>
<NodeTypeSelector disabled={false} setNodeType={onSelectNodeType} nodeType="mixnode" />
</Box>
<NodeFormData onNext={onValidateMixnodeData} mixnodeData={mixnodeData} />
@@ -152,16 +152,18 @@ export const BondMixnodeModal = ({
subHeader={`Step ${step}/2`}
okLabel="Next"
>
<BondMixnodeForm
step={step}
denom={denom}
mixnodeData={mixnodeData}
amountData={amountData}
hasVestingTokens={hasVestingTokens}
onValidateMixnodeData={handleUpdateMixnodeData}
onValidateAmountData={handleUpdateAmountData}
onSelectNodeType={onSelectNodeType}
/>
<Box sx={{ mb: 2 }}>
<BondMixnodeForm
step={step}
denom={denom}
mixnodeData={mixnodeData}
amountData={amountData}
hasVestingTokens={hasVestingTokens}
onValidateMixnodeData={handleUpdateMixnodeData}
onValidateAmountData={handleUpdateAmountData}
onSelectNodeType={onSelectNodeType}
/>
</Box>
</SimpleModal>
);
};
@@ -12,6 +12,7 @@ import { simulateUpdateMixnodeCostParams, simulateVestingUpdateMixnodeCostParams
import { LoadingModal } from 'src/components/Modals/LoadingModal';
import { FeeDetails } from '@nymproject/types';
//Now we are using the node setting page instead of this modal
export const NodeSettings = ({
currentPm,
isVesting,
@@ -25,7 +26,7 @@ export const NodeSettings = ({
onClose: () => void;
onError: (err: string) => void;
}) => {
const [pm, setPm] = useState(currentPm.toString());
const [pm, setPm] = useState(currentPm?.toString());
const [error, setError] = useState(false);
const { fee, getFee, resetFeeState, isFeeLoading, feeError } = useGetFee();
@@ -111,7 +112,7 @@ export const NodeSettings = ({
Set profit margin
</Typography>
<Box sx={{ mb: 3 }}>
<TextField label="Profit margin" value={pm} onChange={(e) => setPm(e.target.value)} fullWidth />
<TextField placeholder="Profit margin" value={pm} onChange={(e) => setPm(e.target.value)} fullWidth />
{error && (
<FormHelperText sx={{ color: 'error.main' }}>
Profit margin should be a number between 0 and 100
@@ -1,6 +1,6 @@
import * as React from 'react';
import { Box, TextField, Typography } from '@mui/material';
import { useEffect, useState } from 'react';
import { useState, useEffect } from 'react';
import { Typography, Alert, TextField } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { TBondedGateway, TBondedMixnode } from 'src/context';
import { useGetFee } from 'src/hooks/useGetFee';
import { isGateway, isMixnode } from 'src/types';
@@ -13,8 +13,6 @@ import {
simulateVestingUnbondGateway,
simulateVestingUnbondMixnode,
} from '../../../requests';
import { ConfirmationModal } from '../../Modals/ConfirmationModal';
import { Error } from '../../Error';
interface Props {
node: TBondedMixnode | TBondedGateway;
@@ -23,12 +21,54 @@ interface Props {
onError: (e: string) => void;
}
export const UnbondModal = ({ node, onConfirm, onClose, onError }: Props) => {
const { fee, isFeeLoading, getFee, feeError } = useGetFee();
const [isConfirmed, setIsConfirmed] = useState(false);
const [showConfirmModal, setShowConfirmModal] = useState(true);
const [confirmField, setConfirmField] = useState('');
type TUnbondModalStep = 1 | 2 | 3;
const UnbondFirstStep = ({ handleVerification, onClose }: { handleVerification: any; onClose: () => void }) => {
const [verificationText, setVerificationText] = useState<string>('');
const theme = useTheme();
return (
<SimpleModal
open
header="Unbond"
okLabel="Unbond"
okDisabled={!verificationText.toUpperCase().match(/^UNBOND$/)}
onOk={handleVerification}
onClose={onClose}
>
<Typography sx={{ fontSize: 14, fontWeight: 600, mb: 3 }}>
If you unbond your node you will loose all your delegators!
</Typography>
<Alert
severity="error"
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
}}
>
This action is irreversible and it will not be possible to restore the current state again
</Alert>
<Typography sx={{ my: 3, fontSize: 14 }}>
To unbond, type <span style={{ color: theme.palette.nym.highlight }}>UNBOND</span> in the field below and click
UNBOND button
</Typography>
<TextField
type="input"
value={verificationText}
onChange={(e) => {
setVerificationText(e.target.value);
}}
fullWidth
/>
</SimpleModal>
);
};
const UnbondSecondStep = ({ node, onConfirm, onClose, onError }: Props) => {
const { fee, isFeeLoading, getFee, feeError } = useGetFee();
useEffect(() => {
if (feeError) {
onError(feeError);
@@ -53,63 +93,42 @@ export const UnbondModal = ({ node, onConfirm, onClose, onError }: Props) => {
}
}, [node]);
if (showConfirmModal) {
return (
<ConfirmationModal
title="Unbond"
confirmButton="UNBOND"
open={showConfirmModal}
onConfirm={() => {
setIsConfirmed(true);
setShowConfirmModal(false);
}}
onClose={onClose}
disabled={confirmField !== 'UNBOND'}
>
<Typography fontWeight={600} mb={2}>
If you unbond your node you will loose all your delegators!
</Typography>
<Error message="This action is irreversible and it will not be possible to restore the current state again" />
<Typography mt={2} mb={2}>
To unbond, type{' '}
<Typography display="inline" component="span" sx={{ color: (t) => t.palette.nym.highlight }}>
UNBOND
</Typography>{' '}
in the field below and click UNBOND button
</Typography>
<TextField fullWidth value={confirmField} onChange={(e) => setConfirmField(e.target.value)} />
</ConfirmationModal>
);
}
if (isConfirmed) {
return (
<SimpleModal
open
header="Unbond"
subHeader="Unbond and remove your node from the mixnet"
okLabel="Unbond"
onOk={onConfirm}
onClose={onClose}
>
return (
<SimpleModal
open
header="Unbond"
subHeader="Unbond and remove your node from the mixnet"
okLabel="Unbond"
onOk={onConfirm}
onClose={onClose}
>
<ModalListItem label="Amount to unbond" value={`${node.bond.amount} ${node.bond.denom.toUpperCase()}`} divider />
{isMixnode(node) && (
<ModalListItem
label="Amount to unbond"
value={`${node.bond.amount} ${node.bond.denom.toUpperCase()}`}
label="Operator rewards"
value={
node.operatorRewards ? `${node.operatorRewards.amount} ${node.operatorRewards.denom.toUpperCase()}` : '-'
}
divider
/>
{isMixnode(node) && (
<ModalListItem
label="Operator rewards"
value={
node.operatorRewards ? `${node.operatorRewards.amount} ${node.operatorRewards.denom.toUpperCase()}` : '-'
}
divider
/>
)}
<ModalFee isLoading={isFeeLoading} fee={fee} divider />
<Typography fontSize="small">Tokens will be transferred to the account you are logged in with now</Typography>
</SimpleModal>
);
}
return <Box />;
)}
<ModalFee isLoading={isFeeLoading} fee={fee} divider />
<Typography fontSize="small">Tokens will be transferred to the account you are logged in with now</Typography>
</SimpleModal>
);
};
export const UnbondModal = ({ node, onConfirm, onClose, onError }: Props) => {
const [step, setStep] = useState<TUnbondModalStep>(1);
const handleVerification = () => {
setStep(2);
};
return (
<div>
{step === 1 && <UnbondFirstStep handleVerification={handleVerification} onClose={onClose} />}
{step === 2 && <UnbondSecondStep onClose={onClose} onConfirm={onConfirm} onError={onError} node={node} />}
</div>
);
};
@@ -10,9 +10,9 @@ export default {
const Template: ComponentStory<typeof ConfirmTx> = (args) => (
<ConfirmTx {...args}>
<ModalListItem label="Transaction type" value="Bond" divider />
<ModalListItem label="Current bond" value="100 NYM" divider />
<ModalListItem label="Additional bond" value="50 NYM" divider />
<ModalListItem label="Transaction type:" value="Bond" divider />
<ModalListItem label="Current bond:" value="100 NYM" divider />
<ModalListItem label="Additional bond:" value="50 NYM" divider />
</ConfirmTx>
);
@@ -213,8 +213,8 @@ export const DelegateModal: React.FC<{
onPrev={resetFeeState}
onConfirm={handleOk}
>
<ModalListItem label="Node identity key" value={identityKey} divider />
<ModalListItem label="Amount" value={`${amount} ${denom.toUpperCase()}`} divider />
<ModalListItem label="Node identity key:" value={identityKey} divider />
<ModalListItem label="Amount:" value={`${amount} ${denom.toUpperCase()}`} divider />
</ConfirmTx>
);
}
@@ -267,7 +267,7 @@ export const DelegateModal: React.FC<{
>
{errorIdentityKey}
</Typography>
<Box display="flex" gap={2} alignItems="center" sx={{ mt: 3 }}>
<Box display="flex" gap={3} alignItems="center" sx={{ mt: 3 }}>
{hasVestingContract && <TokenPoolSelector disabled={false} onSelect={(pool) => setTokenPool(pool)} />}
<CurrencyFormField
required
+1 -18
View File
@@ -18,24 +18,7 @@ export const Mnemonic = ({
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future
</Typography>
</Warning>
<TextField
label="Mnemonic"
type="input"
value={mnemonic}
multiline
autoFocus={false}
fullWidth
inputProps={{
style: {
height: '160px',
},
}}
sx={{
'input::-webkit-textfield-decoration-container': {
alignItems: 'start',
},
}}
/>
<TextField multiline rows={3} value={mnemonic} fullWidth />
<Button
color="inherit"

Some files were not shown because too many files have changed in this diff Show More