Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8dbcd7d07c |
@@ -21,7 +21,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ arc-ubuntu-20.04 ]
|
||||
platform: [ arc-ubuntu-22.04 ]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
|
||||
-142
@@ -4,148 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2024.14-crunch] (2024-12-11)
|
||||
|
||||
- Merge/release/2024.14-crunch ([#5242])
|
||||
- bugfix: added explicit openapi servers to account for route prefixes ([#5237])
|
||||
- Further config score adjustments ([#5225])
|
||||
- feature: remve any filtering on node semver ([#5224])
|
||||
- Backport #5218 ([#5220])
|
||||
- Derive serialize for UserAgent (#5210) ([#5217])
|
||||
- dont consider legacy nodes for rewarded set selection ([#5215])
|
||||
- introduce UNSTABLE endpoints for returning network monitor run details ([#5214])
|
||||
- Nmv2 add debug config ([#5212])
|
||||
- nym-api NMv1 adjustments ([#5209])
|
||||
- adjusted config score penalty calculation ([#5206])
|
||||
- Fix backwards compat mac generation ([#5202])
|
||||
- merge crunch into develop ([#5199])
|
||||
- Update Security disclosure email, public key and policy ([#5195])
|
||||
- Guard storage access with cache ([#5193])
|
||||
- chore: apply 1.84 linter suggestions ([#5192])
|
||||
- improvement: make internal gateway clients use the same topology cache ([#5191])
|
||||
- Bugfix/credential proxy sequencing ([#5187])
|
||||
- Add monitor_run and testing_route indexes ([#5182])
|
||||
- Add indexes to monitor run and testing route ([#5181])
|
||||
- bugfix: fixed nym-node config migrations (again) ([#5179])
|
||||
- bugfix: use default value for verloc config when deserialising missing values ([#5177])
|
||||
- Remove peers with no allowed ip from storage ([#5175])
|
||||
- Move two minor jobs to free tier github hosted runners ([#5169])
|
||||
- Add support for DELETE to nym-http-api-client ([#5166])
|
||||
- Fix env var name ([#5165])
|
||||
- Add strum::EnumIter for TicketType ([#5164])
|
||||
- Add export_to_env to NymNetworkDetails ([#5162])
|
||||
- bugfix: correctly expose ecash-related data on nym-api ([#5155])
|
||||
- fix: validator-rewarder GH job ([#5151])
|
||||
- build(deps): bump cross-spawn from 7.0.3 to 7.0.6 in /testnet-faucet ([#5150])
|
||||
- build(deps): bump mikefarah/yq from 4.44.3 to 4.44.5 ([#5149])
|
||||
- start session collection for exit gateways ([#5148])
|
||||
- add version to clientStatsReport ([#5147])
|
||||
- update serde_json_path due to compilation issue ([#5144])
|
||||
- chore: remove standalone legacy mixnode/gateway binaries ([#5135])
|
||||
- [Product Data] Set up country reporting from vpn-client ([#5134])
|
||||
- removed ci-nym-api-tests.yml which was running outdated (and broken) tests ([#5133])
|
||||
- CI: reduce jobs running on cluster ([#5132])
|
||||
- [DOCS/operators]: Release changes v2024.13-magura & Tokenomics pages v1.0 ([#5128])
|
||||
- NS Agent auth with NS API ([#5127])
|
||||
- [Product Data] Config deserialization bug fix ([#5126])
|
||||
- bugfix: don't send empty BankMsg in ecash contract ([#5121])
|
||||
- [Product data] Data consumption with ecash ticket ([#5120])
|
||||
- feat: add GH workflow for nym-validator-rewarder ([#5119])
|
||||
- feat: add Dockerfile and add env vars for clap arguments ([#5118])
|
||||
- feature: config score ([#5117])
|
||||
- [Product Data] Add stats reporting configuration in client config ([#5115])
|
||||
- Correct IPv6 address generation ([#5113])
|
||||
- feature: rewarding for ticketbook issuance ([#5112])
|
||||
- Add granular log on nym-node ([#5111])
|
||||
- Send mixnet packet stats using task client ([#5109])
|
||||
- Expose time range ([#5108])
|
||||
- [Product Data] Client-side stats collection ([#5107])
|
||||
- chore: ecash contract migration to remove unused 'redemption_gateway_share' ([#5104])
|
||||
- [Product Data] Better unique user count on gateways ([#5084])
|
||||
- feat: add nym node GH workflow ([#5080])
|
||||
- IPv6 support for wireguard ([#5059])
|
||||
- Node Status API ([#5050])
|
||||
- Authenticator CLI client mode ([#5044])
|
||||
- Integrate nym-credential-proxy into workspace ([#5027])
|
||||
- [Product Data] Introduce data persistence on gateways ([#5022])
|
||||
- Bump the patch-updates group across 1 directory with 10 updates ([#5011])
|
||||
- build(deps): bump once_cell from 1.19.0 to 1.20.2 ([#4952])
|
||||
- Create TaskStatusEvent trait instead of piggybacking on Error ([#4919])
|
||||
- build(deps): bump lazy_static from 1.4.0 to 1.5.0 ([#4913])
|
||||
- Sync code with .env in build.rs ([#4876])
|
||||
- build(deps): bump axios from 1.6.0 to 1.7.5 in /nym-api/tests ([#4790])
|
||||
- Bump elliptic from 6.5.4 to 6.5.7 in /testnet-faucet ([#4768])
|
||||
|
||||
[#5242]: https://github.com/nymtech/nym/pull/5242
|
||||
[#5237]: https://github.com/nymtech/nym/pull/5237
|
||||
[#5225]: https://github.com/nymtech/nym/pull/5225
|
||||
[#5224]: https://github.com/nymtech/nym/pull/5224
|
||||
[#5220]: https://github.com/nymtech/nym/pull/5220
|
||||
[#5217]: https://github.com/nymtech/nym/pull/5217
|
||||
[#5215]: https://github.com/nymtech/nym/pull/5215
|
||||
[#5214]: https://github.com/nymtech/nym/pull/5214
|
||||
[#5212]: https://github.com/nymtech/nym/pull/5212
|
||||
[#5209]: https://github.com/nymtech/nym/pull/5209
|
||||
[#5206]: https://github.com/nymtech/nym/pull/5206
|
||||
[#5202]: https://github.com/nymtech/nym/pull/5202
|
||||
[#5199]: https://github.com/nymtech/nym/pull/5199
|
||||
[#5195]: https://github.com/nymtech/nym/pull/5195
|
||||
[#5193]: https://github.com/nymtech/nym/pull/5193
|
||||
[#5192]: https://github.com/nymtech/nym/pull/5192
|
||||
[#5191]: https://github.com/nymtech/nym/pull/5191
|
||||
[#5187]: https://github.com/nymtech/nym/pull/5187
|
||||
[#5182]: https://github.com/nymtech/nym/pull/5182
|
||||
[#5181]: https://github.com/nymtech/nym/pull/5181
|
||||
[#5179]: https://github.com/nymtech/nym/pull/5179
|
||||
[#5177]: https://github.com/nymtech/nym/pull/5177
|
||||
[#5175]: https://github.com/nymtech/nym/pull/5175
|
||||
[#5169]: https://github.com/nymtech/nym/pull/5169
|
||||
[#5166]: https://github.com/nymtech/nym/pull/5166
|
||||
[#5165]: https://github.com/nymtech/nym/pull/5165
|
||||
[#5164]: https://github.com/nymtech/nym/pull/5164
|
||||
[#5162]: https://github.com/nymtech/nym/pull/5162
|
||||
[#5155]: https://github.com/nymtech/nym/pull/5155
|
||||
[#5151]: https://github.com/nymtech/nym/pull/5151
|
||||
[#5150]: https://github.com/nymtech/nym/pull/5150
|
||||
[#5149]: https://github.com/nymtech/nym/pull/5149
|
||||
[#5148]: https://github.com/nymtech/nym/pull/5148
|
||||
[#5147]: https://github.com/nymtech/nym/pull/5147
|
||||
[#5144]: https://github.com/nymtech/nym/pull/5144
|
||||
[#5135]: https://github.com/nymtech/nym/pull/5135
|
||||
[#5134]: https://github.com/nymtech/nym/pull/5134
|
||||
[#5133]: https://github.com/nymtech/nym/pull/5133
|
||||
[#5132]: https://github.com/nymtech/nym/pull/5132
|
||||
[#5128]: https://github.com/nymtech/nym/pull/5128
|
||||
[#5127]: https://github.com/nymtech/nym/pull/5127
|
||||
[#5126]: https://github.com/nymtech/nym/pull/5126
|
||||
[#5121]: https://github.com/nymtech/nym/pull/5121
|
||||
[#5120]: https://github.com/nymtech/nym/pull/5120
|
||||
[#5119]: https://github.com/nymtech/nym/pull/5119
|
||||
[#5118]: https://github.com/nymtech/nym/pull/5118
|
||||
[#5117]: https://github.com/nymtech/nym/pull/5117
|
||||
[#5115]: https://github.com/nymtech/nym/pull/5115
|
||||
[#5113]: https://github.com/nymtech/nym/pull/5113
|
||||
[#5112]: https://github.com/nymtech/nym/pull/5112
|
||||
[#5111]: https://github.com/nymtech/nym/pull/5111
|
||||
[#5109]: https://github.com/nymtech/nym/pull/5109
|
||||
[#5108]: https://github.com/nymtech/nym/pull/5108
|
||||
[#5107]: https://github.com/nymtech/nym/pull/5107
|
||||
[#5104]: https://github.com/nymtech/nym/pull/5104
|
||||
[#5084]: https://github.com/nymtech/nym/pull/5084
|
||||
[#5080]: https://github.com/nymtech/nym/pull/5080
|
||||
[#5059]: https://github.com/nymtech/nym/pull/5059
|
||||
[#5050]: https://github.com/nymtech/nym/pull/5050
|
||||
[#5044]: https://github.com/nymtech/nym/pull/5044
|
||||
[#5027]: https://github.com/nymtech/nym/pull/5027
|
||||
[#5022]: https://github.com/nymtech/nym/pull/5022
|
||||
[#5011]: https://github.com/nymtech/nym/pull/5011
|
||||
[#4952]: https://github.com/nymtech/nym/pull/4952
|
||||
[#4919]: https://github.com/nymtech/nym/pull/4919
|
||||
[#4913]: https://github.com/nymtech/nym/pull/4913
|
||||
[#4876]: https://github.com/nymtech/nym/pull/4876
|
||||
[#4790]: https://github.com/nymtech/nym/pull/4790
|
||||
[#4768]: https://github.com/nymtech/nym/pull/4768
|
||||
|
||||
## [2024.13-magura-drift] (2024-11-29)
|
||||
|
||||
- Optimised syncing bandwidth information to storage
|
||||
|
||||
Generated
+2
-1
@@ -4674,6 +4674,7 @@ dependencies = [
|
||||
"opentelemetry-jaeger",
|
||||
"pretty_env_logger",
|
||||
"schemars",
|
||||
"semver 1.0.23",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing-opentelemetry",
|
||||
@@ -5953,7 +5954,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.2.0"
|
||||
version = "1.1.12"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
|
||||
use crate::commands::try_load_current_config;
|
||||
use crate::{
|
||||
client::SocketClient,
|
||||
client::{config::Config, SocketClient},
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::ClientError,
|
||||
};
|
||||
use clap::Args;
|
||||
use log::*;
|
||||
use nym_bin_common::version_checker::is_minor_version_compatible;
|
||||
use nym_client_core::cli_helpers::client_run::CommonClientRunArgs;
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
@@ -45,12 +48,36 @@ impl From<Run> for OverrideConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
// network version. It might do so in the future.
|
||||
fn version_check(cfg: &Config) -> bool {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = &cfg.base.client.version;
|
||||
if binary_version == config_version {
|
||||
true
|
||||
} else {
|
||||
warn!("The native-client binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
|
||||
if is_minor_version_compatible(binary_version, config_version) {
|
||||
info!("but they are still semver compatible. However, consider running the `upgrade` command");
|
||||
true
|
||||
} else {
|
||||
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
eprintln!("Starting client {}...", args.common_args.id);
|
||||
|
||||
let mut config = try_load_current_config(&args.common_args.id).await?;
|
||||
config = override_config(config, OverrideConfig::from(args.clone()));
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return Err(Box::new(ClientError::FailedLocalVersionCheck));
|
||||
}
|
||||
|
||||
SocketClient::new(config, args.common_args.custom_mixnet)
|
||||
.run_socket_forever()
|
||||
.await
|
||||
|
||||
@@ -17,6 +17,9 @@ pub enum ClientError {
|
||||
#[error("Failed to validate the loaded config")]
|
||||
ConfigValidationFailure,
|
||||
|
||||
#[error("Failed local version check, client and config mismatch")]
|
||||
FailedLocalVersionCheck,
|
||||
|
||||
#[error("Attempted to start the client in invalid socket mode")]
|
||||
InvalidSocketMode,
|
||||
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::try_load_current_config;
|
||||
use crate::commands::{override_config, OverrideConfig};
|
||||
use crate::config::Config;
|
||||
use crate::{
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::Socks5ClientError,
|
||||
};
|
||||
use clap::Args;
|
||||
use log::*;
|
||||
use nym_bin_common::version_checker::is_minor_version_compatible;
|
||||
use nym_client_core::cli_helpers::client_run::CommonClientRunArgs;
|
||||
use nym_client_core::client::base_client::storage::OnDiskPersistent;
|
||||
use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
|
||||
@@ -76,12 +82,38 @@ fn validate_country_group(s: &str) -> Result<CountryGroup, String> {
|
||||
}
|
||||
}
|
||||
|
||||
// this only checks compatibility between config the binary. It does not take into consideration
|
||||
// network version. It might do so in the future.
|
||||
fn version_check(cfg: &Config) -> bool {
|
||||
let binary_version = env!("CARGO_PKG_VERSION");
|
||||
let config_version = &cfg.core.base.client.version;
|
||||
if binary_version == config_version {
|
||||
true
|
||||
} else {
|
||||
warn!(
|
||||
"The socks5-client binary has different version than what is specified in config file! {binary_version} and {config_version}",
|
||||
);
|
||||
if is_minor_version_compatible(binary_version, config_version) {
|
||||
info!("but they are still semver compatible. However, consider running the `upgrade` command");
|
||||
true
|
||||
} else {
|
||||
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
eprintln!("Starting client {}...", args.common_args.id);
|
||||
|
||||
let mut config = try_load_current_config(&args.common_args.id).await?;
|
||||
config = override_config(config, OverrideConfig::from(args.clone()));
|
||||
|
||||
if !version_check(&config) {
|
||||
error!("failed the local version check");
|
||||
return Err(Box::new(Socks5ClientError::FailedLocalVersionCheck));
|
||||
}
|
||||
|
||||
let storage =
|
||||
OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug)
|
||||
.await?;
|
||||
|
||||
@@ -14,6 +14,9 @@ pub enum Socks5ClientError {
|
||||
#[error("Failed to validate the loaded config")]
|
||||
ConfigValidationFailure,
|
||||
|
||||
#[error("Failed local version check, client and config mismatch")]
|
||||
FailedLocalVersionCheck,
|
||||
|
||||
#[error("Fail to bind address")]
|
||||
FailToBindAddress,
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ const-str = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pretty_env_logger = { workspace = true }
|
||||
schemars = { workspace = true, features = ["preserve_order"], optional = true }
|
||||
semver.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
|
||||
@@ -43,5 +44,5 @@ tracing = [
|
||||
"tracing-opentelemetry",
|
||||
"opentelemetry",
|
||||
]
|
||||
clap = ["dep:clap", "dep:clap_complete", "dep:clap_complete_fig"]
|
||||
clap = [ "dep:clap", "dep:clap_complete", "dep:clap_complete_fig" ]
|
||||
models = []
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
pub mod build_information;
|
||||
pub mod logging;
|
||||
pub mod version_checker;
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
pub mod completions;
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use semver::Version;
|
||||
|
||||
/// Checks if the version is minor version compatible.
|
||||
///
|
||||
/// Checks whether given `version` is compatible with a given semantic version requirement `req`
|
||||
/// according to major-minor semver rules. The semantic version requirement can be passed as a full,
|
||||
/// concrete version number, because that's what we'll have in our Cargo.toml files (e.g. 0.3.2).
|
||||
/// The patch number in the requirement gets dropped and replaced with a wildcard (0.3.*) as all
|
||||
/// minor versions should be compatible with each other.
|
||||
pub fn is_minor_version_compatible(version: &str, req: &str) -> bool {
|
||||
let expected_version = match Version::parse(version) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let req_version = match Version::parse(req) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
expected_version.major == req_version.major && expected_version.minor == req_version.minor
|
||||
}
|
||||
|
||||
pub fn parse_version(raw_version: &str) -> Result<Version, semver::Error> {
|
||||
Version::parse(raw_version)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn version_0_3_0_is_compatible_with_requirement_0_3_x() {
|
||||
assert!(is_minor_version_compatible("0.3.0", "0.3.2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_0_3_1_is_compatible_with_minimum_requirement_0_3_x() {
|
||||
assert!(is_minor_version_compatible("0.3.1", "0.3.2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_0_3_2_is_compatible_with_minimum_requirement_0_3_x() {
|
||||
assert!(is_minor_version_compatible("0.3.2", "0.3.0"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_0_2_0_is_not_compatible_with_requirement_0_3_x() {
|
||||
assert!(!is_minor_version_compatible("0.2.0", "0.3.2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_0_4_0_is_not_compatible_with_requirement_0_3_x() {
|
||||
assert!(!is_minor_version_compatible("0.4.0", "0.3.2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_1_3_2_is_not_compatible_with_requirement_0_3_x() {
|
||||
assert!(!is_minor_version_compatible("1.3.2", "0.3.2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_0_4_0_rc_1_is_compatible_with_version_0_4_0_rc_1() {
|
||||
assert!(is_minor_version_compatible("0.4.0-rc.1", "0.4.0-rc.1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_false_on_foo_version() {
|
||||
assert!(!is_minor_version_compatible("foo", "0.3.2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_false_on_bar_version() {
|
||||
assert!(!is_minor_version_compatible("0.3.2", "bar"));
|
||||
}
|
||||
}
|
||||
@@ -514,10 +514,15 @@ where
|
||||
min_gateway_performance: config_topology.minimum_gateway_performance,
|
||||
},
|
||||
nym_api_urls,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
user_agent,
|
||||
)),
|
||||
config::TopologyStructure::GeoAware(group_by) => {
|
||||
Box::new(GeoAwareTopologyProvider::new(nym_api_urls, group_by))
|
||||
Box::new(GeoAwareTopologyProvider::new(
|
||||
nym_api_urls,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
group_by,
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -85,10 +85,15 @@ fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
|
||||
pub struct GeoAwareTopologyProvider {
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
filter_on: GroupBy,
|
||||
client_version: String,
|
||||
}
|
||||
|
||||
impl GeoAwareTopologyProvider {
|
||||
pub fn new(mut nym_api_urls: Vec<Url>, filter_on: GroupBy) -> GeoAwareTopologyProvider {
|
||||
pub fn new(
|
||||
mut nym_api_urls: Vec<Url>,
|
||||
client_version: String,
|
||||
filter_on: GroupBy,
|
||||
) -> GeoAwareTopologyProvider {
|
||||
log::info!(
|
||||
"Creating geo-aware topology provider with filter on {}",
|
||||
filter_on
|
||||
@@ -100,13 +105,14 @@ impl GeoAwareTopologyProvider {
|
||||
nym_api_urls[0].clone(),
|
||||
),
|
||||
filter_on,
|
||||
client_version,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_topology(&self) -> Option<NymTopology> {
|
||||
let mixnodes = match self
|
||||
.validator_client
|
||||
.get_all_basic_active_mixing_assigned_nodes()
|
||||
.get_all_basic_active_mixing_assigned_nodes(Some(self.client_version.clone()))
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
@@ -118,7 +124,7 @@ impl GeoAwareTopologyProvider {
|
||||
|
||||
let gateways = match self
|
||||
.validator_client
|
||||
.get_all_basic_entry_assigned_nodes()
|
||||
.get_all_basic_entry_assigned_nodes(Some(self.client_version.clone()))
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
|
||||
@@ -35,11 +35,18 @@ pub struct NymApiTopologyProvider {
|
||||
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
nym_api_urls: Vec<Url>,
|
||||
|
||||
client_version: String,
|
||||
currently_used_api: usize,
|
||||
}
|
||||
|
||||
impl NymApiTopologyProvider {
|
||||
pub fn new(config: Config, mut nym_api_urls: Vec<Url>, user_agent: Option<UserAgent>) -> Self {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
mut nym_api_urls: Vec<Url>,
|
||||
client_version: String,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Self {
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
let validator_client = if let Some(user_agent) = user_agent {
|
||||
@@ -55,6 +62,7 @@ impl NymApiTopologyProvider {
|
||||
config,
|
||||
validator_client,
|
||||
nym_api_urls,
|
||||
client_version,
|
||||
currently_used_api: 0,
|
||||
}
|
||||
}
|
||||
@@ -91,7 +99,7 @@ impl NymApiTopologyProvider {
|
||||
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
|
||||
let mixnodes = match self
|
||||
.validator_client
|
||||
.get_all_basic_active_mixing_assigned_nodes()
|
||||
.get_all_basic_active_mixing_assigned_nodes(Some(self.client_version.clone()))
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
@@ -103,7 +111,7 @@ impl NymApiTopologyProvider {
|
||||
|
||||
let gateways = match self
|
||||
.validator_client
|
||||
.get_all_basic_entry_assigned_nodes()
|
||||
.get_all_basic_entry_assigned_nodes(Some(self.client_version.clone()))
|
||||
.await
|
||||
{
|
||||
Err(err) => {
|
||||
|
||||
@@ -94,7 +94,7 @@ pub async fn current_gateways<R: Rng>(
|
||||
|
||||
log::debug!("Fetching list of gateways from: {nym_api}");
|
||||
|
||||
let gateways = client.get_all_basic_entry_assigned_nodes().await?;
|
||||
let gateways = client.get_all_basic_entry_assigned_nodes(None).await?;
|
||||
log::debug!("Found {} gateways", gateways.len());
|
||||
log::trace!("Gateways: {:#?}", gateways);
|
||||
|
||||
@@ -121,7 +121,9 @@ pub async fn current_mixnodes<R: Rng>(
|
||||
|
||||
log::trace!("Fetching list of mixnodes from: {nym_api}");
|
||||
|
||||
let mixnodes = client.get_all_basic_active_mixing_assigned_nodes().await?;
|
||||
let mixnodes = client
|
||||
.get_all_basic_active_mixing_assigned_nodes(None)
|
||||
.await?;
|
||||
let valid_mixnodes = mixnodes
|
||||
.iter()
|
||||
.filter_map(|mixnode| mixnode.try_into().ok())
|
||||
|
||||
@@ -87,10 +87,8 @@ impl ClientBandwidth {
|
||||
|
||||
if remaining < 0 {
|
||||
tracing::warn!("OUT OF BANDWIDTH. remaining: {remaining_bi2}");
|
||||
} else if remaining < 1_000_000 {
|
||||
tracing::info!("remaining bandwidth: {remaining_bi2}");
|
||||
} else {
|
||||
tracing::debug!("remaining bandwidth: {remaining_bi2}");
|
||||
tracing::info!("remaining bandwidth: {remaining_bi2}");
|
||||
}
|
||||
|
||||
self.inner
|
||||
|
||||
@@ -139,10 +139,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.gateway_identity
|
||||
}
|
||||
|
||||
pub fn shared_key(&self) -> Option<Arc<SharedGatewayKey>> {
|
||||
self.shared_key.clone()
|
||||
}
|
||||
|
||||
pub fn ws_fd(&self) -> Option<RawFd> {
|
||||
match &self.connection {
|
||||
SocketState::Available(conn) => ws_fd(conn.as_ref()),
|
||||
@@ -412,7 +408,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
Some(_) => {
|
||||
debug!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
info!("the gateway is using exactly the same (or older) protocol version as we are. We're good to continue!");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -996,6 +992,24 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[deprecated(note = "this method does not deal with upgraded keys for legacy clients")]
|
||||
pub async fn authenticate_and_start(
|
||||
&mut self,
|
||||
) -> Result<AuthenticationResponse, GatewayClientError>
|
||||
where
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
St: CredentialStorage,
|
||||
<St as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
{
|
||||
let shared_key = self.perform_initial_authentication().await?;
|
||||
self.claim_initial_bandwidth().await?;
|
||||
|
||||
// this call is NON-blocking
|
||||
self.start_listening_for_mixnet_messages()?;
|
||||
|
||||
Ok(shared_key)
|
||||
}
|
||||
}
|
||||
|
||||
// type alias for an ease of use
|
||||
|
||||
@@ -46,8 +46,7 @@ pub(crate) fn ws_fd(_conn: &WsConn) -> Option<RawFd> {
|
||||
#[cfg(unix)]
|
||||
match _conn.get_ref() {
|
||||
MaybeTlsStream::Plain(stream) => Some(stream.as_raw_fd()),
|
||||
MaybeTlsStream::Rustls(tls_stream) => Some(tls_stream.as_raw_fd()),
|
||||
_ => None,
|
||||
&_ => None,
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
None
|
||||
@@ -111,11 +110,6 @@ impl PartiallyDelegatedRouter {
|
||||
}
|
||||
};
|
||||
|
||||
if self.stream_return.is_canceled() {
|
||||
// nothing to do, receiver has been dropped
|
||||
return;
|
||||
}
|
||||
|
||||
let return_res = match ret {
|
||||
Err(err) => self.stream_return.send(Err(err)),
|
||||
Ok(_) => {
|
||||
|
||||
@@ -19,8 +19,8 @@ use nym_api_requests::ecash::{
|
||||
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
|
||||
};
|
||||
use nym_api_requests::models::{
|
||||
ApiHealthResponse, GatewayBondAnnotated, GatewayCoreStatusResponse, MixnodeCoreStatusResponse,
|
||||
MixnodeStatusResponse, NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
ApiHealthResponse, GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
|
||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||
use nym_api_requests::nym_nodes::SkimmedNode;
|
||||
@@ -257,13 +257,6 @@ impl<C, S> Client<C, S> {
|
||||
Ok(self.nym_api.get_gateways().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_gateways_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<GatewayBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_gateways_detailed_unfiltered().await?)
|
||||
}
|
||||
|
||||
// TODO: combine with NymApiClient...
|
||||
pub async fn get_all_cached_described_nodes(
|
||||
&self,
|
||||
@@ -358,19 +351,34 @@ impl NymApiClient {
|
||||
}
|
||||
|
||||
#[deprecated(note = "use get_all_basic_active_mixing_assigned_nodes instead")]
|
||||
pub async fn get_basic_mixnodes(&self) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_basic_mixnodes().await?.nodes)
|
||||
pub async fn get_basic_mixnodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.get_basic_mixnodes(semver_compatibility)
|
||||
.await?
|
||||
.nodes)
|
||||
}
|
||||
|
||||
#[deprecated(note = "use get_all_basic_entry_assigned_nodes instead")]
|
||||
pub async fn get_basic_gateways(&self) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_basic_gateways().await?.nodes)
|
||||
pub async fn get_basic_gateways(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
Ok(self
|
||||
.nym_api
|
||||
.get_basic_gateways(semver_compatibility)
|
||||
.await?
|
||||
.nodes)
|
||||
}
|
||||
|
||||
/// retrieve basic information for nodes are capable of operating as an entry gateway
|
||||
/// this includes legacy gateways and nym-nodes
|
||||
pub async fn get_all_basic_entry_assigned_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
@@ -379,7 +387,12 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_entry_assigned_nodes(false, Some(page), None)
|
||||
.get_basic_entry_assigned_nodes(
|
||||
semver_compatibility.clone(),
|
||||
false,
|
||||
Some(page),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
@@ -397,6 +410,7 @@ impl NymApiClient {
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
pub async fn get_all_basic_active_mixing_assigned_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
@@ -405,7 +419,12 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_active_mixing_assigned_nodes(false, Some(page), None)
|
||||
.get_basic_active_mixing_assigned_nodes(
|
||||
semver_compatibility.clone(),
|
||||
false,
|
||||
Some(page),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
@@ -423,6 +442,7 @@ impl NymApiClient {
|
||||
/// this includes legacy mixnodes and nym-nodes
|
||||
pub async fn get_all_basic_mixing_capable_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
@@ -431,7 +451,12 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_mixing_capable_nodes(false, Some(page), None)
|
||||
.get_basic_mixing_capable_nodes(
|
||||
semver_compatibility.clone(),
|
||||
false,
|
||||
Some(page),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
@@ -446,7 +471,10 @@ impl NymApiClient {
|
||||
}
|
||||
|
||||
/// retrieve basic information for all bonded nodes on the network
|
||||
pub async fn get_all_basic_nodes(&self) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
pub async fn get_all_basic_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
) -> Result<Vec<SkimmedNode>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut nodes = Vec::new();
|
||||
@@ -454,7 +482,7 @@ impl NymApiClient {
|
||||
loop {
|
||||
let mut res = self
|
||||
.nym_api
|
||||
.get_basic_nodes(false, Some(page), None)
|
||||
.get_basic_nodes(semver_compatibility.clone(), false, Some(page), None)
|
||||
.await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
|
||||
@@ -102,23 +102,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::GATEWAYS,
|
||||
routes::DETAILED_UNFILTERED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_detailed_unfiltered(
|
||||
@@ -205,7 +188,16 @@ pub trait NymApiClientExt: ApiClient {
|
||||
|
||||
#[deprecated]
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
async fn get_basic_mixnodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let params = if let Some(semver_compatibility) = &semver_compatibility {
|
||||
vec![("semver_compatibility", semver_compatibility.as_str())]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
@@ -214,14 +206,23 @@ pub trait NymApiClientExt: ApiClient {
|
||||
"mixnodes",
|
||||
"skimmed",
|
||||
],
|
||||
NO_PARAMS,
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_gateways(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
async fn get_basic_gateways(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let params = if let Some(semver_compatibility) = &semver_compatibility {
|
||||
vec![("semver_compatibility", semver_compatibility.as_str())]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
@@ -230,7 +231,7 @@ pub trait NymApiClientExt: ApiClient {
|
||||
"gateways",
|
||||
"skimmed",
|
||||
],
|
||||
NO_PARAMS,
|
||||
¶ms,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -240,12 +241,17 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_entry_assigned_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if let Some(arg) = &semver_compatibility {
|
||||
params.push(("semver_compatibility", arg.clone()))
|
||||
}
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
@@ -277,12 +283,17 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_active_mixing_assigned_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if let Some(arg) = &semver_compatibility {
|
||||
params.push(("semver_compatibility", arg.clone()))
|
||||
}
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
@@ -314,12 +325,17 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_mixing_capable_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if let Some(arg) = &semver_compatibility {
|
||||
params.push(("semver_compatibility", arg.clone()))
|
||||
}
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
@@ -349,12 +365,17 @@ pub trait NymApiClientExt: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_basic_nodes(
|
||||
&self,
|
||||
semver_compatibility: Option<String>,
|
||||
no_legacy: bool,
|
||||
page: Option<u32>,
|
||||
per_page: Option<u32>,
|
||||
) -> Result<PaginatedCachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
let mut params = Vec::new();
|
||||
|
||||
if let Some(arg) = &semver_compatibility {
|
||||
params.push(("semver_compatibility", arg.clone()))
|
||||
}
|
||||
|
||||
if no_legacy {
|
||||
params.push(("no_legacy", "true".to_string()))
|
||||
}
|
||||
|
||||
+4
-32
@@ -26,11 +26,10 @@ use nym_mixnet_contract_common::{
|
||||
reward_params::{Performance, RewardingParams},
|
||||
rewarding::{EstimatedCurrentEpochRewardResponse, PendingRewardResponse},
|
||||
ContractBuildInformation, ContractState, ContractStateParams, CurrentIntervalResponse,
|
||||
CurrentNymNodeVersionResponse, Delegation, EpochEventId, EpochStatus, GatewayBond,
|
||||
GatewayBondResponse, GatewayOwnershipResponse, HistoricalNymNodeVersionEntry, IdentityKey,
|
||||
IdentityKeyRef, IntervalEventId, MixNodeBond, MixNodeDetails, MixOwnershipResponse,
|
||||
MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse, NodeId,
|
||||
NumberOfPendingEventsResponse, NymNodeBond, NymNodeDetails, NymNodeVersionHistoryResponse,
|
||||
Delegation, EpochEventId, EpochStatus, GatewayBond, GatewayBondResponse,
|
||||
GatewayOwnershipResponse, IdentityKey, IdentityKeyRef, IntervalEventId, MixNodeBond,
|
||||
MixNodeDetails, MixOwnershipResponse, MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse,
|
||||
NodeId, NumberOfPendingEventsResponse, NymNodeBond, NymNodeDetails,
|
||||
PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse,
|
||||
PagedMixnodeBondsResponse, PagedNodeDelegationsResponse, PendingEpochEvent,
|
||||
PendingEpochEventResponse, PendingEpochEventsResponse, PendingIntervalEvent,
|
||||
@@ -72,22 +71,6 @@ pub trait MixnetQueryClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_nym_node_version_history_paged(
|
||||
&self,
|
||||
start_after: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<NymNodeVersionHistoryResponse, NyxdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetNymNodeVersionHistory { limit, start_after })
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_current_nym_node_version(
|
||||
&self,
|
||||
) -> Result<CurrentNymNodeVersionResponse, NyxdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetCurrentNymNodeVersion {})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_mixnet_contract_state(&self) -> Result<ContractState, NyxdError> {
|
||||
self.query_mixnet_contract(MixnetQueryMsg::GetState {})
|
||||
.await
|
||||
@@ -655,12 +638,6 @@ pub trait PagedMixnetQueryClient: MixnetQueryClient {
|
||||
) -> Result<Vec<PendingIntervalEvent>, NyxdError> {
|
||||
collect_paged!(self, get_pending_interval_events_paged, events)
|
||||
}
|
||||
|
||||
async fn get_full_nym_node_version_history(
|
||||
&self,
|
||||
) -> Result<Vec<HistoricalNymNodeVersionEntry>, NyxdError> {
|
||||
collect_paged!(self, get_nym_node_version_history_paged, history)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -747,7 +724,6 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nyxd::contract_traits::tests::IgnoreValue;
|
||||
use nym_mixnet_contract_common::QueryMsg;
|
||||
|
||||
// it's enough that this compiles and clippy is happy about it
|
||||
#[allow(dead_code)]
|
||||
@@ -948,10 +924,6 @@ mod tests {
|
||||
MixnetQueryMsg::GetRewardedSetMetadata {} => {
|
||||
client.get_rewarded_set_metadata().ignore()
|
||||
}
|
||||
QueryMsg::GetCurrentNymNodeVersion {} => client.get_current_nym_node_version().ignore(),
|
||||
QueryMsg::GetNymNodeVersionHistory { limit, start_after } => client
|
||||
.get_nym_node_version_history_paged(start_after, limit)
|
||||
.ignore(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,673 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Decimal;
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
#[cw_serde]
|
||||
pub struct HistoricalNymNodeVersion {
|
||||
/// Version of the nym node that is going to be used for determining the version score of a node.
|
||||
/// note: value stored here is pre-validated `semver::Version`
|
||||
pub semver: String,
|
||||
|
||||
/// Block height of when this version has been added to the contract
|
||||
pub introduced_at_height: u64,
|
||||
|
||||
/// The absolute version difference as compared against the first version introduced into the contract.
|
||||
pub difference_since_genesis: TotalVersionDifference,
|
||||
}
|
||||
|
||||
impl HistoricalNymNodeVersion {
|
||||
pub fn genesis(semver: String, height: u64) -> HistoricalNymNodeVersion {
|
||||
HistoricalNymNodeVersion {
|
||||
semver,
|
||||
introduced_at_height: height,
|
||||
difference_since_genesis: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: the value stored in the contract is always valid
|
||||
// if you manually construct that struct with invalid value, it's on you.
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn semver_unchecked(&self) -> semver::Version {
|
||||
self.semver.parse().unwrap()
|
||||
}
|
||||
|
||||
/// Return [`TotalVersionDifference`] for a new release version that is going to be pushed right after this one
|
||||
/// this function cannot be called against 2 arbitrary versions
|
||||
#[inline]
|
||||
pub fn cumulative_difference_since_genesis(
|
||||
&self,
|
||||
new_version: &semver::Version,
|
||||
) -> TotalVersionDifference {
|
||||
let self_semver = self.semver_unchecked();
|
||||
let mut new_absolute = self.difference_since_genesis;
|
||||
if new_version.major > self_semver.major {
|
||||
new_absolute.major += (new_version.major - self_semver.major) as u32
|
||||
} else if new_version.minor > self_semver.minor {
|
||||
new_absolute.minor += (new_version.minor - self_semver.minor) as u32
|
||||
} else if new_version.patch > self_semver.patch {
|
||||
new_absolute.patch += (new_version.patch - self_semver.patch) as u32
|
||||
} else if new_version.pre != self_semver.pre {
|
||||
new_absolute.prerelease += 1
|
||||
}
|
||||
new_absolute
|
||||
}
|
||||
|
||||
pub fn relative_difference(&self, other: &Self) -> TotalVersionDifference {
|
||||
if self.difference_since_genesis > other.difference_since_genesis {
|
||||
self.difference_since_genesis - other.difference_since_genesis
|
||||
} else {
|
||||
other.difference_since_genesis - self.difference_since_genesis
|
||||
}
|
||||
}
|
||||
|
||||
pub fn difference_against_legacy(
|
||||
&self,
|
||||
legacy_version: &semver::Version,
|
||||
) -> TotalVersionDifference {
|
||||
let current = self.semver_unchecked();
|
||||
let major_diff = (current.major as i64 - legacy_version.major as i64).unsigned_abs() as u32;
|
||||
let minor_diff = (current.minor as i64 - legacy_version.minor as i64).unsigned_abs() as u32;
|
||||
let patch_diff = (current.patch as i64 - legacy_version.patch as i64).unsigned_abs() as u32;
|
||||
let prerelease_diff = if current.pre == legacy_version.pre {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let mut diff = TotalVersionDifference::default();
|
||||
// if there's a major increase, ignore minor and patch and treat it as 0
|
||||
if major_diff != 0 {
|
||||
diff.major += major_diff;
|
||||
return diff;
|
||||
}
|
||||
|
||||
// if there's a minor increase, ignore patch and treat is as 0
|
||||
if minor_diff != 0 {
|
||||
diff.minor += minor_diff;
|
||||
return diff;
|
||||
}
|
||||
|
||||
diff.patch = patch_diff;
|
||||
diff.prerelease = prerelease_diff;
|
||||
diff
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(Default, Copy, PartialOrd, Ord, Eq)]
|
||||
pub struct TotalVersionDifference {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
pub patch: u32,
|
||||
pub prerelease: u32,
|
||||
}
|
||||
|
||||
impl Add for TotalVersionDifference {
|
||||
type Output = TotalVersionDifference;
|
||||
fn add(self, rhs: TotalVersionDifference) -> Self::Output {
|
||||
TotalVersionDifference {
|
||||
major: self.major.add(rhs.major),
|
||||
minor: self.minor.add(rhs.minor),
|
||||
patch: self.patch.add(rhs.patch),
|
||||
prerelease: self.prerelease.add(rhs.prerelease),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for TotalVersionDifference {
|
||||
type Output = TotalVersionDifference;
|
||||
fn sub(self, rhs: TotalVersionDifference) -> Self::Output {
|
||||
TotalVersionDifference {
|
||||
major: self.major.saturating_sub(rhs.major),
|
||||
minor: self.minor.saturating_sub(rhs.minor),
|
||||
patch: self.patch.saturating_sub(rhs.patch),
|
||||
prerelease: self.prerelease.saturating_sub(rhs.prerelease),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct HistoricalNymNodeVersionEntry {
|
||||
/// The unique, ordered, id of this particular entry
|
||||
pub id: u32,
|
||||
|
||||
/// Data associated with this particular version
|
||||
pub version_information: HistoricalNymNodeVersion,
|
||||
}
|
||||
|
||||
impl From<(u32, HistoricalNymNodeVersion)> for HistoricalNymNodeVersionEntry {
|
||||
fn from((id, version_information): (u32, HistoricalNymNodeVersion)) -> Self {
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id,
|
||||
version_information,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for HistoricalNymNodeVersionEntry {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
// we only care about id for the purposes of ordering as they should have unique data
|
||||
self.id.partial_cmp(&other.id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct NymNodeVersionHistoryResponse {
|
||||
pub history: Vec<HistoricalNymNodeVersionEntry>,
|
||||
|
||||
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
|
||||
pub start_next_after: Option<u32>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct CurrentNymNodeVersionResponse {
|
||||
pub version: Option<HistoricalNymNodeVersionEntry>,
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct ConfigScoreParams {
|
||||
/// Defines weights for calculating numbers of versions behind the current release.
|
||||
pub version_weights: OutdatedVersionWeights,
|
||||
|
||||
/// Defines the parameters of the formula for calculating the version score
|
||||
pub version_score_formula_params: VersionScoreFormulaParams,
|
||||
}
|
||||
|
||||
/// Defines weights for calculating numbers of versions behind the current release.
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub struct OutdatedVersionWeights {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
pub patch: u32,
|
||||
pub prerelease: u32,
|
||||
}
|
||||
|
||||
fn is_one_semver_difference(this: &semver::Version, other: &semver::Version) -> bool {
|
||||
let major_diff = (this.major as i64 - other.major as i64).unsigned_abs() as u32;
|
||||
let minor_diff = (this.minor as i64 - other.minor as i64).unsigned_abs() as u32;
|
||||
let patch_diff = (this.patch as i64 - other.patch as i64).unsigned_abs() as u32;
|
||||
let prerelease_diff = if this.pre == other.pre { 0 } else { 1 };
|
||||
|
||||
if major_diff == 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
if major_diff == 0 && minor_diff == 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
if major_diff == 0 && minor_diff == 0 && patch_diff == 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
prerelease_diff == 1
|
||||
}
|
||||
|
||||
impl OutdatedVersionWeights {
|
||||
pub fn difference_to_versions_behind_factor(&self, diff: TotalVersionDifference) -> u32 {
|
||||
diff.major * self.major
|
||||
+ diff.minor * self.minor
|
||||
+ diff.patch * self.patch
|
||||
+ diff.prerelease * self.prerelease
|
||||
}
|
||||
|
||||
// INVARIANT: release chain is sorted
|
||||
// do NOT call this method directly from inside the contract. it's too inefficient
|
||||
// it relies on some external caching.
|
||||
pub fn versions_behind_factor(
|
||||
&self,
|
||||
node_version: &semver::Version,
|
||||
release_chain: &[HistoricalNymNodeVersionEntry],
|
||||
) -> u32 {
|
||||
let Some(latest) = release_chain.last() else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let latest_semver = latest.version_information.semver_unchecked();
|
||||
|
||||
// if you're more recent than the latest, you get the benefit of the doubt, the release might have not yet been commited to the chain
|
||||
// but only if you're only a single semver ahead, otherwise you get penalty equivalent of being major version behind for cheating
|
||||
if node_version > &latest_semver {
|
||||
return if is_one_semver_difference(node_version, &latest_semver) {
|
||||
0
|
||||
} else {
|
||||
self.major
|
||||
};
|
||||
}
|
||||
|
||||
// find your position in the release chain, if we fail, we assume that the node comes from before the changes were introduced
|
||||
// in which case we simply calculate the absolute difference between the genesis entry and add up the total difference
|
||||
let version_diff = match release_chain
|
||||
.iter()
|
||||
.rfind(|h| &h.version_information.semver_unchecked() <= node_version)
|
||||
{
|
||||
Some(h) => {
|
||||
// first chain entry that is smaller (or equal) to the provided node version
|
||||
// now, calculate the difference to the genesis version and ultimately against the current head
|
||||
let diff_since_genesis = if h.version_information.semver == node_version.to_string()
|
||||
{
|
||||
h.version_information.difference_since_genesis
|
||||
} else {
|
||||
h.version_information
|
||||
.cumulative_difference_since_genesis(node_version)
|
||||
};
|
||||
latest.version_information.difference_since_genesis - diff_since_genesis
|
||||
}
|
||||
None => {
|
||||
// SAFETY: since we managed to get 'last' entry, it means the release chain is not empty,
|
||||
// so we must be able to obtain the first entry
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let genesis = release_chain.first().unwrap();
|
||||
|
||||
let difference_from_genesis = genesis
|
||||
.version_information
|
||||
.difference_against_legacy(node_version);
|
||||
difference_from_genesis + latest.version_information.difference_since_genesis
|
||||
}
|
||||
};
|
||||
|
||||
self.difference_to_versions_behind_factor(version_diff)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OutdatedVersionWeights {
|
||||
fn default() -> Self {
|
||||
OutdatedVersionWeights {
|
||||
major: 100,
|
||||
minor: 10,
|
||||
patch: 1,
|
||||
prerelease: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling)
|
||||
/// define the relevant parameters
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub struct VersionScoreFormulaParams {
|
||||
pub penalty: Decimal,
|
||||
pub penalty_scaling: Decimal,
|
||||
}
|
||||
|
||||
impl Default for VersionScoreFormulaParams {
|
||||
fn default() -> Self {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
VersionScoreFormulaParams {
|
||||
penalty: "0.995".parse().unwrap(),
|
||||
penalty_scaling: "1.65".parse().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ops::Deref;
|
||||
|
||||
// simple wrapper for tests
|
||||
struct ReleaseChain {
|
||||
inner: Vec<HistoricalNymNodeVersionEntry>,
|
||||
}
|
||||
|
||||
impl Deref for ReleaseChain {
|
||||
type Target = [HistoricalNymNodeVersionEntry];
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl ReleaseChain {
|
||||
fn new(initial: &str) -> Self {
|
||||
ReleaseChain {
|
||||
inner: vec![HistoricalNymNodeVersionEntry {
|
||||
id: 0,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: initial.to_string(),
|
||||
introduced_at_height: 123,
|
||||
difference_since_genesis: TotalVersionDifference::default(),
|
||||
},
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
fn with_release(mut self, raw: &str) -> Self {
|
||||
self.push_new(raw);
|
||||
self
|
||||
}
|
||||
|
||||
fn push_new(&mut self, raw: &str) {
|
||||
let latest = self.inner.last().unwrap();
|
||||
let new_version: semver::Version = raw.parse().unwrap();
|
||||
|
||||
let new_absolute = latest
|
||||
.version_information
|
||||
.cumulative_difference_since_genesis(&new_version);
|
||||
|
||||
self.inner.push(HistoricalNymNodeVersionEntry {
|
||||
id: latest.id + 1,
|
||||
version_information: HistoricalNymNodeVersion {
|
||||
semver: new_version.to_string(),
|
||||
introduced_at_height: latest.version_information.introduced_at_height + 1,
|
||||
difference_since_genesis: new_absolute,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn versions_behind_factor() {
|
||||
// helper to compact the parsing
|
||||
fn s(raw: &str) -> semver::Version {
|
||||
raw.parse().unwrap()
|
||||
}
|
||||
|
||||
let weights = OutdatedVersionWeights::default();
|
||||
|
||||
// no releases:
|
||||
let res = weights.versions_behind_factor(&s("1.1.13"), &[]);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// ###############################
|
||||
// single released version (1.1.13)
|
||||
// ###############################
|
||||
let mut release_chain = ReleaseChain::new("1.1.13");
|
||||
|
||||
// "legacy" versions
|
||||
let res = weights.versions_behind_factor(&s("1.0.12"), &release_chain);
|
||||
assert_eq!(10, res);
|
||||
let res = weights.versions_behind_factor(&s("1.0.4"), &release_chain);
|
||||
assert_eq!(10, res);
|
||||
let res = weights.versions_behind_factor(&s("1.0.1"), &release_chain);
|
||||
assert_eq!(10, res);
|
||||
let res = weights.versions_behind_factor(&s("0.1.12"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
|
||||
let res = weights.versions_behind_factor(&s("1.1.12"), &release_chain);
|
||||
assert_eq!(1, res);
|
||||
let res = weights.versions_behind_factor(&s("1.1.11"), &release_chain);
|
||||
assert_eq!(2, res);
|
||||
let res = weights.versions_behind_factor(&s("1.1.9"), &release_chain);
|
||||
assert_eq!(4, res);
|
||||
|
||||
// current version
|
||||
let res = weights.versions_behind_factor(&s("1.1.13"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// "ahead" versions
|
||||
let res = weights.versions_behind_factor(&s("1.1.14"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
let res = weights.versions_behind_factor(&s("1.2.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
let res = weights.versions_behind_factor(&s("2.0.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// cheating ahead:
|
||||
let res = weights.versions_behind_factor(&s("1.1.15"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
let res = weights.versions_behind_factor(&s("1.3.0"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
let res = weights.versions_behind_factor(&s("3.0.0"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
|
||||
// ###############################
|
||||
// small patch release chain (1.1.13 => 1.1.14 => 1.1.15 => 1.1.16)
|
||||
// ###############################
|
||||
release_chain.push_new("1.1.14");
|
||||
release_chain.push_new("1.1.15");
|
||||
release_chain.push_new("1.1.16");
|
||||
|
||||
// "legacy" versions
|
||||
let res = weights.versions_behind_factor(&s("1.0.12"), &release_chain);
|
||||
assert_eq!(13, res);
|
||||
let res = weights.versions_behind_factor(&s("1.0.4"), &release_chain);
|
||||
assert_eq!(13, res);
|
||||
let res = weights.versions_behind_factor(&s("1.0.1"), &release_chain);
|
||||
assert_eq!(13, res);
|
||||
let res = weights.versions_behind_factor(&s("0.1.12"), &release_chain);
|
||||
assert_eq!(103, res);
|
||||
|
||||
let res = weights.versions_behind_factor(&s("1.1.12"), &release_chain);
|
||||
assert_eq!(4, res);
|
||||
let res = weights.versions_behind_factor(&s("1.1.11"), &release_chain);
|
||||
assert_eq!(5, res);
|
||||
let res = weights.versions_behind_factor(&s("1.1.9"), &release_chain);
|
||||
assert_eq!(7, res);
|
||||
|
||||
// current version
|
||||
let res = weights.versions_behind_factor(&s("1.1.16"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// present in the chain
|
||||
let res = weights.versions_behind_factor(&s("1.1.15"), &release_chain);
|
||||
assert_eq!(1, res);
|
||||
let res = weights.versions_behind_factor(&s("1.1.14"), &release_chain);
|
||||
assert_eq!(2, res);
|
||||
let res = weights.versions_behind_factor(&s("1.1.13"), &release_chain);
|
||||
assert_eq!(3, res);
|
||||
|
||||
// "ahead" versions
|
||||
let res = weights.versions_behind_factor(&s("1.1.17"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
let res = weights.versions_behind_factor(&s("1.2.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
let res = weights.versions_behind_factor(&s("2.0.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// cheating ahead:
|
||||
let res = weights.versions_behind_factor(&s("1.1.18"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
let res = weights.versions_behind_factor(&s("1.3.0"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
let res = weights.versions_behind_factor(&s("3.0.0"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
|
||||
// ###############################
|
||||
// small minor release chain (1.2.0 => 1.3.0 => 1.4.0)
|
||||
// ###############################
|
||||
let release_chain = ReleaseChain::new("1.2.0")
|
||||
.with_release("1.3.0")
|
||||
.with_release("1.4.0");
|
||||
|
||||
// "legacy" versions
|
||||
let res = weights.versions_behind_factor(&s("1.0.12"), &release_chain);
|
||||
assert_eq!(40, res);
|
||||
let res = weights.versions_behind_factor(&s("1.0.4"), &release_chain);
|
||||
assert_eq!(40, res);
|
||||
let res = weights.versions_behind_factor(&s("1.0.1"), &release_chain);
|
||||
assert_eq!(40, res);
|
||||
let res = weights.versions_behind_factor(&s("0.1.12"), &release_chain);
|
||||
assert_eq!(120, res);
|
||||
|
||||
let res = weights.versions_behind_factor(&s("1.1.12"), &release_chain);
|
||||
assert_eq!(30, res);
|
||||
let res = weights.versions_behind_factor(&s("1.1.11"), &release_chain);
|
||||
assert_eq!(30, res);
|
||||
let res = weights.versions_behind_factor(&s("1.1.9"), &release_chain);
|
||||
assert_eq!(30, res);
|
||||
|
||||
// current version
|
||||
let res = weights.versions_behind_factor(&s("1.4.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// present in the chain
|
||||
let res = weights.versions_behind_factor(&s("1.2.0"), &release_chain);
|
||||
assert_eq!(20, res);
|
||||
let res = weights.versions_behind_factor(&s("1.3.0"), &release_chain);
|
||||
assert_eq!(10, res);
|
||||
|
||||
// weird in between
|
||||
let res = weights.versions_behind_factor(&s("1.2.1"), &release_chain);
|
||||
assert_eq!(20, res);
|
||||
let res = weights.versions_behind_factor(&s("1.3.3"), &release_chain);
|
||||
assert_eq!(10, res);
|
||||
|
||||
// "ahead" versions
|
||||
let res = weights.versions_behind_factor(&s("1.4.1"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
let res = weights.versions_behind_factor(&s("1.5.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
let res = weights.versions_behind_factor(&s("2.0.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// cheating ahead:
|
||||
let res = weights.versions_behind_factor(&s("1.4.2"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
let res = weights.versions_behind_factor(&s("1.6.0"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
let res = weights.versions_behind_factor(&s("3.0.0"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
|
||||
// ###############################
|
||||
// mixed release chain (1.1.13 => 1.2.0 => 1.2.1 => 1.3.0 => 1.3.1 => 1.3.2 => 1.4.0)
|
||||
// ###############################
|
||||
let release_chain = ReleaseChain::new("1.1.13")
|
||||
.with_release("1.2.0")
|
||||
.with_release("1.2.1")
|
||||
.with_release("1.3.0")
|
||||
.with_release("1.3.1-importantpre")
|
||||
.with_release("1.3.1")
|
||||
.with_release("1.3.2")
|
||||
.with_release("1.4.0");
|
||||
|
||||
// "legacy" versions
|
||||
let res = weights.versions_behind_factor(&s("1.0.12"), &release_chain);
|
||||
assert_eq!(44, res);
|
||||
let res = weights.versions_behind_factor(&s("1.0.4"), &release_chain);
|
||||
assert_eq!(44, res);
|
||||
let res = weights.versions_behind_factor(&s("1.0.1"), &release_chain);
|
||||
assert_eq!(44, res);
|
||||
let res = weights.versions_behind_factor(&s("0.1.12"), &release_chain);
|
||||
assert_eq!(134, res);
|
||||
|
||||
let res = weights.versions_behind_factor(&s("1.1.12"), &release_chain);
|
||||
assert_eq!(35, res);
|
||||
let res = weights.versions_behind_factor(&s("1.1.11"), &release_chain);
|
||||
assert_eq!(36, res);
|
||||
let res = weights.versions_behind_factor(&s("1.1.9"), &release_chain);
|
||||
assert_eq!(38, res);
|
||||
|
||||
// current version
|
||||
let res = weights.versions_behind_factor(&s("1.4.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// present in the chain
|
||||
let res = weights.versions_behind_factor(&s("1.1.13"), &release_chain);
|
||||
assert_eq!(34, res);
|
||||
let res = weights.versions_behind_factor(&s("1.2.0"), &release_chain);
|
||||
assert_eq!(24, res);
|
||||
let res = weights.versions_behind_factor(&s("1.2.1"), &release_chain);
|
||||
assert_eq!(23, res);
|
||||
let res = weights.versions_behind_factor(&s("1.3.0"), &release_chain);
|
||||
assert_eq!(13, res);
|
||||
let res = weights.versions_behind_factor(&s("1.3.1-importantpre"), &release_chain);
|
||||
assert_eq!(12, res);
|
||||
let res = weights.versions_behind_factor(&s("1.3.1"), &release_chain);
|
||||
assert_eq!(11, res);
|
||||
let res = weights.versions_behind_factor(&s("1.3.2"), &release_chain);
|
||||
assert_eq!(10, res);
|
||||
|
||||
// weird in between
|
||||
let res = weights.versions_behind_factor(&s("1.2.3"), &release_chain);
|
||||
assert_eq!(21, res);
|
||||
let res = weights.versions_behind_factor(&s("1.3.69"), &release_chain);
|
||||
assert_eq!(10, res);
|
||||
|
||||
// "ahead" versions
|
||||
let res = weights.versions_behind_factor(&s("1.4.1"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
let res = weights.versions_behind_factor(&s("1.5.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
let res = weights.versions_behind_factor(&s("2.0.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// cheating ahead:
|
||||
let res = weights.versions_behind_factor(&s("1.4.2"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
let res = weights.versions_behind_factor(&s("1.6.0"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
let res = weights.versions_behind_factor(&s("3.0.0"), &release_chain);
|
||||
assert_eq!(100, res);
|
||||
|
||||
// ###############################
|
||||
// skipped patch chain (1.1.13 => 1.2.0 => 1.2.1 => 1.2.4 => [1.3.0])
|
||||
// ###############################
|
||||
let mut release_chain = ReleaseChain::new("1.1.13")
|
||||
.with_release("1.2.0")
|
||||
.with_release("1.2.1")
|
||||
.with_release("1.2.4");
|
||||
|
||||
// current
|
||||
let res = weights.versions_behind_factor(&s("1.2.4"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// on 'skipped' version
|
||||
let res = weights.versions_behind_factor(&s("1.2.2"), &release_chain);
|
||||
assert_eq!(2, res);
|
||||
|
||||
// on version before the skip
|
||||
let res = weights.versions_behind_factor(&s("1.2.1"), &release_chain);
|
||||
assert_eq!(3, res);
|
||||
|
||||
release_chain.push_new("1.3.0");
|
||||
// current
|
||||
let res = weights.versions_behind_factor(&s("1.3.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// on 'skipped' version
|
||||
let res = weights.versions_behind_factor(&s("1.2.2"), &release_chain);
|
||||
assert_eq!(12, res);
|
||||
|
||||
// on version before the skip
|
||||
let res = weights.versions_behind_factor(&s("1.2.1"), &release_chain);
|
||||
assert_eq!(13, res);
|
||||
|
||||
// ###############################
|
||||
// skipped minor chain (1.1.13 => 1.2.0 => 1.2.1 => 1.4.0 => [1.5.0])
|
||||
// ###############################
|
||||
let mut release_chain = ReleaseChain::new("1.1.13")
|
||||
.with_release("1.2.0")
|
||||
.with_release("1.2.1")
|
||||
.with_release("1.4.0");
|
||||
|
||||
// current
|
||||
let res = weights.versions_behind_factor(&s("1.4.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
// on 'skipped' version
|
||||
let res = weights.versions_behind_factor(&s("1.3.0"), &release_chain);
|
||||
assert_eq!(10, res);
|
||||
|
||||
// on version before the skip
|
||||
let res = weights.versions_behind_factor(&s("1.2.1"), &release_chain);
|
||||
assert_eq!(20, res);
|
||||
|
||||
let res = weights.versions_behind_factor(&s("1.2.0"), &release_chain);
|
||||
assert_eq!(21, res);
|
||||
|
||||
release_chain.push_new("1.5.0");
|
||||
|
||||
// current
|
||||
let res = weights.versions_behind_factor(&s("1.5.0"), &release_chain);
|
||||
assert_eq!(0, res);
|
||||
|
||||
let res = weights.versions_behind_factor(&s("1.4.0"), &release_chain);
|
||||
assert_eq!(10, res);
|
||||
|
||||
// on 'skipped' version
|
||||
let res = weights.versions_behind_factor(&s("1.3.0"), &release_chain);
|
||||
assert_eq!(20, res);
|
||||
|
||||
// on version before the skip
|
||||
let res = weights.versions_behind_factor(&s("1.2.1"), &release_chain);
|
||||
assert_eq!(30, res);
|
||||
|
||||
let res = weights.versions_behind_factor(&s("1.2.0"), &release_chain);
|
||||
assert_eq!(31, res);
|
||||
}
|
||||
}
|
||||
@@ -275,9 +275,6 @@ pub enum MixnetContractError {
|
||||
|
||||
#[error("the provided nym-node version is not a valid semver. got: {provided}")]
|
||||
InvalidNymNodeSemver { provided: String },
|
||||
|
||||
#[error("the provided nym-node version is not greater than the current one. got: {provided}. current: {current}")]
|
||||
NonIncreasingSemver { provided: String, current: String },
|
||||
}
|
||||
|
||||
impl MixnetContractError {
|
||||
|
||||
@@ -141,7 +141,6 @@ pub const NEW_INTERVAL_OPERATING_COST_RANGE_KEY: &str = "new_interval_operating_
|
||||
pub const NEW_VERSION_WEIGHTS_RANGE_KEY: &str = "new_version_weights_range";
|
||||
pub const NEW_VERSION_SCORE_FORMULA_PARAMS_KEY: &str = "new_version_score_formula_params";
|
||||
pub const NYM_NODE_CURRENT_SEMVER_KEY: &str = "new_current_semver";
|
||||
pub const NYM_NODE_CURRENT_SEMVER_ID_KEY: &str = "new_current_semver_id";
|
||||
|
||||
pub const OLD_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "old_rewarding_validator_address";
|
||||
pub const NEW_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "new_rewarding_validator_address";
|
||||
@@ -482,6 +481,12 @@ pub fn new_settings_update_event(update: &ContractStateParamsUpdate) -> Event {
|
||||
|
||||
// check for config score params updates
|
||||
if let Some(config_score_update) = &update.config_score_params {
|
||||
if let Some(current_nym_node_semver) = &config_score_update.current_nym_node_semver {
|
||||
event.attributes.push(attr(
|
||||
NYM_NODE_CURRENT_SEMVER_KEY,
|
||||
current_nym_node_semver.to_string(),
|
||||
))
|
||||
}
|
||||
if let Some(version_weights) = &config_score_update.version_weights {
|
||||
event.attributes.push(attr(
|
||||
NEW_VERSION_WEIGHTS_RANGE_KEY,
|
||||
@@ -501,10 +506,9 @@ pub fn new_settings_update_event(update: &ContractStateParamsUpdate) -> Event {
|
||||
event
|
||||
}
|
||||
|
||||
pub fn new_update_nym_node_semver_event(new_version: &str, new_id: u32) -> Event {
|
||||
pub fn new_update_nym_node_semver_event(new_version: &str) -> Event {
|
||||
Event::new(MixnetEventType::NymNodeSemverUpdate)
|
||||
.add_attribute(NYM_NODE_CURRENT_SEMVER_KEY, new_version)
|
||||
.add_attribute(NYM_NODE_CURRENT_SEMVER_ID_KEY, new_id.to_string())
|
||||
}
|
||||
|
||||
pub fn new_not_found_node_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#![warn(clippy::unwrap_used)]
|
||||
#![warn(clippy::todo)]
|
||||
|
||||
mod config_score;
|
||||
pub mod constants;
|
||||
pub mod delegation;
|
||||
pub mod error;
|
||||
@@ -22,7 +21,6 @@ pub mod rewarding;
|
||||
pub mod signing_types;
|
||||
pub mod types;
|
||||
|
||||
pub use config_score::*;
|
||||
pub use constants::*;
|
||||
pub use contracts_common::types::*;
|
||||
pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction};
|
||||
|
||||
@@ -25,7 +25,6 @@ use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "schema")]
|
||||
use crate::{
|
||||
config_score::{CurrentNymNodeVersionResponse, NymNodeVersionHistoryResponse},
|
||||
delegation::{
|
||||
NodeDelegationResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
|
||||
PagedNodeDelegationsResponse,
|
||||
@@ -424,20 +423,6 @@ pub enum QueryMsg {
|
||||
#[cfg_attr(feature = "schema", returns(ContractState))]
|
||||
GetState {},
|
||||
|
||||
/// Get the current expected version of a Nym Node.
|
||||
#[cfg_attr(feature = "schema", returns(CurrentNymNodeVersionResponse))]
|
||||
GetCurrentNymNodeVersion {},
|
||||
|
||||
/// Get the version history of Nym Node.
|
||||
#[cfg_attr(feature = "schema", returns(NymNodeVersionHistoryResponse))]
|
||||
GetNymNodeVersionHistory {
|
||||
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
|
||||
limit: Option<u32>,
|
||||
|
||||
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
|
||||
start_after: Option<u32>,
|
||||
},
|
||||
|
||||
/// Gets the current parameters used for reward calculation.
|
||||
#[cfg_attr(feature = "schema", returns(RewardingParams))]
|
||||
GetRewardingParams {},
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config_score::{ConfigScoreParams, OutdatedVersionWeights, VersionScoreFormulaParams};
|
||||
use crate::nym_node::Role;
|
||||
use contracts_common::Percent;
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::Coin;
|
||||
use cosmwasm_std::{Addr, Uint128};
|
||||
use cosmwasm_std::{Coin, Decimal};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
// type aliases for better reasoning about available data
|
||||
@@ -222,14 +221,96 @@ impl OperatorsParamsUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct ConfigScoreParams {
|
||||
/// Current version of the nym node that is going to be used for determining the version score of a node.
|
||||
/// note: value stored here is pre-validated `semver::Version`
|
||||
pub current_nym_node_semver: String,
|
||||
|
||||
/// Defines weights for calculating numbers of versions behind the current release.
|
||||
pub version_weights: OutdatedVersionWeights,
|
||||
|
||||
/// Defines the parameters of the formula for calculating the version score
|
||||
pub version_score_formula_params: VersionScoreFormulaParams,
|
||||
}
|
||||
|
||||
impl ConfigScoreParams {
|
||||
// SAFETY: the value stored in the contract is always valid
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn unchecked_nym_node_version(&self) -> semver::Version {
|
||||
self.current_nym_node_semver.parse().unwrap()
|
||||
}
|
||||
|
||||
pub fn versions_behind(&self, node_semver: &semver::Version) -> u32 {
|
||||
let expected = self.unchecked_nym_node_version();
|
||||
|
||||
let major_diff = (node_semver.major as i64 - expected.major as i64).unsigned_abs() as u32;
|
||||
let minor_diff = (node_semver.minor as i64 - expected.minor as i64).unsigned_abs() as u32;
|
||||
let patch_diff = (node_semver.patch as i64 - expected.patch as i64).unsigned_abs() as u32;
|
||||
let prerelease_diff = if node_semver.pre == expected.pre {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
major_diff * self.version_weights.major
|
||||
+ minor_diff * self.version_weights.minor
|
||||
+ patch_diff * self.version_weights.patch
|
||||
+ prerelease_diff * self.version_weights.prerelease
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines weights for calculating numbers of versions behind the current release.
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub struct OutdatedVersionWeights {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
pub patch: u32,
|
||||
pub prerelease: u32,
|
||||
}
|
||||
|
||||
impl Default for OutdatedVersionWeights {
|
||||
fn default() -> Self {
|
||||
OutdatedVersionWeights {
|
||||
major: 100,
|
||||
minor: 10,
|
||||
patch: 1,
|
||||
prerelease: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling)
|
||||
/// define the relevant parameters
|
||||
#[cw_serde]
|
||||
#[derive(Copy)]
|
||||
pub struct VersionScoreFormulaParams {
|
||||
pub penalty: Decimal,
|
||||
pub penalty_scaling: Decimal,
|
||||
}
|
||||
|
||||
impl Default for VersionScoreFormulaParams {
|
||||
fn default() -> Self {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
VersionScoreFormulaParams {
|
||||
penalty: "0.8".parse().unwrap(),
|
||||
penalty_scaling: "2.0".parse().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct ConfigScoreParamsUpdate {
|
||||
pub current_nym_node_semver: Option<String>,
|
||||
pub version_weights: Option<OutdatedVersionWeights>,
|
||||
pub version_score_formula_params: Option<VersionScoreFormulaParams>,
|
||||
}
|
||||
|
||||
impl ConfigScoreParamsUpdate {
|
||||
pub fn contains_updates(&self) -> bool {
|
||||
self.version_weights.is_some() || self.version_score_formula_params.is_some()
|
||||
self.current_nym_node_semver.is_some()
|
||||
|| self.version_weights.is_some()
|
||||
|| self.version_score_formula_params.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use ed25519_dalek::{Signer, SigningKey};
|
||||
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
@@ -123,14 +122,6 @@ impl PemStorableKeyPair for KeyPair {
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct PublicKey(ed25519_dalek::VerifyingKey);
|
||||
|
||||
impl Hash for PublicKey {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
// each public key has unique bytes representation which can be used
|
||||
// for the hash implementation
|
||||
self.to_bytes().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PublicKey {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(&self.to_base58_string(), f)
|
||||
|
||||
@@ -5,9 +5,8 @@ use std::fmt;
|
||||
|
||||
use http::HeaderValue;
|
||||
use nym_bin_common::build_information::{BinaryBuildInformation, BinaryBuildInformationOwned};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UserAgent {
|
||||
pub application: String,
|
||||
pub version: String,
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::verloc::sender::{PacketSender, TestedNode};
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
use log::*;
|
||||
use nym_bin_common::version_checker::{self, parse_version};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_network_defaults::mainnet::NYM_API;
|
||||
use nym_node_http_api::state::metrics::{SharedVerlocStats, VerlocNodeResult};
|
||||
@@ -29,6 +30,9 @@ pub(crate) mod measurement;
|
||||
pub(crate) mod packet;
|
||||
pub(crate) mod sender;
|
||||
|
||||
// TODO: MUST BE UPDATED BEFORE ACTUAL RELEASE!!
|
||||
pub const MINIMUM_NODE_VERSION: &str = "0.10.1";
|
||||
|
||||
// by default all of those are overwritten by config data from mixnodes directly
|
||||
const DEFAULT_VERLOC_PORT: u16 = 1790;
|
||||
const DEFAULT_PACKETS_PER_NODE: usize = 100;
|
||||
@@ -41,6 +45,9 @@ const DEFAULT_RETRY_TIMEOUT: Duration = Duration::from_secs(60 * 30);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Config {
|
||||
/// Minimum semver version of a node (gateway or mixnode) that is capable of replying to echo packets.
|
||||
minimum_compatible_node_version: version_checker::Version,
|
||||
|
||||
/// Socket address of this node on which it will be listening for the measurement packets.
|
||||
listening_address: SocketAddr,
|
||||
|
||||
@@ -84,6 +91,11 @@ impl ConfigBuilder {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn minimum_compatible_node_version(mut self, version: version_checker::Version) -> Self {
|
||||
self.0.minimum_compatible_node_version = version;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn listening_address(mut self, listening_address: SocketAddr) -> Self {
|
||||
self.0.listening_address = listening_address;
|
||||
self
|
||||
@@ -142,6 +154,7 @@ impl ConfigBuilder {
|
||||
impl Default for ConfigBuilder {
|
||||
fn default() -> Self {
|
||||
ConfigBuilder(Config {
|
||||
minimum_compatible_node_version: parse_version(MINIMUM_NODE_VERSION).unwrap(),
|
||||
listening_address: format!("[::]:{DEFAULT_VERLOC_PORT}").parse().unwrap(),
|
||||
packets_per_node: DEFAULT_PACKETS_PER_NODE,
|
||||
packet_timeout: DEFAULT_PACKET_TIMEOUT,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_bin_common::version_checker;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::hash::Hash;
|
||||
|
||||
pub trait Versioned: Clone {
|
||||
fn version(&self) -> String;
|
||||
}
|
||||
|
||||
pub trait VersionFilterable<T> {
|
||||
#[must_use]
|
||||
fn filter_by_version(&self, expected_version: &str) -> Self;
|
||||
}
|
||||
|
||||
impl<T> VersionFilterable<T> for Vec<T>
|
||||
where
|
||||
T: Versioned,
|
||||
{
|
||||
fn filter_by_version(&self, expected_version: &str) -> Self {
|
||||
self.iter()
|
||||
.filter(|node| {
|
||||
version_checker::is_minor_version_compatible(&node.version(), expected_version)
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, K, V> VersionFilterable<T> for HashMap<K, V>
|
||||
where
|
||||
K: Eq + Hash + Clone,
|
||||
V: VersionFilterable<T>,
|
||||
T: Versioned,
|
||||
{
|
||||
fn filter_by_version(&self, expected_version: &str) -> Self {
|
||||
self.iter()
|
||||
.map(|(k, v)| (k.clone(), v.filter_by_version(expected_version)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, K, V> VersionFilterable<T> for BTreeMap<K, V>
|
||||
where
|
||||
K: Eq + Ord + Clone,
|
||||
V: VersionFilterable<T>,
|
||||
T: Versioned,
|
||||
{
|
||||
fn filter_by_version(&self, expected_version: &str) -> Self {
|
||||
self.iter()
|
||||
.map(|(k, v)| (k.clone(), v.filter_by_version(expected_version)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{NetworkAddress, NodeVersion};
|
||||
use crate::{filter, NetworkAddress, NodeVersion};
|
||||
use nym_api_requests::nym_nodes::SkimmedNode;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
@@ -126,6 +126,13 @@ impl fmt::Display for LegacyNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl filter::Versioned for LegacyNode {
|
||||
fn version(&self) -> String {
|
||||
// TODO: return semver instead
|
||||
self.version.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a LegacyNode> for SphinxNode {
|
||||
fn from(node: &'a LegacyNode) -> Self {
|
||||
let node_address_bytes = NymNodeRoutingAddress::from(node.mix_host)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#![allow(unknown_lints)]
|
||||
// clippy::to_string_trait_impl is not on stable as of 1.77
|
||||
|
||||
use crate::filter::VersionFilterable;
|
||||
pub use error::NymTopologyError;
|
||||
use log::{debug, info, warn};
|
||||
use nym_api_requests::nym_nodes::{CachedNodesResponse, SkimmedNode};
|
||||
@@ -24,6 +25,7 @@ use std::str::FromStr;
|
||||
use ::serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub mod error;
|
||||
pub mod filter;
|
||||
pub mod gateway;
|
||||
pub mod mix;
|
||||
pub mod random_route_provider;
|
||||
@@ -463,6 +465,19 @@ impl NymTopology {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn filter_system_version(&self, expected_version: &str) -> Self {
|
||||
self.filter_node_versions(expected_version)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn filter_node_versions(&self, expected_mix_version: &str) -> Self {
|
||||
NymTopology {
|
||||
mixes: self.mixes.filter_by_version(expected_mix_version),
|
||||
gateways: self.gateways.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serializable")]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{NetworkAddress, NodeVersion};
|
||||
use crate::{filter, NetworkAddress, NodeVersion};
|
||||
use nym_api_requests::nym_nodes::{NodeRole, SkimmedNode};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
pub use nym_mixnet_contract_common::LegacyMixLayer;
|
||||
@@ -89,6 +89,13 @@ impl LegacyNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl filter::Versioned for LegacyNode {
|
||||
fn version(&self) -> String {
|
||||
// TODO: return semver instead
|
||||
self.version.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a LegacyNode> for SphinxNode {
|
||||
fn from(node: &'a LegacyNode) -> Self {
|
||||
let node_address_bytes = NymNodeRoutingAddress::from(node.mix_host)
|
||||
|
||||
@@ -68,9 +68,9 @@ pub async fn current_network_topology_async(
|
||||
|
||||
let api_client = NymApiClient::new(url);
|
||||
let mixnodes = api_client
|
||||
.get_all_basic_active_mixing_assigned_nodes()
|
||||
.get_all_basic_active_mixing_assigned_nodes(None)
|
||||
.await?;
|
||||
let gateways = api_client.get_all_basic_entry_assigned_nodes().await?;
|
||||
let gateways = api_client.get_all_basic_entry_assigned_nodes(None).await?;
|
||||
|
||||
Ok(NymTopology::from_basic(&mixnodes, &gateways).into())
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@
|
||||
},
|
||||
"version_score_params": {
|
||||
"default": {
|
||||
"penalty": "0.995",
|
||||
"penalty_scaling": "1.65"
|
||||
"penalty": "0.8",
|
||||
"penalty_scaling": "2"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
@@ -261,7 +261,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"description": "Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
@@ -1308,6 +1308,12 @@
|
||||
"ConfigScoreParamsUpdate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"current_nym_node_semver": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"version_score_formula_params": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -1932,7 +1938,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"description": "Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
@@ -2037,54 +2043,6 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"description": "Get the current expected version of a Nym Node.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_current_nym_node_version"
|
||||
],
|
||||
"properties": {
|
||||
"get_current_nym_node_version": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"description": "Get the version history of Nym Node.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_nym_node_version_history"
|
||||
],
|
||||
"properties": {
|
||||
"get_nym_node_version_history": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_after": {
|
||||
"description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"description": "Gets the current parameters used for reward calculation.",
|
||||
"type": "object",
|
||||
@@ -3485,8 +3443,8 @@
|
||||
},
|
||||
"version_score_params": {
|
||||
"default": {
|
||||
"penalty": "0.995",
|
||||
"penalty_scaling": "1.65"
|
||||
"penalty": "0.8",
|
||||
"penalty_scaling": "2"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
@@ -3554,7 +3512,7 @@
|
||||
"additionalProperties": false
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"description": "Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
@@ -4222,111 +4180,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_current_nym_node_version": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "CurrentNymNodeVersionResponse",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/HistoricalNymNodeVersionEntry"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"HistoricalNymNodeVersion": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"difference_since_genesis",
|
||||
"introduced_at_height",
|
||||
"semver"
|
||||
],
|
||||
"properties": {
|
||||
"difference_since_genesis": {
|
||||
"description": "The absolute version difference as compared against the first version introduced into the contract.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TotalVersionDifference"
|
||||
}
|
||||
]
|
||||
},
|
||||
"introduced_at_height": {
|
||||
"description": "Block height of when this version has been added to the contract",
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"semver": {
|
||||
"description": "Version of the nym node that is going to be used for determining the version score of a node. note: value stored here is pre-validated `semver::Version`",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"HistoricalNymNodeVersionEntry": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"version_information"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The unique, ordered, id of this particular entry",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"version_information": {
|
||||
"description": "Data associated with this particular version",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/HistoricalNymNodeVersion"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"TotalVersionDifference": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"major",
|
||||
"minor",
|
||||
"patch",
|
||||
"prerelease"
|
||||
],
|
||||
"properties": {
|
||||
"major": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"minor": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"patch": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"prerelease": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_delegation_details": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "NodeDelegationResponse",
|
||||
@@ -7266,119 +7119,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_nym_node_version_history": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "NymNodeVersionHistoryResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"history"
|
||||
],
|
||||
"properties": {
|
||||
"history": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/HistoricalNymNodeVersionEntry"
|
||||
}
|
||||
},
|
||||
"start_next_after": {
|
||||
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"HistoricalNymNodeVersion": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"difference_since_genesis",
|
||||
"introduced_at_height",
|
||||
"semver"
|
||||
],
|
||||
"properties": {
|
||||
"difference_since_genesis": {
|
||||
"description": "The absolute version difference as compared against the first version introduced into the contract.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TotalVersionDifference"
|
||||
}
|
||||
]
|
||||
},
|
||||
"introduced_at_height": {
|
||||
"description": "Block height of when this version has been added to the contract",
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"semver": {
|
||||
"description": "Version of the nym node that is going to be used for determining the version score of a node. note: value stored here is pre-validated `semver::Version`",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"HistoricalNymNodeVersionEntry": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"version_information"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The unique, ordered, id of this particular entry",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"version_information": {
|
||||
"description": "Data associated with this particular version",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/HistoricalNymNodeVersion"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"TotalVersionDifference": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"major",
|
||||
"minor",
|
||||
"patch",
|
||||
"prerelease"
|
||||
],
|
||||
"properties": {
|
||||
"major": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"minor": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"patch": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"prerelease": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"get_nym_nodes_detailed_paged": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "PagedNymNodeDetailsResponse",
|
||||
@@ -10814,10 +10554,15 @@
|
||||
"ConfigScoreParams": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_nym_node_semver",
|
||||
"version_score_formula_params",
|
||||
"version_weights"
|
||||
],
|
||||
"properties": {
|
||||
"current_nym_node_semver": {
|
||||
"description": "Current version of the nym node that is going to be used for determining the version score of a node. note: value stored here is pre-validated `semver::Version`",
|
||||
"type": "string"
|
||||
},
|
||||
"version_score_formula_params": {
|
||||
"description": "Defines the parameters of the formula for calculating the version score",
|
||||
"allOf": [
|
||||
@@ -11007,7 +10752,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"description": "Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
@@ -11081,10 +10826,15 @@
|
||||
"ConfigScoreParams": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_nym_node_semver",
|
||||
"version_score_formula_params",
|
||||
"version_weights"
|
||||
],
|
||||
"properties": {
|
||||
"current_nym_node_semver": {
|
||||
"description": "Current version of the nym node that is going to be used for determining the version score of a node. note: value stored here is pre-validated `semver::Version`",
|
||||
"type": "string"
|
||||
},
|
||||
"version_score_formula_params": {
|
||||
"description": "Defines the parameters of the formula for calculating the version score",
|
||||
"allOf": [
|
||||
@@ -11238,7 +10988,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"description": "Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
|
||||
@@ -1027,6 +1027,12 @@
|
||||
"ConfigScoreParamsUpdate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"current_nym_node_semver": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"version_score_formula_params": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -1651,7 +1657,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"description": "Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
|
||||
@@ -56,8 +56,8 @@
|
||||
},
|
||||
"version_score_params": {
|
||||
"default": {
|
||||
"penalty": "0.995",
|
||||
"penalty_scaling": "1.65"
|
||||
"penalty": "0.8",
|
||||
"penalty_scaling": "2"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
@@ -257,7 +257,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"description": "Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
},
|
||||
"version_score_params": {
|
||||
"default": {
|
||||
"penalty": "0.995",
|
||||
"penalty_scaling": "1.65"
|
||||
"penalty": "0.8",
|
||||
"penalty_scaling": "2"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
@@ -86,7 +86,7 @@
|
||||
"additionalProperties": false
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"description": "Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
|
||||
@@ -85,54 +85,6 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"description": "Get the current expected version of a Nym Node.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_current_nym_node_version"
|
||||
],
|
||||
"properties": {
|
||||
"get_current_nym_node_version": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"description": "Get the version history of Nym Node.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"get_nym_node_version_history"
|
||||
],
|
||||
"properties": {
|
||||
"get_nym_node_version_history": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"start_after": {
|
||||
"description": "Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"description": "Gets the current parameters used for reward calculation.",
|
||||
"type": "object",
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "CurrentNymNodeVersionResponse",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/HistoricalNymNodeVersionEntry"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"HistoricalNymNodeVersion": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"difference_since_genesis",
|
||||
"introduced_at_height",
|
||||
"semver"
|
||||
],
|
||||
"properties": {
|
||||
"difference_since_genesis": {
|
||||
"description": "The absolute version difference as compared against the first version introduced into the contract.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TotalVersionDifference"
|
||||
}
|
||||
]
|
||||
},
|
||||
"introduced_at_height": {
|
||||
"description": "Block height of when this version has been added to the contract",
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"semver": {
|
||||
"description": "Version of the nym node that is going to be used for determining the version score of a node. note: value stored here is pre-validated `semver::Version`",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"HistoricalNymNodeVersionEntry": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"version_information"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The unique, ordered, id of this particular entry",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"version_information": {
|
||||
"description": "Data associated with this particular version",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/HistoricalNymNodeVersion"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"TotalVersionDifference": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"major",
|
||||
"minor",
|
||||
"patch",
|
||||
"prerelease"
|
||||
],
|
||||
"properties": {
|
||||
"major": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"minor": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"patch": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"prerelease": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "NymNodeVersionHistoryResponse",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"history"
|
||||
],
|
||||
"properties": {
|
||||
"history": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/HistoricalNymNodeVersionEntry"
|
||||
}
|
||||
},
|
||||
"start_next_after": {
|
||||
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"HistoricalNymNodeVersion": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"difference_since_genesis",
|
||||
"introduced_at_height",
|
||||
"semver"
|
||||
],
|
||||
"properties": {
|
||||
"difference_since_genesis": {
|
||||
"description": "The absolute version difference as compared against the first version introduced into the contract.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TotalVersionDifference"
|
||||
}
|
||||
]
|
||||
},
|
||||
"introduced_at_height": {
|
||||
"description": "Block height of when this version has been added to the contract",
|
||||
"type": "integer",
|
||||
"format": "uint64",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"semver": {
|
||||
"description": "Version of the nym node that is going to be used for determining the version score of a node. note: value stored here is pre-validated `semver::Version`",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"HistoricalNymNodeVersionEntry": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"version_information"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The unique, ordered, id of this particular entry",
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"version_information": {
|
||||
"description": "Data associated with this particular version",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/HistoricalNymNodeVersion"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"TotalVersionDifference": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"major",
|
||||
"minor",
|
||||
"patch",
|
||||
"prerelease"
|
||||
],
|
||||
"properties": {
|
||||
"major": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"minor": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"patch": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"prerelease": {
|
||||
"type": "integer",
|
||||
"format": "uint32",
|
||||
"minimum": 0.0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,10 +76,15 @@
|
||||
"ConfigScoreParams": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_nym_node_semver",
|
||||
"version_score_formula_params",
|
||||
"version_weights"
|
||||
],
|
||||
"properties": {
|
||||
"current_nym_node_semver": {
|
||||
"description": "Current version of the nym node that is going to be used for determining the version score of a node. note: value stored here is pre-validated `semver::Version`",
|
||||
"type": "string"
|
||||
},
|
||||
"version_score_formula_params": {
|
||||
"description": "Defines the parameters of the formula for calculating the version score",
|
||||
"allOf": [
|
||||
@@ -269,7 +274,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"description": "Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
|
||||
@@ -54,10 +54,15 @@
|
||||
"ConfigScoreParams": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"current_nym_node_semver",
|
||||
"version_score_formula_params",
|
||||
"version_weights"
|
||||
],
|
||||
"properties": {
|
||||
"current_nym_node_semver": {
|
||||
"description": "Current version of the nym node that is going to be used for determining the version score of a node. note: value stored here is pre-validated `semver::Version`",
|
||||
"type": "string"
|
||||
},
|
||||
"version_score_formula_params": {
|
||||
"description": "Defines the parameters of the formula for calculating the version score",
|
||||
"allOf": [
|
||||
@@ -211,7 +216,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"VersionScoreFormulaParams": {
|
||||
"description": "Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling) define the relevant parameters",
|
||||
"description": "Given the formula of version_score = penalty ^ (num_versions_behind ^ penalty_scaling) define the relevant parameters",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"penalty",
|
||||
|
||||
@@ -71,9 +71,6 @@ pub const LAST_INTERVAL_EVENT_ID_KEY: &str = "lie";
|
||||
pub const ADMIN_STORAGE_KEY: &str = "admin";
|
||||
pub const CONTRACT_STATE_KEY: &str = "state";
|
||||
|
||||
pub const VERSION_HISTORY_ID_COUNTER_KEY: &str = "vhid";
|
||||
pub const VERSION_HISTORY_NAMESPACE: &str = "vh";
|
||||
|
||||
pub const NYMNODE_ROLES_ASSIGNMENT_NAMESPACE: &str = "roles";
|
||||
pub const NYMNODE_REWARDED_SET_METADATA_NAMESPACE: &str = "roles_metadata";
|
||||
pub const NYMNODE_ACTIVE_ROLE_ASSIGNMENT_KEY: &str = "active_roles";
|
||||
|
||||
@@ -48,6 +48,7 @@ fn default_initial_state(
|
||||
},
|
||||
|
||||
config_score_params: ConfigScoreParams {
|
||||
current_nym_node_semver: msg.current_nym_node_version.clone(),
|
||||
version_weights: msg.version_score_weights,
|
||||
version_score_formula_params: msg.version_score_params,
|
||||
},
|
||||
@@ -100,13 +101,7 @@ pub fn instantiate(
|
||||
starting_interval,
|
||||
rewarding_validator_address,
|
||||
)?;
|
||||
mixnet_params_storage::initialise_storage(
|
||||
deps.branch(),
|
||||
&env,
|
||||
state,
|
||||
info.sender,
|
||||
msg.current_nym_node_version,
|
||||
)?;
|
||||
mixnet_params_storage::initialise_storage(deps.branch(), state, info.sender)?;
|
||||
RewardingStorage::new().initialise(deps.storage, reward_params)?;
|
||||
nymnodes_storage::initialise_storage(deps.storage)?;
|
||||
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
@@ -143,7 +138,6 @@ pub fn execute(
|
||||
ExecuteMsg::UpdateCurrentNymNodeSemver { current_version } => {
|
||||
crate::mixnet_contract_settings::transactions::try_update_current_nym_node_semver(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
current_version,
|
||||
)
|
||||
@@ -340,16 +334,6 @@ pub fn query(
|
||||
QueryMsg::GetState {} => {
|
||||
to_binary(&crate::mixnet_contract_settings::queries::query_contract_state(deps)?)
|
||||
}
|
||||
QueryMsg::GetCurrentNymNodeVersion {} => to_binary(
|
||||
&crate::mixnet_contract_settings::queries::query_current_nym_node_version(deps)?,
|
||||
),
|
||||
QueryMsg::GetNymNodeVersionHistory { limit, start_after } => to_binary(
|
||||
&crate::mixnet_contract_settings::queries::query_nym_node_version_history_paged(
|
||||
deps,
|
||||
start_after,
|
||||
limit,
|
||||
)?,
|
||||
),
|
||||
QueryMsg::Admin {} => to_binary(&crate::mixnet_contract_settings::queries::query_admin(
|
||||
deps,
|
||||
)?),
|
||||
@@ -603,7 +587,7 @@ pub fn query(
|
||||
#[entry_point]
|
||||
pub fn migrate(
|
||||
mut deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
_env: Env,
|
||||
msg: MigrateMsg,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
set_build_information!(deps.storage)?;
|
||||
@@ -612,7 +596,7 @@ pub fn migrate(
|
||||
let skip_state_updates = msg.unsafe_skip_state_updates.unwrap_or(false);
|
||||
|
||||
if !skip_state_updates {
|
||||
crate::queued_migrations::add_config_score_params(deps.branch(), env, &msg)?;
|
||||
crate::queued_migrations::add_config_score_params(deps.branch(), &msg)?;
|
||||
}
|
||||
|
||||
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
|
||||
@@ -709,6 +693,7 @@ mod tests {
|
||||
},
|
||||
},
|
||||
config_score_params: ConfigScoreParams {
|
||||
current_nym_node_semver: "1.1.10".to_string(),
|
||||
version_weights: Default::default(),
|
||||
version_score_formula_params: Default::default(),
|
||||
},
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
|
||||
use super::storage;
|
||||
use crate::mixnet_contract_settings::storage::ADMIN;
|
||||
use cosmwasm_std::{Deps, Order, StdResult};
|
||||
use cosmwasm_std::{Deps, StdResult};
|
||||
use cw_controllers::AdminResponse;
|
||||
use cw_storage_plus::Bound;
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{
|
||||
ContractBuildInformation, ContractState, ContractStateParams, CurrentNymNodeVersionResponse,
|
||||
HistoricalNymNodeVersionEntry, NymNodeVersionHistoryResponse,
|
||||
};
|
||||
use mixnet_contract_common::{ContractBuildInformation, ContractState, ContractStateParams};
|
||||
use nym_contracts_common::get_build_information;
|
||||
|
||||
pub(crate) fn query_admin(deps: Deps<'_>) -> StdResult<AdminResponse> {
|
||||
@@ -37,36 +32,6 @@ pub(crate) fn query_contract_version() -> ContractBuildInformation {
|
||||
get_build_information!()
|
||||
}
|
||||
|
||||
pub(crate) fn query_nym_node_version_history_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<u32>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<NymNodeVersionHistoryResponse> {
|
||||
let limit = limit.unwrap_or(100).min(200) as usize;
|
||||
let start = start_after.map(Bound::exclusive);
|
||||
|
||||
let history = storage::NymNodeVersionHistory::new()
|
||||
.version_history
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|r| r.map(Into::<HistoricalNymNodeVersionEntry>::into))
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
let start_next_after = history.last().map(|entry| entry.id);
|
||||
|
||||
Ok(NymNodeVersionHistoryResponse {
|
||||
history,
|
||||
start_next_after,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn query_current_nym_node_version(
|
||||
deps: Deps<'_>,
|
||||
) -> Result<CurrentNymNodeVersionResponse, MixnetContractError> {
|
||||
let version = storage::NymNodeVersionHistory::new().current_version(deps.storage)?;
|
||||
Ok(CurrentNymNodeVersionResponse { version })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
@@ -94,6 +59,7 @@ pub(crate) mod tests {
|
||||
interval_operating_cost: Default::default(),
|
||||
},
|
||||
config_score_params: ConfigScoreParams {
|
||||
current_nym_node_semver: "1.1.10".to_string(),
|
||||
version_weights: Default::default(),
|
||||
version_score_formula_params: Default::default(),
|
||||
},
|
||||
|
||||
@@ -1,108 +1,19 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::constants::{
|
||||
ADMIN_STORAGE_KEY, CONTRACT_STATE_KEY, VERSION_HISTORY_ID_COUNTER_KEY,
|
||||
VERSION_HISTORY_NAMESPACE,
|
||||
};
|
||||
use cosmwasm_std::Coin;
|
||||
use cosmwasm_std::{Addr, DepsMut, Env, Storage};
|
||||
use crate::constants::{ADMIN_STORAGE_KEY, CONTRACT_STATE_KEY};
|
||||
use cosmwasm_std::{Addr, DepsMut, Storage};
|
||||
use cosmwasm_std::{Coin, StdResult};
|
||||
use cw_controllers::Admin;
|
||||
use cw_storage_plus::{Item, Map};
|
||||
use cw_storage_plus::Item;
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{
|
||||
ContractState, ContractStateParams, HistoricalNymNodeVersion, HistoricalNymNodeVersionEntry,
|
||||
OperatingCostRange, ProfitMarginRange,
|
||||
ContractState, ContractStateParams, OperatingCostRange, ProfitMarginRange,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub(crate) const CONTRACT_STATE: Item<'_, ContractState> = Item::new(CONTRACT_STATE_KEY);
|
||||
pub(crate) const ADMIN: Admin = Admin::new(ADMIN_STORAGE_KEY);
|
||||
|
||||
pub(crate) struct NymNodeVersionHistory<'a> {
|
||||
pub(crate) id_counter: Item<'a, u32>,
|
||||
pub(crate) version_history: Map<'a, u32, HistoricalNymNodeVersion>,
|
||||
}
|
||||
|
||||
impl NymNodeVersionHistory<'_> {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
id_counter: Item::new(VERSION_HISTORY_ID_COUNTER_KEY),
|
||||
version_history: Map::new(VERSION_HISTORY_NAMESPACE),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_id(&self, storage: &mut dyn Storage) -> Result<u32, MixnetContractError> {
|
||||
let next = self.id_counter.may_load(storage)?.unwrap_or_default();
|
||||
self.id_counter.save(storage, &next)?;
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
pub fn current_version(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Option<HistoricalNymNodeVersionEntry>, MixnetContractError> {
|
||||
let Some(current_id) = self.id_counter.may_load(storage)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let version_information = self.version_history.load(storage, current_id)?;
|
||||
Ok(Some(HistoricalNymNodeVersionEntry {
|
||||
id: current_id,
|
||||
version_information,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn insert_new(
|
||||
&self,
|
||||
storage: &mut dyn Storage,
|
||||
entry: HistoricalNymNodeVersion,
|
||||
) -> Result<u32, MixnetContractError> {
|
||||
let next_id = self.next_id(storage)?;
|
||||
self.version_history.save(storage, next_id, &entry)?;
|
||||
Ok(next_id)
|
||||
}
|
||||
|
||||
pub fn try_insert_new(
|
||||
&self,
|
||||
storage: &mut dyn Storage,
|
||||
env: &Env,
|
||||
raw_semver: &str,
|
||||
) -> Result<u32, MixnetContractError> {
|
||||
let Ok(new_semver) = semver::Version::from_str(raw_semver) else {
|
||||
return Err(MixnetContractError::InvalidNymNodeSemver {
|
||||
provided: raw_semver.to_string(),
|
||||
});
|
||||
};
|
||||
|
||||
let Some(current) = self.current_version(storage)? else {
|
||||
// treat this as genesis
|
||||
let genesis =
|
||||
HistoricalNymNodeVersion::genesis(raw_semver.to_string(), env.block.height);
|
||||
return self.insert_new(storage, genesis);
|
||||
};
|
||||
|
||||
let current_semver = current.version_information.semver_unchecked();
|
||||
if new_semver <= current_semver {
|
||||
// make sure the new semver is strictly more recent than the current head
|
||||
return Err(MixnetContractError::NonIncreasingSemver {
|
||||
provided: raw_semver.to_string(),
|
||||
current: current.version_information.semver,
|
||||
});
|
||||
}
|
||||
|
||||
let diff = current
|
||||
.version_information
|
||||
.cumulative_difference_since_genesis(&new_semver);
|
||||
let entry = HistoricalNymNodeVersion {
|
||||
semver: raw_semver.to_string(),
|
||||
introduced_at_height: env.block.height,
|
||||
difference_since_genesis: diff,
|
||||
};
|
||||
self.insert_new(storage, entry)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rewarding_validator_address(storage: &dyn Storage) -> Result<Addr, MixnetContractError> {
|
||||
Ok(CONTRACT_STATE
|
||||
.load(storage)
|
||||
@@ -160,13 +71,9 @@ pub(crate) fn state_params(
|
||||
|
||||
pub(crate) fn initialise_storage(
|
||||
deps: DepsMut<'_>,
|
||||
env: &Env,
|
||||
initial_state: ContractState,
|
||||
initial_admin: Addr,
|
||||
initial_nymnode_version: String,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
) -> StdResult<()> {
|
||||
CONTRACT_STATE.save(deps.storage, &initial_state)?;
|
||||
NymNodeVersionHistory::new().try_insert_new(deps.storage, env, &initial_nymnode_version)?;
|
||||
ADMIN.set(deps, Some(initial_admin))?;
|
||||
Ok(())
|
||||
ADMIN.set(deps, Some(initial_admin))
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
|
||||
use super::storage;
|
||||
use crate::mixnet_contract_settings::storage::ADMIN;
|
||||
use cosmwasm_std::MessageInfo;
|
||||
use cosmwasm_std::Response;
|
||||
use cosmwasm_std::{DepsMut, StdResult};
|
||||
use cosmwasm_std::{Env, MessageInfo};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::events::{
|
||||
new_rewarding_validator_address_update_event, new_settings_update_event,
|
||||
new_update_nym_node_semver_event,
|
||||
};
|
||||
use mixnet_contract_common::ContractStateParamsUpdate;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn try_update_contract_admin(
|
||||
mut deps: DepsMut<'_>,
|
||||
@@ -91,6 +92,15 @@ pub(crate) fn try_update_contract_settings(
|
||||
|
||||
// check for config score params updates
|
||||
if let Some(config_score_update) = update.config_score_params {
|
||||
// if semver is to be updated - validate the provided value
|
||||
if let Some(current_nym_node_semver) = config_score_update.current_nym_node_semver {
|
||||
if semver::Version::from_str(¤t_nym_node_semver).is_err() {
|
||||
return Err(MixnetContractError::InvalidNymNodeSemver {
|
||||
provided: current_nym_node_semver,
|
||||
});
|
||||
}
|
||||
state.params.config_score_params.current_nym_node_semver = current_nym_node_semver
|
||||
}
|
||||
if let Some(version_weights) = config_score_update.version_weights {
|
||||
state.params.config_score_params.version_weights = version_weights
|
||||
}
|
||||
@@ -109,19 +119,23 @@ pub(crate) fn try_update_contract_settings(
|
||||
|
||||
pub(crate) fn try_update_current_nym_node_semver(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
current_version: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let mut state = storage::CONTRACT_STATE.load(deps.storage)?;
|
||||
ADMIN.assert_admin(deps.as_ref(), &info.sender)?;
|
||||
|
||||
let new_id = storage::NymNodeVersionHistory::new().try_insert_new(
|
||||
deps.storage,
|
||||
&env,
|
||||
¤t_version,
|
||||
)?;
|
||||
let response = Response::new().add_event(new_update_nym_node_semver_event(¤t_version));
|
||||
|
||||
Ok(Response::new().add_event(new_update_nym_node_semver_event(¤t_version, new_id)))
|
||||
if semver::Version::from_str(¤t_version).is_err() {
|
||||
return Err(MixnetContractError::InvalidNymNodeSemver {
|
||||
provided: current_version,
|
||||
});
|
||||
}
|
||||
|
||||
state.params.config_score_params.current_nym_node_semver = current_version;
|
||||
storage::CONTRACT_STATE.save(deps.storage, &state)?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
mod config_score_params {
|
||||
use crate::constants::CONTRACT_STATE_KEY;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnet_contract_settings::storage::NymNodeVersionHistory;
|
||||
use cosmwasm_std::{Addr, Coin, DepsMut, Env};
|
||||
use cosmwasm_std::{Addr, Coin, DepsMut};
|
||||
use cw_storage_plus::Item;
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{
|
||||
@@ -17,7 +16,6 @@ mod config_score_params {
|
||||
|
||||
pub(crate) fn add_config_score_params(
|
||||
deps: DepsMut<'_>,
|
||||
env: Env,
|
||||
msg: &MigrateMsg,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
if semver::Version::from_str(&msg.current_nym_node_semver).is_err() {
|
||||
@@ -64,6 +62,7 @@ mod config_score_params {
|
||||
interval_operating_cost: old_state.params.interval_operating_cost,
|
||||
},
|
||||
config_score_params: ConfigScoreParams {
|
||||
current_nym_node_semver: msg.current_nym_node_semver.to_string(),
|
||||
version_weights: msg.version_score_weights,
|
||||
version_score_formula_params: msg.version_score_params,
|
||||
},
|
||||
@@ -71,14 +70,6 @@ mod config_score_params {
|
||||
};
|
||||
|
||||
mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &new_state)?;
|
||||
|
||||
// initialise the version chain
|
||||
NymNodeVersionHistory::new().try_insert_new(
|
||||
deps.storage,
|
||||
&env,
|
||||
&msg.current_nym_node_semver,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use nym_api_requests::models::{DescribedNodeType, NodePerformance, NymNodeData};
|
||||
use nym_api_requests::models::NodePerformance;
|
||||
use nym_contracts_common::Percent;
|
||||
use nym_mixnet_contract_common::{
|
||||
Addr, Coin, Delegation, Gateway, LegacyMixLayer, MixNode, NodeId, NodeRewarding, NymNodeBond,
|
||||
};
|
||||
use nym_mixnet_contract_common::{Addr, Coin, Gateway, LegacyMixLayer, MixNode, NodeId};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -54,32 +52,3 @@ pub struct PrettyDetailedGatewayBond {
|
||||
pub proxy: Option<Addr>,
|
||||
pub location: Option<Location>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct NymNodeWithDescriptionAndLocation {
|
||||
pub node_id: NodeId,
|
||||
pub contract_node_type: Option<DescribedNodeType>,
|
||||
pub description: Option<NymNodeData>,
|
||||
pub bond_information: NymNodeBond,
|
||||
pub rewarding_details: NodeRewarding,
|
||||
pub location: Option<Location>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct NymNodeWithDescriptionAndLocationAndDelegations {
|
||||
pub node_id: NodeId,
|
||||
pub contract_node_type: Option<DescribedNodeType>,
|
||||
pub description: Option<NymNodeData>,
|
||||
pub bond_information: NymNodeBond,
|
||||
pub rewarding_details: NodeRewarding,
|
||||
pub location: Option<Location>,
|
||||
pub delegations: Option<Vec<Delegation>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct NymVestingAccount {
|
||||
pub locked: Coin,
|
||||
pub vested: Coin,
|
||||
pub vesting: Coin,
|
||||
pub spendable: Coin,
|
||||
}
|
||||
|
||||
@@ -38,14 +38,8 @@ impl CountryStatisticsDistributionTask {
|
||||
/// Retrieves the current list of mixnodes from the validators and calculates how many nodes are in each country
|
||||
async fn calculate_nodes_per_country(&mut self) {
|
||||
let cache = self.state.inner.mixnodes.get_locations().await;
|
||||
let nym_nodes = self
|
||||
.state
|
||||
.inner
|
||||
.nymnodes
|
||||
.get_bonded_nymnodes_locations()
|
||||
.await;
|
||||
|
||||
let mut three_letter_iso_country_codes: Vec<String> = cache
|
||||
let three_letter_iso_country_codes: Vec<String> = cache
|
||||
.values()
|
||||
.flat_map(|i| {
|
||||
i.location
|
||||
@@ -54,10 +48,6 @@ impl CountryStatisticsDistributionTask {
|
||||
})
|
||||
.collect();
|
||||
|
||||
for node in nym_nodes {
|
||||
three_letter_iso_country_codes.push(node.three_letter_iso_country_code);
|
||||
}
|
||||
|
||||
let mut distribution = CountryNodesDistribution::new();
|
||||
|
||||
info!("Calculating country distribution from located mixnodes...");
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{cache::Cache, location::LocationCacheItem};
|
||||
use nym_contracts_common::IdentityKey;
|
||||
use nym_explorer_api_requests::{Location, PrettyDetailedGatewayBond};
|
||||
use nym_mixnet_contract_common::GatewayBond;
|
||||
use nym_validator_client::models::GatewayBondAnnotated;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKey};
|
||||
use serde::Serialize;
|
||||
use std::{sync::Arc, time::SystemTime};
|
||||
use tokio::sync::RwLock;
|
||||
@@ -13,7 +11,7 @@ use tokio::sync::RwLock;
|
||||
use super::location::GatewayLocationCache;
|
||||
|
||||
pub(crate) struct GatewayCache {
|
||||
pub(crate) gateways: Cache<IdentityKey, GatewayBondAnnotated>,
|
||||
pub(crate) gateways: Cache<IdentityKey, GatewayBond>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, JsonSchema)]
|
||||
@@ -24,7 +22,6 @@ pub(crate) struct GatewaySummary {
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ThreadsafeGatewayCache {
|
||||
gateways: Arc<RwLock<GatewayCache>>,
|
||||
legacy_gateway_bonds: Arc<RwLock<GatewayCache>>,
|
||||
locations: Arc<RwLock<GatewayLocationCache>>,
|
||||
}
|
||||
|
||||
@@ -34,9 +31,6 @@ impl ThreadsafeGatewayCache {
|
||||
gateways: Arc::new(RwLock::new(GatewayCache {
|
||||
gateways: Cache::new(),
|
||||
})),
|
||||
legacy_gateway_bonds: Arc::new(RwLock::new(GatewayCache {
|
||||
gateways: Cache::new(),
|
||||
})),
|
||||
locations: Arc::new(RwLock::new(GatewayLocationCache::new())),
|
||||
}
|
||||
}
|
||||
@@ -57,14 +51,7 @@ impl ThreadsafeGatewayCache {
|
||||
}
|
||||
|
||||
pub(crate) async fn get_gateways(&self) -> Vec<GatewayBond> {
|
||||
self.gateways
|
||||
.read()
|
||||
.await
|
||||
.gateways
|
||||
.get_all()
|
||||
.iter()
|
||||
.map(|g| g.gateway_bond.bond.clone())
|
||||
.collect()
|
||||
self.gateways.read().await.gateways.get_all()
|
||||
}
|
||||
|
||||
pub(crate) async fn get_detailed_gateways(&self) -> Vec<PrettyDetailedGatewayBond> {
|
||||
@@ -77,22 +64,7 @@ impl ThreadsafeGatewayCache {
|
||||
.iter()
|
||||
.map(|bond| {
|
||||
let location = location_guard.get(bond.identity());
|
||||
self.create_detailed_gateway(bond.gateway_bond.bond.to_owned(), location)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) async fn get_legacy_detailed_gateways(&self) -> Vec<PrettyDetailedGatewayBond> {
|
||||
let legacy_gateways = self.legacy_gateway_bonds.read().await;
|
||||
let location_guard = self.locations.read().await;
|
||||
|
||||
legacy_gateways
|
||||
.gateways
|
||||
.get_all()
|
||||
.iter()
|
||||
.map(|bond| {
|
||||
let location = location_guard.get(bond.identity());
|
||||
self.create_detailed_gateway(bond.gateway_bond.bond.to_owned(), location)
|
||||
self.create_detailed_gateway(bond.to_owned(), location)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -108,9 +80,6 @@ impl ThreadsafeGatewayCache {
|
||||
gateways: Arc::new(RwLock::new(GatewayCache {
|
||||
gateways: Cache::new(),
|
||||
})),
|
||||
legacy_gateway_bonds: Arc::new(RwLock::new(GatewayCache {
|
||||
gateways: Cache::new(),
|
||||
})),
|
||||
locations: Arc::new(RwLock::new(locations)),
|
||||
}
|
||||
}
|
||||
@@ -137,26 +106,13 @@ impl ThreadsafeGatewayCache {
|
||||
.insert(identy_key, LocationCacheItem::new_from_location(location));
|
||||
}
|
||||
|
||||
pub(crate) async fn update_cache(
|
||||
&self,
|
||||
gateways: Vec<GatewayBondAnnotated>,
|
||||
legacy_gateway_bonds: Vec<GatewayBond>,
|
||||
) {
|
||||
pub(crate) async fn update_cache(&self, gateways: Vec<GatewayBond>) {
|
||||
let mut guard = self.gateways.write().await;
|
||||
let mut guard_legacy_gateways = self.legacy_gateway_bonds.write().await;
|
||||
|
||||
for gateway in gateways {
|
||||
guard
|
||||
.gateways
|
||||
.set(gateway.gateway_bond.gateway.identity_key.clone(), gateway)
|
||||
}
|
||||
|
||||
for legacy_gateway in legacy_gateway_bonds {
|
||||
if let Some(g) = guard.gateways.get(&legacy_gateway.gateway.identity_key) {
|
||||
guard_legacy_gateways
|
||||
.gateways
|
||||
.set(legacy_gateway.gateway.identity_key, g.clone());
|
||||
}
|
||||
.set(gateway.gateway.identity_key.clone(), gateway)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ use crate::gateways::http::gateways_make_default_routes;
|
||||
use crate::http::swagger::get_docs;
|
||||
use crate::mix_node::http::mix_node_make_default_routes;
|
||||
use crate::mix_nodes::http::mix_nodes_make_default_routes;
|
||||
use crate::nym_nodes::http::unstable_temp_nymnodes_make_default_routes;
|
||||
use crate::overview::http::overview_make_default_routes;
|
||||
use crate::ping::http::ping_make_default_routes;
|
||||
use crate::service_providers::http::service_providers_make_default_routes;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use crate::unstable::http::unstable_temp_make_default_routes;
|
||||
use crate::validators::http::validators_make_default_routes;
|
||||
|
||||
mod swagger;
|
||||
@@ -59,7 +59,7 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
|
||||
"/ping" => ping_make_default_routes(&openapi_settings),
|
||||
"/validators" => validators_make_default_routes(&openapi_settings),
|
||||
"/service-providers" => service_providers_make_default_routes(&openapi_settings),
|
||||
"/tmp/unstable" => unstable_temp_make_default_routes(&openapi_settings),
|
||||
"/tmp/unstable" => unstable_temp_nymnodes_make_default_routes(&openapi_settings),
|
||||
};
|
||||
|
||||
building_rocket
|
||||
|
||||
@@ -22,12 +22,12 @@ mod http;
|
||||
mod location;
|
||||
mod mix_node;
|
||||
pub(crate) mod mix_nodes;
|
||||
mod nym_nodes;
|
||||
mod overview;
|
||||
mod ping;
|
||||
pub(crate) mod service_providers;
|
||||
mod state;
|
||||
mod tasks;
|
||||
mod unstable;
|
||||
mod validators;
|
||||
|
||||
const COUNTRY_DATA_REFRESH_INTERVAL: u64 = 60 * 15; // every 15 minutes
|
||||
|
||||
@@ -44,7 +44,7 @@ async fn get_mix_node_stats(host: &str, port: u16) -> Result<NodeStats, ReqwestE
|
||||
.await
|
||||
}
|
||||
|
||||
#[openapi(tag = "mix_node")]
|
||||
#[openapi(tag = "mix_nodes")]
|
||||
#[get("/<mix_id>")]
|
||||
pub(crate) async fn get_by_id(
|
||||
mix_id: NodeId,
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::location::LocationCacheItem;
|
||||
use crate::mix_nodes::CACHE_ENTRY_TTL;
|
||||
use nym_explorer_api_requests::{Location, MixnodeStatus, PrettyDetailedMixNodeBond};
|
||||
use nym_mixnet_contract_common::rewarding::helpers::truncate_reward;
|
||||
use nym_mixnet_contract_common::{MixNodeBond, NodeId};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_validator_client::models::MixNodeBondAnnotated;
|
||||
use serde::Serialize;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -80,7 +80,6 @@ impl MixNodesResult {
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ThreadsafeMixNodesCache {
|
||||
mixnodes: Arc<RwLock<MixNodesResult>>,
|
||||
legacy_mixnode_bonds: Arc<RwLock<MixNodesResult>>,
|
||||
locations: Arc<RwLock<MixnodeLocationCache>>,
|
||||
}
|
||||
|
||||
@@ -88,7 +87,6 @@ impl ThreadsafeMixNodesCache {
|
||||
pub(crate) fn new() -> Self {
|
||||
ThreadsafeMixNodesCache {
|
||||
mixnodes: Arc::new(RwLock::new(MixNodesResult::new())),
|
||||
legacy_mixnode_bonds: Arc::new(RwLock::new(MixNodesResult::new())),
|
||||
locations: Arc::new(RwLock::new(MixnodeLocationCache::new())),
|
||||
}
|
||||
}
|
||||
@@ -96,7 +94,6 @@ impl ThreadsafeMixNodesCache {
|
||||
pub(crate) fn new_with_location_cache(locations: MixnodeLocationCache) -> Self {
|
||||
ThreadsafeMixNodesCache {
|
||||
mixnodes: Arc::new(RwLock::new(MixNodesResult::new())),
|
||||
legacy_mixnode_bonds: Arc::new(RwLock::new(MixNodesResult::new())),
|
||||
locations: Arc::new(RwLock::new(locations)),
|
||||
}
|
||||
}
|
||||
@@ -191,35 +188,13 @@ impl ThreadsafeMixNodesCache {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) async fn get_legacy_detailed_mixnodes(&self) -> Vec<PrettyDetailedMixNodeBond> {
|
||||
let legacy_mixnodes = self.legacy_mixnode_bonds.read().await;
|
||||
let location_guard = self.locations.read().await;
|
||||
|
||||
legacy_mixnodes
|
||||
.all_mixnodes
|
||||
.values()
|
||||
.map(|bond| {
|
||||
let location = location_guard.get(&bond.mix_id());
|
||||
self.create_detailed_mixnode(bond.mix_id(), &legacy_mixnodes, location, bond)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) async fn update_cache(
|
||||
&self,
|
||||
all_bonds: Vec<MixNodeBondAnnotated>,
|
||||
rewarded_nodes: HashSet<NodeId>,
|
||||
active_nodes: HashSet<NodeId>,
|
||||
legacy_mixnode_bonds: Vec<MixNodeBond>,
|
||||
) {
|
||||
let mut guard = self.mixnodes.write().await;
|
||||
let mut guard_legacy_mixnodes = self.legacy_mixnode_bonds.write().await;
|
||||
|
||||
let legacy_mixnode_bond_ids: Vec<&NodeId> = legacy_mixnode_bonds
|
||||
.iter()
|
||||
.map(|bond| &bond.mix_id)
|
||||
.collect();
|
||||
|
||||
guard.all_mixnodes = all_bonds
|
||||
.into_iter()
|
||||
.map(|bond| (bond.mix_id(), bond))
|
||||
@@ -227,11 +202,5 @@ impl ThreadsafeMixNodesCache {
|
||||
guard.rewarded_mixnodes = rewarded_nodes;
|
||||
guard.active_mixnodes = active_nodes;
|
||||
guard.valid_until = SystemTime::now() + CACHE_ENTRY_TTL;
|
||||
guard_legacy_mixnodes.all_mixnodes = guard
|
||||
.all_mixnodes
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|(node_id, _bond)| legacy_mixnode_bond_ids.iter().any(|i| **i == *node_id))
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use nym_explorer_api_requests::PrettyDetailedGatewayBond;
|
||||
use okapi::openapi3::OpenApi;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
|
||||
pub fn unstable_temp_nymnodes_make_default_routes(
|
||||
settings: &OpenApiSettings,
|
||||
) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: all_gateways]
|
||||
}
|
||||
|
||||
#[openapi(tag = "UNSTABLE")]
|
||||
#[get("/gateways")]
|
||||
pub(crate) async fn all_gateways(
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Json<Vec<PrettyDetailedGatewayBond>> {
|
||||
let mut gateways = state.inner.gateways.get_detailed_gateways().await;
|
||||
gateways.append(&mut state.inner.nymnodes.pretty_gateways().await);
|
||||
|
||||
Json(gateways)
|
||||
}
|
||||
@@ -2,13 +2,11 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::location::{LocationCache, LocationCacheItem};
|
||||
use crate::unstable::location::NymNodeLocationCache;
|
||||
use crate::unstable::CACHE_ENTRY_TTL;
|
||||
use nym_explorer_api_requests::{
|
||||
Location, NymNodeWithDescriptionAndLocation, PrettyDetailedGatewayBond,
|
||||
};
|
||||
use crate::nym_nodes::location::NymNodeLocationCache;
|
||||
use crate::nym_nodes::CACHE_ENTRY_TTL;
|
||||
use nym_explorer_api_requests::{Location, PrettyDetailedGatewayBond};
|
||||
use nym_mixnet_contract_common::{Gateway, NodeId, NymNodeDetails};
|
||||
use nym_validator_client::models::{NymNodeData, NymNodeDescription};
|
||||
use nym_validator_client::models::NymNodeDescription;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
@@ -72,54 +70,6 @@ impl ThreadSafeNymNodesCache {
|
||||
RwLockReadGuard::map(guard, |n| &n.bonded_nym_nodes)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_bonded_nymnodes_descriptions(&self) -> Vec<NymNodeData> {
|
||||
let guard = self.nymnodes.read().await;
|
||||
guard
|
||||
.described_nodes
|
||||
.values()
|
||||
.map(|i| i.description.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) async fn get_bonded_nymnodes_locations(&self) -> Vec<Location> {
|
||||
let guard_locations = self.locations.read().await;
|
||||
let mut locations: Vec<Location> = vec![];
|
||||
for location in guard_locations.values() {
|
||||
if let Some(l) = &location.location {
|
||||
locations.push(l.clone());
|
||||
}
|
||||
}
|
||||
locations
|
||||
}
|
||||
|
||||
pub(crate) async fn get_bonded_nymnodes_with_description_and_location(
|
||||
&self,
|
||||
) -> HashMap<NodeId, NymNodeWithDescriptionAndLocation> {
|
||||
let guard_nodes = self.nymnodes.read().await;
|
||||
let guard_locations = self.locations.read().await;
|
||||
|
||||
let mut map: HashMap<NodeId, NymNodeWithDescriptionAndLocation> = HashMap::new();
|
||||
|
||||
for (node_id, node) in guard_nodes.bonded_nym_nodes.clone() {
|
||||
let description = guard_nodes.described_nodes.get(&node_id);
|
||||
let location = guard_locations.get(&node_id);
|
||||
|
||||
map.insert(
|
||||
node_id,
|
||||
NymNodeWithDescriptionAndLocation {
|
||||
node_id,
|
||||
description: description.map(|d| d.description.clone()),
|
||||
location: location.and_then(|l| l.location.clone()),
|
||||
contract_node_type: description.map(|d| d.contract_node_type),
|
||||
bond_information: node.bond_information,
|
||||
rewarding_details: node.rewarding_details,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
pub(crate) async fn get_locations(&self) -> NymNodeLocationCache {
|
||||
self.locations.read().await.clone()
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
use nym_validator_client::models::NymNodeData;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
@@ -6,51 +5,19 @@ use rocket_okapi::openapi_get_routes_spec;
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
|
||||
use crate::mix_nodes::http::get_mixnode_summary;
|
||||
use crate::overview::models::{NymNodeSummary, OverviewSummary, RoleSummary};
|
||||
use crate::overview::models::OverviewSummary;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
|
||||
pub fn overview_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: summary]
|
||||
}
|
||||
|
||||
fn get_nym_nodes_by_role(nodes: &Vec<NymNodeData>) -> RoleSummary {
|
||||
let mut summary = RoleSummary::default();
|
||||
|
||||
for node in nodes {
|
||||
if node.declared_role.entry {
|
||||
summary.entry += 1;
|
||||
}
|
||||
if node.declared_role.exit_ipr {
|
||||
summary.exit_ipr += 1;
|
||||
}
|
||||
if node.declared_role.exit_nr {
|
||||
summary.exit_nr += 1;
|
||||
}
|
||||
if node.declared_role.mixnode {
|
||||
summary.mixnode += 1;
|
||||
}
|
||||
}
|
||||
|
||||
summary
|
||||
}
|
||||
|
||||
#[openapi(tag = "overview")]
|
||||
#[get("/summary")]
|
||||
pub(crate) async fn summary(state: &State<ExplorerApiStateContext>) -> Json<OverviewSummary> {
|
||||
let nym_nodes = state
|
||||
.inner
|
||||
.nymnodes
|
||||
.get_bonded_nymnodes_descriptions()
|
||||
.await;
|
||||
let roles = get_nym_nodes_by_role(&nym_nodes);
|
||||
|
||||
Json(OverviewSummary {
|
||||
mixnodes: get_mixnode_summary(state).await,
|
||||
validators: state.inner.validators.get_validator_summary().await,
|
||||
gateways: state.inner.gateways.get_gateway_summary().await,
|
||||
nymnodes: NymNodeSummary {
|
||||
count: nym_nodes.len(),
|
||||
roles,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,24 +7,9 @@ use crate::gateways::models::GatewaySummary;
|
||||
use crate::mix_nodes::models::MixNodeSummary;
|
||||
use crate::validators::models::ValidatorSummary;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, JsonSchema, Default)]
|
||||
pub(crate) struct RoleSummary {
|
||||
pub mixnode: usize,
|
||||
pub entry: usize,
|
||||
pub exit_nr: usize,
|
||||
pub exit_ipr: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, JsonSchema, Default)]
|
||||
pub(crate) struct NymNodeSummary {
|
||||
pub count: usize,
|
||||
pub roles: RoleSummary,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, JsonSchema)]
|
||||
pub(crate) struct OverviewSummary {
|
||||
pub mixnodes: MixNodeSummary,
|
||||
pub gateways: GatewaySummary,
|
||||
pub validators: ValidatorSummary,
|
||||
pub nymnodes: NymNodeSummary,
|
||||
}
|
||||
|
||||
+3
-153
@@ -3,18 +3,12 @@ use std::path::Path;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::info;
|
||||
use nym_explorer_api_requests::NymVestingAccount;
|
||||
use nym_mixnet_contract_common::{Addr, Delegation, NodeId, PendingRewardResponse};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::client::ThreadsafeValidatorClient;
|
||||
use crate::geo_ip::location::ThreadsafeGeoIp;
|
||||
use nym_mixnet_contract_common::Coin as CosmWasmCoin;
|
||||
use nym_validator_client::models::MixNodeBondAnnotated;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
MixnetQueryClient, PagedMixnetQueryClient, VestingQueryClient,
|
||||
};
|
||||
use nym_validator_client::nyxd::{AccountId, Coin, CosmWasmClient};
|
||||
|
||||
use crate::country_statistics::country_nodes_distribution::{
|
||||
CountryNodesDistribution, ThreadsafeCountryNodesDistribution,
|
||||
@@ -24,9 +18,9 @@ use crate::gateways::models::ThreadsafeGatewayCache;
|
||||
use crate::mix_node::models::ThreadsafeMixNodeCache;
|
||||
use crate::mix_nodes::location::MixnodeLocationCache;
|
||||
use crate::mix_nodes::models::ThreadsafeMixNodesCache;
|
||||
use crate::nym_nodes::location::NymNodeLocationCache;
|
||||
use crate::nym_nodes::models::ThreadSafeNymNodesCache;
|
||||
use crate::ping::models::ThreadsafePingCache;
|
||||
use crate::unstable::location::NymNodeLocationCache;
|
||||
use crate::unstable::models::ThreadSafeNymNodesCache;
|
||||
use crate::validators::models::ThreadsafeValidatorCache;
|
||||
|
||||
// TODO: change to an environment variable with a default value
|
||||
@@ -51,150 +45,6 @@ impl ExplorerApiState {
|
||||
pub(crate) async fn get_mix_node(&self, mix_id: NodeId) -> Option<MixNodeBondAnnotated> {
|
||||
self.mixnodes.get_mixnode(mix_id).await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_delegations_by_node(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
) -> Result<Vec<Delegation>, rocket::response::status::NotFound<String>> {
|
||||
match self
|
||||
.validator_client
|
||||
.0
|
||||
.nyxd
|
||||
.get_all_single_mixnode_delegations(node_id)
|
||||
.await
|
||||
{
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => Err(rocket::response::status::NotFound(format!("{}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_balance(
|
||||
&self,
|
||||
addr: &AccountId,
|
||||
) -> Result<Vec<Coin>, rocket::response::status::NotFound<String>> {
|
||||
match self.validator_client.0.nyxd.get_all_balances(addr).await {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => Err(rocket::response::status::NotFound(format!("{}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_vesting_balance(
|
||||
&self,
|
||||
addr: &AccountId,
|
||||
) -> Result<Option<NymVestingAccount>, rocket::response::status::NotFound<String>> {
|
||||
match nym_validator_client::nyxd::contract_traits::VestingQueryClient::get_account(
|
||||
&self.validator_client.0.nyxd,
|
||||
addr.as_ref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
// 1. is there a vesting account?
|
||||
Ok(_res) => {
|
||||
// 2. there is vesting account, get all the coins
|
||||
let mut locked = CosmWasmCoin::default();
|
||||
let mut vested = CosmWasmCoin::default();
|
||||
let mut vesting = CosmWasmCoin::default();
|
||||
let mut spendable = CosmWasmCoin::default();
|
||||
|
||||
// 3. try to get each coin type
|
||||
if let Ok(coin) = self
|
||||
.validator_client
|
||||
.0
|
||||
.nyxd
|
||||
.locked_coins(addr.as_ref(), None)
|
||||
.await
|
||||
{
|
||||
locked = coin.into();
|
||||
}
|
||||
if let Ok(coin) = self
|
||||
.validator_client
|
||||
.0
|
||||
.nyxd
|
||||
.vested_coins(addr.as_ref(), None)
|
||||
.await
|
||||
{
|
||||
vested = coin.into();
|
||||
}
|
||||
if let Ok(coin) = self
|
||||
.validator_client
|
||||
.0
|
||||
.nyxd
|
||||
.vesting_coins(addr.as_ref(), None)
|
||||
.await
|
||||
{
|
||||
vesting = coin.into();
|
||||
}
|
||||
if let Ok(coin) = self
|
||||
.validator_client
|
||||
.0
|
||||
.nyxd
|
||||
.spendable_coins(addr.as_ref(), None)
|
||||
.await
|
||||
{
|
||||
spendable = coin.into();
|
||||
}
|
||||
|
||||
// 4.combine into a response
|
||||
Ok(Some(NymVestingAccount {
|
||||
locked,
|
||||
vested,
|
||||
vesting,
|
||||
spendable,
|
||||
}))
|
||||
}
|
||||
Err(e) => Err(rocket::response::status::NotFound(format!("{}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_delegations(
|
||||
&self,
|
||||
addr: &AccountId,
|
||||
) -> Result<Vec<Delegation>, rocket::response::status::NotFound<String>> {
|
||||
match self
|
||||
.validator_client
|
||||
.0
|
||||
.nyxd
|
||||
.get_all_delegator_delegations(addr)
|
||||
.await
|
||||
{
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => Err(rocket::response::status::NotFound(format!("{}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_delegation_rewards(
|
||||
&self,
|
||||
addr: &AccountId,
|
||||
node_id: &NodeId,
|
||||
proxy: &Option<Addr>,
|
||||
) -> Result<PendingRewardResponse, rocket::response::status::NotFound<String>> {
|
||||
match self
|
||||
.validator_client
|
||||
.0
|
||||
.nyxd
|
||||
.get_pending_delegator_reward(addr, *node_id, proxy.clone().map(|d| d.to_string()))
|
||||
.await
|
||||
{
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => Err(rocket::response::status::NotFound(format!("{}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_operator_rewards(
|
||||
&self,
|
||||
addr: &AccountId,
|
||||
) -> Result<PendingRewardResponse, rocket::response::status::NotFound<String>> {
|
||||
match self
|
||||
.validator_client
|
||||
.0
|
||||
.nyxd
|
||||
.get_pending_operator_reward(addr)
|
||||
.await
|
||||
{
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => Err(rocket::response::status::NotFound(format!("{}", e))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
||||
@@ -3,12 +3,9 @@
|
||||
|
||||
use crate::mix_nodes::CACHE_REFRESH_RATE;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use nym_mixnet_contract_common::{GatewayBond, MixNodeBond, NymNodeDetails};
|
||||
use nym_mixnet_contract_common::{GatewayBond, NymNodeDetails};
|
||||
use nym_task::TaskClient;
|
||||
use nym_validator_client::models::{
|
||||
GatewayBondAnnotated, MixNodeBondAnnotated, NymNodeDescription,
|
||||
};
|
||||
use nym_validator_client::nyxd::contract_traits::PagedMixnetQueryClient;
|
||||
use nym_validator_client::models::{MixNodeBondAnnotated, NymNodeDescription};
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use nym_validator_client::nyxd::{Paging, TendermintRpcClient, ValidatorResponse};
|
||||
use nym_validator_client::{QueryHttpRpcValidatorClient, ValidatorClientError};
|
||||
@@ -74,15 +71,13 @@ impl ExplorerApiTasks {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn retrieve_all_gateways(
|
||||
&self,
|
||||
) -> Result<Vec<GatewayBondAnnotated>, ValidatorClientError> {
|
||||
async fn retrieve_all_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
||||
info!("About to retrieve all gateways...");
|
||||
self.state
|
||||
.inner
|
||||
.validator_client
|
||||
.0
|
||||
.get_cached_gateways_detailed_unfiltered()
|
||||
.get_cached_gateways()
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -120,30 +115,7 @@ impl ExplorerApiTasks {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn retrieve_legacy_gateway_bonds(&self) -> Vec<GatewayBond> {
|
||||
self.state
|
||||
.inner
|
||||
.validator_client
|
||||
.0
|
||||
.nyxd
|
||||
.get_all_gateways()
|
||||
.await
|
||||
.unwrap_or(vec![])
|
||||
}
|
||||
|
||||
async fn retrieve_legacy_mixnode_bonds(&self) -> Vec<MixNodeBond> {
|
||||
self.state
|
||||
.inner
|
||||
.validator_client
|
||||
.0
|
||||
.nyxd
|
||||
.get_all_mixnode_bonds()
|
||||
.await
|
||||
.unwrap_or(vec![])
|
||||
}
|
||||
|
||||
async fn update_mixnode_cache(&self) {
|
||||
let legacy_mixnode_bonds = self.retrieve_legacy_mixnode_bonds().await;
|
||||
let all_bonds = self.retrieve_all_mixnodes().await;
|
||||
let rewarded_nodes = self
|
||||
.retrieve_rewarded_mixnodes()
|
||||
@@ -160,12 +132,7 @@ impl ExplorerApiTasks {
|
||||
self.state
|
||||
.inner
|
||||
.mixnodes
|
||||
.update_cache(
|
||||
all_bonds,
|
||||
rewarded_nodes,
|
||||
active_nodes,
|
||||
legacy_mixnode_bonds,
|
||||
)
|
||||
.update_cache(all_bonds, rewarded_nodes, active_nodes)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -179,15 +146,8 @@ impl ExplorerApiTasks {
|
||||
}
|
||||
|
||||
async fn update_gateways_cache(&self) {
|
||||
let legacy_gateway_bonds = self.retrieve_legacy_gateway_bonds().await;
|
||||
match self.retrieve_all_gateways().await {
|
||||
Ok(response) => {
|
||||
self.state
|
||||
.inner
|
||||
.gateways
|
||||
.update_cache(response, legacy_gateway_bonds)
|
||||
.await
|
||||
}
|
||||
Ok(response) => self.state.inner.gateways.update_cache(response).await,
|
||||
Err(err) => {
|
||||
error!("Failed to get gateways: {err}")
|
||||
}
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use nym_explorer_api_requests::{
|
||||
NymNodeWithDescriptionAndLocation, NymNodeWithDescriptionAndLocationAndDelegations,
|
||||
NymVestingAccount, PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond,
|
||||
};
|
||||
use nym_mixnet_contract_common::{Addr, Coin, NodeId};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use okapi::openapi3::OpenApi;
|
||||
use rocket::response::status::NotFound;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::serde::{Deserialize, Serialize};
|
||||
use rocket::{Route, State};
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn unstable_temp_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: all_gateways, all_gateway_bonds, all_mixnode_bonds, all_nym_nodes, get_nym_node_by_id, get_account_by_addr]
|
||||
}
|
||||
|
||||
#[openapi(tag = "UNSTABLE")]
|
||||
#[get("/gateways")]
|
||||
pub(crate) async fn all_gateways(
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Json<Vec<PrettyDetailedGatewayBond>> {
|
||||
let mut gateways = state.inner.gateways.get_legacy_detailed_gateways().await;
|
||||
let mut nym_node_gateways: Vec<PrettyDetailedGatewayBond> = state
|
||||
.inner
|
||||
.nymnodes
|
||||
.pretty_gateways()
|
||||
.await
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|g| {
|
||||
!gateways
|
||||
.iter()
|
||||
.any(|g2| g.gateway.identity_key == g2.gateway.identity_key)
|
||||
})
|
||||
.collect();
|
||||
gateways.append(&mut nym_node_gateways);
|
||||
|
||||
Json(gateways)
|
||||
}
|
||||
|
||||
#[openapi(tag = "UNSTABLE")]
|
||||
#[get("/legacy-gateway-bonds")]
|
||||
pub(crate) async fn all_gateway_bonds(
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Json<Vec<PrettyDetailedGatewayBond>> {
|
||||
Json(state.inner.gateways.get_legacy_detailed_gateways().await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "UNSTABLE")]
|
||||
#[get("/legacy-mixnode-bonds")]
|
||||
pub(crate) async fn all_mixnode_bonds(
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Json<Vec<PrettyDetailedMixNodeBond>> {
|
||||
Json(state.inner.mixnodes.get_legacy_detailed_mixnodes().await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "UNSTABLE")]
|
||||
#[get("/nym-nodes")]
|
||||
pub(crate) async fn all_nym_nodes(
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Json<Vec<NymNodeWithDescriptionAndLocation>> {
|
||||
let nodes = state
|
||||
.inner
|
||||
.nymnodes
|
||||
.get_bonded_nymnodes_with_description_and_location()
|
||||
.await;
|
||||
Json(nodes.values().cloned().collect())
|
||||
}
|
||||
|
||||
#[openapi(tag = "UNSTABLE")]
|
||||
#[get("/nym-nodes/<node_id>")]
|
||||
pub(crate) async fn get_nym_node_by_id(
|
||||
node_id: NodeId,
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Json<Option<NymNodeWithDescriptionAndLocationAndDelegations>> {
|
||||
let nodes = state
|
||||
.inner
|
||||
.nymnodes
|
||||
.get_bonded_nymnodes_with_description_and_location()
|
||||
.await;
|
||||
Json(match nodes.get(&node_id).cloned() {
|
||||
None => None,
|
||||
Some(node) => {
|
||||
let delegations = state.inner.get_delegations_by_node(node_id).await.ok();
|
||||
Some(NymNodeWithDescriptionAndLocationAndDelegations {
|
||||
node_id: node.node_id,
|
||||
contract_node_type: node.contract_node_type,
|
||||
description: node.description,
|
||||
bond_information: node.bond_information,
|
||||
rewarding_details: node.rewarding_details,
|
||||
location: node.location,
|
||||
delegations,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct NyxAccountDelegationDetails {
|
||||
pub node_id: NodeId,
|
||||
pub delegated: Coin,
|
||||
pub height: u64,
|
||||
pub proxy: Option<Addr>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct NyxAccountDelegationRewardDetails {
|
||||
pub node_id: NodeId,
|
||||
pub rewards: Coin,
|
||||
pub amount_staked: Coin,
|
||||
pub node_still_fully_bonded: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct NyxAccountDetails {
|
||||
pub address: String,
|
||||
pub balances: Vec<Coin>,
|
||||
pub total_value: Coin,
|
||||
pub delegations: Vec<NyxAccountDelegationDetails>,
|
||||
pub accumulated_rewards: Vec<NyxAccountDelegationRewardDetails>,
|
||||
pub total_delegations: Coin,
|
||||
pub claimable_rewards: Coin,
|
||||
pub vesting_account: Option<NymVestingAccount>,
|
||||
pub operator_rewards: Option<Coin>,
|
||||
}
|
||||
|
||||
#[openapi(tag = "UNSTABLE")]
|
||||
#[get("/account/<addr>")]
|
||||
pub(crate) async fn get_account_by_addr(
|
||||
addr: String,
|
||||
state: &State<ExplorerApiStateContext>,
|
||||
) -> Result<Json<NyxAccountDetails>, NotFound<String>> {
|
||||
match AccountId::from_str(&addr) {
|
||||
Ok(address) => {
|
||||
let mut total_value = 0u128;
|
||||
|
||||
// 1. get balances of chain tokens
|
||||
let balances: Vec<Coin> = state
|
||||
.inner
|
||||
.get_balance(&address)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|c| {
|
||||
if c.denom == "unym" {
|
||||
total_value += c.amount;
|
||||
}
|
||||
c.into()
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 2. get list of delegations (history)
|
||||
let delegations: Vec<NyxAccountDelegationDetails> = state
|
||||
.inner
|
||||
.get_delegations(&address)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|d| NyxAccountDelegationDetails {
|
||||
delegated: d.amount,
|
||||
height: d.height,
|
||||
node_id: d.node_id,
|
||||
proxy: d.proxy,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 3. get the current reward for each active delegation
|
||||
let mut rewards_map: HashMap<&NodeId, NyxAccountDelegationRewardDetails> =
|
||||
HashMap::new();
|
||||
for d in &delegations {
|
||||
if rewards_map.contains_key(&d.node_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(r) = state
|
||||
.inner
|
||||
.get_delegation_rewards(&address, &d.node_id, &d.proxy)
|
||||
.await
|
||||
{
|
||||
if let Some(rewards) = r.amount_earned {
|
||||
rewards_map.insert(
|
||||
&d.node_id,
|
||||
NyxAccountDelegationRewardDetails {
|
||||
node_id: d.node_id,
|
||||
rewards,
|
||||
amount_staked: r.amount_staked.unwrap_or_default(),
|
||||
node_still_fully_bonded: r.node_still_fully_bonded,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. make the map of rewards into a vec and sum the rewards and delegations
|
||||
let accumulated_rewards: Vec<NyxAccountDelegationRewardDetails> =
|
||||
rewards_map.values().cloned().collect();
|
||||
|
||||
let mut claimable_rewards = 0u128;
|
||||
let mut total_delegations = 0u128;
|
||||
for r in &accumulated_rewards {
|
||||
claimable_rewards += r.rewards.amount.u128();
|
||||
total_delegations += r.amount_staked.amount.u128();
|
||||
total_value += r.rewards.amount.u128();
|
||||
total_value += r.amount_staked.amount.u128();
|
||||
}
|
||||
|
||||
// 5. get vesting account details (if present)
|
||||
let vesting_account = state
|
||||
.inner
|
||||
.get_vesting_balance(&address)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(vesting_account) = vesting_account.clone() {
|
||||
total_value += vesting_account.locked.amount.u128();
|
||||
total_value += vesting_account.spendable.amount.u128();
|
||||
}
|
||||
|
||||
// 6. get operator rewards
|
||||
|
||||
let operator_rewards: Option<Coin> = if let Ok(operator_rewards_res) =
|
||||
state.inner.get_operator_rewards(&address).await
|
||||
{
|
||||
if let Some(operator_reward_amount) = &operator_rewards_res.amount_earned {
|
||||
total_value += operator_reward_amount.amount.u128();
|
||||
}
|
||||
|
||||
operator_rewards_res.amount_earned
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 7. convert totals
|
||||
|
||||
let claimable_rewards = Coin::new(claimable_rewards, "unym");
|
||||
let total_delegations = Coin::new(total_delegations, "unym");
|
||||
let total_value = Coin::new(total_value, "unym");
|
||||
|
||||
Ok(Json(NyxAccountDetails {
|
||||
address: address.to_string(),
|
||||
balances,
|
||||
delegations,
|
||||
accumulated_rewards,
|
||||
claimable_rewards,
|
||||
total_delegations,
|
||||
total_value,
|
||||
vesting_account,
|
||||
operator_rewards,
|
||||
}))
|
||||
}
|
||||
Err(_e) => Err(NotFound("Account not found".to_string())),
|
||||
}
|
||||
}
|
||||
@@ -1,407 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import {Alert, AlertTitle, Box, Button, Chip, CircularProgress, Grid, Tooltip, Typography} from '@mui/material'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useMainContext } from '@/app/context/main'
|
||||
import { Title } from '@/app/components/Title'
|
||||
import { MaterialReactTable, MRT_ColumnDef, useMaterialReactTable } from "material-react-table";
|
||||
import { useMemo } from "react";
|
||||
import { humanReadableCurrencyToString } from "@/app/utils/currency";
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
||||
import { PieChart } from '@mui/x-charts/PieChart';
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useIsMobile } from "@/app/hooks";
|
||||
import { StyledLink } from "@/app/components";
|
||||
|
||||
const AccumulatedRewards = ({account}: { account?: any}) => {
|
||||
const columns = useMemo<
|
||||
MRT_ColumnDef<any>[]
|
||||
>(() => {
|
||||
return [
|
||||
{
|
||||
id: 'accumulated-rewards-data',
|
||||
header: 'Accumulated Rewards Data',
|
||||
columns: [
|
||||
{
|
||||
id: 'node_id',
|
||||
accessorKey: 'node_id',
|
||||
header: 'Node ID',
|
||||
size: 150,
|
||||
Cell: ({ row }) => (<StyledLink
|
||||
color="text.primary"
|
||||
to={`/network-components/nodes/${row.original.node_id}`}>
|
||||
{row.original.node_id}
|
||||
</StyledLink>),
|
||||
},
|
||||
{
|
||||
id: 'node_still_fully_bonded',
|
||||
accessorKey: 'node_still_fully_bonded',
|
||||
header: 'Node still bonded?',
|
||||
width: 150,
|
||||
Cell: ({ row }) => (
|
||||
<>{row.original.node_still_fully_bonded ? <CheckCircleOutlineIcon/> :
|
||||
<Typography fontSize="inherit" alignItems="center" display="flex" sx={{ color: theme => theme.palette.warning.main }}>
|
||||
<WarningAmberIcon sx={{ mr: 1 }}/>
|
||||
Unbonded
|
||||
</Typography>}</>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'amount_staked',
|
||||
accessorKey: 'amount_staked',
|
||||
header: 'Amount',
|
||||
width: 150,
|
||||
Cell: ({ row }) => (
|
||||
<>{humanReadableCurrencyToString(row.original.amount_staked)}</>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'rewards',
|
||||
accessorKey: 'rewards',
|
||||
header: 'Rewards',
|
||||
width: 150,
|
||||
Cell: ({ row }) => (
|
||||
<Typography fontSize="inherit" color="success.main">{humanReadableCurrencyToString(row.original.rewards)}</Typography>
|
||||
)
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}, [])
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
columns,
|
||||
data: account?.accumulated_rewards || [],
|
||||
enableFullScreenToggle: false,
|
||||
})
|
||||
|
||||
return (<MaterialReactTable table={table} />);
|
||||
}
|
||||
|
||||
const DelegationHistory = ({account}: { account?: any}) => {
|
||||
const columns = useMemo<
|
||||
MRT_ColumnDef<any>[]
|
||||
>(() => {
|
||||
return [
|
||||
{
|
||||
id: 'delegation-history-data',
|
||||
header: 'Delegation History',
|
||||
columns: [
|
||||
{
|
||||
id: 'node_id',
|
||||
accessorKey: 'node_id',
|
||||
header: 'Node ID',
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
id: 'delegated',
|
||||
accessorKey: 'delegated',
|
||||
header: 'Amount',
|
||||
width: 150,
|
||||
Cell: ({ row }) => (
|
||||
<>{humanReadableCurrencyToString(row.original.delegated)}</>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'height',
|
||||
accessorKey: 'height',
|
||||
header: 'Delegated at height',
|
||||
width: 150,
|
||||
Cell: ({ row }) => (
|
||||
<>{row.original.height}</>
|
||||
)
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}, [])
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
columns,
|
||||
data: account?.delegations || [],
|
||||
enableFullScreenToggle: false,
|
||||
})
|
||||
|
||||
return (<MaterialReactTable table={table} />);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows account details
|
||||
*/
|
||||
const PageAccountWithState = ({ account }: {
|
||||
account?: any;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const pieChartData = React.useMemo(() => {
|
||||
if(!account) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
|
||||
const nymBalance = Number.parseFloat(account.balances.find((b: any) => b.denom === "unym")?.amount || "0") / 1e6;
|
||||
|
||||
if(nymBalance > 0) {
|
||||
parts.push({label: "Spendable", value: nymBalance, color: theme.palette.primary.main});
|
||||
}
|
||||
|
||||
if(account.vesting_account) {
|
||||
if (`${account.vesting_account.locked?.amount}` !== "0") {
|
||||
const value = Number.parseFloat(account.vesting_account.locked.amount) / 1e6;
|
||||
if(value > 0) {
|
||||
parts.push({
|
||||
label: "Vesting locked",
|
||||
value,
|
||||
color: 'red'
|
||||
});
|
||||
}
|
||||
}
|
||||
if (`${account.vesting_account.spendable?.amount}` !== "0") {
|
||||
const value = Number.parseFloat(account.vesting_account.spendable.amount) / 1e6;
|
||||
if(value > 0) {
|
||||
parts.push({
|
||||
label: "Vesting spendable",
|
||||
value,
|
||||
color: theme.palette.primary.light
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (account.claimable_rewards &&`${account.claimable_rewards.amount}` !== "0") {
|
||||
const value = Number.parseFloat(account.claimable_rewards.amount) / 1e6;
|
||||
if(value > 0) {
|
||||
parts.push({
|
||||
label: "Claimable delegation rewards",
|
||||
value,
|
||||
color: theme.palette.success.light
|
||||
});
|
||||
}
|
||||
}
|
||||
if (account.operator_rewards && `${account.operator_rewards.amount}` !== "0") {
|
||||
const value = Number.parseFloat(account.operator_rewards.amount) / 1e6;
|
||||
if(value > 0) {
|
||||
parts.push({
|
||||
label: "Claimable operator rewards",
|
||||
value,
|
||||
color: theme.palette.success.dark
|
||||
});
|
||||
}
|
||||
}
|
||||
if (account.total_delegations && `${account.total_delegations.amount}` !== "0") {
|
||||
const value = Number.parseFloat(account.total_delegations.amount) / 1e6;
|
||||
if(value > 0) {
|
||||
parts.push({
|
||||
label: "Total delegations",
|
||||
value,
|
||||
color: '#888'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}, [account]);
|
||||
|
||||
return (
|
||||
<Box component="main">
|
||||
<Box overflow="scroll">
|
||||
<Title text={`Account ${account.address}`} />
|
||||
</Box>
|
||||
|
||||
<Box mt={4} sx={{ maxWidth: "600px" }}>
|
||||
<PieChart
|
||||
series={[
|
||||
{
|
||||
data: pieChartData,
|
||||
innerRadius: 40,
|
||||
outerRadius: 80,
|
||||
cy: isMobile ? 200 : undefined,
|
||||
},
|
||||
]}
|
||||
height={300}
|
||||
slotProps={isMobile ? {
|
||||
legend: { position: { vertical: "top", horizontal: "right" } }
|
||||
} : undefined}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box mt={4}>
|
||||
<TableContainer component={Paper} sx={{ maxWidth: "400px" }}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow sx={{ color: theme => theme.palette.primary.main }}>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
<strong>Spendable Balance</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ color: "inherit" }}>
|
||||
{account.balances.map((b: any) => (<strong key={`balance-${b.denom}`}>{humanReadableCurrencyToString(b)}<br/></strong>))}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row">
|
||||
Total delegations
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{humanReadableCurrencyToString(account.total_delegations)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{account.claimable_rewards && <TableRow sx={{ color: theme => theme.palette.success.light }}>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
Claimable delegation rewards
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ color: "inherit" }}>
|
||||
{humanReadableCurrencyToString(account.claimable_rewards)}
|
||||
</TableCell>
|
||||
</TableRow>}
|
||||
{account.operator_rewards && `${account.operator_rewards.amount}` !== "0" && <TableRow sx={{ color: theme => theme.palette.success.light }}>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
Claimable operator rewards
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ color: "inherit" }}>
|
||||
{humanReadableCurrencyToString(account.operator_rewards)}
|
||||
</TableCell>
|
||||
</TableRow>}
|
||||
{account.vesting_account && (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" colSpan={2}>
|
||||
Vesting account
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{`${account.vesting_account.locked.amount}` !== "0" &&
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ pl: 4 }}>
|
||||
Locked
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ color: "inherit" }}>
|
||||
{humanReadableCurrencyToString(account.vesting_account.locked)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
{`${account.vesting_account.vested.amount}` !== "0" &&
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ pl: 4 }}>
|
||||
Vested
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ color: "inherit" }}>
|
||||
{humanReadableCurrencyToString(account.vesting_account.vested)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
{`${account.vesting_account.vesting.amount}` !== "0" &&
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ pl: 4 }}>
|
||||
Vesting
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ color: "inherit" }}>
|
||||
{humanReadableCurrencyToString(account.vesting_account.vesting)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
{`${account.vesting_account.spendable.amount}` !== "0" &&
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ pl: 4 }}>
|
||||
Spendable
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ color: "inherit" }}>
|
||||
{humanReadableCurrencyToString(account.vesting_account.spendable)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
}
|
||||
</>
|
||||
)}
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
<h3>Total value</h3>
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ color: "inherit" }}>
|
||||
<h3>{humanReadableCurrencyToString(account.total_value)}</h3>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
<Box mt={4}>
|
||||
<AccumulatedRewards account={account}/>
|
||||
</Box>
|
||||
<Box mt={4}>
|
||||
<DelegationHistory account={account}/>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard component to handle loading and not found states
|
||||
*/
|
||||
const PageAccountDetailGuard = ({ account } : { account: string }) => {
|
||||
const [accountDetails, setAccountDetails] = React.useState<any>();
|
||||
const [isLoading, setLoading] = React.useState<boolean>(true);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const { fetchAccountById } = useMainContext()
|
||||
const { id } = useParams()
|
||||
|
||||
React.useEffect(() => {
|
||||
setLoading(true);
|
||||
(async () => {
|
||||
if(typeof(id) === "string") {
|
||||
try {
|
||||
const res = await fetchAccountById(account);
|
||||
setAccountDetails(res);
|
||||
} catch(e: any) {
|
||||
setError(e.message);
|
||||
}
|
||||
finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [id])
|
||||
|
||||
if (isLoading) {
|
||||
return <CircularProgress />
|
||||
}
|
||||
|
||||
// loaded, but not found
|
||||
if (error) {
|
||||
return (
|
||||
<Alert severity="warning">
|
||||
<AlertTitle>Account not found</AlertTitle>
|
||||
Sorry, we could not find the account <code>{id || ''}</code>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
return <PageAccountWithState account={accountDetails} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper component that adds the account details based on the `id` in the address URL
|
||||
*/
|
||||
const PageAccountDetail = () => {
|
||||
const { id } = useParams()
|
||||
|
||||
if (!id || typeof id !== 'string') {
|
||||
return (
|
||||
<Alert severity="error">Oh no! Could not find that account</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<PageAccountDetailGuard account={id} />
|
||||
)
|
||||
}
|
||||
|
||||
export default PageAccountDetail
|
||||
@@ -5,33 +5,28 @@ export const NYM_API_BASE_URL = process.env.NEXT_PUBLIC_NYM_API_URL || 'https://
|
||||
export const NYX_RPC_BASE_URL = process.env.NEXT_PUBLIC_NYX_RPC_BASE_URL || 'https://rpc.nymtech.net';
|
||||
|
||||
export const VALIDATOR_BASE_URL = process.env.NEXT_PUBLIC_VALIDATOR_URL || 'https://rpc.nymtech.net';
|
||||
export const BLOCK_EXPLORER_BASE_URL = process.env.NEXT_PUBLIC_BIG_DIPPER_URL || 'https://nym.explorers.guru';
|
||||
export const BIG_DIPPER = process.env.NEXT_PUBLIC_BIG_DIPPER_URL || 'https://nym.explorers.guru';
|
||||
|
||||
// specific API routes
|
||||
export const OVERVIEW_API = `${API_BASE_URL}/overview`;
|
||||
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_EXPLORER_API = `${API_BASE_URL}/gateways`;
|
||||
export const GATEWAYS_API = `${NYM_API_BASE_URL}/api/v1/status/gateways/detailed`;
|
||||
export const VALIDATORS_API = `${NYX_RPC_BASE_URL}/validators`;
|
||||
export const BLOCK_API = `${NYX_RPC_BASE_URL}/block`;
|
||||
export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`;
|
||||
export const UPTIME_STORY_API = `${NYM_API_BASE_URL}/api/v1/status/mixnode`; // add ID then '/history' to this.
|
||||
export const UPTIME_STORY_API_GATEWAY = `${NYM_API_BASE_URL}/api/v1/status/gateway`; // add ID then '/history' or '/report' to this
|
||||
export const SERVICE_PROVIDERS = `${API_BASE_URL}/service-providers`;
|
||||
export const TEMP_UNSTABLE_NYM_NODES = `${API_BASE_URL}/tmp/unstable/nym-nodes`;
|
||||
export const TEMP_UNSTABLE_ACCOUNT = `${API_BASE_URL}/tmp/unstable/account`;
|
||||
export const NYM_API_NODE_UPTIME = `${NYM_API_BASE_URL}/api/v1/nym-nodes/uptime-history`;
|
||||
export const NYM_API_NODE_PERFORMANCE = `${NYM_API_BASE_URL}/api/v1/nym-nodes/performance-history`;
|
||||
|
||||
export const LEGACY_MIXNODES_API = `${API_BASE_URL}/tmp/unstable/legacy-mixnode-bonds`;
|
||||
export const LEGACY_GATEWAYS_API = `${API_BASE_URL}/tmp/unstable/legacy-gateway-bonds`;
|
||||
|
||||
// errors
|
||||
export const MIXNODE_API_ERROR = "We're having trouble finding that record, please try again or Contact Us.";
|
||||
|
||||
export const NYM_WEBSITE = 'https://nymtech.net';
|
||||
|
||||
export const EXPLORER_FOR_ACCOUNTS = ''; // set to empty to use this Nym Explorer and NOT an external one
|
||||
export const NYM_BIG_DIPPER = 'https://mixnet.explorers.guru';
|
||||
|
||||
export const NYM_MIXNET_CONTRACT =
|
||||
process.env.NYM_MIXNET_CONTRACT || 'n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr';
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
API_BASE_URL,
|
||||
BLOCK_API,
|
||||
COUNTRY_DATA_API,
|
||||
GATEWAYS_API,
|
||||
UPTIME_STORY_API_GATEWAY,
|
||||
MIXNODE_API,
|
||||
MIXNODE_PING,
|
||||
@@ -11,11 +12,7 @@ import {
|
||||
UPTIME_STORY_API,
|
||||
VALIDATORS_API,
|
||||
SERVICE_PROVIDERS,
|
||||
TEMP_UNSTABLE_NYM_NODES,
|
||||
NYM_API_NODE_UPTIME,
|
||||
NYM_API_NODE_PERFORMANCE,
|
||||
TEMP_UNSTABLE_ACCOUNT,
|
||||
LEGACY_MIXNODES_API, LEGACY_GATEWAYS_API,
|
||||
GATEWAYS_EXPLORER_API,
|
||||
} from './constants';
|
||||
|
||||
import {
|
||||
@@ -62,14 +59,7 @@ export class Api {
|
||||
return cache;
|
||||
}
|
||||
const res = await fetch(`${OVERVIEW_API}/summary`);
|
||||
const json: SummaryOverviewResponse = await res.json();
|
||||
|
||||
if (json.nymnodes?.roles) {
|
||||
json.mixnodes.count += json.nymnodes.roles.mixnode;
|
||||
json.gateways.count += json.nymnodes.roles.entry;
|
||||
json.gateways.count += Math.max(json.nymnodes.roles.exit_ipr, json.nymnodes.roles.exit_nr);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
storeInCache('overview-summary', JSON.stringify(json));
|
||||
return json;
|
||||
};
|
||||
@@ -80,7 +70,7 @@ export class Api {
|
||||
return cachedMixnodes;
|
||||
}
|
||||
|
||||
const res = await fetch(LEGACY_MIXNODES_API);
|
||||
const res = await fetch(MIXNODES_API);
|
||||
const json = await res.json();
|
||||
storeInCache('mixnodes', JSON.stringify(json));
|
||||
return json;
|
||||
@@ -108,21 +98,17 @@ export class Api {
|
||||
return response.json();
|
||||
};
|
||||
|
||||
static fetchGateways = async (): Promise<LocatedGateway[]> => {
|
||||
// const res = await fetch(GATEWAYS_API);
|
||||
// const gatewaysAnnotated: GatewayBondAnnotated[] = await res.json();
|
||||
// const res2 = await fetch(GATEWAYS_EXPLORER_API);
|
||||
// const locatedGateways: LocatedGateway[] = await res2.json();
|
||||
// const locatedGatewaysByOwner = keyBy(locatedGateways, 'owner');
|
||||
// return gatewaysAnnotated.map(({ gateway_bond, node_performance }) => ({
|
||||
// ...gateway_bond,
|
||||
// node_performance,
|
||||
// location: locatedGatewaysByOwner[gateway_bond.owner]?.location,
|
||||
// }));
|
||||
|
||||
const res = await fetch(LEGACY_GATEWAYS_API);
|
||||
const locatedGateways: LocatedGateway[] = await res.json();
|
||||
return locatedGateways;
|
||||
static fetchGateways = async (): Promise<GatewayBond[]> => {
|
||||
const res = await fetch(GATEWAYS_API);
|
||||
const gatewaysAnnotated: GatewayBondAnnotated[] = await res.json();
|
||||
const res2 = await fetch(GATEWAYS_EXPLORER_API);
|
||||
const locatedGateways: LocatedGateway[] = await res2.json();
|
||||
const locatedGatewaysByOwner = keyBy(locatedGateways, 'owner');
|
||||
return gatewaysAnnotated.map(({ gateway_bond, node_performance }) => ({
|
||||
...gateway_bond,
|
||||
node_performance,
|
||||
location: locatedGatewaysByOwner[gateway_bond.owner]?.location,
|
||||
}));
|
||||
};
|
||||
|
||||
static fetchGatewayUptimeStoryById = async (id: string): Promise<UptimeStoryResponse> =>
|
||||
@@ -179,36 +165,6 @@ export class Api {
|
||||
const json = await res.json();
|
||||
return json;
|
||||
};
|
||||
|
||||
static fetchNodes = async () => {
|
||||
const res = await fetch(TEMP_UNSTABLE_NYM_NODES);
|
||||
const json = await res.json();
|
||||
return json;
|
||||
}
|
||||
|
||||
static fetchNodeById = async (id: number) => {
|
||||
const res = await fetch(`${TEMP_UNSTABLE_NYM_NODES}/${id}`);
|
||||
const json = await res.json();
|
||||
return json;
|
||||
}
|
||||
|
||||
static fetchNymNodeUptimeHistoryById = async (id: number | string) => {
|
||||
const res = await fetch(`${NYM_API_NODE_UPTIME}/${id}`)
|
||||
const json = await res.json();
|
||||
return json;
|
||||
}
|
||||
|
||||
static fetchNymNodePerformanceById = async (id: number | string) => {
|
||||
const res = await fetch(`${NYM_API_NODE_PERFORMANCE}/${id}`)
|
||||
const json = await res.json();
|
||||
return json;
|
||||
}
|
||||
|
||||
static fetchAccountById = async (id: string) => {
|
||||
const res = await fetch(`${TEMP_UNSTABLE_ACCOUNT}/${id}`);
|
||||
const json = await res.json();
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
export const getEnvironment = (): Environment => {
|
||||
|
||||
@@ -18,7 +18,6 @@ import { unymToNym } from '@/app/utils/currency'
|
||||
import { GatewayEnrichedRowType } from './Gateways/Gateways'
|
||||
import { MixnodeRowType } from './MixNodes'
|
||||
import { StakeSaturationProgressBar } from './MixNodes/Economics/StakeSaturationProgressBar'
|
||||
import {EXPLORER_FOR_ACCOUNTS} from "@/app/api/constants";
|
||||
|
||||
export type ColumnsType = {
|
||||
field: string
|
||||
@@ -58,7 +57,7 @@ function formatCellValues(val: string | number, field: string) {
|
||||
underline="none"
|
||||
color="inherit"
|
||||
target="_blank"
|
||||
href={`${EXPLORER_FOR_ACCOUNTS}/account/${val}`}
|
||||
href={`https://mixnet.explorers.guru/account/${val}`}
|
||||
>
|
||||
{val}
|
||||
</Link>
|
||||
@@ -75,7 +74,7 @@ function formatCellValues(val: string | number, field: string) {
|
||||
export const DetailTable: FCWithChildren<{
|
||||
tableName: string
|
||||
columnsData: ColumnsType[]
|
||||
rows: MixnodeRowType[] | GatewayEnrichedRowType[] | any[]
|
||||
rows: MixnodeRowType[] | GatewayEnrichedRowType[]
|
||||
}> = ({ tableName, columnsData, rows }: UniversalTableProps) => {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {GatewayResponse, GatewayBond, GatewayReportResponse, LocatedGateway} from '@/app/typeDefs/explorer-api';
|
||||
import { GatewayResponse, GatewayBond, GatewayReportResponse } from '@/app/typeDefs/explorer-api';
|
||||
import { toPercentInteger } from '@/app/utils';
|
||||
|
||||
export type GatewayRowType = {
|
||||
@@ -9,7 +9,7 @@ export type GatewayRowType = {
|
||||
host: string;
|
||||
location: string;
|
||||
version: string;
|
||||
// node_performance: number;
|
||||
node_performance: number;
|
||||
};
|
||||
|
||||
export type GatewayEnrichedRowType = GatewayRowType & {
|
||||
@@ -30,11 +30,11 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy
|
||||
bond: gw.pledge_amount.amount || 0,
|
||||
host: gw.gateway.host || '',
|
||||
version: gw.gateway.version || '',
|
||||
// node_performance: toPercentInteger(gw.node_performance.last_24h),
|
||||
node_performance: toPercentInteger(gw.node_performance.last_24h),
|
||||
}));
|
||||
}
|
||||
|
||||
export function gatewayEnrichedToGridRow(gateway: LocatedGateway, report: GatewayReportResponse): GatewayEnrichedRowType {
|
||||
export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayReportResponse): GatewayEnrichedRowType {
|
||||
return {
|
||||
id: gateway.owner,
|
||||
owner: gateway.owner,
|
||||
@@ -47,6 +47,6 @@ export function gatewayEnrichedToGridRow(gateway: LocatedGateway, report: Gatewa
|
||||
mixPort: gateway.gateway.mix_port || 0,
|
||||
routingScore: `${report.most_recent}%`,
|
||||
avgUptime: `${report.last_day || report.last_hour}%`,
|
||||
// node_performance: toPercentInteger(gateway.node_performance.most_recent),
|
||||
node_performance: toPercentInteger(gateway.node_performance.most_recent),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import { DarkLightSwitchDesktop } from '@/app/components/Switch'
|
||||
import { Footer } from '@/app/components/Footer'
|
||||
import { ConnectKeplrWallet } from '@/app/components/Wallet/ConnectKeplrWallet'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import {SearchToolbar} from "@/app/components/Nav/Search";
|
||||
|
||||
const drawerWidth = 255
|
||||
const bannerHeight = 80
|
||||
@@ -293,9 +292,6 @@ export const Nav: FCWithChildren = ({ children }) => {
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<SearchToolbar/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
|
||||
@@ -22,7 +22,6 @@ import { ExpandableButton } from './DesktopNav'
|
||||
import { ConnectKeplrWallet } from '../Wallet/ConnectKeplrWallet'
|
||||
import { NetworkTitle } from '../NetworkTitle'
|
||||
import { originalNavOptions } from '@/app/context/nav'
|
||||
import {SearchToolbar} from "@/app/components/Nav/Search";
|
||||
|
||||
export const MobileNav: FCWithChildren = ({ children }) => {
|
||||
const theme = useTheme()
|
||||
@@ -71,15 +70,7 @@ export const MobileNav: FCWithChildren = ({ children }) => {
|
||||
</IconButton>
|
||||
{!isSmallMobile && <NetworkTitle />}
|
||||
</Box>
|
||||
<Box sx={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
}}>
|
||||
<Box mr={0.5}>
|
||||
<SearchToolbar/>
|
||||
</Box>
|
||||
<ConnectKeplrWallet />
|
||||
</Box>
|
||||
<ConnectKeplrWallet />
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Drawer
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import React from "react";
|
||||
import { styled, alpha } from '@mui/material/styles';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Box from '@mui/material/Box';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import InputBase from '@mui/material/InputBase';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import {useRouter} from "next/navigation";
|
||||
|
||||
const Search = styled('div')(({ theme }) => ({
|
||||
position: 'relative',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.15),
|
||||
'&:hover': {
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.25),
|
||||
},
|
||||
marginLeft: 0,
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
marginLeft: theme.spacing(1),
|
||||
width: 'auto',
|
||||
},
|
||||
}));
|
||||
|
||||
const SearchIconWrapper = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(0, 2),
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}));
|
||||
|
||||
const StyledInputBase = styled(InputBase)(({ theme }) => ({
|
||||
color: 'inherit',
|
||||
width: '100%',
|
||||
'& .MuiInputBase-input': {
|
||||
padding: theme.spacing(1, 1, 1, 0),
|
||||
// vertical padding + font size from searchIcon
|
||||
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: '30ch',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export const SearchToolbar = () => {
|
||||
const [search, setSearch] = React.useState<string>();
|
||||
const router = useRouter();
|
||||
const handleSubmit = async (e: React.SyntheticEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
if(search?.trim().length) {
|
||||
router.push(`/account/${search.trim()}`);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Search>
|
||||
<SearchIconWrapper>
|
||||
<SearchIcon />
|
||||
</SearchIconWrapper>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<StyledInputBase
|
||||
placeholder="Search for account id…"
|
||||
inputProps={{ 'aria-label': 'search' }}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
</Search>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import Link from 'next/link'
|
||||
|
||||
type StyledLinkProps = {
|
||||
to: string
|
||||
children: React.ReactNode
|
||||
children: string
|
||||
target?: React.HTMLAttributeAnchorTarget
|
||||
dataTestId?: string
|
||||
color?: string
|
||||
|
||||
@@ -26,7 +26,6 @@ interface StateData {
|
||||
gateways?: ApiState<GatewayResponse>
|
||||
globalError?: string | undefined
|
||||
mixnodes?: ApiState<MixNodeResponse>
|
||||
nodes?: ApiState<any>
|
||||
mode: PaletteMode
|
||||
validators?: ApiState<ValidatorsResponse>
|
||||
environment?: Environment
|
||||
@@ -38,9 +37,6 @@ interface StateApi {
|
||||
status?: MixnodeStatus
|
||||
) => Promise<MixNodeResponse | undefined>
|
||||
filterMixnodes: (filters: any, status: any) => void
|
||||
fetchNodes: () => Promise<any>
|
||||
fetchNodeById: (id: number) => Promise<any>
|
||||
fetchAccountById: (accountAddr: string) => Promise<any>
|
||||
toggleMode: () => void
|
||||
}
|
||||
|
||||
@@ -51,9 +47,6 @@ export const MainContext = React.createContext<State>({
|
||||
toggleMode: () => undefined,
|
||||
filterMixnodes: () => null,
|
||||
fetchMixnodes: () => Promise.resolve(undefined),
|
||||
fetchNodes: async () => undefined,
|
||||
fetchNodeById: async () => undefined,
|
||||
fetchAccountById: async () => undefined,
|
||||
})
|
||||
|
||||
export const useMainContext = (): React.ContextType<typeof MainContext> =>
|
||||
@@ -72,7 +65,6 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
// various APIs for Overview page
|
||||
const [summaryOverview, setSummaryOverview] =
|
||||
React.useState<ApiState<SummaryOverviewResponse>>()
|
||||
const [nodes, setNodes] = React.useState<ApiState<any>>()
|
||||
const [mixnodes, setMixnodes] = React.useState<ApiState<MixNodeResponse>>()
|
||||
const [gateways, setGateways] = React.useState<ApiState<GatewayResponse>>()
|
||||
const [validators, setValidators] =
|
||||
@@ -100,11 +92,13 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchMixnodes = async () => {
|
||||
const fetchMixnodes = async (status?: MixnodeStatus) => {
|
||||
let data
|
||||
setMixnodes((d) => ({ ...d, isLoading: true }))
|
||||
try {
|
||||
data = await Api.fetchMixnodes()
|
||||
data = status
|
||||
? await Api.fetchMixnodesActiveSetByStatus(status)
|
||||
: await Api.fetchMixnodes()
|
||||
setMixnodes({ data, isLoading: false })
|
||||
} catch (error) {
|
||||
setMixnodes({
|
||||
@@ -120,7 +114,9 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
status?: MixnodeStatus
|
||||
) => {
|
||||
setMixnodes((d) => ({ ...d, isLoading: true }))
|
||||
const mxns = await Api.fetchMixnodes()
|
||||
const mxns = status
|
||||
? await Api.fetchMixnodesActiveSetByStatus(status)
|
||||
: await Api.fetchMixnodes()
|
||||
|
||||
const filtered = mxns?.filter(
|
||||
(m) =>
|
||||
@@ -209,38 +205,6 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchNodes = async () => {
|
||||
setNodes({ data: undefined, isLoading: true })
|
||||
try {
|
||||
const res = await Api.fetchNodes();
|
||||
res.forEach((node: any) => node.total_stake =
|
||||
Math.round(Number.parseFloat(node.rewarding_details?.operator || "0")
|
||||
+ Number.parseFloat(node.rewarding_details?.delegates || "0"))
|
||||
);
|
||||
setNodes({
|
||||
data: res.sort((a: any, b: any) => b.total_stake - a.total_stake),
|
||||
isLoading: false,
|
||||
})
|
||||
} catch (error) {
|
||||
setNodes({
|
||||
error:
|
||||
error instanceof Error
|
||||
? error
|
||||
: new Error('Service provider api fail'),
|
||||
isLoading: false,
|
||||
})
|
||||
} };
|
||||
|
||||
const fetchNodeById = async (id: number) => {
|
||||
const res = await Api.fetchNodeById(id);
|
||||
return res;
|
||||
};
|
||||
|
||||
const fetchAccountById = async (id: string) => {
|
||||
const res = await Api.fetchAccountById(id);
|
||||
return res;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (environment === 'mainnet') {
|
||||
fetchServiceProviders()
|
||||
@@ -267,16 +231,12 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
globalError,
|
||||
mixnodes,
|
||||
mode,
|
||||
nodes,
|
||||
summaryOverview,
|
||||
validators,
|
||||
serviceProviders,
|
||||
toggleMode,
|
||||
fetchMixnodes,
|
||||
filterMixnodes,
|
||||
fetchNodes,
|
||||
fetchNodeById,
|
||||
fetchAccountById,
|
||||
}),
|
||||
[
|
||||
environment,
|
||||
@@ -286,7 +246,6 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
globalError,
|
||||
mixnodes,
|
||||
mode,
|
||||
nodes,
|
||||
summaryOverview,
|
||||
validators,
|
||||
serviceProviders,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import * as React from 'react'
|
||||
import { DelegateIcon } from '@/app/icons/DelevateSVG'
|
||||
import { BLOCK_EXPLORER_BASE_URL } from '@/app/api/constants'
|
||||
import { BIG_DIPPER } from '@/app/api/constants'
|
||||
import { OverviewSVG } from '@/app/icons/OverviewSVG'
|
||||
import { NodemapSVG } from '@/app/icons/NodemapSVG'
|
||||
import { NetworkComponentsSVG } from '@/app/icons/NetworksSVG'
|
||||
@@ -27,23 +27,23 @@ export const originalNavOptions: NavOptionType[] = [
|
||||
title: 'Network Components',
|
||||
Icon: <NetworkComponentsSVG />,
|
||||
nested: [
|
||||
{
|
||||
url: '/network-components/nodes',
|
||||
title: 'Nodes',
|
||||
},
|
||||
{
|
||||
url: '/network-components/mixnodes',
|
||||
title: 'Mixnodes (legacy)',
|
||||
title: 'Mixnodes',
|
||||
},
|
||||
{
|
||||
url: '/network-components/gateways',
|
||||
title: 'Gateways (legacy)',
|
||||
title: 'Gateways',
|
||||
},
|
||||
{
|
||||
url: `${BLOCK_EXPLORER_BASE_URL}/validators`,
|
||||
url: `${BIG_DIPPER}/validators`,
|
||||
title: 'Validators',
|
||||
isExternal: true,
|
||||
},
|
||||
{
|
||||
url: '/network-components/service-providers',
|
||||
title: 'Service Providers',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import {
|
||||
ApiState,
|
||||
NymNodeReportResponse,
|
||||
UptimeStoryResponse,
|
||||
} from '@/app/typeDefs/explorer-api'
|
||||
import { Api } from '@/app/api'
|
||||
import { useApiState } from './hooks'
|
||||
|
||||
/**
|
||||
* This context provides the state for a single gateway by identity key.
|
||||
*/
|
||||
|
||||
interface NymNodeState {
|
||||
uptimeReport?: ApiState<NymNodeReportResponse>
|
||||
uptimeHistory?: ApiState<UptimeStoryResponse>
|
||||
}
|
||||
|
||||
export const NymNodeContext = React.createContext<NymNodeState>({})
|
||||
|
||||
export const useNymNodeContext = (): React.ContextType<typeof NymNodeContext> =>
|
||||
React.useContext<NymNodeState>(NymNodeContext)
|
||||
|
||||
/**
|
||||
* Provides a state context for a gateway by identity
|
||||
* @param gatewayIdentityKey The identity key of the gateway
|
||||
*/
|
||||
export const NymNodeContextProvider = ({
|
||||
nymNodeId,
|
||||
children,
|
||||
}: {
|
||||
nymNodeId: string
|
||||
children: JSX.Element
|
||||
}) => {
|
||||
const [uptimeReport, fetchUptimeReportById, clearUptimeReportById] =
|
||||
useApiState<any>(
|
||||
nymNodeId,
|
||||
Api.fetchNymNodePerformanceById,
|
||||
'Failed to fetch gateway uptime report by id'
|
||||
)
|
||||
|
||||
const [uptimeHistory, fetchUptimeHistory, clearUptimeHistory] =
|
||||
useApiState<UptimeStoryResponse>(
|
||||
nymNodeId,
|
||||
async (arg) => {
|
||||
const res = await Api.fetchNymNodeUptimeHistoryById(arg);
|
||||
const uptimeHistory: UptimeStoryResponse = {
|
||||
history: res.history.data,
|
||||
identity: '',
|
||||
owner: '',
|
||||
}
|
||||
return uptimeHistory;
|
||||
},
|
||||
'Failed to fetch gateway uptime history'
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
// when the identity key changes, remove all previous data
|
||||
clearUptimeReportById()
|
||||
clearUptimeHistory()
|
||||
Promise.all([fetchUptimeReportById(), fetchUptimeHistory()])
|
||||
}, [nymNodeId])
|
||||
|
||||
const state = React.useMemo<NymNodeState>(
|
||||
() => ({
|
||||
uptimeReport,
|
||||
uptimeHistory,
|
||||
}),
|
||||
[uptimeReport, uptimeHistory]
|
||||
)
|
||||
|
||||
return (
|
||||
<NymNodeContext.Provider value={state}>{children}</NymNodeContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
import * as React from 'react'
|
||||
import { Alert, AlertTitle, Box, CircularProgress, Grid } from '@mui/material'
|
||||
import { useParams } from 'next/navigation'
|
||||
import {GatewayBond, LocatedGateway} from '@/app/typeDefs/explorer-api'
|
||||
import { GatewayBond } from '@/app/typeDefs/explorer-api'
|
||||
import { ColumnsType, DetailTable } from '@/app/components/DetailTable'
|
||||
import {
|
||||
gatewayEnrichedToGridRow,
|
||||
@@ -32,6 +32,13 @@ const columns: ColumnsType[] = [
|
||||
title: 'Bond',
|
||||
headerAlign: 'left',
|
||||
},
|
||||
{
|
||||
field: 'node_performance',
|
||||
title: 'Routing Score',
|
||||
headerAlign: 'left',
|
||||
tooltipInfo:
|
||||
"Gateway's most recent score (measured in the last 15 minutes). Routing score is relative to that of the network. Each time a gateway is tested, the test packets have to go through the full path of the network (gateway + 3 nodes). If a node in the path drop packets it will affect the score of the gateway and other nodes in the test",
|
||||
},
|
||||
{
|
||||
field: 'avgUptime',
|
||||
title: 'Avg. Score',
|
||||
@@ -67,7 +74,7 @@ const columns: ColumnsType[] = [
|
||||
const PageGatewayDetailsWithState = ({
|
||||
selectedGateway,
|
||||
}: {
|
||||
selectedGateway: LocatedGateway | undefined
|
||||
selectedGateway: GatewayBond | undefined
|
||||
}) => {
|
||||
const [enrichGateway, setEnrichGateway] =
|
||||
React.useState<GatewayEnrichedRowType>()
|
||||
@@ -90,13 +97,7 @@ const PageGatewayDetailsWithState = ({
|
||||
|
||||
return (
|
||||
<Box component="main">
|
||||
<Title text="Legacy Gateway Detail" />
|
||||
|
||||
<Alert variant="filled" severity="warning" sx={{ my : 2, pt: 2 }}>
|
||||
<AlertTitle>
|
||||
Please update to the latest <code>nym-node</code> binary and migrate your bond and delegations from the wallet
|
||||
</AlertTitle>
|
||||
</Alert>
|
||||
<Title text="Gateway Detail" />
|
||||
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
@@ -145,7 +146,7 @@ const PageGatewayDetailsWithState = ({
|
||||
* Guard component to handle loadingW and not found states
|
||||
*/
|
||||
const PageGatewayDetailGuard = () => {
|
||||
const [selectedGateway, setSelectedGateway] = React.useState<LocatedGateway>()
|
||||
const [selectedGateway, setSelectedGateway] = React.useState<GatewayBond>()
|
||||
const { gateways } = useMainContext()
|
||||
const { id } = useParams()
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { CustomColumnHeading } from '@/app/components/CustomColumnHeading'
|
||||
import { Title } from '@/app/components/Title'
|
||||
import { unymToNym } from '@/app/utils/currency'
|
||||
import { Tooltip } from '@/app/components/Tooltip'
|
||||
import { EXPLORER_FOR_ACCOUNTS } from '@/app/api/constants'
|
||||
import { NYM_BIG_DIPPER } from '@/app/api/constants'
|
||||
import { splice } from '@/app/utils'
|
||||
import {
|
||||
VersionDisplaySelector,
|
||||
@@ -29,23 +29,6 @@ import {
|
||||
GatewayRowType,
|
||||
gatewayToGridRow,
|
||||
} from '@/app/components/Gateways/Gateways'
|
||||
import {LocatedGateway} from "@/app/typeDefs/explorer-api";
|
||||
|
||||
const gatewaySanitize = (g?: LocatedGateway): boolean => {
|
||||
if(!g) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!g.gateway.version || !g.gateway.version.trim().length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(g.gateway.version === "null") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const PageGateways = () => {
|
||||
const { gateways } = useMainContext()
|
||||
@@ -56,7 +39,7 @@ const PageGateways = () => {
|
||||
|
||||
const highestVersion = React.useMemo(() => {
|
||||
if (gateways?.data) {
|
||||
const versions = gateways.data.filter(gatewaySanitize).reduce(
|
||||
const versions = gateways.data.reduce(
|
||||
(a: string[], b) => [...a, b.gateway.version],
|
||||
[]
|
||||
)
|
||||
@@ -68,7 +51,7 @@ const PageGateways = () => {
|
||||
}, [gateways])
|
||||
|
||||
const filterByLatestVersions = React.useMemo(() => {
|
||||
const filtered = gateways?.data?.filter(gatewaySanitize).filter((gw) => {
|
||||
const filtered = gateways?.data?.filter((gw) => {
|
||||
const versionDiff = diff(highestVersion, gw.gateway.version)
|
||||
return versionDiff === 'patch' || versionDiff === null
|
||||
})
|
||||
@@ -77,7 +60,7 @@ const PageGateways = () => {
|
||||
}, [gateways])
|
||||
|
||||
const filterByOlderVersions = React.useMemo(() => {
|
||||
const filtered = gateways?.data?.filter(gatewaySanitize).filter((gw) => {
|
||||
const filtered = gateways?.data?.filter((gw) => {
|
||||
const versionDiff = diff(highestVersion, gw.gateway.version)
|
||||
return versionDiff === 'major' || versionDiff === 'minor'
|
||||
})
|
||||
@@ -106,7 +89,7 @@ const PageGateways = () => {
|
||||
return [
|
||||
{
|
||||
id: 'gateway-data',
|
||||
header: 'Gateways Data',
|
||||
header: 'Gatewsay Data',
|
||||
columns: [
|
||||
{
|
||||
id: 'identity_key',
|
||||
@@ -133,6 +116,41 @@ const PageGateways = () => {
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'node_performance',
|
||||
header: 'Node Performance',
|
||||
accessorKey: 'node_performance',
|
||||
size: 200,
|
||||
Header: () => {
|
||||
return (
|
||||
<Box display="flex">
|
||||
<InfoTooltip
|
||||
id="gateways-list-routing-score"
|
||||
title="Gateway's most recent score (measured in the last 15 minutes). Routing score is relative to that of the network. Each time a gateway is tested, the test packets have to go through the full path of the network (gateway + 3 nodes). If a node in the path drop packets it will affect the score of the gateway and other nodes in the test"
|
||||
placement="top-start"
|
||||
textColor={theme.palette.nym.networkExplorer.tooltip.color}
|
||||
bgColor={
|
||||
theme.palette.nym.networkExplorer.tooltip.background
|
||||
}
|
||||
maxWidth={230}
|
||||
arrow
|
||||
/>
|
||||
<CustomColumnHeading headingTitle="Routing Score" />
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<StyledLink
|
||||
to={`/network-components/gateways/${row.original.identity_key}`}
|
||||
data-testid="node-performance"
|
||||
color="text.primary"
|
||||
>
|
||||
{`${row.original.node_performance}%`}
|
||||
</StyledLink>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'version',
|
||||
header: 'Version',
|
||||
@@ -204,7 +222,7 @@ const PageGateways = () => {
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<StyledLink
|
||||
to={`${EXPLORER_FOR_ACCOUNTS}/account/${row.original.owner}`}
|
||||
to={`${NYM_BIG_DIPPER}/account/${row.original.owner}`}
|
||||
target="_blank"
|
||||
data-testid="owner"
|
||||
color="text.primary"
|
||||
@@ -219,6 +237,137 @@ const PageGateways = () => {
|
||||
]
|
||||
}, [])
|
||||
|
||||
const _columns: GridColDef[] = [
|
||||
{
|
||||
field: 'node_performance',
|
||||
align: 'center',
|
||||
renderHeader: () => (
|
||||
<>
|
||||
<InfoTooltip
|
||||
id="gateways-list-routing-score"
|
||||
title="Gateway's most recent score (measured in the last 15 minutes). Routing score is relative to that of the network. Each time a gateway is tested, the test packets have to go through the full path of the network (gateway + 3 nodes). If a node in the path drop packets it will affect the score of the gateway and other nodes in the test"
|
||||
placement="top-start"
|
||||
textColor={theme.palette.nym.networkExplorer.tooltip.color}
|
||||
bgColor={theme.palette.nym.networkExplorer.tooltip.background}
|
||||
maxWidth={230}
|
||||
arrow
|
||||
/>
|
||||
<CustomColumnHeading headingTitle="Routing Score" />
|
||||
</>
|
||||
),
|
||||
width: 120,
|
||||
disableColumnMenu: true,
|
||||
headerAlign: 'center',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<StyledLink
|
||||
to={`/network-components/gateways/${params.row.identity_key}`}
|
||||
data-testid="pledge-amount"
|
||||
>
|
||||
{`${params.value}%`}
|
||||
</StyledLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'version',
|
||||
align: 'center',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Version" />,
|
||||
width: 150,
|
||||
disableColumnMenu: true,
|
||||
headerAlign: 'center',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<StyledLink
|
||||
to={`/network-components/gateways/${params.row.identity_key}`}
|
||||
data-testid="version"
|
||||
>
|
||||
{params.value}
|
||||
</StyledLink>
|
||||
),
|
||||
sortComparator: (a, b) => {
|
||||
if (gte(a, b)) return 1
|
||||
return -1
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'location',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Location" />,
|
||||
width: 180,
|
||||
disableColumnMenu: true,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<Box
|
||||
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }}
|
||||
data-testid="location-button"
|
||||
>
|
||||
<Tooltip text={params.value} id="gateway-location-text">
|
||||
<Box
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{params.value}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'host',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="IP:Port" />,
|
||||
width: 180,
|
||||
disableColumnMenu: true,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<StyledLink
|
||||
to={`/network-components/gateways/${params.row.identity_key}`}
|
||||
data-testid="host"
|
||||
>
|
||||
{params.value}
|
||||
</StyledLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'owner',
|
||||
headerName: 'Owner',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Owner" />,
|
||||
width: 180,
|
||||
disableColumnMenu: true,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<StyledLink
|
||||
to={`${NYM_BIG_DIPPER}/account/${params.value}`}
|
||||
target="_blank"
|
||||
data-testid="owner"
|
||||
>
|
||||
{splice(7, 29, params.value)}
|
||||
</StyledLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'bond',
|
||||
width: 150,
|
||||
disableColumnMenu: true,
|
||||
type: 'number',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Bond" />,
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
headerAlign: 'left',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<StyledLink
|
||||
to={`/network-components/gateways/${params.row.identity_key}`}
|
||||
data-testid="pledge-amount"
|
||||
>
|
||||
{`${unymToNym(params.value, 6)}`}
|
||||
</StyledLink>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
columns,
|
||||
data,
|
||||
@@ -227,7 +376,7 @@ const PageGateways = () => {
|
||||
return (
|
||||
<>
|
||||
<Box mb={2}>
|
||||
<Title text="Legacy Gateways" />
|
||||
<Title text="Gateways" />
|
||||
</Box>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
|
||||
@@ -94,12 +94,7 @@ const PageMixnodeDetailWithState = () => {
|
||||
const isMobile = useIsMobile()
|
||||
return (
|
||||
<Box component="main">
|
||||
<Title text="Legacy Mixnode Detail" />
|
||||
<Alert variant="filled" severity="warning" sx={{ my : 2, pt: 2 }}>
|
||||
<AlertTitle>
|
||||
Please update to the latest <code>nym-node</code> binary and migrate your bond and delegations from the wallet
|
||||
</AlertTitle>
|
||||
</Alert>
|
||||
<Title text="Mixnode Detail" />
|
||||
<Grid container spacing={2} mt={1} mb={6}>
|
||||
<Grid item xs={12}>
|
||||
{mixNodeRow && description?.data && (
|
||||
|
||||
@@ -29,7 +29,7 @@ import { useMainContext } from '@/app/context/main'
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard'
|
||||
import { splice } from '@/app/utils'
|
||||
import { currencyToString } from '@/app/utils/currency'
|
||||
import { EXPLORER_FOR_ACCOUNTS } from '@/app/api/constants'
|
||||
import { NYM_BIG_DIPPER } from '@/app/api/constants'
|
||||
import {
|
||||
MixnodeStatusWithAll,
|
||||
toMixnodeStatus,
|
||||
@@ -229,6 +229,24 @@ export default function MixnodesPage() {
|
||||
>{`${row.original.operating_cost} NYM`}</StyledLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'node_performance',
|
||||
accessorKey: 'node_performance',
|
||||
size: 200,
|
||||
header: 'Routing Score',
|
||||
Header: () => (
|
||||
<CustomColumnHeading
|
||||
headingTitle="Routing Score"
|
||||
tooltipInfo="Mixnode's most recent score (measured in the last 15 minutes). Routing score is relative to that of the network. Each time a gateway is tested, the test packets have to go through the full path of the network (gateway + 3 nodes). If a node in the path drop packets it will affect the score of the gateway and other nodes in the test."
|
||||
/>
|
||||
),
|
||||
Cell: ({ row }) => (
|
||||
<StyledLink
|
||||
to={`/network-components/mixnodes/${row.original.mix_id}`}
|
||||
color={useGetMixNodeStatusColor(row.original.status)}
|
||||
>{`${row.original.node_performance}%`}</StyledLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'owner',
|
||||
accessorKey: 'owner',
|
||||
@@ -237,7 +255,7 @@ export default function MixnodesPage() {
|
||||
Header: () => <CustomColumnHeading headingTitle="Owner" />,
|
||||
Cell: ({ row }) => (
|
||||
<StyledLink
|
||||
to={`${EXPLORER_FOR_ACCOUNTS}/account/${row.original.owner}`}
|
||||
to={`${NYM_BIG_DIPPER}/account/${row.original.owner}`}
|
||||
color={useGetMixNodeStatusColor(row.original.status)}
|
||||
target="_blank"
|
||||
data-testid="big-dipper-link"
|
||||
@@ -308,7 +326,7 @@ export default function MixnodesPage() {
|
||||
return (
|
||||
<DelegationsProvider>
|
||||
<Box mb={2}>
|
||||
<Title text="Legacy Mixnodes" />
|
||||
<Title text="Mixnodes" />
|
||||
</Box>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from "react";
|
||||
import { Chip } from "@mui/material";
|
||||
|
||||
export const DeclaredRole = ({ declared_role }: { declared_role?: any }) => (
|
||||
<>
|
||||
{declared_role?.mixnode && <Chip size="small" label="Mixnode" sx={{ mr: 0.5 }} color="info" />}
|
||||
{declared_role?.entry && <Chip size="small" label="Entry" sx={{ mr: 0.5 }} color="success" />}
|
||||
{declared_role?.exit_nr && <Chip size="small" label="Exit NR" sx={{ mr: 0.5 }} color="warning" />}
|
||||
{declared_role?.exit_ipr && <Chip size="small" label="Exit IPR" sx={{ mr: 0.5 }} color="warning" />}
|
||||
</>
|
||||
)
|
||||
@@ -1,93 +0,0 @@
|
||||
import React, {useMemo} from "react";
|
||||
import {MaterialReactTable, MRT_ColumnDef, useMaterialReactTable} from "material-react-table";
|
||||
import StyledLink from "../../../components/StyledLink";
|
||||
import {EXPLORER_FOR_ACCOUNTS} from "@/app/api/constants";
|
||||
import {splice} from "@/app/utils";
|
||||
import {humanReadableCurrencyToString} from "@/app/utils/currency";
|
||||
import {Typography} from "@mui/material";
|
||||
import {useTheme} from "@mui/material/styles";
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Tooltip } from '@/app/components/Tooltip'
|
||||
|
||||
export const NodeDelegationsTable = ({ node }: { node: any}) => {
|
||||
const columns = useMemo<MRT_ColumnDef<any>[]>(() => {
|
||||
return [
|
||||
{
|
||||
id: 'nym-node-delegation-data',
|
||||
header: 'Nym Node Delegations',
|
||||
columns: [
|
||||
{
|
||||
id: 'owner',
|
||||
header: 'Delegator',
|
||||
accessorKey: 'owner',
|
||||
size: 150,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<StyledLink
|
||||
to={`${EXPLORER_FOR_ACCOUNTS}/account/${row.original.owner || "-"}`}
|
||||
target="_blank"
|
||||
data-testid="bond_information.node.owner"
|
||||
color="text.primary"
|
||||
>
|
||||
{splice(7, 29, row.original.owner)}
|
||||
</StyledLink>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'amount',
|
||||
header: 'Amount',
|
||||
accessorKey: 'amount',
|
||||
size: 150,
|
||||
Cell: ({ row }) => (
|
||||
<>{humanReadableCurrencyToString(row.original.amount)}</>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'height',
|
||||
header: 'Delegated at height',
|
||||
accessorKey: 'height',
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
id: 'proxy',
|
||||
header: 'From vesting account?',
|
||||
accessorKey: 'proxy',
|
||||
size: 250,
|
||||
Cell: ({ row }) => {
|
||||
if(row.original.proxy?.length) {
|
||||
return (
|
||||
<VestingDelegationWarning>Please re-delegate from your main account</VestingDelegationWarning>
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
columns,
|
||||
data: node ? node.delegations : [],
|
||||
});
|
||||
|
||||
return (
|
||||
<MaterialReactTable table={table} />
|
||||
);
|
||||
}
|
||||
|
||||
export const VestingDelegationWarning = ({children, plural}: { plural?: boolean, children: React.ReactNode}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Tooltip
|
||||
text={`${plural ? 'These delegations have' : 'This delegation has'} been made with a vesting account. All tokens are liquid, if you are the delegator, please move the tokens into your main account and make the delegation from there.`}
|
||||
id="delegations"
|
||||
>
|
||||
<Typography fontSize="inherit" color={theme.palette.warning.main} display="flex" alignItems="center">
|
||||
<WarningIcon sx={{ mr: 0.5 }}/>
|
||||
{children}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { Alert, AlertTitle, Box, CircularProgress, Grid } from '@mui/material'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { ColumnsType, DetailTable } from '@/app/components/DetailTable'
|
||||
import { ComponentError } from '@/app/components/ComponentError'
|
||||
import { ContentCard } from '@/app/components/ContentCard'
|
||||
import { UptimeChart } from '@/app/components/UptimeChart'
|
||||
import {
|
||||
NymNodeContextProvider,
|
||||
useNymNodeContext,
|
||||
} from '@/app/context/node'
|
||||
import { useMainContext } from '@/app/context/main'
|
||||
import { Title } from '@/app/components/Title'
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import {humanReadableCurrencyToString} from "@/app/utils/currency";
|
||||
import {DeclaredRole} from "@/app/network-components/nodes/DeclaredRole";
|
||||
import {NodeDelegationsTable, VestingDelegationWarning} from "@/app/network-components/nodes/[id]/NodeDelegationsTable";
|
||||
|
||||
const columns: ColumnsType[] = [
|
||||
{
|
||||
field: 'identity_key',
|
||||
title: 'Identity Key',
|
||||
headerAlign: 'left',
|
||||
width: 230,
|
||||
},
|
||||
{
|
||||
field: 'bond',
|
||||
title: 'Bond',
|
||||
headerAlign: 'left',
|
||||
},
|
||||
{
|
||||
field: 'host',
|
||||
title: 'IP',
|
||||
headerAlign: 'left',
|
||||
width: 99,
|
||||
},
|
||||
{
|
||||
field: 'location',
|
||||
title: 'Location',
|
||||
headerAlign: 'left',
|
||||
},
|
||||
{
|
||||
field: 'owner',
|
||||
title: 'Owner',
|
||||
headerAlign: 'left',
|
||||
},
|
||||
{
|
||||
field: 'version',
|
||||
title: 'Version',
|
||||
headerAlign: 'left',
|
||||
},
|
||||
]
|
||||
|
||||
interface NodeEnrichedRowType {
|
||||
node_id: number;
|
||||
identity_key: string;
|
||||
bond: string;
|
||||
host: string;
|
||||
location: string;
|
||||
owner: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
function nodeEnrichedToGridRow(node: any): NodeEnrichedRowType {
|
||||
return {
|
||||
node_id: node.node_id,
|
||||
owner: node.bond_information?.owner || '',
|
||||
identity_key: node.bond_information?.node?.identity_key || '',
|
||||
location: node.location?.country_name || '',
|
||||
bond: node.bond_information?.original_pledge.amount || 0, // TODO: format
|
||||
host: node.bond_information?.node?.host || '',
|
||||
version: node.description?.build_information?.build_version || '',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows nym node details
|
||||
*/
|
||||
const PageNymNodeDetailsWithState = ({
|
||||
selectedNymNode,
|
||||
}: {
|
||||
selectedNymNode?: any
|
||||
}) => {
|
||||
const { uptimeHistory } = useNymNodeContext()
|
||||
const enrichedData = React.useMemo(() => selectedNymNode ? [nodeEnrichedToGridRow(selectedNymNode)] : [], []);
|
||||
|
||||
const hasVestingContractDelegations = React.useMemo(() => selectedNymNode?.delegations?.filter((d: any) => d.proxy)?.length, [selectedNymNode]);
|
||||
|
||||
return (
|
||||
<Box component="main">
|
||||
<Title text="Nym Node Detail" />
|
||||
|
||||
<Grid container mt={4}>
|
||||
<Grid item xs={12}>
|
||||
<DetailTable
|
||||
columnsData={columns}
|
||||
tableName="Node detail table"
|
||||
rows={enrichedData}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid container mt={2} spacing={2}>
|
||||
{selectedNymNode.rewarding_details &&
|
||||
<Grid item xs={12} md={4}>
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>
|
||||
Delegations and Rewards
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
<strong>Operator</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{humanReadableCurrencyToString({ amount : selectedNymNode.rewarding_details.operator.split('.')[0], denom: "unym" })}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
<strong>
|
||||
{hasVestingContractDelegations ?
|
||||
<VestingDelegationWarning plural={true}>
|
||||
Delegates ({selectedNymNode.rewarding_details.unique_delegations} delegates)
|
||||
</VestingDelegationWarning> :
|
||||
<>Delegates ({selectedNymNode.rewarding_details.unique_delegations} delegates)</>
|
||||
}
|
||||
</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{humanReadableCurrencyToString({ amount : selectedNymNode.rewarding_details.delegates.split('.')[0], denom: "unym" })}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
<strong>Profit margin</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{selectedNymNode.rewarding_details.cost_params.profit_margin_percent * 100}%
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
|
||||
<strong>Operator costs</strong>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{humanReadableCurrencyToString(selectedNymNode.rewarding_details.cost_params.interval_operating_cost)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Grid>}
|
||||
|
||||
{selectedNymNode.description?.declared_role && <Grid item xs={12} md={4}>
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>
|
||||
Node roles
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
Self declared roles
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DeclaredRole declared_role={selectedNymNode.description?.declared_role}/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Grid>}
|
||||
</Grid>
|
||||
|
||||
<Grid container spacing={2} mt={2}>
|
||||
<Grid item xs={12} md={8}>
|
||||
{uptimeHistory && (
|
||||
<ContentCard title="Routing Score">
|
||||
{uptimeHistory.error && (
|
||||
<ComponentError text="There was a problem retrieving routing score." />
|
||||
)}
|
||||
<UptimeChart
|
||||
loading={uptimeHistory.isLoading}
|
||||
xLabel="Date"
|
||||
yLabel="Daily average"
|
||||
uptimeStory={uptimeHistory}
|
||||
/>
|
||||
</ContentCard>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Box mt={2}>
|
||||
<NodeDelegationsTable node={selectedNymNode}/>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard component to handle loading and not found states
|
||||
*/
|
||||
const PageNymNodeDetailGuard = () => {
|
||||
const [selectedNode, setSelectedNode] = React.useState<any>()
|
||||
const [isLoading, setLoading] = React.useState<boolean>(true);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const { fetchNodeById } = useMainContext()
|
||||
const { id } = useParams()
|
||||
|
||||
React.useEffect(() => {
|
||||
setSelectedNode(undefined);
|
||||
setLoading(true);
|
||||
(async () => {
|
||||
if(typeof(id) === "string") {
|
||||
try {
|
||||
const res = await fetchNodeById(Number.parseInt(id));
|
||||
setSelectedNode(res);
|
||||
} catch(e: any) {
|
||||
setError(e.message);
|
||||
}
|
||||
finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, [id])
|
||||
|
||||
if (isLoading) {
|
||||
return <CircularProgress />
|
||||
}
|
||||
|
||||
// loaded, but not found
|
||||
if (error) {
|
||||
return (
|
||||
<Alert severity="warning">
|
||||
<AlertTitle>Nym node not found</AlertTitle>
|
||||
Sorry, we could not find a node with id <code>{id || ''}</code>
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
return <PageNymNodeDetailsWithState selectedNymNode={selectedNode} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper component that adds the node content based on the `id` in the address URL
|
||||
*/
|
||||
const PageNymNodeDetail = () => {
|
||||
const { id } = useParams()
|
||||
|
||||
if (!id || typeof id !== 'string') {
|
||||
return (
|
||||
<Alert severity="error">Oh no! Could not find that node</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<NymNodeContextProvider nymNodeId={id}>
|
||||
<PageNymNodeDetailGuard />
|
||||
</NymNodeContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageNymNodeDetail
|
||||
@@ -1,254 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import React, { useMemo } from 'react'
|
||||
import { Box, Card, Grid, Stack, Chip } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import {
|
||||
MRT_ColumnDef,
|
||||
MaterialReactTable,
|
||||
useMaterialReactTable,
|
||||
} from 'material-react-table'
|
||||
import { diff, gte, rcompare } from 'semver'
|
||||
import { GridColDef, GridRenderCellParams } from '@mui/x-data-grid'
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard'
|
||||
import { Tooltip as InfoTooltip } from '@nymproject/react/tooltip/Tooltip'
|
||||
import { useMainContext } from '@/app/context/main'
|
||||
import { CustomColumnHeading } from '@/app/components/CustomColumnHeading'
|
||||
import { Title } from '@/app/components/Title'
|
||||
import { humanReadableCurrencyToString } from '@/app/utils/currency'
|
||||
import { Tooltip } from '@/app/components/Tooltip'
|
||||
import { EXPLORER_FOR_ACCOUNTS } from '@/app/api/constants'
|
||||
import { splice } from '@/app/utils'
|
||||
|
||||
import StyledLink from '@/app/components/StyledLink'
|
||||
import {DeclaredRole} from "@/app/network-components/nodes/DeclaredRole";
|
||||
|
||||
function getFlagEmoji(countryCode: string) {
|
||||
const codePoints = countryCode
|
||||
.toUpperCase()
|
||||
.split('')
|
||||
.map(char => 127397 + char.charCodeAt(0));
|
||||
return String.fromCodePoint(...codePoints);
|
||||
}
|
||||
|
||||
const PageNodes = () => {
|
||||
const [isLoading, setLoading] = React.useState(true);
|
||||
const { nodes, fetchNodes } = useMainContext()
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
await fetchNodes();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const columns = useMemo<MRT_ColumnDef<any>[]>(() => {
|
||||
return [
|
||||
{
|
||||
id: 'nym-node-data',
|
||||
header: 'Nym Node Data',
|
||||
columns: [
|
||||
{
|
||||
id: 'node_id',
|
||||
header: 'Node Id',
|
||||
accessorKey: 'node_id',
|
||||
size: 75,
|
||||
},
|
||||
{
|
||||
id: 'identity_key',
|
||||
header: 'Identity Key',
|
||||
accessorKey: 'identity_key',
|
||||
size: 250,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<CopyToClipboard
|
||||
sx={{ mr: 0.5, color: 'grey.400' }}
|
||||
smallIcons
|
||||
value={row.original.bond_information.node.identity_key}
|
||||
tooltip={`Copy identity key ${row.original.bond_information.node.identity_key} to clipboard`}
|
||||
/>
|
||||
<StyledLink
|
||||
to={`/network-components/nodes/${row.original.node_id}`}
|
||||
dataTestId="identity-link"
|
||||
color="text.primary"
|
||||
>
|
||||
{splice(7, 29, row.original.bond_information.node.identity_key)}
|
||||
</StyledLink>
|
||||
</Stack>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'version',
|
||||
header: 'Version',
|
||||
accessorKey: 'description.build_information.build_version',
|
||||
size: 75,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<StyledLink
|
||||
to={`/network-components/nodes/${row.original.node_id}`}
|
||||
data-testid="version"
|
||||
color="text.primary"
|
||||
>
|
||||
{row.original.description?.build_information?.build_version || "-"}
|
||||
</StyledLink>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'contract_node_type',
|
||||
header: 'Kind',
|
||||
accessorKey: 'contract_node_type',
|
||||
size: 150,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<StyledLink
|
||||
to={`/network-components/nodes/${row.original.node_id}`}
|
||||
data-testid="contract_node_type"
|
||||
color="text.primary"
|
||||
>
|
||||
<code>{row.original.contract_node_type || "-"}</code>
|
||||
</StyledLink>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'declared_role',
|
||||
header: 'Declare Role',
|
||||
accessorKey: 'description.declared_role',
|
||||
size: 250,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }}
|
||||
data-testid="declared_role-button"
|
||||
>
|
||||
<DeclaredRole declared_role={row.original.description?.declared_role}/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'total_stake',
|
||||
header: 'Total Stake',
|
||||
accessorKey: 'description.total_stake',
|
||||
size: 250,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }}
|
||||
data-testid="total_stake-button"
|
||||
>
|
||||
{humanReadableCurrencyToString({ amount: row.original.total_stake || 0, denom: "unym" })}
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'location',
|
||||
header: 'Location',
|
||||
accessorKey: 'location.country_name',
|
||||
size: 75,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }}
|
||||
data-testid="location-button"
|
||||
>
|
||||
<Tooltip
|
||||
text={row.original.location?.country_name || "-"}
|
||||
id="nym-node-location-text"
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{row.original.location?.country_name ? <>{getFlagEmoji(row.original.location.two_letter_iso_country_code.toUpperCase())} {row.original.location.two_letter_iso_country_code}</> : <>-</> }
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'host',
|
||||
header: 'IP',
|
||||
accessorKey: 'bond_information.node.host',
|
||||
size: 150,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<StyledLink
|
||||
to={`/network-components/nodes/${row.original.node_id}`}
|
||||
data-testid="host"
|
||||
color="text.primary"
|
||||
>
|
||||
{row.original.bond_information?.node?.host || "-"}
|
||||
</StyledLink>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'owner',
|
||||
header: 'Owner',
|
||||
accessorKey: 'bond_information.owner',
|
||||
size: 150,
|
||||
Cell: ({ row }) => {
|
||||
return (
|
||||
<StyledLink
|
||||
to={`${EXPLORER_FOR_ACCOUNTS}/account/${row.original.bond_information?.owner || "-"}`}
|
||||
target="_blank"
|
||||
data-testid="bond_information.node.owner"
|
||||
color="text.primary"
|
||||
>
|
||||
{splice(7, 29, row.original.bond_information?.owner)}
|
||||
</StyledLink>
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}, [])
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
columns,
|
||||
data: nodes?.data || [],
|
||||
state: {
|
||||
isLoading,
|
||||
showLoadingOverlay: isLoading,
|
||||
},
|
||||
initialState: {
|
||||
isLoading: true,
|
||||
showLoadingOverlay: true,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box mb={2}>
|
||||
<Title text="Nym Nodes" />
|
||||
</Box>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
padding: 2,
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<MaterialReactTable table={table} />
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageNodes
|
||||
@@ -0,0 +1,140 @@
|
||||
'use client'
|
||||
|
||||
import React, { useMemo } from 'react'
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
FormControl,
|
||||
Grid,
|
||||
ListItem,
|
||||
Menu,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import { TableToolbar } from '@/app/components/TableToolbar'
|
||||
import { Title } from '@/app/components/Title'
|
||||
import { useMainContext } from '@/app/context/main'
|
||||
import { CustomColumnHeading } from '@/app/components/CustomColumnHeading'
|
||||
import {
|
||||
MRT_ColumnDef,
|
||||
MaterialReactTable,
|
||||
useMaterialReactTable,
|
||||
} from 'material-react-table'
|
||||
import { DirectoryServiceProvider } from '@/app/typeDefs/explorer-api'
|
||||
|
||||
const SupportedApps = () => {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null)
|
||||
const open = Boolean(anchorEl)
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
}
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
const anchorRef = React.useRef<HTMLButtonElement>(null)
|
||||
|
||||
return (
|
||||
<FormControl size="small">
|
||||
<Button
|
||||
ref={anchorRef}
|
||||
onClick={handleClick}
|
||||
size="large"
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
sx={{ mr: 2, textTransform: 'capitalize' }}
|
||||
>
|
||||
Supported Apps
|
||||
</Button>
|
||||
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
|
||||
<ListItem>Keybase</ListItem>
|
||||
<ListItem>Telegram</ListItem>
|
||||
<ListItem>Electrum</ListItem>
|
||||
<ListItem>Blockstream Green</ListItem>
|
||||
</Menu>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
const ServiceProviders = () => {
|
||||
const { serviceProviders } = useMainContext()
|
||||
|
||||
const columns = useMemo<MRT_ColumnDef<DirectoryServiceProvider>[]>(() => {
|
||||
return [
|
||||
{
|
||||
id: 'service-providers-data',
|
||||
header: 'Service Providers Data',
|
||||
columns: [
|
||||
{
|
||||
id: 'address',
|
||||
accessorKey: 'address',
|
||||
header: 'Client ID',
|
||||
size: 450,
|
||||
},
|
||||
{
|
||||
id: 'service_type-type',
|
||||
accessorKey: 'service_type',
|
||||
header: 'Type',
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
id: 'routing_score-score',
|
||||
accessorKey: 'routing_score',
|
||||
header: 'Routing score',
|
||||
Header() {
|
||||
return (
|
||||
<CustomColumnHeading
|
||||
headingTitle="Routing score"
|
||||
tooltipInfo="Routing score is only displayed for the service providers that had a successful ping within the last two hours"
|
||||
/>
|
||||
)
|
||||
},
|
||||
Cell({ row }) {
|
||||
return row.original.routing_score || '-'
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}, [])
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
columns,
|
||||
data: serviceProviders?.data || [],
|
||||
layoutMode: 'grid',
|
||||
state: {
|
||||
isLoading: serviceProviders?.isLoading,
|
||||
},
|
||||
initialState: {
|
||||
sorting: [
|
||||
{
|
||||
id: 'routing_score',
|
||||
desc: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box mb={2}>
|
||||
<Title text="Service Providers" />
|
||||
</Box>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Card
|
||||
sx={{
|
||||
padding: 2,
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<TableToolbar childrenBefore={<SupportedApps />} />
|
||||
<MaterialReactTable table={table} />
|
||||
</>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServiceProviders
|
||||
@@ -13,7 +13,7 @@ import { GatewaysSVG } from '@/app/icons/GatewaysSVG'
|
||||
import { ValidatorsSVG } from '@/app/icons/ValidatorsSVG'
|
||||
import { ContentCard } from '@/app/components/ContentCard'
|
||||
import { WorldMap } from '@/app/components/WorldMap'
|
||||
import { BLOCK_EXPLORER_BASE_URL } from '@/app/api/constants'
|
||||
import { BIG_DIPPER } from '@/app/api/constants'
|
||||
import { formatNumber } from '@/app/utils'
|
||||
import { useMainContext } from './context/main'
|
||||
import { useRouter } from 'next/navigation'
|
||||
@@ -42,59 +42,71 @@ const PageOverview = () => {
|
||||
<>
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() => router.push('/network-components/nodes')}
|
||||
title="Nodes"
|
||||
onClick={() => router.push('/network-components/mixnodes')}
|
||||
title="Mixnodes"
|
||||
icon={<MixnodesSVG />}
|
||||
count={summaryOverview.data?.nymnodes?.count || ''}
|
||||
count={summaryOverview.data?.mixnodes.count || ''}
|
||||
errorMsg={summaryOverview?.error}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() =>
|
||||
router.push('/network-components/mixnodes?status=active')
|
||||
}
|
||||
title="Active nodes"
|
||||
icon={<Icons.Mixnodes.Status.Active />}
|
||||
color={
|
||||
theme.palette.nym.networkExplorer.mixnodes.status.active
|
||||
}
|
||||
count={summaryOverview.data?.mixnodes.activeset.active}
|
||||
errorMsg={summaryOverview?.error}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() =>
|
||||
router.push('/network-components/mixnodes?status=standby')
|
||||
}
|
||||
title="Standby nodes"
|
||||
color={
|
||||
theme.palette.nym.networkExplorer.mixnodes.status.standby
|
||||
}
|
||||
icon={<Icons.Mixnodes.Status.Standby />}
|
||||
count={summaryOverview.data?.mixnodes.activeset.standby}
|
||||
errorMsg={summaryOverview?.error}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{summaryOverview && (
|
||||
{gateways && (
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() => router.push('/network-components/nodes')}
|
||||
title="Mixnodes"
|
||||
count={summaryOverview.data?.nymnodes?.roles?.mixnode || ''}
|
||||
onClick={() => router.push('/network-components/gateways')}
|
||||
title="Gateways"
|
||||
count={gateways?.data?.length || ''}
|
||||
errorMsg={gateways?.error}
|
||||
icon={<GatewaysSVG />}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{summaryOverview && (
|
||||
{serviceProviders && (
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() => router.push('/network-components/nodes')}
|
||||
title="Entry Gateways"
|
||||
count={summaryOverview.data?.nymnodes?.roles?.entry || ''}
|
||||
icon={<GatewaysSVG />}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{summaryOverview && (
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() => router.push('/network-components/nodes')}
|
||||
title="Exit Gateways"
|
||||
count={summaryOverview.data?.nymnodes?.roles?.exit_ipr || ''}
|
||||
icon={<GatewaysSVG />}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{summaryOverview && (
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() => router.push('/network-components/nodes')}
|
||||
title="SOCKS5 Network Requesters"
|
||||
count={summaryOverview.data?.nymnodes?.roles?.exit_nr || ''}
|
||||
icon={<GatewaysSVG />}
|
||||
onClick={() =>
|
||||
router.push('/network-components/service-providers')
|
||||
}
|
||||
title="Service providers"
|
||||
icon={<PeopleAlt />}
|
||||
count={serviceProviders.data?.length}
|
||||
errorMsg={summaryOverview?.error}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{validators && (
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() => window.open(`${BLOCK_EXPLORER_BASE_URL}/validators`)}
|
||||
onClick={() => window.open(`${BIG_DIPPER}/validators`)}
|
||||
title="Validators"
|
||||
count={validators?.data?.count || ''}
|
||||
errorMsg={validators?.error}
|
||||
@@ -105,7 +117,7 @@ const PageOverview = () => {
|
||||
{block?.data && (
|
||||
<Grid item xs={12}>
|
||||
<Link
|
||||
href={`${BLOCK_EXPLORER_BASE_URL}/blocks`}
|
||||
href={`${BIG_DIPPER}/blocks`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
underline="none"
|
||||
|
||||
@@ -20,15 +20,6 @@ export interface SummaryOverviewResponse {
|
||||
validators: {
|
||||
count: number;
|
||||
};
|
||||
nymnodes: {
|
||||
count: number;
|
||||
roles: {
|
||||
mixnode: number;
|
||||
entry: number;
|
||||
exit_nr: number;
|
||||
exit_ipr: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface MixNode {
|
||||
@@ -165,15 +156,7 @@ export interface LocatedGateway {
|
||||
location?: Location;
|
||||
}
|
||||
|
||||
export type GatewayResponse = LocatedGateway[];
|
||||
|
||||
export interface NymNodeReportResponse {
|
||||
identity: string;
|
||||
owner: string;
|
||||
most_recent: number;
|
||||
last_hour: number;
|
||||
last_day: number;
|
||||
}
|
||||
export type GatewayResponse = GatewayBond[];
|
||||
|
||||
export interface GatewayReportResponse {
|
||||
identity: string;
|
||||
|
||||
@@ -31,16 +31,6 @@ export const currencyToString = ({ amount, dp, denom = DENOM }: { amount: string
|
||||
return `${toDisplay(printableAmount, dp)} ${printableDenom}`;
|
||||
};
|
||||
|
||||
function addThousandsSeparator(value: string) {
|
||||
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
||||
}
|
||||
|
||||
export const humanReadableCurrencyToString = ({ amount, denom }: { amount: string, denom: string }) => {
|
||||
const str = currencyToString({ amount, denom, dp: 2 });
|
||||
const parts = str.split('.');
|
||||
return [addThousandsSeparator(parts[0]), parts[1]].join('.');
|
||||
}
|
||||
|
||||
export const stakingCurrencyToString = (amount: string, denom: string = DENOM_STAKING) =>
|
||||
printableCoin({
|
||||
amount,
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"material-react-table": "^2.12.1",
|
||||
"@mui/x-date-pickers": "7.1.1",
|
||||
"@mui/x-data-grid": "7.1.1",
|
||||
"@mui/x-charts": "^7.22.3"
|
||||
"@mui/x-data-grid": "7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
|
||||
@@ -31,6 +31,7 @@ impl GatewayTopologyProvider {
|
||||
min_gateway_performance: 0,
|
||||
},
|
||||
nym_api_url,
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
Some(user_agent),
|
||||
),
|
||||
cache_ttl,
|
||||
|
||||
@@ -540,7 +540,7 @@ impl<St> Gateway<St> {
|
||||
// as opposed to the validator API
|
||||
for api_url in self.config.get_nym_api_endpoints() {
|
||||
let client = nym_validator_client::NymApiClient::new(api_url.clone());
|
||||
match client.get_all_basic_nodes().await {
|
||||
match client.get_all_basic_nodes(None).await {
|
||||
Ok(nodes) => {
|
||||
return nodes.iter().any(|node| {
|
||||
&node.ed25519_identity_pubkey == self.identity_keypair.public_key()
|
||||
|
||||
@@ -160,7 +160,7 @@ impl MixNode {
|
||||
// as opposed to the validator API
|
||||
for api_url in self.config.get_nym_api_endpoints() {
|
||||
let client = nym_validator_client::NymApiClient::new(api_url.clone());
|
||||
match client.get_all_basic_nodes().await {
|
||||
match client.get_all_basic_nodes(None).await {
|
||||
Ok(nodes) => {
|
||||
return nodes.iter().any(|node| {
|
||||
&node.ed25519_identity_pubkey == self.identity_keypair.public_key()
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
CREATE TABLE monitor_run_report
|
||||
(
|
||||
monitor_run_id INTEGER PRIMARY KEY REFERENCES monitor_run (id),
|
||||
network_reliability FLOAT NOT NULL,
|
||||
packets_sent INTEGER NOT NULL,
|
||||
packets_received INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE monitor_run_score
|
||||
(
|
||||
-- mixnode or gateway
|
||||
typ TEXT NOT NULL,
|
||||
monitor_run_id INTEGER NOT NULL REFERENCES monitor_run_report (monitor_run_id),
|
||||
rounded_score INTEGER NOT NULL,
|
||||
nodes_count INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX monitor_run_score_id ON monitor_run_score (monitor_run_id);
|
||||
@@ -32,7 +32,6 @@ use schemars::gen::SchemaGenerator;
|
||||
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::net::IpAddr;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
@@ -1256,18 +1255,6 @@ pub struct PartialTestResult {
|
||||
pub type MixnodeTestResultResponse = PaginatedResponse<PartialTestResult>;
|
||||
pub type GatewayTestResultResponse = PaginatedResponse<PartialTestResult>;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
pub struct NetworkMonitorRunDetailsResponse {
|
||||
pub monitor_run_id: i64,
|
||||
pub network_reliability: f64,
|
||||
pub total_sent: usize,
|
||||
pub total_received: usize,
|
||||
|
||||
// integer score to number of nodes with that score
|
||||
pub mixnode_results: BTreeMap<u8, usize>,
|
||||
pub gateway_results: BTreeMap<u8, usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
pub struct NoiseDetails {
|
||||
#[schemars(with = "String")]
|
||||
@@ -1340,138 +1327,6 @@ impl NodeRefreshBody {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
pub struct RewardedSetResponse {
|
||||
pub entry_gateways: Vec<NodeId>,
|
||||
|
||||
pub exit_gateways: Vec<NodeId>,
|
||||
|
||||
pub layer1: Vec<NodeId>,
|
||||
|
||||
pub layer2: Vec<NodeId>,
|
||||
|
||||
pub layer3: Vec<NodeId>,
|
||||
|
||||
pub standby: Vec<NodeId>,
|
||||
}
|
||||
|
||||
pub use config_score::*;
|
||||
pub mod config_score {
|
||||
use nym_contracts_common::NaiveFloat;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
pub struct ConfigScoreDataResponse {
|
||||
pub parameters: ConfigScoreParams,
|
||||
pub version_history: Vec<HistoricalNymNodeVersionEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)]
|
||||
pub struct HistoricalNymNodeVersionEntry {
|
||||
/// The unique, ordered, id of this particular entry
|
||||
pub id: u32,
|
||||
|
||||
/// Data associated with this particular version
|
||||
pub version_information: HistoricalNymNodeVersion,
|
||||
}
|
||||
|
||||
impl PartialOrd for HistoricalNymNodeVersionEntry {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
// we only care about id for the purposes of ordering as they should have unique data
|
||||
self.id.partial_cmp(&other.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nym_mixnet_contract_common::HistoricalNymNodeVersionEntry>
|
||||
for HistoricalNymNodeVersionEntry
|
||||
{
|
||||
fn from(value: nym_mixnet_contract_common::HistoricalNymNodeVersionEntry) -> Self {
|
||||
HistoricalNymNodeVersionEntry {
|
||||
id: value.id,
|
||||
version_information: value.version_information.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, PartialEq)]
|
||||
pub struct HistoricalNymNodeVersion {
|
||||
/// Version of the nym node that is going to be used for determining the version score of a node.
|
||||
/// note: value stored here is pre-validated `semver::Version`
|
||||
pub semver: String,
|
||||
|
||||
/// Block height of when this version has been added to the contract
|
||||
pub introduced_at_height: u64,
|
||||
// for now ignore that field. it will give nothing useful to the users
|
||||
// pub difference_since_genesis: TotalVersionDifference,
|
||||
}
|
||||
|
||||
impl From<nym_mixnet_contract_common::HistoricalNymNodeVersion> for HistoricalNymNodeVersion {
|
||||
fn from(value: nym_mixnet_contract_common::HistoricalNymNodeVersion) -> Self {
|
||||
HistoricalNymNodeVersion {
|
||||
semver: value.semver,
|
||||
introduced_at_height: value.introduced_at_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
pub struct ConfigScoreParams {
|
||||
/// Defines weights for calculating numbers of versions behind the current release.
|
||||
pub version_weights: OutdatedVersionWeights,
|
||||
|
||||
/// Defines the parameters of the formula for calculating the version score
|
||||
pub version_score_formula_params: VersionScoreFormulaParams,
|
||||
}
|
||||
|
||||
/// Defines weights for calculating numbers of versions behind the current release.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
pub struct OutdatedVersionWeights {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
pub patch: u32,
|
||||
pub prerelease: u32,
|
||||
}
|
||||
|
||||
/// Given the formula of version_score = penalty ^ (versions_behind_factor ^ penalty_scaling)
|
||||
/// define the relevant parameters
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
pub struct VersionScoreFormulaParams {
|
||||
pub penalty: f64,
|
||||
pub penalty_scaling: f64,
|
||||
}
|
||||
|
||||
impl From<nym_mixnet_contract_common::ConfigScoreParams> for ConfigScoreParams {
|
||||
fn from(value: nym_mixnet_contract_common::ConfigScoreParams) -> Self {
|
||||
ConfigScoreParams {
|
||||
version_weights: value.version_weights.into(),
|
||||
version_score_formula_params: value.version_score_formula_params.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nym_mixnet_contract_common::OutdatedVersionWeights> for OutdatedVersionWeights {
|
||||
fn from(value: nym_mixnet_contract_common::OutdatedVersionWeights) -> Self {
|
||||
OutdatedVersionWeights {
|
||||
major: value.major,
|
||||
minor: value.minor,
|
||||
patch: value.patch,
|
||||
prerelease: value.prerelease,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nym_mixnet_contract_common::VersionScoreFormulaParams> for VersionScoreFormulaParams {
|
||||
fn from(value: nym_mixnet_contract_common::VersionScoreFormulaParams) -> Self {
|
||||
VersionScoreFormulaParams {
|
||||
penalty: value.penalty.naive_to_f64(),
|
||||
penalty_scaling: value.penalty_scaling.naive_to_f64(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -120,9 +120,14 @@ impl EpochAdvancer {
|
||||
|
||||
let epoch_end = interval.current_epoch_end();
|
||||
|
||||
let legacy_mixnodes = self.nym_contract_cache.legacy_mixnodes_filtered().await;
|
||||
let legacy_gateways = self.nym_contract_cache.legacy_gateways_filtered().await;
|
||||
|
||||
// TODO: for the purposes of rewarding, this might have to grab some pre-filtered nodes instead,
|
||||
// such as ones that use up to date version or have correct 'peanut' score
|
||||
let nym_nodes = self.nym_contract_cache.nym_nodes().await;
|
||||
|
||||
if nym_nodes.is_empty() {
|
||||
if legacy_mixnodes.is_empty() && legacy_gateways.is_empty() && nym_nodes.is_empty() {
|
||||
// that's a bit weird, but ok
|
||||
warn!("there don't seem to be any nodes on the network!")
|
||||
}
|
||||
@@ -155,7 +160,7 @@ impl EpochAdvancer {
|
||||
// note: those operations don't really have to be atomic, so it's fine to send them
|
||||
// as separate transactions
|
||||
self.reconcile_epoch_events().await?;
|
||||
self.update_rewarded_set_and_advance_epoch(&nym_nodes)
|
||||
self.update_rewarded_set_and_advance_epoch(&legacy_mixnodes, &legacy_gateways, &nym_nodes)
|
||||
.await?;
|
||||
|
||||
info!("Purging old node statuses from the storage...");
|
||||
|
||||
@@ -5,6 +5,8 @@ use crate::epoch_operations::error::RewardingError;
|
||||
use crate::epoch_operations::helpers::stake_to_f64;
|
||||
use crate::EpochAdvancer;
|
||||
use cosmwasm_std::Decimal;
|
||||
use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer};
|
||||
use nym_mixnet_contract_common::helpers::IntoBaseDecimal;
|
||||
use nym_mixnet_contract_common::reward_params::{Performance, RewardedSetParams};
|
||||
use nym_mixnet_contract_common::{EpochState, NodeId, NymNodeDetails, RewardedSet};
|
||||
use rand::prelude::SliceRandom;
|
||||
@@ -202,6 +204,8 @@ impl EpochAdvancer {
|
||||
|
||||
async fn attach_performance_to_eligible_nodes(
|
||||
&self,
|
||||
legacy_mixnodes: &[LegacyMixNodeDetailsWithLayer],
|
||||
legacy_gateways: &[LegacyGatewayBondWithId],
|
||||
nym_nodes: &[NymNodeDetails],
|
||||
) -> Vec<NodeWithStakeAndPerformance> {
|
||||
let mut with_performance = Vec::new();
|
||||
@@ -214,6 +218,62 @@ impl EpochAdvancer {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
for mix in legacy_mixnodes {
|
||||
let node_id = mix.mix_id();
|
||||
let total_stake = mix.total_stake();
|
||||
|
||||
let Some(annotation) = status_cache.get(&node_id) else {
|
||||
debug!("couldn't find annotation for legacy mixnode {node_id}");
|
||||
continue;
|
||||
};
|
||||
|
||||
if mix.bond_information.proxy.is_some() {
|
||||
debug!("legacy mixnode {node_id} is using vested tokens");
|
||||
continue;
|
||||
}
|
||||
|
||||
let performance = annotation.detailed_performance.to_rewarding_performance();
|
||||
debug!(
|
||||
"legacy mixnode {}: stake: {total_stake}, performance: {performance}",
|
||||
mix.mix_id()
|
||||
);
|
||||
|
||||
with_performance.push(NodeWithStakeAndPerformance {
|
||||
node_id: mix.mix_id(),
|
||||
available_roles: vec![AvailableRole::Mix],
|
||||
total_stake,
|
||||
performance,
|
||||
})
|
||||
}
|
||||
for gateway in legacy_gateways {
|
||||
let node_id = gateway.node_id;
|
||||
let total_stake = gateway
|
||||
.bond
|
||||
.pledge_amount
|
||||
.amount
|
||||
.into_base_decimal()
|
||||
.unwrap_or_default();
|
||||
|
||||
let Some(annotation) = status_cache.get(&node_id) else {
|
||||
debug!("couldn't find annotation for legacy gateway {node_id}");
|
||||
continue;
|
||||
};
|
||||
|
||||
let performance = annotation.detailed_performance.to_rewarding_performance();
|
||||
|
||||
debug!(
|
||||
"legacy gateway {}: stake: {total_stake}, performance: {performance}",
|
||||
gateway.node_id
|
||||
);
|
||||
|
||||
with_performance.push(NodeWithStakeAndPerformance {
|
||||
node_id: gateway.node_id,
|
||||
available_roles: vec![AvailableRole::EntryGateway],
|
||||
total_stake,
|
||||
performance,
|
||||
})
|
||||
}
|
||||
|
||||
for nym_node in nym_nodes {
|
||||
let node_id = nym_node.node_id();
|
||||
let total_stake = nym_node.total_stake();
|
||||
@@ -223,7 +283,7 @@ impl EpochAdvancer {
|
||||
};
|
||||
|
||||
let Some(annotation) = status_cache.get(&node_id) else {
|
||||
debug!("couldn't find annotation for nym-node {node_id}");
|
||||
debug!("couldn't find annotation for nym-node gateway {node_id}");
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -259,6 +319,8 @@ impl EpochAdvancer {
|
||||
|
||||
pub(super) async fn update_rewarded_set_and_advance_epoch(
|
||||
&self,
|
||||
legacy_mixnodes: &[LegacyMixNodeDetailsWithLayer],
|
||||
legacy_gateways: &[LegacyGatewayBondWithId],
|
||||
nym_nodes: &[NymNodeDetails],
|
||||
) -> Result<(), RewardingError> {
|
||||
let epoch_status = self.nyxd_client.get_current_epoch_status().await?;
|
||||
@@ -271,8 +333,13 @@ impl EpochAdvancer {
|
||||
}
|
||||
|
||||
info!("attempting to assign the rewarded set for the upcoming epoch...");
|
||||
let nodes_with_performance =
|
||||
self.attach_performance_to_eligible_nodes(nym_nodes).await;
|
||||
let nodes_with_performance = self
|
||||
.attach_performance_to_eligible_nodes(
|
||||
legacy_mixnodes,
|
||||
legacy_gateways,
|
||||
nym_nodes,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = self
|
||||
._update_rewarded_set_and_advance_epoch(nodes_with_performance)
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use futures::Stream;
|
||||
use nym_crypto::asymmetric::{ed25519, identity};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
|
||||
use nym_mixnet_contract_common::IdentityKey;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio_stream::StreamMap;
|
||||
@@ -14,8 +15,8 @@ pub(crate) enum GatewayMessages {
|
||||
}
|
||||
|
||||
pub(crate) struct GatewaysReader {
|
||||
ack_map: StreamMap<ed25519::PublicKey, AcknowledgementReceiver>,
|
||||
stream_map: StreamMap<ed25519::PublicKey, MixnetMessageReceiver>,
|
||||
ack_map: StreamMap<IdentityKey, AcknowledgementReceiver>,
|
||||
stream_map: StreamMap<IdentityKey, MixnetMessageReceiver>,
|
||||
}
|
||||
|
||||
impl GatewaysReader {
|
||||
@@ -32,18 +33,19 @@ impl GatewaysReader {
|
||||
message_receiver: MixnetMessageReceiver,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
) {
|
||||
self.stream_map.insert(id, message_receiver);
|
||||
self.ack_map.insert(id, ack_receiver);
|
||||
let channel_id = id.to_string();
|
||||
self.stream_map.insert(channel_id.clone(), message_receiver);
|
||||
self.ack_map.insert(channel_id, ack_receiver);
|
||||
}
|
||||
|
||||
pub fn remove_receivers(&mut self, id: ed25519::PublicKey) {
|
||||
self.stream_map.remove(&id);
|
||||
self.ack_map.remove(&id);
|
||||
pub fn remove_receivers(&mut self, id: &str) {
|
||||
self.stream_map.remove(id);
|
||||
self.ack_map.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for GatewaysReader {
|
||||
type Item = (ed25519::PublicKey, GatewayMessages);
|
||||
type Item = (IdentityKey, GatewayMessages);
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match Pin::new(&mut self.ack_map).poll_next(cx) {
|
||||
|
||||
@@ -12,12 +12,10 @@ use crate::network_monitor::monitor::sender::PacketSender;
|
||||
use crate::network_monitor::monitor::summary_producer::SummaryProducer;
|
||||
use crate::network_monitor::monitor::Monitor;
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::NodeStatusCache;
|
||||
use crate::nym_contract_cache::cache::NymContractCache;
|
||||
use crate::storage::NymApiStorage;
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::config::Config;
|
||||
use crate::support::nyxd;
|
||||
use crate::support::{config, nyxd};
|
||||
use futures::channel::mpsc;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
@@ -37,10 +35,9 @@ pub(crate) mod test_route;
|
||||
pub(crate) const ROUTE_TESTING_TEST_NONCE: u64 = 0;
|
||||
|
||||
pub(crate) fn setup<'a>(
|
||||
config: &'a Config,
|
||||
config: &'a config::NetworkMonitor,
|
||||
nym_contract_cache: &NymContractCache,
|
||||
described_cache: SharedCache<DescribedNodes>,
|
||||
node_status_cache: NodeStatusCache,
|
||||
storage: &NymApiStorage,
|
||||
nyxd_client: nyxd::Client,
|
||||
) -> NetworkMonitorBuilder<'a> {
|
||||
@@ -50,27 +47,24 @@ pub(crate) fn setup<'a>(
|
||||
storage.to_owned(),
|
||||
nym_contract_cache.clone(),
|
||||
described_cache,
|
||||
node_status_cache,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) struct NetworkMonitorBuilder<'a> {
|
||||
config: &'a Config,
|
||||
config: &'a config::NetworkMonitor,
|
||||
nyxd_client: nyxd::Client,
|
||||
node_status_storage: NymApiStorage,
|
||||
contract_cache: NymContractCache,
|
||||
described_cache: SharedCache<DescribedNodes>,
|
||||
node_status_cache: NodeStatusCache,
|
||||
}
|
||||
|
||||
impl<'a> NetworkMonitorBuilder<'a> {
|
||||
pub(crate) fn new(
|
||||
config: &'a Config,
|
||||
config: &'a config::NetworkMonitor,
|
||||
nyxd_client: nyxd::Client,
|
||||
node_status_storage: NymApiStorage,
|
||||
contract_cache: NymContractCache,
|
||||
described_cache: SharedCache<DescribedNodes>,
|
||||
node_status_cache: NodeStatusCache,
|
||||
) -> Self {
|
||||
NetworkMonitorBuilder {
|
||||
config,
|
||||
@@ -78,11 +72,10 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
node_status_storage,
|
||||
contract_cache,
|
||||
described_cache,
|
||||
node_status_cache,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn build<R: MessageReceiver + Send + Sync + 'static>(
|
||||
pub(crate) async fn build<R: MessageReceiver + Send + 'static>(
|
||||
self,
|
||||
) -> NetworkMonitorRunnables<R> {
|
||||
// TODO: those keys change constant throughout the whole execution of the monitor.
|
||||
@@ -101,8 +94,7 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
let packet_preparer = new_packet_preparer(
|
||||
self.contract_cache,
|
||||
self.described_cache,
|
||||
self.node_status_cache,
|
||||
self.config.network_monitor.debug.per_node_test_packets,
|
||||
self.config.debug.per_node_test_packets,
|
||||
Arc::clone(&ack_key),
|
||||
*identity_keypair.public_key(),
|
||||
*encryption_keypair.public_key(),
|
||||
@@ -111,11 +103,7 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
let bandwidth_controller = {
|
||||
BandwidthController::new(
|
||||
nym_credential_storage::initialise_persistent_storage(
|
||||
&self
|
||||
.config
|
||||
.network_monitor
|
||||
.storage_paths
|
||||
.credentials_database_path,
|
||||
&self.config.storage_paths.credentials_database_path,
|
||||
)
|
||||
.await,
|
||||
self.nyxd_client.clone(),
|
||||
@@ -126,7 +114,9 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
self.config,
|
||||
gateway_status_update_sender,
|
||||
Arc::clone(&identity_keypair),
|
||||
self.config.debug.gateway_sending_rate,
|
||||
bandwidth_controller,
|
||||
self.config.debug.disabled_credentials_mode,
|
||||
);
|
||||
|
||||
let received_processor = new_received_processor(
|
||||
@@ -134,15 +124,14 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
Arc::clone(&encryption_keypair),
|
||||
ack_key,
|
||||
);
|
||||
let summary_producer =
|
||||
new_summary_producer(self.config.network_monitor.debug.per_node_test_packets);
|
||||
let summary_producer = new_summary_producer(self.config.debug.per_node_test_packets);
|
||||
let packet_receiver = new_packet_receiver(
|
||||
gateway_status_update_receiver,
|
||||
received_processor_sender_channel,
|
||||
);
|
||||
|
||||
let monitor = Monitor::new(
|
||||
&self.config.network_monitor,
|
||||
self.config,
|
||||
packet_preparer,
|
||||
packet_sender,
|
||||
received_processor,
|
||||
@@ -158,12 +147,12 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct NetworkMonitorRunnables<R: MessageReceiver + Send + Sync + 'static> {
|
||||
pub(crate) struct NetworkMonitorRunnables<R: MessageReceiver + Send + 'static> {
|
||||
monitor: Monitor<R>,
|
||||
packet_receiver: PacketReceiver,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver + Send + Sync + 'static> NetworkMonitorRunnables<R> {
|
||||
impl<R: MessageReceiver + Send + 'static> NetworkMonitorRunnables<R> {
|
||||
// TODO: note, that is not exactly doing what we want, because when
|
||||
// `ReceivedProcessor` is constructed, it already spawns a future
|
||||
// this needs to be refactored!
|
||||
@@ -180,7 +169,6 @@ impl<R: MessageReceiver + Send + Sync + 'static> NetworkMonitorRunnables<R> {
|
||||
fn new_packet_preparer(
|
||||
contract_cache: NymContractCache,
|
||||
described_cache: SharedCache<DescribedNodes>,
|
||||
node_status_cache: NodeStatusCache,
|
||||
per_node_test_packets: usize,
|
||||
ack_key: Arc<AckKey>,
|
||||
self_public_identity: identity::PublicKey,
|
||||
@@ -189,7 +177,6 @@ fn new_packet_preparer(
|
||||
PacketPreparer::new(
|
||||
contract_cache,
|
||||
described_cache,
|
||||
node_status_cache,
|
||||
per_node_test_packets,
|
||||
ack_key,
|
||||
self_public_identity,
|
||||
@@ -198,16 +185,22 @@ fn new_packet_preparer(
|
||||
}
|
||||
|
||||
fn new_packet_sender(
|
||||
config: &Config,
|
||||
config: &config::NetworkMonitor,
|
||||
gateways_status_updater: GatewayClientUpdateSender,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
max_sending_rate: usize,
|
||||
bandwidth_controller: BandwidthController<nyxd::Client, PersistentStorage>,
|
||||
disabled_credentials_mode: bool,
|
||||
) -> PacketSender {
|
||||
PacketSender::new(
|
||||
config,
|
||||
gateways_status_updater,
|
||||
local_identity,
|
||||
config.debug.gateway_response_timeout,
|
||||
config.debug.gateway_connection_timeout,
|
||||
config.debug.max_concurrent_gateway_clients,
|
||||
max_sending_rate,
|
||||
bandwidth_controller,
|
||||
disabled_credentials_mode,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -234,11 +227,10 @@ fn new_packet_receiver(
|
||||
|
||||
// TODO: 1) does it still have to have separate builder or could we get rid of it now?
|
||||
// TODO: 2) how do we make it non-async as other 'start' methods?
|
||||
pub(crate) async fn start<R: MessageReceiver + Send + Sync + 'static>(
|
||||
config: &Config,
|
||||
pub(crate) async fn start<R: MessageReceiver + Send + 'static>(
|
||||
config: &config::NetworkMonitor,
|
||||
nym_contract_cache: &NymContractCache,
|
||||
described_cache: SharedCache<DescribedNodes>,
|
||||
node_status_cache: NodeStatusCache,
|
||||
storage: &NymApiStorage,
|
||||
nyxd_client: nyxd::Client,
|
||||
shutdown: &TaskManager,
|
||||
@@ -247,7 +239,6 @@ pub(crate) async fn start<R: MessageReceiver + Send + Sync + 'static>(
|
||||
config,
|
||||
nym_contract_cache,
|
||||
described_cache,
|
||||
node_status_cache,
|
||||
storage,
|
||||
nyxd_client,
|
||||
);
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::network_monitor::monitor::receiver::{GatewayClientUpdate, GatewayClientUpdateSender};
|
||||
use crate::support::nyxd;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use tracing::warn;
|
||||
|
||||
pub(crate) struct GatewayClientHandle {
|
||||
client: GatewayClient<nyxd::Client, PersistentStorage>,
|
||||
gateways_status_updater: GatewayClientUpdateSender,
|
||||
}
|
||||
|
||||
impl GatewayClientHandle {
|
||||
pub(crate) fn new(
|
||||
client: GatewayClient<nyxd::Client, PersistentStorage>,
|
||||
gateways_status_updater: GatewayClientUpdateSender,
|
||||
) -> Self {
|
||||
GatewayClientHandle {
|
||||
client,
|
||||
gateways_status_updater,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GatewayClientHandle {
|
||||
fn drop(&mut self) {
|
||||
if self
|
||||
.gateways_status_updater
|
||||
.unbounded_send(GatewayClientUpdate::Disconnect(
|
||||
self.client.gateway_identity(),
|
||||
))
|
||||
.is_err()
|
||||
{
|
||||
warn!("fail to cleanly shutdown gateway connection")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for GatewayClientHandle {
|
||||
type Target = GatewayClient<nyxd::Client, PersistentStorage>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for GatewayClientHandle {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.client
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user