Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d16c4becb | |||
| 9361e9bec2 | |||
| 6c5803e93b | |||
| 527d1fb93c | |||
| 4ed3d2bfc2 | |||
| b5c1a1b076 | |||
| c6a7f4e0ef | |||
| b9dddd32ca | |||
| 19173c9168 | |||
| b557b8d915 | |||
| b676a899b9 | |||
| fcfabb7ce1 | |||
| bb0cb06066 | |||
| 1fdbb56895 | |||
| 7b271a3993 | |||
| b8bc78cdb9 | |||
| e9133c4a75 | |||
| cd5332f26f | |||
| eb84fe7da3 | |||
| a95ba05e78 | |||
| 99ceabb0b0 | |||
| 25df7bcd4d | |||
| 1cdca7bec3 | |||
| c809c7733d | |||
| 7b53003edb | |||
| 831d9d2bf8 | |||
| cb7c51ba12 | |||
| 0310f0a8a9 | |||
| bb79d08f6d | |||
| 414c86b500 | |||
| 4304ffcf3c | |||
| 309b23e18a | |||
| 52703583f0 | |||
| 6473ef13c6 | |||
| 9a45f15ba4 | |||
| 746795b7ce | |||
| 8b81247044 | |||
| c6cd787950 | |||
| f9ab20b10f | |||
| acffd496ed | |||
| 466ac1a1e0 | |||
| d53adcd17e | |||
| 36e82e831f | |||
| cbe0115f01 |
@@ -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
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -63,7 +63,6 @@ members = [
|
||||
"common/topology",
|
||||
"common/types",
|
||||
"common/wasm-utils",
|
||||
"common/completions",
|
||||
"explorer-api",
|
||||
"gateway",
|
||||
"gateway/gateway-requests",
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
)]
|
||||
}
|
||||
@@ -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"),
|
||||
)]
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 +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.
@@ -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 +0,0 @@
|
||||
pub(crate) mod location;
|
||||
@@ -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 +0,0 @@
|
||||
pub(crate) mod location;
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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" }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Generated
-29
@@ -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",
|
||||
|
||||
Generated
-2
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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,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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user