Merge branch 'release/2024.13-magura' into merge/release/2024.13-magura

This commit is contained in:
Jędrzej Stuczyński
2024-11-19 12:23:01 +00:00
committed by Bogdan-Ștefan Neacşu
78 changed files with 1979 additions and 393 deletions
@@ -55,6 +55,7 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Build all binaries
uses: actions-rs/cargo@v1
+4 -3
View File
@@ -14,13 +14,14 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: 1.77
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- name: Install wasm-opt
run: cargo install --version 0.114.0 wasm-opt
uses: ./.github/actions/install-wasm-opt
with:
version: '114'
- name: Build release contracts
run: make contracts
@@ -58,4 +58,4 @@ jobs:
- name: BuildAndPushImageOnHarbor
run: |
docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
+192
View File
@@ -4,6 +4,198 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2024.13-magura] (2024-11-18)
- Limit race probability ([#5145])
- bugifx: assign 'node_id' when converting from 'GatewayDetails' to 'TestNode' ([#5143])
- bugfix: make sure to assign correct node_id and identity during 'gateway_details' table migration ([#5142])
- Respond to auth messages with same version ([#5140])
- Pain/polyfill deprecated endpoints ([#5131])
- change: dont select mixnodes bonded with vested tokens into the rewarded set ([#5129])
- nym-credential-proxy-requests: reqwest use rustls-tls ([#5116])
- bugfix: preserve as much as possible of the rewarded set during migration ([#5103])
- Feature/force refresh node ([#5101])
- Add NYM_VPN_API to env files ([#5099])
- bugfix: fixed historical uptimes for nodes ([#5097])
- Remove old use of 1GB constant ([#5096])
- Graceful agent 1.1.5 ([#5093])
- Add more translations from v2 to v3 authenticator ([#5091])
- Nym node - Fix claim delegator rewards ([#5090])
- Make 250 GB/30 days for free ride mode ([#5083])
- Don't increase bandwidth two times ([#5081])
- Fix expiration date as today + 7 days ([#5076])
- Fix gateway decreasing bandwidth ([#5075])
- Allow custom http port to be reset ([#5073])
- bugfix: additional checks inside credential proxy ([#5072])
- chore: deprecated old nym-api client methods and replaced them when possible ([#5069])
- NS API with directory v2 (#5058) ([#5068])
- bugfix: credential-proxy obtain-async ([#5067])
- Allow nym node config updates ([#5066])
- bugfix: use corrext axum extractors for ecash route arguments ([#5065])
- Merge2/release/2024.13 magura ([#5063])
- bugfix/feature: added NymApiClient method to get all skimmed nodes ([#5062])
- Merge1/release/2024.13 magura ([#5061])
- added hacky routes to return nymnodes alongside legacy nodes ([#5051])
- bugfix: mark migrated gateways as rewarded in the previous epoch in case theyre in the rewarded set ([#5049])
- bugfix: adjust runtime storage migration ([#5047])
- bugfix: supersede 'cb13be27f8f61d9ae74d924e85d2e6787895eb14' by using… ([#5046])
- bugfix: restore default http port for nym-api ([#5045])
- bugfix: fix ecash handlers routes ([#5043])
- bugfix: don't assign exit gateways to standby set ([#5041])
- bugfix: make sure nym-nodes are also tested by network monitor ([#5040])
- bugfix: use bonded nym-nodes for determining initial network monitor … ([#5039])
- bugfix: make gateways insert themselves into [local] topology ([#5038])
- Pass poisson flag ([#5037])
- bugfix: use human readable roles for annotations ([#5036])
- bugfix: use old name for 'epoch_role' in SkimmedNode ([#5034])
- bugfix: make sure to use correct highest node id when assigning role ([#5032])
- feature: use axum_client_ip for attempting to extract source ip ([#5031])
- bugfix: fixed backwards incompatibility for /gateways/described endpoint ([#5030])
- bugfix: verifying signed information of legacy nodes ([#5029])
- bugfix: introduce 'LegacyPendingMixNodeChanges' that does not contain 'cost_params_change' ([#5028])
- bugfix: missing #[serde(default)] for announce port ([#5024])
- bugfix: directory v2.1 `get_all_avg_gateway_reliability_in_interval` query ([#5023])
- added 'get_all_described_nodes' to NymApiClient and adjusted return t… ([#5016])
- Reapply fixes to new branch ([#5014])
- Consume only positive bandwidth ([#5013])
- feature: adjusted ticket sizes to the agreed amounts ([#5009])
- Push private ip before inserting ([#5008])
- chore: update itertools in compact ecash ([#4994])
- feature: make accepting t&c a hard requirement for rewarded set selection ([#4993])
- Fix rustfmt in nym-credential-proxy ([#4992])
- bugfix: client memory leak ([#4991])
- Eliminate 0 bandwidth race check ([#4988])
- [DOCs;/operators]: Release notes for v2024.12 aero ([#4984])
- Add topup req constructor ([#4983])
- Fix critical issues SI86 and SI87 from Cure53 ([#4982])
- Rename nym-vpn-api to nym-credential-proxy ([#4981])
- enable global ecash routes even if api is not a signer ([#4980])
- resolve beta clippy issues in contracts ([#4978])
- Re-enable vested delegation migration ([#4977])
- feature: require reporting using nym-node binary for rewarded set selection ([#4976])
- Top up bandwidth ([#4975])
- [Product Data] Add session type based on ecash ticket received ([#4974])
- Bugfix/additional directory fixes ([#4973])
- feat: add Dockerfile for nym node ([#4972])
- chore: remove unused rocket code ([#4968])
- Import nym-vpn-api crates ([#4967])
- feature: importer-cli to correctly handle mixnet/vesting import ([#4966])
- bugfix: fix expected return type on /v1/gateways endpoint ([#4965])
- [Product Data] First step in gateway usage data collection ([#4963])
- Bump sqlx to 0.7.4 ([#4959])
- Add env feature to clap and make clap parameters available as env variables ([#4957])
- Feature/contract state tools ([#4954])
- expose authenticator address along other address in node-details ([#4953])
- Extract packet processing from mixnode-common ([#4949])
- nym-api container ([#4948])
- Ticket type storage ([#4947])
- Add "utoipa" feature to nym-node ([#4945])
- build(deps): bump the patch-updates group across 1 directory with 9 updates ([#4944])
- V2 performance monitoring feature flag ([#4943])
- Bugfix/rewarder post pruning adjustments ([#4942])
- Switch over the last set of jobs to arc runners ([#4938])
- Fix broken build after merge ([#4937])
- bugfix: correctly paginate through 'search_tx' endpoint ([#4936])
- Add more conversions for responses of authenticator messages ([#4929])
- Directory Sevices v2.1 ([#4903])
- Migrate Legacy Node (Frontend) ([#4826])
- Fix critical issues SI84 and SI85 from Cure53 ([#4758])
[#5145]: https://github.com/nymtech/nym/pull/5145
[#5143]: https://github.com/nymtech/nym/pull/5143
[#5142]: https://github.com/nymtech/nym/pull/5142
[#5140]: https://github.com/nymtech/nym/pull/5140
[#5131]: https://github.com/nymtech/nym/pull/5131
[#5129]: https://github.com/nymtech/nym/pull/5129
[#5116]: https://github.com/nymtech/nym/pull/5116
[#5103]: https://github.com/nymtech/nym/pull/5103
[#5101]: https://github.com/nymtech/nym/pull/5101
[#5099]: https://github.com/nymtech/nym/pull/5099
[#5097]: https://github.com/nymtech/nym/pull/5097
[#5096]: https://github.com/nymtech/nym/pull/5096
[#5093]: https://github.com/nymtech/nym/pull/5093
[#5091]: https://github.com/nymtech/nym/pull/5091
[#5090]: https://github.com/nymtech/nym/pull/5090
[#5083]: https://github.com/nymtech/nym/pull/5083
[#5081]: https://github.com/nymtech/nym/pull/5081
[#5076]: https://github.com/nymtech/nym/pull/5076
[#5075]: https://github.com/nymtech/nym/pull/5075
[#5073]: https://github.com/nymtech/nym/pull/5073
[#5072]: https://github.com/nymtech/nym/pull/5072
[#5069]: https://github.com/nymtech/nym/pull/5069
[#5068]: https://github.com/nymtech/nym/pull/5068
[#5067]: https://github.com/nymtech/nym/pull/5067
[#5066]: https://github.com/nymtech/nym/pull/5066
[#5065]: https://github.com/nymtech/nym/pull/5065
[#5063]: https://github.com/nymtech/nym/pull/5063
[#5062]: https://github.com/nymtech/nym/pull/5062
[#5061]: https://github.com/nymtech/nym/pull/5061
[#5051]: https://github.com/nymtech/nym/pull/5051
[#5049]: https://github.com/nymtech/nym/pull/5049
[#5047]: https://github.com/nymtech/nym/pull/5047
[#5046]: https://github.com/nymtech/nym/pull/5046
[#5045]: https://github.com/nymtech/nym/pull/5045
[#5043]: https://github.com/nymtech/nym/pull/5043
[#5041]: https://github.com/nymtech/nym/pull/5041
[#5040]: https://github.com/nymtech/nym/pull/5040
[#5039]: https://github.com/nymtech/nym/pull/5039
[#5038]: https://github.com/nymtech/nym/pull/5038
[#5037]: https://github.com/nymtech/nym/pull/5037
[#5036]: https://github.com/nymtech/nym/pull/5036
[#5034]: https://github.com/nymtech/nym/pull/5034
[#5032]: https://github.com/nymtech/nym/pull/5032
[#5031]: https://github.com/nymtech/nym/pull/5031
[#5030]: https://github.com/nymtech/nym/pull/5030
[#5029]: https://github.com/nymtech/nym/pull/5029
[#5028]: https://github.com/nymtech/nym/pull/5028
[#5024]: https://github.com/nymtech/nym/pull/5024
[#5023]: https://github.com/nymtech/nym/pull/5023
[#5016]: https://github.com/nymtech/nym/pull/5016
[#5014]: https://github.com/nymtech/nym/pull/5014
[#5013]: https://github.com/nymtech/nym/pull/5013
[#5009]: https://github.com/nymtech/nym/pull/5009
[#5008]: https://github.com/nymtech/nym/pull/5008
[#4994]: https://github.com/nymtech/nym/pull/4994
[#4993]: https://github.com/nymtech/nym/pull/4993
[#4992]: https://github.com/nymtech/nym/pull/4992
[#4991]: https://github.com/nymtech/nym/pull/4991
[#4988]: https://github.com/nymtech/nym/pull/4988
[#4984]: https://github.com/nymtech/nym/pull/4984
[#4983]: https://github.com/nymtech/nym/pull/4983
[#4982]: https://github.com/nymtech/nym/pull/4982
[#4981]: https://github.com/nymtech/nym/pull/4981
[#4980]: https://github.com/nymtech/nym/pull/4980
[#4978]: https://github.com/nymtech/nym/pull/4978
[#4977]: https://github.com/nymtech/nym/pull/4977
[#4976]: https://github.com/nymtech/nym/pull/4976
[#4975]: https://github.com/nymtech/nym/pull/4975
[#4974]: https://github.com/nymtech/nym/pull/4974
[#4973]: https://github.com/nymtech/nym/pull/4973
[#4972]: https://github.com/nymtech/nym/pull/4972
[#4968]: https://github.com/nymtech/nym/pull/4968
[#4967]: https://github.com/nymtech/nym/pull/4967
[#4966]: https://github.com/nymtech/nym/pull/4966
[#4965]: https://github.com/nymtech/nym/pull/4965
[#4963]: https://github.com/nymtech/nym/pull/4963
[#4959]: https://github.com/nymtech/nym/pull/4959
[#4957]: https://github.com/nymtech/nym/pull/4957
[#4954]: https://github.com/nymtech/nym/pull/4954
[#4953]: https://github.com/nymtech/nym/pull/4953
[#4949]: https://github.com/nymtech/nym/pull/4949
[#4948]: https://github.com/nymtech/nym/pull/4948
[#4947]: https://github.com/nymtech/nym/pull/4947
[#4945]: https://github.com/nymtech/nym/pull/4945
[#4944]: https://github.com/nymtech/nym/pull/4944
[#4943]: https://github.com/nymtech/nym/pull/4943
[#4942]: https://github.com/nymtech/nym/pull/4942
[#4938]: https://github.com/nymtech/nym/pull/4938
[#4937]: https://github.com/nymtech/nym/pull/4937
[#4936]: https://github.com/nymtech/nym/pull/4936
[#4929]: https://github.com/nymtech/nym/pull/4929
[#4903]: https://github.com/nymtech/nym/pull/4903
[#4826]: https://github.com/nymtech/nym/pull/4826
[#4758]: https://github.com/nymtech/nym/pull/4758
## [2024.12-aero] (2024-10-17)
- nym-node: don't use bloomfilters for double spending checks ([#4960])
Generated
+11 -10
View File
@@ -2428,7 +2428,7 @@ dependencies = [
[[package]]
name = "explorer-api"
version = "1.1.41"
version = "1.1.42"
dependencies = [
"chrono",
"clap 4.5.20",
@@ -4451,7 +4451,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "nym-api"
version = "1.1.45"
version = "1.1.46"
dependencies = [
"anyhow",
"async-trait",
@@ -4700,7 +4700,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.43"
version = "1.1.44"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -4781,7 +4781,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.42"
version = "1.1.43"
dependencies = [
"bs58",
"clap 4.5.20",
@@ -5895,7 +5895,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.43"
version = "1.1.44"
dependencies = [
"addr",
"anyhow",
@@ -5946,7 +5946,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.1.9"
version = "1.1.10"
dependencies = [
"anyhow",
"bip39",
@@ -5973,6 +5973,7 @@ dependencies = [
"nym-sphinx-addressing",
"nym-task",
"nym-types",
"nym-validator-client",
"nym-wireguard",
"nym-wireguard-types",
"rand",
@@ -6048,7 +6049,7 @@ dependencies = [
[[package]]
name = "nym-node-status-agent"
version = "0.1.4"
version = "0.1.6"
dependencies = [
"anyhow",
"clap 4.5.20",
@@ -6064,7 +6065,7 @@ dependencies = [
[[package]]
name = "nym-node-status-api"
version = "0.1.5"
version = "0.1.6"
dependencies = [
"anyhow",
"axum 0.7.7",
@@ -6293,7 +6294,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.42"
version = "1.1.43"
dependencies = [
"bs58",
"clap 4.5.20",
@@ -6895,7 +6896,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.8"
version = "0.1.9"
dependencies = [
"anyhow",
"bytes",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.42"
version = "1.1.43"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.42"
version = "1.1.43"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -22,4 +22,7 @@ pub enum Error {
#[error("conversion: {0}")]
Conversion(String),
#[error("failed to serialize response packet: {source}")]
FailedToSerializeResponsePacket { source: Box<bincode::ErrorKind> },
}
+1
View File
@@ -1,6 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod traits;
pub mod v1;
pub mod v2;
pub mod v3;
+343
View File
@@ -0,0 +1,343 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_credentials_interface::CredentialSpendingData;
use nym_crypto::asymmetric::x25519::PrivateKey;
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_sphinx::addressing::clients::Recipient;
use nym_wireguard_types::PeerPublicKey;
use crate::{
v1, v2, v3,
v4::{self, registration::IpPair},
Error,
};
#[derive(Copy, Clone, Debug)]
pub enum AuthenticatorVersion {
V1,
V2,
V3,
V4,
UNKNOWN,
}
impl From<Protocol> for AuthenticatorVersion {
fn from(value: Protocol) -> Self {
if value.service_provider_type != ServiceProviderType::Authenticator {
AuthenticatorVersion::UNKNOWN
} else if value.version == v1::VERSION {
AuthenticatorVersion::V1
} else if value.version == v2::VERSION {
AuthenticatorVersion::V2
} else if value.version == v3::VERSION {
AuthenticatorVersion::V3
} else if value.version == v4::VERSION {
AuthenticatorVersion::V4
} else {
AuthenticatorVersion::UNKNOWN
}
}
}
pub trait InitMessage {
fn pub_key(&self) -> PeerPublicKey;
}
impl InitMessage for v1::registration::InitMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
impl InitMessage for v2::registration::InitMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
impl InitMessage for v3::registration::InitMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
impl InitMessage for v4::registration::InitMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
}
pub trait FinalMessage {
fn pub_key(&self) -> PeerPublicKey;
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>;
fn private_ips(&self) -> IpPair;
fn credential(&self) -> Option<CredentialSpendingData>;
}
impl FinalMessage for v1::GatewayClient {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.verify(private_key, nonce)
}
fn private_ips(&self) -> IpPair {
self.private_ip.into()
}
fn credential(&self) -> Option<CredentialSpendingData> {
None
}
}
impl FinalMessage for v2::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
fn private_ips(&self) -> IpPair {
self.gateway_client.private_ip.into()
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
impl FinalMessage for v3::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
fn private_ips(&self) -> IpPair {
self.gateway_client.private_ip.into()
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
impl FinalMessage for v4::registration::FinalMessage {
fn pub_key(&self) -> PeerPublicKey {
self.gateway_client.pub_key
}
fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error> {
self.gateway_client.verify(private_key, nonce)
}
fn private_ips(&self) -> IpPair {
self.gateway_client.private_ips
}
fn credential(&self) -> Option<CredentialSpendingData> {
self.credential.clone()
}
}
pub trait QueryBandwidthMessage {
fn pub_key(&self) -> PeerPublicKey;
}
impl QueryBandwidthMessage for PeerPublicKey {
fn pub_key(&self) -> PeerPublicKey {
*self
}
}
pub trait TopUpMessage {
fn pub_key(&self) -> PeerPublicKey;
fn credential(&self) -> CredentialSpendingData;
}
impl TopUpMessage for v3::topup::TopUpMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
fn credential(&self) -> CredentialSpendingData {
self.credential.clone()
}
}
impl TopUpMessage for v4::topup::TopUpMessage {
fn pub_key(&self) -> PeerPublicKey {
self.pub_key
}
fn credential(&self) -> CredentialSpendingData {
self.credential.clone()
}
}
pub enum AuthenticatorRequest {
Initial {
msg: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
request_id: u64,
},
Final {
msg: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
request_id: u64,
},
QueryBandwidth {
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
request_id: u64,
},
TopUpBandwidth {
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
protocol: Protocol,
reply_to: Recipient,
request_id: u64,
},
}
impl From<v1::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v1::request::AuthenticatorRequest) -> Self {
match value.data {
v1::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::Final(gateway_client) => Self::Final {
msg: Box::new(gateway_client),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
request_id: value.request_id,
},
v1::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: Protocol {
version: value.version,
service_provider_type: ServiceProviderType::Authenticator,
},
reply_to: value.reply_to,
request_id: value.request_id,
}
}
}
}
}
impl From<v2::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v2::request::AuthenticatorRequest) -> Self {
match value.data {
v2::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
},
v2::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
}
}
}
}
}
impl From<v3::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v3::request::AuthenticatorRequest) -> Self {
match value.data {
v3::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
},
v3::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
}
}
v3::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
}
}
}
}
}
impl From<v4::request::AuthenticatorRequest> for AuthenticatorRequest {
fn from(value: v4::request::AuthenticatorRequest) -> Self {
match value.data {
v4::request::AuthenticatorRequestData::Initial(init_message) => Self::Initial {
msg: Box::new(init_message),
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::Final(final_message) => Self::Final {
msg: final_message,
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
},
v4::request::AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
Self::QueryBandwidth {
msg: Box::new(peer_public_key),
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
}
}
v4::request::AuthenticatorRequestData::TopUpBandwidth(top_up_message) => {
Self::TopUpBandwidth {
msg: top_up_message,
protocol: value.protocol,
reply_to: value.reply_to,
request_id: value.request_id,
}
}
}
}
}
@@ -98,6 +98,16 @@ impl TryFrom<v3::response::AuthenticatorResponse> for v2::response::Authenticato
}
}
impl From<v2::response::AuthenticatorResponse> for v3::response::AuthenticatorResponse {
fn from(value: v2::response::AuthenticatorResponse) -> Self {
Self {
protocol: value.protocol,
data: value.data.into(),
reply_to: value.reply_to,
}
}
}
impl TryFrom<v3::response::AuthenticatorResponseData> for v2::response::AuthenticatorResponseData {
type Error = crate::Error;
@@ -129,6 +139,22 @@ impl TryFrom<v3::response::AuthenticatorResponseData> for v2::response::Authenti
}
}
impl From<v2::response::AuthenticatorResponseData> for v3::response::AuthenticatorResponseData {
fn from(value: v2::response::AuthenticatorResponseData) -> Self {
match value {
v2::response::AuthenticatorResponseData::PendingRegistration(
pending_registration_response,
) => Self::PendingRegistration(pending_registration_response.into()),
v2::response::AuthenticatorResponseData::Registered(registered_response) => {
Self::Registered(registered_response.into())
}
v2::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => Self::RemainingBandwidth(remaining_bandwidth_response.into()),
}
}
}
impl From<v3::response::PendingRegistrationResponse> for v2::response::PendingRegistrationResponse {
fn from(value: v3::response::PendingRegistrationResponse) -> Self {
Self {
@@ -139,6 +165,16 @@ impl From<v3::response::PendingRegistrationResponse> for v2::response::PendingRe
}
}
impl From<v2::response::PendingRegistrationResponse> for v3::response::PendingRegistrationResponse {
fn from(value: v2::response::PendingRegistrationResponse) -> Self {
Self {
request_id: value.request_id,
reply_to: value.reply_to,
reply: value.reply.into(),
}
}
}
impl From<v3::response::RegisteredResponse> for v2::response::RegisteredResponse {
fn from(value: v3::response::RegisteredResponse) -> Self {
Self {
@@ -149,6 +185,16 @@ impl From<v3::response::RegisteredResponse> for v2::response::RegisteredResponse
}
}
impl From<v2::response::RegisteredResponse> for v3::response::RegisteredResponse {
fn from(value: v2::response::RegisteredResponse) -> Self {
Self {
request_id: value.request_id,
reply_to: value.reply_to,
reply: value.reply.into(),
}
}
}
impl From<v3::response::RemainingBandwidthResponse> for v2::response::RemainingBandwidthResponse {
fn from(value: v3::response::RemainingBandwidthResponse) -> Self {
Self {
@@ -159,6 +205,16 @@ impl From<v3::response::RemainingBandwidthResponse> for v2::response::RemainingB
}
}
impl From<v2::response::RemainingBandwidthResponse> for v3::response::RemainingBandwidthResponse {
fn from(value: v2::response::RemainingBandwidthResponse) -> Self {
Self {
request_id: value.request_id,
reply_to: value.reply_to,
reply: value.reply.map(Into::into),
}
}
}
impl From<v3::registration::RegistrationData> for v2::registration::RegistrationData {
fn from(value: v3::registration::RegistrationData) -> Self {
Self {
@@ -169,6 +225,16 @@ impl From<v3::registration::RegistrationData> for v2::registration::Registration
}
}
impl From<v2::registration::RegistrationData> for v3::registration::RegistrationData {
fn from(value: v2::registration::RegistrationData) -> Self {
Self {
nonce: value.nonce,
gateway_data: value.gateway_data.into(),
wg_port: value.wg_port,
}
}
}
impl From<v3::registration::RegistredData> for v2::registration::RegistredData {
fn from(value: v3::registration::RegistredData) -> Self {
Self {
@@ -179,6 +245,16 @@ impl From<v3::registration::RegistredData> for v2::registration::RegistredData {
}
}
impl From<v2::registration::RegistredData> for v3::registration::RegistredData {
fn from(value: v2::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ip: value.private_ip,
wg_port: value.wg_port,
}
}
}
impl From<v3::registration::RemainingBandwidthData> for v2::registration::RemainingBandwidthData {
fn from(value: v3::registration::RemainingBandwidthData) -> Self {
Self {
@@ -186,3 +262,11 @@ impl From<v3::registration::RemainingBandwidthData> for v2::registration::Remain
}
}
}
impl From<v2::registration::RemainingBandwidthData> for v3::registration::RemainingBandwidthData {
fn from(value: v2::registration::RemainingBandwidthData) -> Self {
Self {
available_bandwidth: value.available_bandwidth,
}
}
}
@@ -27,7 +27,7 @@ pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InitMessage {
@@ -3,7 +3,7 @@
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use crate::{v3, v4};
use crate::{v2, v3, v4};
impl From<v3::request::AuthenticatorRequest> for v4::request::AuthenticatorRequest {
fn from(authenticator_request: v3::request::AuthenticatorRequest) -> Self {
@@ -64,6 +64,16 @@ impl From<Box<v3::topup::TopUpMessage>> for Box<v4::topup::TopUpMessage> {
}
}
impl From<v2::registration::GatewayClient> for v4::registration::GatewayClient {
fn from(gw_client: v2::registration::GatewayClient) -> Self {
Self {
pub_key: gw_client.pub_key,
private_ips: gw_client.private_ip.into(),
mac: gw_client.mac.into(),
}
}
}
impl From<v3::registration::GatewayClient> for v4::registration::GatewayClient {
fn from(gw_client: v3::registration::GatewayClient) -> Self {
Self {
@@ -84,6 +94,22 @@ impl From<v4::registration::GatewayClient> for v3::registration::GatewayClient {
}
}
impl From<v4::registration::GatewayClient> for v2::registration::GatewayClient {
fn from(gw_client: v4::registration::GatewayClient) -> Self {
Self {
pub_key: gw_client.pub_key,
private_ip: gw_client.private_ips.ipv4.into(),
mac: gw_client.mac.into(),
}
}
}
impl From<v2::registration::ClientMac> for v4::registration::ClientMac {
fn from(mac: v2::registration::ClientMac) -> Self {
Self::new(mac.to_vec())
}
}
impl From<v3::registration::ClientMac> for v4::registration::ClientMac {
fn from(mac: v3::registration::ClientMac) -> Self {
Self::new(mac.to_vec())
@@ -96,6 +122,12 @@ impl From<v4::registration::ClientMac> for v3::registration::ClientMac {
}
}
impl From<v4::registration::ClientMac> for v2::registration::ClientMac {
fn from(mac: v4::registration::ClientMac) -> Self {
Self::new(mac.to_vec())
}
}
impl TryFrom<v4::response::AuthenticatorResponse> for v3::response::AuthenticatorResponse {
type Error = crate::Error;
@@ -42,6 +42,12 @@ impl IpPair {
}
}
impl From<(Ipv4Addr, Ipv6Addr)> for IpPair {
fn from((ipv4, ipv6): (Ipv4Addr, Ipv6Addr)) -> Self {
IpPair { ipv4, ipv6 }
}
}
impl fmt::Display for IpPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.ipv4, self.ipv6)
@@ -1,7 +1,6 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nym_api::NymApiClientExt;
use crate::nyxd::{self, NyxdClient};
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
use crate::signing::signer::{NoSigner, OfflineSigner};
@@ -32,6 +31,7 @@ use nym_network_defaults::NymNetworkDetails;
use time::Date;
use url::Url;
pub use crate::nym_api::NymApiClientExt;
pub use nym_mixnet_contract_common::{
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId, NymNodeDetails,
};
@@ -332,10 +332,10 @@ impl NymApiClient {
NymApiClient { nym_api }
}
pub fn new_with_user_agent(api_url: Url, user_agent: UserAgent) -> Self {
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
let nym_api = nym_api::Client::builder::<_, ValidatorClientError>(api_url)
.expect("invalid api url")
.with_user_agent(user_agent)
.with_user_agent(user_agent.into())
.build::<ValidatorClientError>()
.expect("failed to build nym api client");
@@ -13,7 +13,7 @@ use nym_api_requests::ecash::models::{
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_api_requests::models::{
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
NymNodeDescription,
NodeRefreshBody, NymNodeDescription,
};
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
use nym_api_requests::pagination::PaginatedResponse;
@@ -695,16 +695,32 @@ pub trait NymApiClientExt: ApiClient {
&self,
node_id: NodeId,
) -> Result<NodePerformanceResponse, NymAPIError> {
self.get_json_from(format!("/v1/nym-nodes/performance/{node_id}"))
.await
self.get_json(
&[
routes::API_VERSION,
"nym-nodes",
"performance",
&node_id.to_string(),
],
NO_PARAMS,
)
.await
}
async fn get_node_annotation(
&self,
node_id: NodeId,
) -> Result<AnnotationResponse, NymAPIError> {
self.get_json_from(format!("/v1/nym-nodes/annotation/{node_id}"))
.await
self.get_json(
&[
routes::API_VERSION,
"nym-nodes",
"annotation",
&node_id.to_string(),
],
NO_PARAMS,
)
.await
}
#[deprecated]
@@ -917,6 +933,18 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn force_refresh_describe_cache(
&self,
request: &NodeRefreshBody,
) -> Result<(), NymAPIError> {
self.post_json(
&[routes::API_VERSION, "nym-nodes", "refresh-described"],
NO_PARAMS,
request,
)
.await
}
async fn issued_ticketbooks_for(
&self,
expiration_date: Date,
@@ -10,10 +10,10 @@ use cosmrs::AccountId;
use nym_contracts_common::signing::Nonce;
use nym_mixnet_contract_common::gateway::{PreassignedGatewayIdsResponse, PreassignedId};
use nym_mixnet_contract_common::nym_node::{
EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeOwnershipResponse,
NodeRewardingDetailsResponse, PagedNymNodeBondsResponse, PagedNymNodeDetailsResponse,
PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse, StakeSaturationResponse,
UnbondedNodeResponse, UnbondedNymNode,
EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeDetailsResponse,
NodeOwnershipResponse, NodeRewardingDetailsResponse, PagedNymNodeBondsResponse,
PagedNymNodeDetailsResponse, PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse,
StakeSaturationResponse, UnbondedNodeResponse, UnbondedNymNode,
};
use nym_mixnet_contract_common::reward_params::WorkFactor;
use nym_mixnet_contract_common::{
@@ -311,10 +311,7 @@ pub trait MixnetQueryClient {
.await
}
async fn get_nymnode_details(
&self,
node_id: NodeId,
) -> Result<NodeOwnershipResponse, NyxdError> {
async fn get_nymnode_details(&self, node_id: NodeId) -> Result<NodeDetailsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetNymNodeDetails { node_id })
.await
}
+1
View File
@@ -315,6 +315,7 @@ impl Client {
parse_response(res, true).await
}
#[instrument(level = "debug", skip_all)]
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
@@ -3,7 +3,7 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[repr(u8)]
pub enum ServiceProviderType {
NetworkRequester = 0,
+9 -1
View File
@@ -3,7 +3,7 @@ use crate::deprecated::DelegationEvent;
use crate::error::TypesError;
use crate::mixnode::NodeCostParams;
use cosmwasm_std::Decimal;
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId};
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId, NodeRewarding};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -70,6 +70,14 @@ pub struct DelegationWithEverything {
pub mixnode_is_unbonding: Option<bool>,
}
pub struct NodeInformation {
pub owner: String,
pub mix_id: NodeId,
pub node_identity: String,
pub rewarding_details: NodeRewarding,
pub is_unbonding: bool,
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
+7 -3
View File
@@ -7,8 +7,8 @@ use defguard_wireguard_rs::{
WireguardInterfaceApi,
};
use futures::channel::oneshot;
use nym_authenticator_requests::{
latest::registration::RemainingBandwidthData, v1::registration::BANDWIDTH_CAP_PER_DAY,
use nym_authenticator_requests::latest::registration::{
RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY,
};
use nym_credential_verification::{
bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig,
@@ -194,6 +194,10 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
);
self.bw_storage_managers
.insert(peer.public_key.clone(), bandwidth_storage_manager);
// try to immediately update the host information, to eliminate races
if let Ok(host_information) = self.wg_api.inner.read_interface_data() {
*self.host_information.write().await = host_information;
}
tokio::spawn(async move {
if let Err(e) = handle.run().await {
log::error!("Peer handle shut down ungracefully - {e}");
@@ -230,7 +234,7 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
// host information not updated yet
return Ok(None);
};
BANDWIDTH_CAP_PER_DAY.saturating_sub((peer.rx_bytes + peer.tx_bytes) as i64)
BANDWIDTH_CAP_PER_DAY.saturating_sub(peer.rx_bytes + peer.tx_bytes) as i64
};
Ok(Some(RemainingBandwidthData {
+3 -3
View File
@@ -6,7 +6,7 @@ use crate::peer_controller::PeerControlRequest;
use defguard_wireguard_rs::host::Peer;
use defguard_wireguard_rs::{host::Host, key::Key};
use futures::channel::oneshot;
use nym_authenticator_requests::v2::registration::BANDWIDTH_CAP_PER_DAY;
use nym_authenticator_requests::latest::registration::BANDWIDTH_CAP_PER_DAY;
use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager;
use nym_gateway_storage::models::WireguardPeer;
use nym_gateway_storage::Storage;
@@ -18,7 +18,7 @@ use tokio::sync::{mpsc, RwLock};
use tokio_stream::{wrappers::IntervalStream, StreamExt};
pub(crate) type SharedBandwidthStorageManager<St> = Arc<RwLock<BandwidthStorageManager<St>>>;
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24); // 24 hours
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 30); // 30 days
pub struct PeerHandle<St> {
storage: St,
@@ -98,7 +98,7 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
} else {
if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER {
log::debug!(
"Peer {} has been present for 24 hours, removing it",
"Peer {} has been present for 30 days, removing it",
self.public_key.to_string()
);
let success = self.remove_peer().await?;
+8 -17
View File
@@ -104,7 +104,10 @@ pub(crate) fn next_nymnode_id_counter(store: &mut dyn Storage) -> StdResult<Node
}
pub(crate) fn initialise_storage(storage: &mut dyn Storage) -> Result<(), MixnetContractError> {
ACTIVE_ROLES_BUCKET.save(storage, &RoleStorageBucket::default())?;
let active_bucket = RoleStorageBucket::default();
let inactive_bucket = active_bucket.other();
ACTIVE_ROLES_BUCKET.save(storage, &active_bucket)?;
let roles = vec![
Role::Layer1,
Role::Layer2,
@@ -114,24 +117,12 @@ pub(crate) fn initialise_storage(storage: &mut dyn Storage) -> Result<(), Mixnet
Role::Standby,
];
for role in roles {
ROLES.save(storage, (RoleStorageBucket::default() as u8, role), &vec![])?;
ROLES.save(
storage,
(RoleStorageBucket::default().other() as u8, role),
&vec![],
)?
ROLES.save(storage, (active_bucket as u8, role), &vec![])?;
ROLES.save(storage, (inactive_bucket as u8, role), &vec![])?
}
ROLES_METADATA.save(
storage,
RoleStorageBucket::default() as u8,
&Default::default(),
)?;
ROLES_METADATA.save(
storage,
RoleStorageBucket::default().other() as u8,
&Default::default(),
)?;
ROLES_METADATA.save(storage, active_bucket as u8, &Default::default())?;
ROLES_METADATA.save(storage, inactive_bucket as u8, &Default::default())?;
Ok(())
}
+3 -2
View File
@@ -18,6 +18,7 @@ GROUP_CONTRACT_ADDRESS=n1qg5ega6dykkxc307y25pecuufrjkxkaggkkxh7nad0vhyhtuhw3sa07
MULTISIG_CONTRACT_ADDRESS=n1zwv6feuzhy6a9wekh96cd57lsarmqlwxdypdsplw6zhfncqw6ftqx5a364
COCONUT_DKG_CONTRACT_ADDRESS=n1aakfpghcanxtc45gpqlx8j3rq0zcpyf49qmhm9mdjrfx036h4z5sy2vfh9
EXPLORER_API=https://canary-explorer.performance.nymte.ch/api
EXPLORER_API=https://canary-explorer.performance.nymte.ch/api/
NYXD=https://canary-validator.performance.nymte.ch
NYM_API=https://canary-api.performance.nymte.ch/api
NYM_API=https://canary-api.performance.nymte.ch/api/
NYM_VPN_API=https://nym-vpn-api-git-deploy-canary-nyx-network-staging.vercel.app/api/
+3 -2
View File
@@ -18,6 +18,7 @@ COCONUT_DKG_CONTRACT_ADDRESS=n1pk8jgr6y4c5k93gz7qf3xc0hvygmp7csk88c2tf8l39tkq683
VESTING_CONTRACT_ADDRESS=n1jlzdxnyces4hrhqz68dqk28mrw5jgwtcfq0c2funcwrmw0dx9l9s8nnnvj
REWARDING_VALIDATOR_ADDRESS=n1rfvpsynktze6wvn6ldskj8xgwfzzk5v6pnff39
EXPLORER_API=https://qa-network-explorer.qa.nymte.ch/api
EXPLORER_API=https://qa-network-explorer.qa.nymte.ch/api/
NYXD=https://qa-validator.qa.nymte.ch
NYM_API=https://qa-nym-api.qa.nymte.ch/api
NYM_API=https://qa-nym-api.qa.nymte.ch/api/
NYM_VPN_API=https://nym-vpn-api-git-deploy-qa-nyx-network-staging.vercel.app/api/
+4 -3
View File
@@ -19,7 +19,8 @@ COCONUT_DKG_CONTRACT_ADDRESS=n1v3n2ly2dp3a9ng3ff6rh26yfkn0pc5hed7w2shc5u9ca5c865
ECASH_CONTRACT_ADDRESS=n1v3vydvs2ued84yv3khqwtgldmgwn0elljsdh08dr5s2j9x4rc5fs9jlwz9
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
EXPLORER_API=https://sandbox-explorer.nymtech.net/api
EXPLORER_API=https://sandbox-explorer.nymtech.net/api/
NYXD=https://rpc.sandbox.nymtech.net
NYXD_WS=wss://rpc.sandbox.nymtech.net/websocket
NYM_API=https://sandbox-nym-api1.nymtech.net/api
NYXD_WS=wss://rpc.sandbox.nymtech.net/websocket/
NYM_API=https://sandbox-nym-api1.nymtech.net/api/
NYM_VPN_API=https://nym-vpn-api-git-deploy-sandbox-nyx-network-staging.vercel.app/api/
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.41"
version = "1.1.42"
edition = "2021"
license.workspace = true
-1
View File
@@ -102,7 +102,6 @@ export const isLessThan = (a: number, b: number) => a < b;
*/
export const isBalanceEnough = (fee: string, tx: string = '0', balance: string = '0') => {
console.log('balance', balance, fee, tx);
try {
return Big(balance).gte(Big(fee).plus(Big(tx)));
} catch (e) {
+1 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.45"
version = "1.1.46"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
+62
View File
@@ -4,6 +4,7 @@
#![allow(deprecated)]
use crate::helpers::unix_epoch;
use crate::helpers::PlaceholderJsonSchemaImpl;
use crate::legacy::{
LegacyGatewayBondWithId, LegacyMixNodeBondWithLayer, LegacyMixNodeDetailsWithLayer,
};
@@ -1265,6 +1266,67 @@ pub struct NoiseDetails {
pub ip_addresses: Vec<IpAddr>,
}
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct NodeRefreshBody {
#[serde(with = "bs58_ed25519_pubkey")]
#[schemars(with = "String")]
pub node_identity: ed25519::PublicKey,
// a poor man's nonce
pub request_timestamp: i64,
#[schemars(with = "PlaceholderJsonSchemaImpl")]
pub signature: ed25519::Signature,
}
impl NodeRefreshBody {
pub fn plaintext(node_identity: ed25519::PublicKey, request_timestamp: i64) -> Vec<u8> {
node_identity
.to_bytes()
.into_iter()
.chain(request_timestamp.to_be_bytes())
.chain(b"describe-cache-refresh-request".iter().copied())
.collect()
}
pub fn new(private_key: &ed25519::PrivateKey) -> Self {
let node_identity = private_key.public_key();
let request_timestamp = OffsetDateTime::now_utc().unix_timestamp();
let signature = private_key.sign(Self::plaintext(node_identity, request_timestamp));
NodeRefreshBody {
node_identity,
request_timestamp,
signature,
}
}
pub fn verify_signature(&self) -> bool {
self.node_identity
.verify(
Self::plaintext(self.node_identity, self.request_timestamp),
&self.signature,
)
.is_ok()
}
pub fn is_stale(&self) -> bool {
let Ok(encoded) = OffsetDateTime::from_unix_timestamp(self.request_timestamp) else {
return true;
};
let now = OffsetDateTime::now_utc();
if encoded > now {
return true;
}
if (encoded + Duration::from_secs(30)) < now {
return true;
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
+1
View File
@@ -1264,6 +1264,7 @@ struct TestFixture {
impl TestFixture {
fn build_app_state(storage: NymApiStorage) -> AppState {
AppState {
forced_refresh: Default::default(),
nym_contract_cache: NymContractCache::new(),
node_status_cache: NodeStatusCache::new(),
circulating_supply_cache: CirculatingSupplyCache::new("unym".to_owned()),
@@ -227,6 +227,11 @@ impl EpochAdvancer {
continue;
};
if mix.bond_information.proxy.is_some() {
debug!("legacy mixnode {node_id} is using vested tokens");
continue;
}
let performance = annotation.detailed_performance.to_rewarding_performance();
debug!(
"legacy mixnode {}: stake: {total_stake}, performance: {performance}",
+49 -22
View File
@@ -9,9 +9,10 @@ use crate::support::config;
use crate::support::config::DEFAULT_NODE_DESCRIBE_BATCH_SIZE;
use async_trait::async_trait;
use futures::{stream, StreamExt};
use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer};
use nym_api_requests::models::{DescribedNodeType, NymNodeData, NymNodeDescription};
use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_mixnet_contract_common::{LegacyMixLayer, NodeId};
use nym_mixnet_contract_common::{LegacyMixLayer, NodeId, NymNodeDetails};
use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::MixnodeConversionError;
@@ -151,6 +152,10 @@ pub struct DescribedNodes {
}
impl DescribedNodes {
pub fn force_update(&mut self, node: NymNodeDescription) {
self.nodes.insert(node.node_id, node);
}
pub fn get_description(&self, node_id: &NodeId) -> Option<&NymNodeData> {
self.nodes.get(node_id).map(|n| &n.description)
}
@@ -292,7 +297,7 @@ async fn try_get_description(
}
#[derive(Debug)]
struct RefreshData {
pub(crate) struct RefreshData {
host: String,
node_id: NodeId,
node_type: DescribedNodeType,
@@ -300,6 +305,39 @@ struct RefreshData {
port: Option<u16>,
}
impl<'a> From<&'a LegacyMixNodeDetailsWithLayer> for RefreshData {
fn from(node: &'a LegacyMixNodeDetailsWithLayer) -> Self {
RefreshData::new(
&node.bond_information.mix_node.host,
DescribedNodeType::LegacyMixnode,
node.mix_id(),
Some(node.bond_information.mix_node.http_api_port),
)
}
}
impl<'a> From<&'a LegacyGatewayBondWithId> for RefreshData {
fn from(node: &'a LegacyGatewayBondWithId) -> Self {
RefreshData::new(
&node.bond.gateway.host,
DescribedNodeType::LegacyGateway,
node.node_id,
None,
)
}
}
impl<'a> From<&'a NymNodeDetails> for RefreshData {
fn from(node: &'a NymNodeDetails) -> Self {
RefreshData::new(
&node.bond_information.node.host,
DescribedNodeType::NymNode,
node.node_id(),
node.bond_information.node.custom_http_port,
)
}
}
impl RefreshData {
pub fn new(
host: impl Into<String>,
@@ -315,7 +353,11 @@ impl RefreshData {
}
}
async fn try_refresh(self) -> Option<NymNodeDescription> {
pub(crate) fn node_id(&self) -> NodeId {
self.node_id
}
pub(crate) async fn try_refresh(self) -> Option<NymNodeDescription> {
match try_get_description(self).await {
Ok(description) => Some(description),
Err(err) => {
@@ -341,18 +383,13 @@ impl CacheItemProvider for NodeDescriptionProvider {
// - legacy gateways (because they might already be running nym-nodes, but haven't updated contract info)
// - nym-nodes
let mut nodes_to_query = Vec::new();
let mut nodes_to_query: Vec<RefreshData> = Vec::new();
match self.contract_cache.all_cached_legacy_mixnodes().await {
None => error!("failed to obtain mixnodes information from the cache"),
Some(legacy_mixnodes) => {
for node in &**legacy_mixnodes {
nodes_to_query.push(RefreshData::new(
&node.bond_information.mix_node.host,
DescribedNodeType::LegacyMixnode,
node.mix_id(),
Some(node.bond_information.mix_node.http_api_port),
))
nodes_to_query.push(node.into())
}
}
}
@@ -361,12 +398,7 @@ impl CacheItemProvider for NodeDescriptionProvider {
None => error!("failed to obtain gateways information from the cache"),
Some(legacy_gateways) => {
for node in &**legacy_gateways {
nodes_to_query.push(RefreshData::new(
&node.bond.gateway.host,
DescribedNodeType::LegacyGateway,
node.node_id,
None,
))
nodes_to_query.push(node.into())
}
}
}
@@ -375,12 +407,7 @@ impl CacheItemProvider for NodeDescriptionProvider {
None => error!("failed to obtain nym-nodes information from the cache"),
Some(nym_nodes) => {
for node in &**nym_nodes {
nodes_to_query.push(RefreshData::new(
&node.bond_information.node.host,
DescribedNodeType::NymNode,
node.node_id(),
node.bond_information.node.custom_http_port,
))
nodes_to_query.push(node.into())
}
}
}
+14
View File
@@ -356,6 +356,13 @@ impl AxumErrorResponse {
}
}
pub(crate) fn unauthorised(msg: impl Display) -> Self {
Self {
message: RequestError::new(msg.to_string()),
status: StatusCode::UNAUTHORIZED,
}
}
pub(crate) fn unprocessable_entity(msg: impl Display) -> Self {
Self {
message: RequestError::new(msg.to_string()),
@@ -376,6 +383,13 @@ impl AxumErrorResponse {
status: StatusCode::BAD_REQUEST,
}
}
pub(crate) fn too_many(msg: impl Display) -> Self {
Self {
message: RequestError::new(msg.to_string()),
status: StatusCode::TOO_MANY_REQUESTS,
}
}
}
impl From<UninitialisedCache> for AxumErrorResponse {
+5 -11
View File
@@ -1,4 +1,4 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_status_api::models::{
@@ -9,7 +9,7 @@ use crate::storage::NymApiStorage;
use nym_task::{TaskClient, TaskManager};
use std::time::Duration;
use time::{OffsetDateTime, PrimitiveDateTime, Time};
use tokio::time::{interval, sleep};
use tokio::time::{interval_at, Instant};
use tracing::error;
use tracing::{info, trace, warn};
@@ -93,15 +93,8 @@ impl HistoricalUptimeUpdater {
"waiting until {update_datetime} to update the historical uptimes for the first time ({} seconds left)", time_left.as_secs()
);
tokio::select! {
biased;
_ = shutdown.recv() => {
trace!("UpdateHandler: Received shutdown");
}
_ = sleep(time_left) => {}
}
let mut interval = interval(ONE_DAY);
let start = Instant::now() + time_left;
let mut interval = interval_at(start, ONE_DAY);
while !shutdown.is_shutdown() {
tokio::select! {
biased;
@@ -109,6 +102,7 @@ impl HistoricalUptimeUpdater {
trace!("UpdateHandler: Received shutdown");
}
_ = interval.tick() => {
info!("updating historical uptimes of nodes");
// we don't want to have another select here; uptime update is relatively speedy
// and we don't want to exit while we're in the middle of database update
if let Err(err) = self.update_uptimes().await {
+44
View File
@@ -1,6 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_describe_cache::RefreshData;
use crate::nym_contract_cache::cache::data::CachedContractsInfo;
use crate::support::caching::Cache;
use data::ContractCacheData;
@@ -8,6 +9,7 @@ use nym_api_requests::legacy::{
LegacyGatewayBondWithId, LegacyMixNodeBondWithLayer, LegacyMixNodeDetailsWithLayer,
};
use nym_api_requests::models::MixnodeStatus;
use nym_crypto::asymmetric::ed25519;
use nym_mixnet_contract_common::{
ConfigScoreParams, Interval, NodeId, NymNodeDetails, RewardedSet, RewardingParams,
};
@@ -364,6 +366,48 @@ impl NymContractCache {
self.legacy_mixnode_details(mix_id).await.1
}
pub async fn get_node_refresh_data(
&self,
node_identity: ed25519::PublicKey,
) -> Option<RefreshData> {
if !self.initialised() {
return None;
}
let inner = self.inner.read().await;
let encoded_identity = node_identity.to_base58_string();
// 1. check nymnodes
if let Some(nym_node) = inner
.nym_nodes
.iter()
.find(|n| n.bond_information.identity() == encoded_identity)
{
return Some(nym_node.into());
}
// 2. check legacy mixnodes
if let Some(mixnode) = inner
.legacy_mixnodes
.iter()
.find(|n| n.bond_information.identity() == encoded_identity)
{
return Some(mixnode.into());
}
// 3. check legacy gateways
if let Some(gateway) = inner
.legacy_gateways
.iter()
.find(|n| n.identity() == &encoded_identity)
{
return Some(gateway.into());
}
None
}
pub fn initialised(&self) -> bool {
self.initialised.load(Ordering::Relaxed)
}
+141 -17
View File
@@ -6,10 +6,12 @@ use crate::node_status_api::helpers::{
_get_rewarded_set_legacy_mixnodes_detailed,
};
use crate::support::http::state::AppState;
use crate::support::legacy_helpers::{to_legacy_gateway, to_legacy_mixnode};
use axum::extract::State;
use axum::{Json, Router};
use nym_api_requests::legacy::LegacyMixNodeDetailsWithLayer;
use nym_api_requests::models::MixNodeBondAnnotated;
use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::{reward_params::RewardingParams, GatewayBond, Interval, NodeId};
use std::collections::HashSet;
@@ -58,11 +60,47 @@ pub(crate) fn nym_contract_cache_routes() -> Router<AppState> {
)]
#[deprecated]
async fn get_mixnodes(State(state): State<AppState>) -> Json<Vec<LegacyMixNodeDetailsWithLayer>> {
state
.nym_contract_cache()
.legacy_mixnodes_filtered()
.await
.into()
let mut out = state.nym_contract_cache().legacy_mixnodes_filtered().await;
let Ok(describe_cache) = state.described_nodes_cache.get().await else {
return Json(out);
};
let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else {
return Json(out);
};
let Ok(annotations) = state.node_annotations().await else {
return Json(out);
};
// safety: valid percentage value
#[allow(clippy::unwrap_used)]
let p50 = Performance::from_percentage_value(50).unwrap();
for nym_node in &**migrated_nymnodes {
// if we can't get it self-described data, ignore it
let Some(description) = describe_cache.get_description(&nym_node.node_id()) else {
continue;
};
// if the node hasn't declared it can be a mixnode, ignore it
if !description.declared_role.mixnode {
continue;
}
// if we don't have annotation for this node, ignore it
let Some(annotation) = annotations.get(&nym_node.node_id()) else {
continue;
};
// equivalent of legacy mixnode being blacklisted
if annotation.last_24h_performance < p50 {
continue;
}
let node = to_legacy_mixnode(nym_node, description);
out.push(node);
}
Json(out)
}
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
@@ -97,15 +135,54 @@ async fn get_mixnodes_detailed(State(state): State<AppState>) -> Json<Vec<MixNod
)]
#[deprecated]
async fn get_gateways(State(state): State<AppState>) -> Json<Vec<GatewayBond>> {
Json(
state
.nym_contract_cache()
.legacy_gateways_filtered()
.await
.into_iter()
.map(Into::into)
.collect(),
)
// legacy
let mut out: Vec<GatewayBond> = state
.nym_contract_cache()
.legacy_gateways_filtered()
.await
.into_iter()
.map(Into::into)
.collect();
let Ok(describe_cache) = state.described_nodes_cache.get().await else {
return Json(out);
};
let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else {
return Json(out);
};
let Ok(annotations) = state.node_annotations().await else {
return Json(out);
};
// safety: valid percentage value
#[allow(clippy::unwrap_used)]
let p50 = Performance::from_percentage_value(50).unwrap();
for nym_node in &**migrated_nymnodes {
// if we can't get it self-described data, ignore it
let Some(description) = describe_cache.get_description(&nym_node.node_id()) else {
continue;
};
// if the node hasn't declared it can be a gateway, ignore it
if !description.declared_role.entry {
continue;
}
// if we don't have annotation for this node, ignore it
let Some(annotation) = annotations.get(&nym_node.node_id()) else {
continue;
};
// equivalent of legacy gateway being blacklisted
if annotation.last_24h_performance < p50 {
continue;
}
let node = to_legacy_gateway(nym_node, description);
out.push(node);
}
Json(out)
}
#[utoipa::path(
@@ -166,12 +243,59 @@ async fn get_rewarded_set_detailed(
)]
#[deprecated]
async fn get_active_set(State(state): State<AppState>) -> Json<Vec<LegacyMixNodeDetailsWithLayer>> {
state
let mut out = state
.nym_contract_cache()
.legacy_v1_active_set_mixnodes()
.await
.clone()
.into()
.clone();
let Some(rewarded_set) = state.nym_contract_cache().rewarded_set().await else {
return Json(out);
};
let Ok(describe_cache) = state.described_nodes_cache.get().await else {
return Json(out);
};
let Some(migrated_nymnodes) = state.nym_contract_cache().all_cached_nym_nodes().await else {
return Json(out);
};
let Ok(annotations) = state.node_annotations().await else {
return Json(out);
};
// safety: valid percentage value
#[allow(clippy::unwrap_used)]
let p50 = Performance::from_percentage_value(50).unwrap();
for nym_node in &**migrated_nymnodes {
// if we can't get it self-described data, ignore it
let Some(description) = describe_cache.get_description(&nym_node.node_id()) else {
continue;
};
// if the node hasn't declared it can be a mixnode, ignore it
if !description.declared_role.mixnode {
continue;
}
// if we don't have annotation for this node, ignore it
let Some(annotation) = annotations.get(&nym_node.node_id()) else {
continue;
};
// equivalent of legacy mixnode being blacklisted
if annotation.last_24h_performance < p50 {
continue;
}
// if the node is not in the active set, ignore it
if !rewarded_set.is_active_mixnode(&nym_node.node_id()) {
continue;
}
let node = to_legacy_mixnode(nym_node, description);
out.push(node);
}
Json(out)
}
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
+67 -31
View File
@@ -2,8 +2,10 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::support::http::state::AppState;
use crate::support::legacy_helpers::{to_legacy_gateway, to_legacy_mixnode};
use axum::extract::State;
use axum::{Json, Router};
use nym_api_requests::legacy::LegacyMixNodeBondWithLayer;
use nym_api_requests::models::{LegacyDescribedGateway, LegacyDescribedMixNode};
// we want to mark the routes as deprecated in swagger, but still expose them
@@ -34,25 +36,44 @@ async fn get_gateways_described(
) -> Json<Vec<LegacyDescribedGateway>> {
let contract_cache = state.nym_contract_cache();
let describe_cache = state.described_nodes_cache();
let gateways = contract_cache.legacy_gateways_filtered().await;
if gateways.is_empty() {
return Json(Vec::new());
}
// legacy
let legacy = contract_cache.legacy_gateways_filtered().await;
// if the self describe cache is unavailable, well, don't attach describe data and only return legacy gateways
let Ok(self_descriptions) = describe_cache.get().await else {
return Json(gateways.into_iter().map(Into::into).collect());
let Ok(describe_cache) = describe_cache.get().await else {
return Json(legacy.into_iter().map(Into::into).collect());
};
Json(
gateways
.into_iter()
.map(|bond| LegacyDescribedGateway {
self_described: self_descriptions.get_description(&bond.node_id).cloned(),
bond: bond.bond,
})
.collect(),
)
let migrated_nymnodes = state.nym_contract_cache().nym_nodes().await;
let mut out = Vec::new();
for legacy_bond in legacy {
out.push(LegacyDescribedGateway {
self_described: describe_cache
.get_description(&legacy_bond.node_id)
.cloned(),
bond: legacy_bond.bond,
})
}
for nym_node in migrated_nymnodes {
// we ALWAYS need description to set legacy fields
let Some(description) = describe_cache.get_description(&nym_node.node_id()) else {
continue;
};
// if the node hasn't declared it can be a gateway, ignore it
if !description.declared_role.entry {
continue;
}
out.push(LegacyDescribedGateway {
bond: to_legacy_gateway(&nym_node, description),
self_described: Some(description.clone()),
})
}
Json(out)
}
#[utoipa::path(
@@ -70,28 +91,43 @@ async fn get_mixnodes_described(
let contract_cache = state.nym_contract_cache();
let describe_cache = state.described_nodes_cache();
let mixnodes = contract_cache
let legacy: Vec<LegacyMixNodeBondWithLayer> = contract_cache
.legacy_mixnodes_filtered()
.await
.into_iter()
.map(|m| m.bond_information)
.collect::<Vec<_>>();
if mixnodes.is_empty() {
return Json(Vec::new());
}
// if the self describe cache is unavailable, well, don't attach describe data
let Ok(self_descriptions) = describe_cache.get().await else {
return Json(mixnodes.into_iter().map(Into::into).collect());
// if the self describe cache is unavailable, well, don't attach describe data and only return legacy mixnodes
let Ok(describe_cache) = describe_cache.get().await else {
return Json(legacy.into_iter().map(Into::into).collect());
};
Json(
mixnodes
.into_iter()
.map(|bond| LegacyDescribedMixNode {
self_described: self_descriptions.get_description(&bond.mix_id).cloned(),
bond,
})
.collect(),
)
let migrated_nymnodes = state.nym_contract_cache().nym_nodes().await;
let mut out = Vec::new();
for legacy_bond in legacy {
out.push(LegacyDescribedMixNode {
self_described: describe_cache.get_description(&legacy_bond.mix_id).cloned(),
bond: legacy_bond,
})
}
for nym_node in migrated_nymnodes {
// we ALWAYS need description to set legacy fields
let Some(description) = describe_cache.get_description(&nym_node.node_id()) else {
continue;
};
// if the node hasn't declared it can be a gateway, ignore it
if !description.declared_role.mixnode {
continue;
}
out.push(LegacyDescribedMixNode {
bond: to_legacy_mixnode(&nym_node, description).bond_information,
self_described: Some(description.clone()),
})
}
Json(out)
}
+63 -4
View File
@@ -5,11 +5,11 @@ use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::support::http::helpers::{NodeIdParam, PaginationRequest};
use crate::support::http::state::AppState;
use axum::extract::{Path, Query, State};
use axum::routing::get;
use axum::routing::{get, post};
use axum::{Json, Router};
use nym_api_requests::models::{
AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NoiseDetails,
NymNodeDescription, PerformanceHistoryResponse, UptimeHistoryResponse,
AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NodeRefreshBody,
NoiseDetails, NymNodeDescription, PerformanceHistoryResponse, UptimeHistoryResponse,
};
use nym_api_requests::pagination::{PaginatedResponse, Pagination};
use nym_contracts_common::NaiveFloat;
@@ -17,7 +17,8 @@ use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::NymNodeDetails;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use time::Date;
use std::time::Duration;
use time::{Date, OffsetDateTime};
use utoipa::{IntoParams, ToSchema};
pub(crate) mod legacy;
@@ -25,6 +26,7 @@ pub(crate) mod unstable;
pub(crate) fn nym_node_routes() -> Router<AppState> {
Router::new()
.route("/refresh-described", post(refresh_described))
.route("/noise", get(nodes_noise))
.route("/bonded", get(get_bonded_nodes))
.route("/described", get(get_described_nodes))
@@ -42,6 +44,63 @@ pub(crate) fn nym_node_routes() -> Router<AppState> {
.route("/uptime-history/:node_id", get(get_node_uptime_history))
}
#[utoipa::path(
tag = "Nym Nodes",
post,
request_body = NodeRefreshBody,
path = "/refresh-described",
context_path = "/v1/nym-nodes",
)]
async fn refresh_described(
State(state): State<AppState>,
Json(request_body): Json<NodeRefreshBody>,
) -> AxumResult<Json<()>> {
let Some(refresh_data) = state
.nym_contract_cache()
.get_node_refresh_data(request_body.node_identity)
.await
else {
return Err(AxumErrorResponse::not_found(format!(
"node with identity {} does not seem to exist",
request_body.node_identity
)));
};
if !request_body.verify_signature() {
return Err(AxumErrorResponse::unauthorised("invalid request signature"));
}
if request_body.is_stale() {
return Err(AxumErrorResponse::bad_request("the request is stale"));
}
let node_id = refresh_data.node_id();
if let Some(last) = state.forced_refresh.last_refreshed(node_id).await {
// max 1 refresh a minute
let minute_ago = OffsetDateTime::now_utc() - Duration::from_secs(60);
if last > minute_ago {
return Err(AxumErrorResponse::too_many(
"already refreshed node in the last minute",
));
}
}
// to make sure you can't ddos the endpoint while a request is in progress
state.forced_refresh.set_last_refreshed(node_id).await;
if let Some(updated_data) = refresh_data.try_refresh().await {
let Ok(mut describe_cache) = state.described_nodes_cache.write().await else {
return Err(AxumErrorResponse::service_unavailable());
};
describe_cache.get_mut().force_update(updated_data)
} else {
return Err(AxumErrorResponse::unprocessable_entity(
"failed to refresh node description",
));
}
Ok(Json(()))
}
#[utoipa::path(
tag = "Nym Nodes",
get,
+12 -1
View File
@@ -6,7 +6,7 @@ use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
use time::OffsetDateTime;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::sync::{RwLock, RwLockMappedWriteGuard, RwLockReadGuard, RwLockWriteGuard};
#[derive(Debug, Error)]
#[error("the cache item has not been initialised")]
@@ -45,6 +45,13 @@ impl<T> SharedCache<T> {
RwLockReadGuard::try_map(guard, |a| a.inner.as_ref()).map_err(|_| UninitialisedCache)
}
pub(crate) async fn write(
&self,
) -> Result<RwLockMappedWriteGuard<'_, Cache<T>>, UninitialisedCache> {
let guard = self.0.write().await;
RwLockWriteGuard::try_map(guard, |a| a.inner.as_mut()).map_err(|_| UninitialisedCache)
}
// ignores expiration data
#[allow(dead_code)]
pub(crate) async fn unchecked_get_inner(
@@ -134,6 +141,10 @@ impl<T> Cache<T> {
self.as_at = OffsetDateTime::now_utc()
}
pub(crate) fn get_mut(&mut self) -> &mut T {
&mut self.value
}
#[allow(dead_code)]
pub fn has_expired(&self, ttl: Duration, now: Option<OffsetDateTime>) -> bool {
let now = now.unwrap_or(OffsetDateTime::now_utc());
+1
View File
@@ -188,6 +188,7 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
};
let router = router.with_state(AppState {
forced_refresh: Default::default(),
nym_contract_cache: nym_contract_cache_state.clone(),
node_status_cache: node_status_cache_state.clone(),
circulating_supply_cache: circulating_supply_cache.clone(),
+1
View File
@@ -22,6 +22,7 @@ use utoipauto::utoipauto;
models::CirculatingSupplyResponse,
models::CoinSchema,
nym_mixnet_contract_common::Interval,
nym_api_requests::models::NodeRefreshBody,
nym_api_requests::models::GatewayStatusReportResponse,
nym_api_requests::models::GatewayUptimeHistoryResponse,
nym_api_requests::models::GatewayCoreStatusResponse,
+22 -1
View File
@@ -15,7 +15,9 @@ use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeA
use nym_mixnet_contract_common::NodeId;
use nym_task::TaskManager;
use std::collections::HashMap;
use tokio::sync::RwLockReadGuard;
use std::sync::Arc;
use time::OffsetDateTime;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
@@ -70,6 +72,7 @@ type AxumJoinHandle = JoinHandle<std::io::Result<()>>;
#[derive(Clone)]
pub(crate) struct AppState {
pub(crate) forced_refresh: ForcedRefresh,
pub(crate) nym_contract_cache: NymContractCache,
pub(crate) node_status_cache: NodeStatusCache,
pub(crate) circulating_supply_cache: CirculatingSupplyCache,
@@ -79,6 +82,24 @@ pub(crate) struct AppState {
pub(crate) node_info_cache: unstable::NodeInfoCache,
}
#[derive(Clone, Default)]
pub(crate) struct ForcedRefresh {
pub(crate) refreshes: Arc<RwLock<HashMap<NodeId, OffsetDateTime>>>,
}
impl ForcedRefresh {
pub(crate) async fn last_refreshed(&self, node_id: NodeId) -> Option<OffsetDateTime> {
self.refreshes.read().await.get(&node_id).copied()
}
pub(crate) async fn set_last_refreshed(&self, node_id: NodeId) {
self.refreshes
.write()
.await
.insert(node_id, OffsetDateTime::now_utc());
}
}
impl AppState {
pub(crate) fn nym_contract_cache(&self) -> &NymContractCache {
&self.nym_contract_cache
+85
View File
@@ -0,0 +1,85 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_api_requests::legacy::{LegacyMixNodeBondWithLayer, LegacyMixNodeDetailsWithLayer};
use nym_api_requests::models::NymNodeData;
use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_crypto::aes::cipher::crypto_common::rand_core::OsRng;
use nym_mixnet_contract_common::mixnode::LegacyPendingMixNodeChanges;
use nym_mixnet_contract_common::{
Gateway, GatewayBond, LegacyMixLayer, MixNode, MixNodeBond, NymNodeDetails,
};
use rand::prelude::SliceRandom;
pub(crate) fn to_legacy_mixnode(
nym_node: &NymNodeDetails,
description: &NymNodeData,
) -> LegacyMixNodeDetailsWithLayer {
let layer_choices = [
LegacyMixLayer::One,
LegacyMixLayer::Two,
LegacyMixLayer::Three,
];
let mut rng = OsRng;
// slap a random layer on it because legacy clients don't understand a concept of layerless mixnodes
// SAFETY: the slice is not empty so the unwrap is fine
#[allow(clippy::unwrap_used)]
let layer = layer_choices.choose(&mut rng).copied().unwrap();
LegacyMixNodeDetailsWithLayer {
bond_information: LegacyMixNodeBondWithLayer {
bond: MixNodeBond {
mix_id: nym_node.node_id(),
owner: nym_node.bond_information.owner.clone(),
original_pledge: nym_node.bond_information.original_pledge.clone(),
mix_node: MixNode {
host: nym_node.bond_information.node.host.clone(),
mix_port: description.mix_port(),
verloc_port: description.verloc_port(),
http_api_port: nym_node
.bond_information
.node
.custom_http_port
.unwrap_or(DEFAULT_NYM_NODE_HTTP_PORT),
sphinx_key: description.host_information.keys.x25519.to_base58_string(),
identity_key: nym_node.bond_information.node.identity_key.clone(),
version: description.build_information.build_version.clone(),
},
proxy: None,
bonding_height: nym_node.bond_information.bonding_height,
is_unbonding: nym_node.bond_information.is_unbonding,
},
layer,
},
rewarding_details: nym_node.rewarding_details.clone(),
pending_changes: LegacyPendingMixNodeChanges {
pledge_change: nym_node.pending_changes.pledge_change,
},
}
}
pub(crate) fn to_legacy_gateway(
nym_node: &NymNodeDetails,
description: &NymNodeData,
) -> GatewayBond {
GatewayBond {
pledge_amount: nym_node.bond_information.original_pledge.clone(),
owner: nym_node.bond_information.owner.clone(),
block_height: nym_node.bond_information.bonding_height,
gateway: Gateway {
host: nym_node.bond_information.node.host.clone(),
mix_port: description.mix_port(),
clients_port: description.mixnet_websockets.ws_port,
location: description
.auxiliary_details
.location
.map(|c| c.to_string())
.unwrap_or_default(),
sphinx_key: description.host_information.keys.x25519.to_base58_string(),
identity_key: nym_node.bond_information.node.identity_key.clone(),
version: description.build_information.build_version.clone(),
},
proxy: None,
}
}
+2
View File
@@ -7,3 +7,5 @@ pub(crate) mod config;
pub(crate) mod http;
pub(crate) mod nyxd;
pub(crate) mod storage;
pub(crate) mod legacy_helpers;
+5 -4
View File
@@ -983,7 +983,8 @@ impl StorageManager {
since: i64,
until: i64,
) -> Result<Vec<ActiveGateway>, sqlx::Error> {
sqlx::query_as(
sqlx::query_as!(
ActiveGateway,
r#"
SELECT DISTINCT identity, node_id as "node_id: NodeId", id
FROM gateway_details
@@ -993,9 +994,9 @@ impl StorageManager {
SELECT 1 FROM gateway_status WHERE timestamp > ? AND timestamp < ?
)
"#,
since,
until
)
.bind(since)
.bind(until)
.fetch_all(&self.connection_pool)
.await
}
@@ -1328,7 +1329,7 @@ pub(crate) mod v3_migration {
identity VARCHAR NOT NULL UNIQUE
);
INSERT INTO gateway_details_temp SELECT * FROM gateway_details;
INSERT INTO gateway_details_temp(id, node_id, identity) SELECT id, node_id, identity FROM gateway_details;
DROP TABLE gateway_details;
ALTER TABLE gateway_details_temp RENAME TO gateway_details;
"#,
+1 -1
View File
@@ -87,7 +87,7 @@ pub struct GatewayDetails {
impl From<GatewayDetails> for TestNode {
fn from(value: GatewayDetails) -> Self {
TestNode {
node_id: None,
node_id: Some(value.node_id),
identity_key: Some(value.identity),
}
}
@@ -18,7 +18,7 @@ serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
time = { workspace = true, features = ["serde", "formatting", "parsing"] }
tsify = { workspace = true, optional = true }
reqwest = { workspace = true, features = ["json"] }
reqwest = { workspace = true, features = ["json", "rustls-tls"] }
wasm-bindgen = { workspace = true, optional = true }
## openapi:
+1 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-node-status-agent"
version = "0.1.4"
version = "0.1.6"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+1 -1
View File
@@ -37,4 +37,4 @@ COPY --from=builder /usr/src/nym/target/release/nym-node-status-agent ./
COPY --from=builder /usr/src/nym-vpn-client/nym-vpn-core/target/release/nym-gateway-probe ./
ENV NODE_STATUS_AGENT_PROBE_PATH=/nym/nym-gateway-probe
ENTRYPOINT [ "/nym/nym-node-status-agent", "run-probe" ]
ENTRYPOINT [ "/nym/nym-node-status-agent", "run-probe" ]
+25 -12
View File
@@ -1,3 +1,4 @@
use anyhow::bail;
use clap::{Parser, Subcommand};
use nym_bin_common::bin_info;
use nym_common_models::ns_api::TestrunAssignment;
@@ -51,11 +52,13 @@ impl Args {
let version = probe.version().await;
tracing::info!("Probe version:\n{}", version);
let testrun = request_testrun(&server_address).await?;
if let Some(testrun) = request_testrun(&server_address).await? {
let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key));
let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key));
submit_results(&server_address, testrun.testrun_id, log).await?;
submit_results(&server_address, testrun.testrun_id, log).await?;
} else {
tracing::info!("No testruns available, exiting")
}
Ok(())
}
@@ -64,16 +67,26 @@ impl Args {
const URL_BASE: &str = "internal/testruns";
#[instrument(level = "debug", skip_all)]
async fn request_testrun(server_addr: &str) -> anyhow::Result<TestrunAssignment> {
async fn request_testrun(server_addr: &str) -> anyhow::Result<Option<TestrunAssignment>> {
let target_url = format!("{}/{}", server_addr, URL_BASE);
let client = reqwest::Client::new();
let res = client
.get(target_url)
.send()
.await
.and_then(|response| response.error_for_status())?;
res.json()
.await
let res = client.get(target_url).send().await?;
let status = res.status();
let response_text = res.text().await?;
if status.is_client_error() {
bail!("{}: {}", status, response_text);
} else if status.is_server_error() {
if matches!(status, reqwest::StatusCode::SERVICE_UNAVAILABLE)
&& response_text.contains("No testruns available")
{
return Ok(None);
} else {
bail!("{}: {}", status, response_text);
}
}
serde_json::from_str(&response_text)
.map(|testrun| {
tracing::info!("Received testrun assignment: {:?}", testrun);
testrun
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node-status-api"
version = "0.1.5"
version = "0.1.6"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+2 -8
View File
@@ -1,10 +1,7 @@
use anyhow::anyhow;
use axum::{response::Redirect, Router};
use tokio::net::ToSocketAddrs;
use tower_http::{
cors::CorsLayer,
trace::{DefaultOnResponse, TraceLayer},
};
use tower_http::{cors::CorsLayer, trace::TraceLayer};
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
@@ -61,10 +58,7 @@ impl RouterBuilder {
// CORS layer needs to wrap other API layers
.layer(setup_cors())
// logger should be outermost layer
.layer(
TraceLayer::new_for_http()
.on_response(DefaultOnResponse::new().level(tracing::Level::DEBUG)),
)
.layer(TraceLayer::new_for_http())
}
}
+2 -1
View File
@@ -49,7 +49,8 @@ async fn request_testrun(State(state): State<AppState>) -> HttpResult<Json<Testr
);
Ok(Json(testrun))
} else {
Err(HttpError::no_available_testruns())
tracing::debug!("No testruns available for agent");
Err(HttpError::no_testruns_available())
}
}
Err(err) => Err(HttpError::internal_with_logging(err)),
+2 -2
View File
@@ -27,9 +27,9 @@ impl HttpError {
}
}
pub(crate) fn no_available_testruns() -> Self {
pub(crate) fn no_testruns_available() -> Self {
Self {
message: serde_json::json!({"message": "No available testruns"}).to_string(),
message: serde_json::json!({"message": "No testruns available"}).to_string(),
status: axum::http::StatusCode::SERVICE_UNAVAILABLE,
}
}
+2 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node"
version = "1.1.9"
version = "1.1.10"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -53,6 +53,7 @@ nym-sphinx-acknowledgements = { path = "../common/nymsphinx/acknowledgements" }
nym-sphinx-addressing = { path = "../common/nymsphinx/addressing" }
nym-task = { path = "../common/task" }
nym-types = { path = "../common/types" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-wireguard = { path = "../common/wireguard" }
nym-wireguard-types = { path = "../common/wireguard-types", default-features = false }
+40 -1
View File
@@ -32,13 +32,18 @@ use nym_node_http_api::{NymNodeHTTPServer, NymNodeRouter};
use nym_sphinx_acknowledgements::AckKey;
use nym_sphinx_addressing::Recipient;
use nym_task::{TaskClient, TaskManager};
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::models::NodeRefreshBody;
use nym_validator_client::NymApiClient;
use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData};
use rand::rngs::OsRng;
use rand::{CryptoRng, RngCore};
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc;
use tracing::{debug, error, info, trace};
use tokio::time::timeout;
use tracing::{debug, error, info, trace, warn};
use zeroize::Zeroizing;
use self::helpers::load_x25519_wireguard_keypair;
@@ -750,6 +755,38 @@ impl NymNode {
.await?)
}
async fn try_refresh_remote_nym_api_cache(&self) {
info!("attempting to request described cache request from nym-api...");
if self.config.mixnet.nym_api_urls.is_empty() {
warn!("no nym-api urls available");
return;
}
for nym_api in &self.config.mixnet.nym_api_urls {
info!("trying {nym_api}...");
let client = NymApiClient::new_with_user_agent(nym_api.clone(), bin_info_owned!());
// make new request every time in case previous one takes longer and invalidates the signature
let request = NodeRefreshBody::new(self.ed25519_identity_keys.private_key());
match timeout(
Duration::from_secs(10),
client.nym_api.force_refresh_describe_cache(&request),
)
.await
{
Ok(Ok(_)) => {
info!("managed to refresh own self-described data cache")
}
Ok(Err(request_failure)) => {
warn!("failed to resolve the refresh request: {request_failure}")
}
Err(_timeout) => {
warn!("timed out while attempting to resolve the request. the cache might be stale")
}
};
}
}
pub(crate) async fn run(self) -> Result<(), NymNodeError> {
let mut task_manager = TaskManager::default().named("NymNode");
let http_server = self
@@ -764,6 +801,8 @@ impl NymNode {
}
});
self.try_refresh_remote_nym_api_cache().await;
match self.config.mode {
NodeMode::Mixnode => {
self.start_mixnode(task_manager.subscribe_named("mixnode"))?;
+10
View File
@@ -2,6 +2,16 @@
## [Unreleased]
## [2024.13-magura] (2024-11-18)
- bugfix: [wallet] displaying delegations for native nymnodes ([#5087])
- bugfix: wallet backend fixes ([#5070])
- Feature/wallet bonding fixes ([#5064])
[#5087]: https://github.com/nymtech/nym/pull/5087
[#5070]: https://github.com/nymtech/nym/pull/5070
[#5064]: https://github.com/nymtech/nym/pull/5064
## [v1.2.13] (2024-05-08)
- Bug fix: wallet delegations list is empty when RPC node doesn't hold block ([#4565])
+1 -1
View File
@@ -3535,7 +3535,7 @@ dependencies = [
[[package]]
name = "nym_wallet"
version = "1.2.14"
version = "1.2.15"
dependencies = [
"async-trait",
"base64 0.13.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nymproject/nym-wallet-app",
"version": "1.2.14",
"version": "1.2.15",
"license": "MIT",
"main": "index.js",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym_wallet"
version = "1.2.14"
version = "1.2.15"
description = "Nym Native Wallet"
authors = ["Nym Technologies SA"]
license = ""
@@ -7,7 +7,9 @@ use crate::vesting::delegate::vesting_undelegate_from_mixnode;
use nym_mixnet_contract_common::mixnode::MixStakeSaturationResponse;
use nym_mixnet_contract_common::NodeId;
use nym_types::currency::DecCoin;
use nym_types::delegation::{Delegation, DelegationWithEverything, DelegationsSummaryResponse};
use nym_types::delegation::{
Delegation, DelegationWithEverything, DelegationsSummaryResponse, NodeInformation,
};
use nym_types::deprecated::{
convert_to_delegation_events, DelegationEvent, WrappedDelegationEvent,
};
@@ -19,6 +21,7 @@ use nym_validator_client::nyxd::contract_traits::{
MixnetQueryClient, MixnetSigningClient, NymContractsProvider, PagedMixnetQueryClient,
};
use nym_validator_client::nyxd::Fee;
use nym_validator_client::DirectSigningHttpRpcValidatorClient;
use tap::TapFallible;
#[tauri::command]
@@ -141,6 +144,58 @@ pub async fn undelegate_all_from_mixnode(
Ok(res)
}
pub(crate) async fn get_node_information(
client: &DirectSigningHttpRpcValidatorClient,
node_id: NodeId,
error_strings: &mut Vec<String>,
) -> Result<Option<NodeInformation>, BackendError> {
let native_nymnode = client
.nyxd
.get_nymnode_details(node_id)
.await
.inspect_err(|err| {
let str_err =
format!("Failed to get nymnode details for node_id = {node_id}. Error: {err}");
log::error!(" <<< {str_err}",);
error_strings.push(str_err);
})?;
if let Some(native) = native_nymnode.details {
return Ok(Some(NodeInformation {
is_unbonding: native.is_unbonding(),
owner: native.bond_information.owner.to_string(),
mix_id: node_id,
node_identity: native.bond_information.node.identity_key,
rewarding_details: native.rewarding_details,
}));
}
let legacy_mixnode = client
.nyxd
.get_mixnode_details(node_id)
.await
.inspect_err(|err| {
let str_err = format!(
"Failed to get legacy mixnode details for node_id = {node_id}. Error: {err}",
);
log::error!(" <<< {}", str_err);
error_strings.push(str_err);
})?
.mixnode_details;
if let Some(legacy) = legacy_mixnode {
return Ok(Some(NodeInformation {
is_unbonding: legacy.is_unbonding(),
owner: legacy.bond_information.owner.to_string(),
mix_id: node_id,
node_identity: legacy.bond_information.mix_node.identity_key,
rewarding_details: legacy.rewarding_details,
}));
}
Ok(None)
}
// TODO: fix later (yeah...)
#[allow(deprecated)]
#[tauri::command]
@@ -208,21 +263,9 @@ pub async fn get_all_mix_delegations(
d.amount
);
let mixnode = client
.nyxd
.get_mixnode_details(d.mix_id)
.await
.tap_err(|err| {
let str_err = format!(
"Failed to get mixnode details for mix_id = {}. Error: {}",
d.mix_id, err
);
log::error!(" <<< {}", str_err);
error_strings.push(str_err);
})?
.mixnode_details;
let node_details = get_node_information(client, d.mix_id, &mut error_strings).await?;
let accumulated_by_operator = mixnode
let accumulated_by_operator = node_details
.as_ref()
.map(|m| {
guard.display_coin_from_base_decimal(&base_mix_denom, m.rewarding_details.operator)
@@ -238,10 +281,13 @@ pub async fn get_all_mix_delegations(
})
.unwrap_or_default();
let accumulated_by_delegates = mixnode
let accumulated_by_delegates = node_details
.as_ref()
.map(|m| {
guard.display_coin_from_base_decimal(&base_mix_denom, m.rewarding_details.delegates)
reg.attempt_create_display_coin_from_base_dec_amount(
&base_mix_denom,
m.rewarding_details.delegates,
)
})
.transpose()
.tap_err(|err| {
@@ -254,7 +300,7 @@ pub async fn get_all_mix_delegations(
})
.unwrap_or_default();
let cost_params = mixnode
let cost_params = node_details
.as_ref()
.map(|m| {
NodeCostParams::from_mixnet_contract_mixnode_cost_params(
@@ -335,21 +381,26 @@ pub async fn get_all_mix_delegations(
" >>> Get average uptime percentage: mix_iid = {}",
d.mix_id
);
let avg_uptime_percent = client
let current_performance = client
.nym_api
.get_mixnode_avg_uptime(d.mix_id)
.get_current_node_performance(d.mix_id)
.await
.tap_err(|err| {
.inspect_err(|err| {
let str_err = format!(
"Failed to get average uptime percentage for mix_id = {}. Error: {}",
d.mix_id, err
"Failed to get current node performance for node_id = {}. Error: {err}",
d.mix_id
);
log::error!(" <<< {}", str_err);
error_strings.push(str_err);
})
.ok()
.map(|r| r.avg_uptime);
log::trace!(" <<< {:?}", avg_uptime_percent);
.and_then(|r| r.performance);
// convert to old u8
let current_uptime = current_performance.map(|p| (p * 100.) as u8);
log::trace!(" <<< {:?}", current_uptime);
log::trace!(
" >>> Convert delegated on block height to timestamp: block_height = {}",
@@ -377,9 +428,9 @@ pub async fn get_all_mix_delegations(
pending_events.len()
);
let mixnode_is_unbonding = mixnode.as_ref().map(|m| m.is_unbonding());
let mixnode_is_unbonding = node_details.as_ref().map(|m| m.is_unbonding);
log::trace!(
" >>> mixnode with mix_id: {} is unbonding: {:?}",
" >>> node with mix_id: {} is unbonding: {:?}",
d.mix_id,
mixnode_is_unbonding
);
@@ -387,16 +438,14 @@ pub async fn get_all_mix_delegations(
with_everything.push(DelegationWithEverything {
owner: d.owner,
mix_id: d.mix_id,
node_identity: mixnode
.map(|m| m.bond_information.mix_node.identity_key)
.unwrap_or_default(),
node_identity: node_details.map(|m| m.node_identity).unwrap_or_default(),
amount: d.amount,
block_height: d.height,
uses_vesting_contract_tokens,
delegated_on_iso_datetime,
stake_saturation: stake_saturation.uncapped_saturation,
accumulated_by_operator,
avg_uptime_percent,
avg_uptime_percent: current_uptime,
accumulated_by_delegates,
cost_params,
unclaimed_rewards: accumulated_rewards,
+1 -1
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-wallet",
"version": "1.2.14"
"version": "1.2.15"
},
"build": {
"distDir": "../dist",
@@ -113,6 +113,7 @@ export const BondedGateway = ({
startIcon={<UpgradeRounded />}
variant="contained"
disableElevation
disabled={!!gateway.proxy}
onClick={onShowMigrateToNymNodeModal}
>
Migrate to Nym Node
@@ -190,15 +190,21 @@ export const BondedMixnode = ({
</Button>
</Box>
</Tooltip>
<Button
startIcon={<UpgradeRounded />}
variant="contained"
disableElevation
onClick={onShowMigrateToNymNodeModal}
disabled={isUnbonding}
<Tooltip
title={!!mixnode.proxy && 'You must migrate your vested tokens before you can migrate your mixnode'}
>
Migrate to Nym Node
</Button>
<Box>
<Button
startIcon={<UpgradeRounded />}
variant="contained"
disableElevation
onClick={onShowMigrateToNymNodeModal}
disabled={isUnbonding || !!mixnode.proxy}
>
Migrate to Nym Node
</Button>
</Box>
</Tooltip>
</Stack>
{nextEpoch instanceof Error ? null : (
@@ -4,11 +4,10 @@ import { ModalListItem } from 'src/components/Modals/ModalListItem';
import { SimpleModal } from 'src/components/Modals/SimpleModal';
import { ModalFee } from 'src/components/Modals/ModalFee';
import { useGetFee } from 'src/hooks/useGetFee';
import { simulateClaimOperatorReward, simulateVestingClaimOperatorReward } from 'src/requests';
import { AppContext } from 'src/context';
import { BalanceWarning } from 'src/components/FeeWarning';
import { Box } from '@mui/material';
import { TBondedMixnode } from 'src/requests/mixnodeDetails';
import { TBondedNymNode } from 'src/requests/nymNodeDetails';
export const RedeemRewardsModal = ({
node,
@@ -16,23 +15,18 @@ export const RedeemRewardsModal = ({
onError,
onClose,
}: {
node: TBondedMixnode;
node: TBondedNymNode;
onConfirm: (fee?: FeeDetails) => Promise<void>;
onError: (err: string) => void;
onClose: () => void;
}) => {
const { fee, getFee, isFeeLoading, feeError } = useGetFee();
const { fee, isFeeLoading, feeError } = useGetFee();
const { userBalance } = useContext(AppContext);
useEffect(() => {
if (feeError) onError(feeError);
}, [feeError]);
useEffect(() => {
if (node.proxy) getFee(simulateVestingClaimOperatorReward, {});
else getFee(simulateClaimOperatorReward, {});
}, []);
const handleOnOK = async () => onConfirm(fee);
return (
+20 -10
View File
@@ -217,24 +217,34 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen
const migrateVestedMixnode = async () => {
setIsLoading(true);
const tx = await tauriMigrateVestedMixnode();
setIsLoading(false);
return tx;
try {
const tx = await tauriMigrateVestedMixnode();
setIsLoading(false);
return tx;
} catch (e) {
Console.error(e);
setError(`an error occurred: ${e}`);
}
};
const migrateLegacyNode = async () => {
setIsLoading(true);
let tx: TransactionExecuteResult | undefined;
try {
let tx: TransactionExecuteResult | undefined;
if (bondedNode && isMixnode(bondedNode)) {
tx = await migrateLegacyMixnodeReq();
}
if (bondedNode && isGateway(bondedNode)) {
tx = await migrateLegacyGatewayReq();
if (bondedNode && isMixnode(bondedNode)) {
tx = await migrateLegacyMixnodeReq();
}
if (bondedNode && isGateway(bondedNode)) {
tx = await migrateLegacyGatewayReq();
}
return tx;
} catch (e) {
Console.error(e);
setError(`an error occurred: ${e}`);
}
setIsLoading(false);
return tx;
};
const memoizedValue = useMemo(
+10 -3
View File
@@ -53,7 +53,7 @@ export const Bonding = () => {
if (!bondedNode) {
return false;
}
if (isMixnode(bondedNode) && !bondedNode.isUnbonding) {
if (isMixnode(bondedNode) && !bondedNode.isUnbonding && !bondedNode.proxy) {
return true;
}
if (isGateway(bondedNode)) {
@@ -197,7 +197,14 @@ export const Bonding = () => {
};
if (error) {
return <ErrorModal open message="An error occured, please check logs for details" onClose={() => refresh()} />;
return (
<ErrorModal
open
title="An error occured, please check logs for details"
message={error}
onClose={() => refresh()}
/>
);
}
return (
@@ -292,7 +299,7 @@ export const Bonding = () => {
/>
)}
{showModal === 'redeem' && bondedNode && isMixnode(bondedNode) && (
{showModal === 'redeem' && bondedNode && isNymNode(bondedNode) && (
<RedeemRewardsModal
node={bondedNode}
onClose={() => setShowModal(undefined)}
@@ -39,9 +39,9 @@ export const NodeSettings = () => {
if (location.state === 'unbond') {
setValue('Unbond');
}
if (location.state === 'test-node') {
setValue('Test my node');
}
// if (location.state === 'test-node') {
// setValue('Test my node');
// }
}, [location]);
const handleUnbond = async (fee?: FeeDetails) => {
@@ -129,11 +129,11 @@ export const NodeSettings = () => {
>
<Divider />
{value === 'General' && bondedNode && <NodeGeneralSettings bondedNode={bondedNode} />}
{value === 'Test my node' && <NodeTestPage />}
{/* {value === 'Test my node' && <NodeTestPage />} */}
{value === 'Unbond' && bondedNode && (
<NodeUnbondPage bondedNode={bondedNode} onConfirm={handleUnbond} onError={handleError} />
)}
{value === 'Playground' && bondedNode && <ApyPlayground bondedNode={bondedNode as TBondedMixnode} />}
{/* {value === 'Playground' && bondedNode && <ApyPlayground bondedNode={bondedNode as TBondedMixnode} />} */}
{confirmationDetails && confirmationDetails.status === 'success' && (
<ConfirmationDetailsModal
title={confirmationDetails.title}
@@ -5,11 +5,15 @@ export const makeNavItems = (bondedNode: TBondedNode) => {
const navItems: NavItems[] = ['Unbond'];
if (isNymNode(bondedNode)) {
// add these items to the beginning of the array "General", "Test my node", "Playground"
navItems.unshift('General', 'Test my node', 'Playground');
// Add these items to the beginning of the array "General", "Test my node", "Playground"
// Temporarily removed , 'Test my node due to wasm issues which we need to fix
// 'Playground' due to freezing issues
navItems.unshift('General');
}
return navItems;
};
export type NavItems = 'General' | 'Unbond' | 'Test my node' | 'Playground';
// And these back in once fixed.
// 'Playground' | 'Test my node' include in array at a later point
export type NavItems = 'General' | 'Unbond' ;
+1 -1
View File
@@ -7,7 +7,7 @@ import {
NodeConfigUpdate,
GatewayConfigUpdate,
} from '@nymproject/types';
import { TBondGatewayArgs, TBondGatewaySignatureArgs, TNodeConfigUpdateArgs } from '../types';
import { TBondGatewayArgs, TBondGatewaySignatureArgs } from '../types';
import { invokeWrapper } from './wrapper';
export const bondGateway = async (args: TBondGatewayArgs) =>
+1 -1
View File
@@ -6,7 +6,7 @@ export const claimOperatorReward = async (fee?: Fee) =>
export const claimDelegatorRewards = async (mixId: number, fee?: FeeDetails) =>
invokeWrapper<TransactionExecuteResult[]>('claim_locked_and_unlocked_delegator_reward', {
mixId,
nodeId: mixId,
fee: fee?.fee,
});
@@ -89,6 +89,9 @@ pub enum AuthenticatorError {
#[error("storage should have the requested bandwidht entry")]
MissingClientBandwidthEntry,
#[error("unknown version number")]
UnknownVersion,
#[error("{0}")]
PublicKey(#[from] nym_wireguard_types::Error),
@@ -6,18 +6,17 @@ use std::{
time::{Duration, SystemTime},
};
use crate::{config::Config, error::*};
use crate::{error::AuthenticatorError, peer_manager::PeerManager};
use defguard_wireguard_rs::net::IpAddrMask;
use defguard_wireguard_rs::{host::Peer, key::Key};
use futures::StreamExt;
use nym_authenticator_requests::latest::registration::RegistrationData;
use nym_authenticator_requests::{
latest::{
registration::{
FinalMessage, GatewayClient, InitMessage, IpPair, PendingRegistrations, PrivateIPs,
RegistrationData, RegistredData, RemainingBandwidthData,
},
request::{AuthenticatorRequest, AuthenticatorRequestData},
response::AuthenticatorResponse,
latest::registration::{GatewayClient, PendingRegistrations, PrivateIPs},
traits::{
AuthenticatorRequest, AuthenticatorVersion, FinalMessage, InitMessage,
QueryBandwidthMessage, TopUpMessage,
},
v1, v2, v3, v4, CURRENT_VERSION,
};
@@ -30,7 +29,7 @@ use nym_crypto::asymmetric::x25519::KeyPair;
use nym_gateway_requests::models::CredentialSpendingRequest;
use nym_gateway_storage::Storage;
use nym_sdk::mixnet::{InputMessage, MixnetMessageSender, Recipient, TransmissionLane};
use nym_service_provider_requests_common::ServiceProviderType;
use nym_service_provider_requests_common::{Protocol, ServiceProviderType};
use nym_sphinx::receiver::ReconstructedMessage;
use nym_task::TaskHandle;
use nym_wireguard::WireguardGatewayData;
@@ -39,9 +38,7 @@ use rand::{prelude::IteratorRandom, thread_rng};
use tokio::sync::RwLock;
use tokio_stream::wrappers::IntervalStream;
use crate::{config::Config, error::*};
type AuthenticatorHandleResult = Result<AuthenticatorResponse>;
type AuthenticatorHandleResult = Result<(Vec<u8>, Recipient)>;
const DEFAULT_REGISTRATION_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute
pub(crate) struct RegistredAndFree {
@@ -151,22 +148,87 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
async fn on_initial_request(
&mut self,
init_message: InitMessage,
init_message: Box<dyn InitMessage + Send + Sync + 'static>,
protocol: Protocol,
request_id: u64,
reply_to: Recipient,
) -> AuthenticatorHandleResult {
let remote_public = init_message.pub_key;
let remote_public = init_message.pub_key();
let nonce: u64 = fastrand::u64(..);
let mut registred_and_free = self.registred_and_free.write().await;
if let Some(registration_data) = registred_and_free
.registration_in_progres
.get(&remote_public)
{
return Ok(AuthenticatorResponse::new_pending_registration_success(
registration_data.clone(),
request_id,
reply_to,
));
let gateway_data = registration_data.gateway_data.clone();
let bytes = match AuthenticatorVersion::from(protocol) {
AuthenticatorVersion::V1 => {
v1::response::AuthenticatorResponse::new_pending_registration_success(
v1::registration::RegistrationData {
nonce: registration_data.nonce,
gateway_data: v1::GatewayClient {
pub_key: gateway_data.pub_key,
private_ip: gateway_data.private_ips.ipv4.into(),
mac: v1::ClientMac::new(gateway_data.mac.to_vec()),
},
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V2 => {
v2::response::AuthenticatorResponse::new_pending_registration_success(
v2::registration::RegistrationData {
nonce: registration_data.nonce,
gateway_data: registration_data.gateway_data.clone().into(),
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V3 => {
v3::response::AuthenticatorResponse::new_pending_registration_success(
v3::registration::RegistrationData {
nonce: registration_data.nonce,
gateway_data: registration_data.gateway_data.clone().into(),
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V4 => {
v4::response::AuthenticatorResponse::new_pending_registration_success(
v4::registration::RegistrationData {
nonce: registration_data.nonce,
gateway_data: registration_data.gateway_data.clone(),
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion),
};
return Ok((bytes, reply_to));
}
let peer = self.peer_manager.query_peer(remote_public).await?;
@@ -190,15 +252,62 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
"there should be one private IP pair in the list".to_string(),
));
};
return Ok(AuthenticatorResponse::new_registered(
RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ips: IpPair::new(allowed_ipv4, allowed_ipv6),
wg_port: self.config.authenticator.announced_port,
},
reply_to,
request_id,
));
let bytes = match AuthenticatorVersion::from(protocol) {
AuthenticatorVersion::V1 => v1::response::AuthenticatorResponse::new_registered(
v1::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
},
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?,
AuthenticatorVersion::V2 => v2::response::AuthenticatorResponse::new_registered(
v2::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
},
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?,
AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::new_registered(
v3::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ip: allowed_ipv4.into(),
wg_port: self.config.authenticator.announced_port,
},
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?,
AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_registered(
v4::registration::RegistredData {
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
private_ips: (allowed_ipv4, allowed_ipv6).into(),
wg_port: self.config.authenticator.announced_port,
},
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?,
AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion),
};
return Ok((bytes, reply_to));
}
let private_ip_ref = registred_and_free
@@ -217,57 +326,117 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
);
let registration_data = RegistrationData {
nonce,
gateway_data,
gateway_data: gateway_data.clone(),
wg_port: self.config.authenticator.announced_port,
};
registred_and_free
.registration_in_progres
.insert(remote_public, registration_data.clone());
let bytes = match AuthenticatorVersion::from(protocol) {
AuthenticatorVersion::V1 => {
v1::response::AuthenticatorResponse::new_pending_registration_success(
v1::registration::RegistrationData {
nonce: registration_data.nonce,
gateway_data: v1::GatewayClient {
pub_key: gateway_data.pub_key,
private_ip: gateway_data.private_ips.ipv4.into(),
mac: v1::ClientMac::new(gateway_data.mac.to_vec()),
},
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V2 => {
v2::response::AuthenticatorResponse::new_pending_registration_success(
v2::registration::RegistrationData {
nonce: registration_data.nonce,
gateway_data: registration_data.gateway_data.into(),
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V3 => {
v3::response::AuthenticatorResponse::new_pending_registration_success(
v3::registration::RegistrationData {
nonce: registration_data.nonce,
gateway_data: registration_data.gateway_data.into(),
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V4 => {
v4::response::AuthenticatorResponse::new_pending_registration_success(
v4::registration::RegistrationData {
nonce: registration_data.nonce,
gateway_data: registration_data.gateway_data,
wg_port: registration_data.wg_port,
},
request_id,
reply_to,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion),
};
Ok(AuthenticatorResponse::new_pending_registration_success(
registration_data,
request_id,
reply_to,
))
Ok((bytes, reply_to))
}
async fn on_final_request(
&mut self,
final_message: FinalMessage,
final_message: Box<dyn FinalMessage + Send + Sync + 'static>,
protocol: Protocol,
request_id: u64,
reply_to: Recipient,
) -> AuthenticatorHandleResult {
let mut registred_and_free = self.registred_and_free.write().await;
let registration_data = registred_and_free
.registration_in_progres
.get(&final_message.gateway_client.pub_key())
.get(&final_message.pub_key())
.ok_or(AuthenticatorError::RegistrationNotInProgress)?
.clone();
if final_message
.gateway_client
.verify(self.keypair().private_key(), registration_data.nonce)
.is_err()
{
return Err(AuthenticatorError::MacVerificationFailure);
}
let mut peer = Peer::new(Key::new(final_message.gateway_client.pub_key.to_bytes()));
let mut peer = Peer::new(Key::new(final_message.pub_key().to_bytes()));
peer.allowed_ips
.push(IpAddrMask::new(final_message.private_ips().ipv4.into(), 32));
peer.allowed_ips.push(IpAddrMask::new(
final_message.gateway_client.private_ips.ipv4.into(),
32,
));
peer.allowed_ips.push(IpAddrMask::new(
final_message.gateway_client.private_ips.ipv6.into(),
final_message.private_ips().ipv6.into(),
128,
));
// If gateway does ecash verification and client sends a credential, we do the additional
// credential verification. Later this will become mandatory.
if let (Some(ecash_verifier), Some(credential)) = (
self.ecash_verifier.clone(),
final_message.credential.clone(),
) {
if let (Some(ecash_verifier), Some(credential)) =
(self.ecash_verifier.clone(), final_message.credential())
{
let client_id = ecash_verifier
.storage()
.insert_wireguard_peer(&peer, true)
@@ -297,17 +466,56 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
}
registred_and_free
.registration_in_progres
.remove(&final_message.gateway_client.pub_key());
.remove(&final_message.pub_key());
Ok(AuthenticatorResponse::new_registered(
RegistredData {
pub_key: registration_data.gateway_data.pub_key,
private_ips: registration_data.gateway_data.private_ips,
wg_port: registration_data.wg_port,
},
reply_to,
request_id,
))
let bytes = match AuthenticatorVersion::from(protocol) {
AuthenticatorVersion::V1 => v1::response::AuthenticatorResponse::new_registered(
v1::registration::RegistredData {
pub_key: registration_data.gateway_data.pub_key,
private_ip: registration_data.gateway_data.private_ips.ipv4.into(),
wg_port: registration_data.wg_port,
},
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| AuthenticatorError::FailedToSerializeResponsePacket { source: err })?,
AuthenticatorVersion::V2 => v2::response::AuthenticatorResponse::new_registered(
v2::registration::RegistredData {
pub_key: registration_data.gateway_data.pub_key,
private_ip: registration_data.gateway_data.private_ips.ipv4.into(),
wg_port: registration_data.wg_port,
},
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| AuthenticatorError::FailedToSerializeResponsePacket { source: err })?,
AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::new_registered(
v3::registration::RegistredData {
pub_key: registration_data.gateway_data.pub_key,
private_ip: registration_data.gateway_data.private_ips.ipv4.into(),
wg_port: registration_data.wg_port,
},
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| AuthenticatorError::FailedToSerializeResponsePacket { source: err })?,
AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_registered(
v4::registration::RegistredData {
pub_key: registration_data.gateway_data.pub_key,
private_ips: registration_data.gateway_data.private_ips,
wg_port: registration_data.wg_port,
},
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| AuthenticatorError::FailedToSerializeResponsePacket { source: err })?,
AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion),
};
Ok((bytes, reply_to))
}
async fn credential_verification(
@@ -343,22 +551,75 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
async fn on_query_bandwidth_request(
&mut self,
peer_public_key: PeerPublicKey,
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
protocol: Protocol,
request_id: u64,
reply_to: Recipient,
) -> AuthenticatorHandleResult {
let bandwidth_data = self.peer_manager.query_bandwidth(peer_public_key).await?;
Ok(AuthenticatorResponse::new_remaining_bandwidth(
bandwidth_data,
reply_to,
request_id,
))
let bandwidth_data = self.peer_manager.query_bandwidth(msg).await?;
let bytes = match AuthenticatorVersion::from(protocol) {
AuthenticatorVersion::V1 => {
v1::response::AuthenticatorResponse::new_remaining_bandwidth(
bandwidth_data.map(|data| v1::registration::RemainingBandwidthData {
available_bandwidth: data.available_bandwidth as u64,
suspended: false,
}),
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V2 => {
v2::response::AuthenticatorResponse::new_remaining_bandwidth(
bandwidth_data.map(|data| v2::registration::RemainingBandwidthData {
available_bandwidth: data.available_bandwidth,
}),
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V3 => {
v3::response::AuthenticatorResponse::new_remaining_bandwidth(
bandwidth_data.map(|data| v3::registration::RemainingBandwidthData {
available_bandwidth: data.available_bandwidth,
}),
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::V4 => {
v4::response::AuthenticatorResponse::new_remaining_bandwidth(
bandwidth_data.map(|data| v4::registration::RemainingBandwidthData {
available_bandwidth: data.available_bandwidth,
}),
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| {
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?
}
AuthenticatorVersion::UNKNOWN => return Err(AuthenticatorError::UnknownVersion),
};
Ok((bytes, reply_to))
}
async fn on_topup_bandwidth_request(
&mut self,
peer_public_key: PeerPublicKey,
credential: CredentialSpendingData,
msg: Box<dyn TopUpMessage + Send + Sync + 'static>,
protocol: Protocol,
request_id: u64,
reply_to: Recipient,
) -> AuthenticatorHandleResult {
@@ -367,7 +628,7 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
};
let client_id = ecash_verifier
.storage()
.get_wireguard_peer(&peer_public_key.to_string())
.get_wireguard_peer(&msg.pub_key().to_string())
.await?
.ok_or(AuthenticatorError::MissingClientBandwidthEntry)?
.client_id
@@ -382,7 +643,7 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
let client_bandwidth = ClientBandwidth::new(bandwidth.into());
let mut verifier = CredentialVerifier::new(
CredentialSpendingRequest::new(credential),
CredentialSpendingRequest::new(msg.credential()),
ecash_verifier.clone(),
BandwidthStorageManager::new(
ecash_verifier.storage().clone(),
@@ -394,13 +655,31 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
);
let available_bandwidth = verifier.verify().await?;
Ok(AuthenticatorResponse::new_topup_bandwidth(
RemainingBandwidthData {
available_bandwidth,
},
reply_to,
request_id,
))
let bytes = match AuthenticatorVersion::from(protocol) {
AuthenticatorVersion::V4 => v4::response::AuthenticatorResponse::new_topup_bandwidth(
v4::registration::RemainingBandwidthData {
available_bandwidth,
},
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| AuthenticatorError::FailedToSerializeResponsePacket { source: err })?,
AuthenticatorVersion::V3 => v3::response::AuthenticatorResponse::new_topup_bandwidth(
v3::registration::RemainingBandwidthData {
available_bandwidth,
},
reply_to,
request_id,
)
.to_bytes()
.map_err(|err| AuthenticatorError::FailedToSerializeResponsePacket { source: err })?,
AuthenticatorVersion::V1 | AuthenticatorVersion::V2 | AuthenticatorVersion::UNKNOWN => {
return Err(AuthenticatorError::UnknownVersion)
}
};
Ok((bytes, reply_to))
}
async fn on_reconstructed_message(
@@ -412,62 +691,52 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
reconstructed.sender_tag
);
let request = match deserialize_request(&reconstructed) {
Err(AuthenticatorError::InvalidPacketVersion(version)) => {
return self.on_version_mismatch(version, &reconstructed);
}
req => req,
}?;
let request = deserialize_request(&reconstructed)?;
match request.data {
AuthenticatorRequestData::Initial(init_msg) => {
self.on_initial_request(init_msg, request.request_id, request.reply_to)
match request {
AuthenticatorRequest::Initial {
msg,
reply_to,
request_id,
protocol,
} => {
self.on_initial_request(msg, protocol, request_id, reply_to)
.await
}
AuthenticatorRequestData::Final(final_msg) => {
self.on_final_request(*final_msg, request.request_id, request.reply_to)
AuthenticatorRequest::Final {
msg,
reply_to,
request_id,
protocol,
} => {
self.on_final_request(msg, protocol, request_id, reply_to)
.await
}
AuthenticatorRequestData::QueryBandwidth(peer_public_key) => {
self.on_query_bandwidth_request(
peer_public_key,
request.request_id,
request.reply_to,
)
.await
AuthenticatorRequest::QueryBandwidth {
msg,
reply_to,
request_id,
protocol,
} => {
self.on_query_bandwidth_request(msg, protocol, request_id, reply_to)
.await
}
AuthenticatorRequestData::TopUpBandwidth(topup_message) => {
self.on_topup_bandwidth_request(
topup_message.pub_key,
topup_message.credential,
request.request_id,
request.reply_to,
)
.await
AuthenticatorRequest::TopUpBandwidth {
msg,
reply_to,
request_id,
protocol,
} => {
self.on_topup_bandwidth_request(msg, protocol, request_id, reply_to)
.await
}
}
}
fn on_version_mismatch(
&self,
version: u8,
_reconstructed: &ReconstructedMessage,
) -> AuthenticatorHandleResult {
// If it's possible to parse, do so and return back a response, otherwise just drop
Err(AuthenticatorError::InvalidPacketVersion(version))
}
// When an incoming mixnet message triggers a response that we send back.
async fn handle_response(&self, response: AuthenticatorResponse) -> Result<()> {
let recipient = response.recipient();
let response_packet = response.to_bytes().map_err(|err| {
log::error!("Failed to serialize response packet");
AuthenticatorError::FailedToSerializeResponsePacket { source: err }
})?;
async fn handle_response(&self, response: Vec<u8>, recipient: Recipient) -> Result<()> {
let input_message =
InputMessage::new_regular(recipient, response_packet, TransmissionLane::General, None);
InputMessage::new_regular(recipient, response, TransmissionLane::General, None);
self.mixnet_client
.send(input_message)
.await
@@ -491,8 +760,8 @@ impl<S: Storage + Clone + 'static> MixnetListener<S> {
msg = self.mixnet_client.next() => {
if let Some(msg) = msg {
match self.on_reconstructed_message(msg).await {
Ok(response) => {
if let Err(err) = self.handle_response(response).await {
Ok((response, recipient)) => {
if let Err(err) = self.handle_response(response, recipient).await {
log::error!("Mixnet listener failed to handle response: {err}");
}
}
@@ -524,8 +793,6 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result<Authentic
match request_version {
[1, _] => v1::request::AuthenticatorRequest::from_reconstructed_message(reconstructed)
.map_err(|err| AuthenticatorError::FailedToDeserializeTaggedPacket { source: err })
.map(Into::<v2::request::AuthenticatorRequest>::into)
.map(Into::<v3::request::AuthenticatorRequest>::into)
.map(Into::into),
[2, request_type] => {
if request_type == ServiceProviderType::Authenticator as u8 {
@@ -556,6 +823,7 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result<Authentic
.map_err(|err| AuthenticatorError::FailedToDeserializeTaggedPacket {
source: err,
})
.map(Into::into)
} else {
Err(AuthenticatorError::InvalidPacketType(request_type))
}
@@ -4,7 +4,10 @@
use crate::error::*;
use defguard_wireguard_rs::{host::Peer, key::Key};
use futures::channel::oneshot;
use nym_authenticator_requests::latest::registration::{GatewayClient, RemainingBandwidthData};
use nym_authenticator_requests::{
latest::registration::{GatewayClient, RemainingBandwidthData},
traits::QueryBandwidthMessage,
};
use nym_wireguard::{
peer_controller::{
AddPeerControlResponse, PeerControlRequest, QueryBandwidthControlResponse,
@@ -92,9 +95,9 @@ impl PeerManager {
pub async fn query_bandwidth(
&mut self,
peer_public_key: PeerPublicKey,
msg: Box<dyn QueryBandwidthMessage + Send + Sync + 'static>,
) -> Result<Option<RemainingBandwidthData>> {
let key = Key::new(peer_public_key.to_bytes());
let key = Key::new(msg.pub_key().to_bytes());
let (response_tx, response_rx) = oneshot::channel();
let msg = PeerControlRequest::QueryBandwidth { key, response_tx };
self.wireguard_gateway_data
@@ -4,7 +4,7 @@
[package]
name = "nym-network-requester"
license = "GPL-3.0"
version = "1.1.43"
version = "1.1.44"
authors.workspace = true
edition.workspace = true
rust-version = "1.70"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-cli"
version = "1.1.43"
version = "1.1.44"
authors.workspace = true
edition = "2021"
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nymvisor"
version = "0.1.8"
version = "0.1.9"
authors.workspace = true
repository.workspace = true
homepage.workspace = true