Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d29c83405b | |||
| 95dd050555 | |||
| 6bbf913f3a | |||
| a795eb1351 | |||
| d8f11888d9 | |||
| 0a73982dda | |||
| ab0f3b5e6a | |||
| 198c3a6a00 | |||
| b83c28a9e8 |
-146
@@ -4,152 +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
|
||||
|
||||
## [2024.13-magura-patched] (2024-11-22)
|
||||
|
||||
- [experimental] allow clients to change between deterministic route selection based on packet headers and a pseudorandom distribution
|
||||
|
||||
Generated
+3
-3
@@ -4674,6 +4674,7 @@ dependencies = [
|
||||
"opentelemetry-jaeger",
|
||||
"pretty_env_logger",
|
||||
"schemars",
|
||||
"semver 1.0.23",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing-opentelemetry",
|
||||
@@ -5092,7 +5093,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.1.6"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -5953,7 +5954,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.2.0"
|
||||
version = "1.1.12"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
@@ -6889,7 +6890,6 @@ dependencies = [
|
||||
"nym-task",
|
||||
"nym-wireguard-types",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"x25519-dalek",
|
||||
|
||||
@@ -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,
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ pub struct TopologyReadPermit<'a> {
|
||||
permit: RwLockReadGuard<'a, Option<NymTopology>>,
|
||||
}
|
||||
|
||||
impl Deref for TopologyReadPermit<'_> {
|
||||
impl<'a> Deref for TopologyReadPermit<'a> {
|
||||
type Target = Option<NymTopology>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ impl Div<GasPrice> for Coin {
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<GasPrice> for &Coin {
|
||||
impl<'a> Div<GasPrice> for &'a Coin {
|
||||
type Output = Gas;
|
||||
|
||||
fn div(self, rhs: GasPrice) -> Self::Output {
|
||||
|
||||
+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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct GasPrice {
|
||||
pub denom: String,
|
||||
}
|
||||
|
||||
impl Mul<Gas> for &GasPrice {
|
||||
impl<'a> Mul<Gas> for &'a GasPrice {
|
||||
type Output = Coin;
|
||||
|
||||
fn mul(self, gas_limit: Gas) -> Self::Output {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -32,7 +32,7 @@ pub(crate) mod string_rfc3339_offset_date_time {
|
||||
|
||||
struct Rfc3339OffsetDateTimeVisitor;
|
||||
|
||||
impl Visitor<'_> for Rfc3339OffsetDateTimeVisitor {
|
||||
impl<'de> Visitor<'de> for Rfc3339OffsetDateTimeVisitor {
|
||||
type Value = OffsetDateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ impl<S: Storage + Clone + 'static> BandwidthStorageManager<S> {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
pub async fn sync_storage_bandwidth(&mut self) -> Result<()> {
|
||||
async fn sync_storage_bandwidth(&mut self) -> Result<()> {
|
||||
trace!("syncing client bandwidth with the underlying storage");
|
||||
let updated = self
|
||||
.storage
|
||||
|
||||
@@ -8,8 +8,8 @@ use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_secs(5 * 60); // 5 minutes
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 5 * 1024 * 1024; // 5MB
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5);
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BandwidthFlushingBehaviourConfig {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -26,8 +26,9 @@ const PARALLEL_RUNS: usize = 32;
|
||||
/// `lambda` ($\lambda$) in the DKG paper
|
||||
const SECURITY_PARAMETER: usize = 256;
|
||||
|
||||
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
|
||||
/// ceil(SECURITY_PARAMETER / PARALLEL_RUNS) in the paper
|
||||
const NUM_CHALLENGE_BITS: usize = SECURITY_PARAMETER.div_ceil(PARALLEL_RUNS);
|
||||
const NUM_CHALLENGE_BITS: usize = (SECURITY_PARAMETER + PARALLEL_RUNS - 1) / PARALLEL_RUNS;
|
||||
|
||||
// type alias for ease of use
|
||||
type FirstChallenge = Vec<Vec<Vec<u64>>>;
|
||||
|
||||
@@ -196,7 +196,7 @@ impl<'b> Add<&'b Polynomial> for Polynomial {
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Polynomial> for &Polynomial {
|
||||
impl<'a> Add<Polynomial> for &'a Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: Polynomial) -> Polynomial {
|
||||
@@ -212,10 +212,10 @@ impl Add<Polynomial> for Polynomial {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Add<&'a Polynomial> for &Polynomial {
|
||||
impl<'a, 'b> Add<&'b Polynomial> for &'a Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: &'a Polynomial) -> Self::Output {
|
||||
fn add(self, rhs: &'b Polynomial) -> Self::Output {
|
||||
let len = self.coefficients.len();
|
||||
let rhs_len = rhs.coefficients.len();
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ pub struct GatewayHandshake<'a> {
|
||||
handshake_future: BoxFuture<'a, Result<SharedGatewayKey, HandshakeError>>,
|
||||
}
|
||||
|
||||
impl Future for GatewayHandshake<'_> {
|
||||
impl<'a> Future for GatewayHandshake<'a> {
|
||||
type Output = Result<SharedGatewayKey, HandshakeError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -324,6 +324,18 @@ pub fn unchecked_aggregate_indices_signatures(
|
||||
_aggregate_indices_signatures(params, vk, signatures_shares, false)
|
||||
}
|
||||
|
||||
/// Generates parameters for the scheme setup.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `total_coins` - it is the number of coins in a freshly generated wallet. It is the public parameter of the scheme.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Parameters` struct containing group parameters, public key, the number of signatures (`total_coins`),
|
||||
/// and a map of signatures for each index `l`.
|
||||
///
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -264,7 +264,7 @@ impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Scalar> for &VerificationKeyAuth {
|
||||
impl<'a> Mul<Scalar> for &'a VerificationKeyAuth {
|
||||
type Output = VerificationKeyAuth;
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -984,7 +984,7 @@ pub struct SerialNumberRef<'a> {
|
||||
pub(crate) inner: &'a [G1Projective],
|
||||
}
|
||||
|
||||
impl SerialNumberRef<'_> {
|
||||
impl<'a> SerialNumberRef<'a> {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let ss_len = self.inner.len();
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(ss_len * 48);
|
||||
|
||||
@@ -206,10 +206,10 @@ impl Deref for PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<&'a Scalar> for &PublicKey {
|
||||
impl<'a, 'b> Mul<&'b Scalar> for &'a PublicKey {
|
||||
type Output = G1Projective;
|
||||
|
||||
fn mul(self, rhs: &'a Scalar) -> Self::Output {
|
||||
fn mul(self, rhs: &'b Scalar) -> Self::Output {
|
||||
self.0 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ impl<'b> Add<&'b VerificationKey> for VerificationKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Scalar> for &VerificationKey {
|
||||
impl<'a> Mul<Scalar> for &'a VerificationKey {
|
||||
type Output = VerificationKey;
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -64,7 +64,7 @@ impl<'de> Deserialize<'de> for Recipient {
|
||||
{
|
||||
struct RecipientVisitor;
|
||||
|
||||
impl Visitor<'_> for RecipientVisitor {
|
||||
impl<'de> Visitor<'de> for RecipientVisitor {
|
||||
type Value = Recipient;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Encodoing and decoding node routing information.
|
||||
//!
|
||||
//! This module is responsible for encoding and decoding node routing information, so that
|
||||
//! they could be later put into an appropriate field in a sphinx header.
|
||||
//! Currently, that routing information is an IP address, but in principle it can be anything
|
||||
//! for as long as it's going to fit in the field.
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx_types::{NodeAddressBytes, NODE_ADDRESS_LENGTH};
|
||||
|
||||
@@ -18,6 +12,13 @@ use thiserror::Error;
|
||||
pub type NodeIdentity = identity::PublicKey;
|
||||
pub const NODE_IDENTITY_SIZE: usize = identity::PUBLIC_KEY_LENGTH;
|
||||
|
||||
/// Encodoing and decoding node routing information.
|
||||
///
|
||||
/// This module is responsible for encoding and decoding node routing information, so that
|
||||
/// they could be later put into an appropriate field in a sphinx header.
|
||||
/// Currently, that routing information is an IP address, but in principle it can be anything
|
||||
/// for as long as it's going to fit in the field.
|
||||
|
||||
/// MAX_UNPADDED_LEN represents maximum length an unpadded address could have.
|
||||
/// In this case it's an ipv6 socket address (with version prefix)
|
||||
pub const MAX_NODE_ADDRESS_UNPADDED_LEN: usize = 19;
|
||||
|
||||
@@ -56,7 +56,7 @@ impl<'de> Deserialize<'de> for ReplySurb {
|
||||
{
|
||||
struct ReplySurbVisitor;
|
||||
|
||||
impl Visitor<'_> for ReplySurbVisitor {
|
||||
impl<'de> Visitor<'de> for ReplySurbVisitor {
|
||||
type Value = ReplySurb;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||
|
||||
@@ -253,15 +253,25 @@ impl Socks5RequestContent {
|
||||
/// Deserialize the request type, connection id, destination address and port,
|
||||
/// and the request body from bytes.
|
||||
///
|
||||
// TODO: this was already inaccurate
|
||||
// /// Serialized bytes looks like this:
|
||||
// ///
|
||||
// /// --------------------------------------------------------------------------------------
|
||||
// /// request_flag | connection_id | address_length | remote_address_bytes | request_data |
|
||||
// /// 1 | 8 | 2 | address_length | ... |
|
||||
// /// --------------------------------------------------------------------------------------
|
||||
///
|
||||
/// The request_flag tells us whether this is a new connection request (`new_connect`),
|
||||
/// an already-established connection we should send up (`new_send`), or
|
||||
/// a request to close an established connection (`new_close`).
|
||||
|
||||
// connect:
|
||||
// RequestFlag::Connect || CONN_ID || ADDR_LEN || ADDR || <RETURN_ADDR>
|
||||
//
|
||||
// send:
|
||||
// RequestFlag::Send || CONN_ID || LOCAL_CLOSED || DATA
|
||||
// where DATA: SEQ || TRUE_DATA
|
||||
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<Socks5RequestContent, RequestDeserializationError> {
|
||||
// each request needs to at least contain flag and ConnectionId
|
||||
if b.is_empty() {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ log.workspace = true
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
|
||||
tokio-stream = { workspace = true }
|
||||
time = { workspace = true }
|
||||
|
||||
nym-authenticator-requests = { path = "../authenticator-requests" }
|
||||
nym-credential-verification = { path = "../credential-verification" }
|
||||
|
||||
@@ -20,7 +20,6 @@ use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
pub(crate) mod error;
|
||||
pub mod peer_controller;
|
||||
pub mod peer_handle;
|
||||
pub mod peer_storage_manager;
|
||||
|
||||
pub struct WgApiWrapper {
|
||||
inner: WGApi,
|
||||
@@ -119,7 +118,7 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
|
||||
storage
|
||||
.insert_wireguard_peer(peer, bandwidth_manager.is_some())
|
||||
.await?;
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone()));
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), bandwidth_manager);
|
||||
}
|
||||
wg_api.create_interface()?;
|
||||
let interface_config = InterfaceConfiguration {
|
||||
|
||||
@@ -20,9 +20,9 @@ use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
use crate::peer_handle::PeerHandle;
|
||||
use crate::WgApiWrapper;
|
||||
use crate::{error::Error, peer_handle::SharedBandwidthStorageManager};
|
||||
use crate::{peer_handle::PeerHandle, peer_storage_manager::PeerStorageManager};
|
||||
|
||||
pub enum PeerControlRequest {
|
||||
AddPeer {
|
||||
@@ -79,7 +79,7 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
storage: St,
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
initial_host_information: Host,
|
||||
bw_storage_managers: HashMap<Key, (Option<SharedBandwidthStorageManager<St>>, Peer)>,
|
||||
bw_storage_managers: HashMap<Key, Option<SharedBandwidthStorageManager<St>>>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
request_rx: mpsc::Receiver<PeerControlRequest>,
|
||||
task_client: nym_task::TaskClient,
|
||||
@@ -88,16 +88,11 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK),
|
||||
);
|
||||
let host_information = Arc::new(RwLock::new(initial_host_information));
|
||||
for (public_key, (bandwidth_storage_manager, peer)) in bw_storage_managers.iter() {
|
||||
let peer_storage_manager = PeerStorageManager::new(
|
||||
storage.clone(),
|
||||
peer.clone(),
|
||||
bandwidth_storage_manager.is_some(),
|
||||
);
|
||||
for (public_key, bandwidth_storage_manager) in bw_storage_managers.iter() {
|
||||
let mut handle = PeerHandle::new(
|
||||
storage.clone(),
|
||||
public_key.clone(),
|
||||
host_information.clone(),
|
||||
peer_storage_manager,
|
||||
bandwidth_storage_manager.clone(),
|
||||
request_tx.clone(),
|
||||
&task_client,
|
||||
@@ -108,10 +103,6 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
}
|
||||
});
|
||||
}
|
||||
let bw_storage_managers = bw_storage_managers
|
||||
.into_iter()
|
||||
.map(|(k, (m, _))| (k, m))
|
||||
.collect();
|
||||
|
||||
PeerController {
|
||||
storage,
|
||||
@@ -193,15 +184,10 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
Self::generate_bandwidth_manager(self.storage.clone(), &peer.public_key)
|
||||
.await?
|
||||
.map(|bw_m| Arc::new(RwLock::new(bw_m)));
|
||||
let peer_storage_manager = PeerStorageManager::new(
|
||||
self.storage.clone(),
|
||||
peer.clone(),
|
||||
bandwidth_storage_manager.is_some(),
|
||||
);
|
||||
let mut handle = PeerHandle::new(
|
||||
self.storage.clone(),
|
||||
peer.public_key.clone(),
|
||||
self.host_information.clone(),
|
||||
peer_storage_manager,
|
||||
bandwidth_storage_manager.clone(),
|
||||
self.request_tx.clone(),
|
||||
&self.task_client,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::peer_controller::PeerControlRequest;
|
||||
use crate::peer_storage_manager::PeerStorageManager;
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use defguard_wireguard_rs::{host::Host, key::Key};
|
||||
use futures::channel::oneshot;
|
||||
@@ -22,9 +21,9 @@ pub(crate) type SharedBandwidthStorageManager<St> = Arc<RwLock<BandwidthStorageM
|
||||
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 30); // 30 days
|
||||
|
||||
pub struct PeerHandle<St> {
|
||||
storage: St,
|
||||
public_key: Key,
|
||||
host_information: Arc<RwLock<Host>>,
|
||||
peer_storage_manager: PeerStorageManager<St>,
|
||||
bandwidth_storage_manager: Option<SharedBandwidthStorageManager<St>>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
timeout_check_interval: IntervalStream,
|
||||
@@ -34,9 +33,9 @@ pub struct PeerHandle<St> {
|
||||
|
||||
impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
pub fn new(
|
||||
storage: St,
|
||||
public_key: Key,
|
||||
host_information: Arc<RwLock<Host>>,
|
||||
peer_storage_manager: PeerStorageManager<St>,
|
||||
bandwidth_storage_manager: Option<SharedBandwidthStorageManager<St>>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
task_client: &TaskClient,
|
||||
@@ -47,9 +46,9 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
let mut task_client = task_client.fork(format!("peer-{public_key}"));
|
||||
task_client.disarm();
|
||||
PeerHandle {
|
||||
storage,
|
||||
public_key,
|
||||
host_information,
|
||||
peer_storage_manager,
|
||||
bandwidth_storage_manager,
|
||||
request_tx,
|
||||
timeout_check_interval,
|
||||
@@ -85,19 +84,16 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
.ok_or(Error::InconsistentConsumedBytes)?
|
||||
.try_into()
|
||||
.map_err(|_| Error::InconsistentConsumedBytes)?;
|
||||
if spent_bandwidth > 0 {
|
||||
self.peer_storage_manager.update_trx(kernel_peer);
|
||||
if bandwidth_manager
|
||||
if spent_bandwidth > 0
|
||||
&& bandwidth_manager
|
||||
.write()
|
||||
.await
|
||||
.try_use_bandwidth(spent_bandwidth)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
let success = self.remove_peer().await?;
|
||||
self.peer_storage_manager.remove_peer();
|
||||
return Ok(!success);
|
||||
}
|
||||
{
|
||||
let success = self.remove_peer().await?;
|
||||
return Ok(!success);
|
||||
}
|
||||
} else {
|
||||
if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER {
|
||||
@@ -136,7 +132,7 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
// the host information hasn't beed updated yet
|
||||
continue;
|
||||
};
|
||||
let Some(storage_peer) = self.peer_storage_manager.get_peer() else {
|
||||
let Some(storage_peer) = self.storage.get_wireguard_peer(&self.public_key.to_string()).await? else {
|
||||
log::debug!("Peer {:?} not in storage anymore, shutting down handle", self.public_key);
|
||||
return Ok(());
|
||||
};
|
||||
@@ -145,18 +141,12 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
return Ok(());
|
||||
} else {
|
||||
// Update storage values
|
||||
self.peer_storage_manager.sync_storage_peer().await?;
|
||||
self.storage.insert_wireguard_peer(&kernel_peer, self.bandwidth_storage_manager.is_some()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
_ = self.task_client.recv() => {
|
||||
log::trace!("PeerHandle: Received shutdown");
|
||||
if let Some(bandwidth_manager) = &self.bandwidth_storage_manager {
|
||||
if let Err(e) = bandwidth_manager.write().await.sync_storage_bandwidth().await {
|
||||
log::error!("Storage sync failed - {e}, unaccounted bandwidth might have been consumed");
|
||||
}
|
||||
}
|
||||
log::trace!("PeerHandle: Finished shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Error;
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use nym_gateway_storage::models::WireguardPeer;
|
||||
use nym_gateway_storage::Storage;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
const DEFAULT_PEER_MAX_FLUSHING_RATE: Duration = Duration::from_secs(60 * 60 * 24); // 24h
|
||||
const DEFAULT_PEER_MAX_DELTA_FLUSHING_AMOUNT: u64 = 512 * 1024 * 1024; // 512MB
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PeerFlushingBehaviourConfig {
|
||||
/// Defines maximum delay between peer information being flushed to the persistent storage.
|
||||
pub peer_max_flushing_rate: Duration,
|
||||
|
||||
/// Defines a maximum change in peer before it gets flushed to the persistent storage.
|
||||
pub peer_max_delta_flushing_amount: u64,
|
||||
}
|
||||
|
||||
impl Default for PeerFlushingBehaviourConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
peer_max_flushing_rate: DEFAULT_PEER_MAX_FLUSHING_RATE,
|
||||
peer_max_delta_flushing_amount: DEFAULT_PEER_MAX_DELTA_FLUSHING_AMOUNT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PeerStorageManager<S> {
|
||||
pub(crate) storage: S,
|
||||
pub(crate) peer_information: Option<PeerInformation>,
|
||||
pub(crate) cfg: PeerFlushingBehaviourConfig,
|
||||
pub(crate) with_client_id: bool,
|
||||
}
|
||||
|
||||
impl<S: Storage + Clone + 'static> PeerStorageManager<S> {
|
||||
pub(crate) fn new(storage: S, peer: Peer, with_client_id: bool) -> Self {
|
||||
let peer_information = Some(PeerInformation::new(peer));
|
||||
Self {
|
||||
storage,
|
||||
peer_information,
|
||||
cfg: PeerFlushingBehaviourConfig::default(),
|
||||
with_client_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_peer(&self) -> Option<WireguardPeer> {
|
||||
self.peer_information
|
||||
.as_ref()
|
||||
.map(|p| p.peer.clone().into())
|
||||
}
|
||||
|
||||
pub(crate) fn remove_peer(&mut self) {
|
||||
self.peer_information = None;
|
||||
}
|
||||
|
||||
pub(crate) fn update_trx(&mut self, kernel_peer: &Peer) {
|
||||
if let Some(peer_information) = self.peer_information.as_mut() {
|
||||
peer_information.update_trx_bytes(kernel_peer.tx_bytes, kernel_peer.rx_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn sync_storage_peer(&mut self) -> Result<(), Error> {
|
||||
let Some(peer_information) = self.peer_information.as_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
if !peer_information.should_sync(self.cfg) {
|
||||
return Ok(());
|
||||
}
|
||||
if self
|
||||
.storage
|
||||
.get_wireguard_peer(&peer_information.peer().public_key.to_string())
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
self.peer_information = None;
|
||||
return Ok(());
|
||||
}
|
||||
self.storage
|
||||
.insert_wireguard_peer(peer_information.peer(), self.with_client_id)
|
||||
.await?;
|
||||
|
||||
peer_information.resync_peer_with_storage();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct PeerInformation {
|
||||
pub(crate) peer: Peer,
|
||||
pub(crate) last_synced: OffsetDateTime,
|
||||
|
||||
pub(crate) bytes_delta_since_sync: u64,
|
||||
}
|
||||
|
||||
impl PeerInformation {
|
||||
pub fn new(peer: Peer) -> PeerInformation {
|
||||
PeerInformation {
|
||||
peer,
|
||||
last_synced: OffsetDateTime::now_utc(),
|
||||
bytes_delta_since_sync: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn should_sync(&self, cfg: PeerFlushingBehaviourConfig) -> bool {
|
||||
if self.bytes_delta_since_sync >= cfg.peer_max_delta_flushing_amount {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.last_synced + cfg.peer_max_flushing_rate < OffsetDateTime::now_utc()
|
||||
&& self.bytes_delta_since_sync != 0
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn peer(&self) -> &Peer {
|
||||
&self.peer
|
||||
}
|
||||
|
||||
pub(crate) fn update_trx_bytes(&mut self, tx_bytes: u64, rx_bytes: u64) {
|
||||
self.bytes_delta_since_sync += tx_bytes.saturating_sub(self.peer.tx_bytes)
|
||||
+ rx_bytes.saturating_sub(self.peer.rx_bytes);
|
||||
self.peer.tx_bytes = tx_bytes;
|
||||
self.peer.rx_bytes = rx_bytes;
|
||||
}
|
||||
|
||||
pub(crate) fn resync_peer_with_storage(&mut self) {
|
||||
self.bytes_delta_since_sync = 0;
|
||||
self.last_synced = OffsetDateTime::now_utc();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ impl GeoIp {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&City<'_>> for Location {
|
||||
impl<'a> TryFrom<&City<'a>> for Location {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(city: &City) -> Result<Self, Self::Error> {
|
||||
|
||||
@@ -65,7 +65,7 @@ impl<'r> FromRequest<'r> for Location {
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenApiFromRequest<'_> for Location {
|
||||
impl<'a> OpenApiFromRequest<'a> for Location {
|
||||
fn from_request_input(
|
||||
_gen: &mut OpenApiGenerator,
|
||||
_name: String,
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user