From 2569deb080e90b2800664cd787caad4c2271ae44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 4 Nov 2024 21:15:29 +0000 Subject: [PATCH 01/27] bugfix: [wallet] displaying delegations for native nymnodes (#5087) * fixed return type for getting nymnode details * fixed nym-api queries if using relative paths * fixed queries for delegations of native nymnodes --- .../validator-client/src/nym_api/mod.rs | 24 +++- .../contract_traits/mixnet_query_client.rs | 13 +- common/http-api-client/src/lib.rs | 1 + common/types/src/delegation.rs | 10 +- .../src/operations/mixnet/delegate.rs | 111 +++++++++++++----- 5 files changed, 115 insertions(+), 44 deletions(-) diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 6556a80102..13a1ca7671 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -696,16 +696,32 @@ pub trait NymApiClientExt: ApiClient { &self, node_id: NodeId, ) -> Result { - 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 { - 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] diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs index 21529ad086..b6fdadbce1 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/mixnet_query_client.rs @@ -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::{ @@ -316,10 +316,7 @@ pub trait MixnetQueryClient { .await } - async fn get_nymnode_details( - &self, - node_id: NodeId, - ) -> Result { + async fn get_nymnode_details(&self, node_id: NodeId) -> Result { self.query_mixnet_contract(MixnetQueryMsg::GetNymNodeDetails { node_id }) .await } diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index ce502c112a..549c843958 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -315,6 +315,7 @@ impl Client { parse_response(res, true).await } + #[instrument(level = "debug", skip_all)] pub async fn get_json_endpoint(&self, endpoint: S) -> Result> where for<'a> T: Deserialize<'a>, diff --git a/common/types/src/delegation.rs b/common/types/src/delegation.rs index f906c04cd1..3060a02e3a 100644 --- a/common/types/src/delegation.rs +++ b/common/types/src/delegation.rs @@ -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, } +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", diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index 6d960031fe..744aeb1bb7 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -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, +) -> Result, 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, From c6959d3e2d5fd241f62cb64484f2890c4fa46eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Tue, 5 Nov 2024 11:14:43 +0200 Subject: [PATCH 02/27] Make 250 GB/30 days for free ride mode (#5083) --- common/authenticator-requests/src/v3/registration.rs | 2 +- common/wireguard/src/peer_handle.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/authenticator-requests/src/v3/registration.rs b/common/authenticator-requests/src/v3/registration.rs index 70dceb7690..37234f7e1f 100644 --- a/common/authenticator-requests/src/v3/registration.rs +++ b/common/authenticator-requests/src/v3/registration.rs @@ -27,7 +27,7 @@ pub type HmacSha256 = Hmac; pub type Nonce = u64; pub type Taken = Option; -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 { diff --git a/common/wireguard/src/peer_handle.rs b/common/wireguard/src/peer_handle.rs index 3b6f13d960..71fa06f847 100644 --- a/common/wireguard/src/peer_handle.rs +++ b/common/wireguard/src/peer_handle.rs @@ -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 = Arc>>; -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 { storage: St, @@ -98,7 +98,7 @@ impl PeerHandle { } 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?; From fa551b6d9d090e18d6cc52fc0ea2583da3fd4926 Mon Sep 17 00:00:00 2001 From: Fouad Date: Tue, 5 Nov 2024 13:01:22 +0000 Subject: [PATCH 03/27] Nym node - Fix claim delegator rewards (#5090) * update function param from mixId to nodeId * fix claim operator rewards --- explorer/src/utils/index.ts | 1 - .../components/Bonding/modals/RedeemRewardsModal.tsx | 12 +++--------- nym-wallet/src/pages/bonding/Bonding.tsx | 2 +- nym-wallet/src/requests/actions.ts | 2 +- nym-wallet/src/requests/rewards.ts | 2 +- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/explorer/src/utils/index.ts b/explorer/src/utils/index.ts index 5375377b5a..91b082c5ca 100644 --- a/explorer/src/utils/index.ts +++ b/explorer/src/utils/index.ts @@ -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) { diff --git a/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx b/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx index 8e00ddbfa5..0243e765f3 100644 --- a/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx @@ -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; 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 ( diff --git a/nym-wallet/src/pages/bonding/Bonding.tsx b/nym-wallet/src/pages/bonding/Bonding.tsx index b66ffbe58e..d618775e05 100644 --- a/nym-wallet/src/pages/bonding/Bonding.tsx +++ b/nym-wallet/src/pages/bonding/Bonding.tsx @@ -292,7 +292,7 @@ export const Bonding = () => { /> )} - {showModal === 'redeem' && bondedNode && isMixnode(bondedNode) && ( + {showModal === 'redeem' && bondedNode && isNymNode(bondedNode) && ( setShowModal(undefined)} diff --git a/nym-wallet/src/requests/actions.ts b/nym-wallet/src/requests/actions.ts index 6907cf054a..835fcb465b 100644 --- a/nym-wallet/src/requests/actions.ts +++ b/nym-wallet/src/requests/actions.ts @@ -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) => diff --git a/nym-wallet/src/requests/rewards.ts b/nym-wallet/src/requests/rewards.ts index 36b90838e7..ed5dcb797a 100644 --- a/nym-wallet/src/requests/rewards.ts +++ b/nym-wallet/src/requests/rewards.ts @@ -6,7 +6,7 @@ export const claimOperatorReward = async (fee?: Fee) => export const claimDelegatorRewards = async (mixId: number, fee?: FeeDetails) => invokeWrapper('claim_locked_and_unlocked_delegator_reward', { - mixId, + nodeId: mixId, fee: fee?.fee, }); From 15ca24b848f1e0714e5e347c518496fd4ba55039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Tue, 5 Nov 2024 15:30:00 +0200 Subject: [PATCH 04/27] Add more translations from v2 to v3 authenticator (#5091) --- .../src/v3/conversion.rs | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/common/authenticator-requests/src/v3/conversion.rs b/common/authenticator-requests/src/v3/conversion.rs index 15659271b2..2ceaeb0301 100644 --- a/common/authenticator-requests/src/v3/conversion.rs +++ b/common/authenticator-requests/src/v3/conversion.rs @@ -98,6 +98,16 @@ impl TryFrom for v2::response::Authenticato } } +impl From 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 for v2::response::AuthenticatorResponseData { type Error = crate::Error; @@ -129,6 +139,22 @@ impl TryFrom for v2::response::Authenti } } +impl From 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 for v2::response::PendingRegistrationResponse { fn from(value: v3::response::PendingRegistrationResponse) -> Self { Self { @@ -139,6 +165,16 @@ impl From for v2::response::PendingRe } } +impl From 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 for v2::response::RegisteredResponse { fn from(value: v3::response::RegisteredResponse) -> Self { Self { @@ -149,6 +185,16 @@ impl From for v2::response::RegisteredResponse } } +impl From 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 for v2::response::RemainingBandwidthResponse { fn from(value: v3::response::RemainingBandwidthResponse) -> Self { Self { @@ -159,6 +205,16 @@ impl From for v2::response::RemainingB } } +impl From 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 for v2::registration::RegistrationData { fn from(value: v3::registration::RegistrationData) -> Self { Self { @@ -169,6 +225,16 @@ impl From for v2::registration::Registration } } +impl From 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 for v2::registration::RegistredData { fn from(value: v3::registration::RegistredData) -> Self { Self { @@ -179,6 +245,16 @@ impl From for v2::registration::RegistredData { } } +impl From 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 for v2::registration::RemainingBandwidthData { fn from(value: v3::registration::RemainingBandwidthData) -> Self { Self { @@ -186,3 +262,11 @@ impl From for v2::registration::Remain } } } + +impl From for v3::registration::RemainingBandwidthData { + fn from(value: v2::registration::RemainingBandwidthData) -> Self { + Self { + available_bandwidth: value.available_bandwidth, + } + } +} From 69e97b3bbc4e9ef2cf58586d4deddb91a7ddbb7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Tue, 5 Nov 2024 16:16:59 +0200 Subject: [PATCH 05/27] Remove old use of 1GB constant (#5096) * Remove old use of 1GB constant * Fix clippy --- common/wireguard/src/peer_controller.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/wireguard/src/peer_controller.rs b/common/wireguard/src/peer_controller.rs index 0c77bcf010..bc143745c4 100644 --- a/common/wireguard/src/peer_controller.rs +++ b/common/wireguard/src/peer_controller.rs @@ -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, @@ -230,7 +230,7 @@ impl PeerController { // 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 { From d03c5b365013ae595e6fed8d407c6440eac0110a Mon Sep 17 00:00:00 2001 From: Dinko Zdravac <173912580+dynco-nym@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:36:16 +0100 Subject: [PATCH 06/27] Graceful agent 1.1.5 (#5093) * Bump NS agent to 0.1.5 * API improvements - agent exits gracefully when no testrun available - API doesn't log every error * Bump NSAPI to 0.1.6 --- Cargo.lock | 4 +-- nym-node-status-agent/Cargo.toml | 2 +- nym-node-status-agent/src/cli.rs | 37 +++++++++++++------- nym-node-status-api/Cargo.toml | 2 +- nym-node-status-api/src/http/api/mod.rs | 10 ++---- nym-node-status-api/src/http/api/testruns.rs | 3 +- nym-node-status-api/src/http/error.rs | 4 +-- 7 files changed, 35 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65bf102040..fc972ebc8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5980,7 +5980,7 @@ dependencies = [ [[package]] name = "nym-node-status-agent" -version = "0.1.4" +version = "0.1.5" dependencies = [ "anyhow", "clap 4.5.18", @@ -5996,7 +5996,7 @@ dependencies = [ [[package]] name = "nym-node-status-api" -version = "0.1.5" +version = "0.1.6" dependencies = [ "anyhow", "axum 0.7.7", diff --git a/nym-node-status-agent/Cargo.toml b/nym-node-status-agent/Cargo.toml index f89065b5b3..45363a8cb6 100644 --- a/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-agent/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-node-status-agent" -version = "0.1.4" +version = "0.1.5" authors.workspace = true repository.workspace = true homepage.workspace = true diff --git a/nym-node-status-agent/src/cli.rs b/nym-node-status-agent/src/cli.rs index 81e70e2da9..a7bbef5ebe 100644 --- a/nym-node-status-agent/src/cli.rs +++ b/nym-node-status-agent/src/cli.rs @@ -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 { +async fn request_testrun(server_addr: &str) -> anyhow::Result> { 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 diff --git a/nym-node-status-api/Cargo.toml b/nym-node-status-api/Cargo.toml index 0969d31045..511ab91b59 100644 --- a/nym-node-status-api/Cargo.toml +++ b/nym-node-status-api/Cargo.toml @@ -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 diff --git a/nym-node-status-api/src/http/api/mod.rs b/nym-node-status-api/src/http/api/mod.rs index 4e4693ac1f..ed24fa80f5 100644 --- a/nym-node-status-api/src/http/api/mod.rs +++ b/nym-node-status-api/src/http/api/mod.rs @@ -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()) } } diff --git a/nym-node-status-api/src/http/api/testruns.rs b/nym-node-status-api/src/http/api/testruns.rs index e55e462110..b13d62ba88 100644 --- a/nym-node-status-api/src/http/api/testruns.rs +++ b/nym-node-status-api/src/http/api/testruns.rs @@ -49,7 +49,8 @@ async fn request_testrun(State(state): State) -> HttpResult Err(HttpError::internal_with_logging(err)), diff --git a/nym-node-status-api/src/http/error.rs b/nym-node-status-api/src/http/error.rs index 808ace9cec..a7fe9d98ab 100644 --- a/nym-node-status-api/src/http/error.rs +++ b/nym-node-status-api/src/http/error.rs @@ -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, } } From fd8dc63c88ea296491ca594dc72b631a193c6e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Tue, 5 Nov 2024 14:40:50 +0000 Subject: [PATCH 07/27] fixed HistoricalUptimeUpdater (#5097) --- nym-api/src/node_status_api/uptime_updater.rs | 16 +++++----------- nym-api/src/support/storage/manager.rs | 7 ++++--- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/nym-api/src/node_status_api/uptime_updater.rs b/nym-api/src/node_status_api/uptime_updater.rs index 67de44839b..affca9f7f2 100644 --- a/nym-api/src/node_status_api/uptime_updater.rs +++ b/nym-api/src/node_status_api/uptime_updater.rs @@ -1,4 +1,4 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2024 - Nym Technologies SA // 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 { diff --git a/nym-api/src/support/storage/manager.rs b/nym-api/src/support/storage/manager.rs index f41eabf508..4731f90ac3 100644 --- a/nym-api/src/support/storage/manager.rs +++ b/nym-api/src/support/storage/manager.rs @@ -983,7 +983,8 @@ impl StorageManager { since: i64, until: i64, ) -> Result, 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 } From c001059af9e18f1e0ccf2454a1aead44eba8a9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 6 Nov 2024 09:17:44 +0000 Subject: [PATCH 08/27] Feature/force refresh node (#5101) * introduced nym-api endpoint for force refreshing described node data * client code + updated return types * nym-node to update self-described data cache on startup + change request type * send request to all available nym-apis * fixed 'is_stale' check --- Cargo.lock | 1 + .../validator-client/src/client.rs | 6 +- .../validator-client/src/nym_api/mod.rs | 14 +++- nym-api/nym-api-requests/src/models.rs | 62 ++++++++++++++++ nym-api/src/ecash/tests/mod.rs | 1 + nym-api/src/node_describe_cache/mod.rs | 71 +++++++++++++------ nym-api/src/node_status_api/models.rs | 14 ++++ nym-api/src/nym_contract_cache/cache/mod.rs | 44 ++++++++++++ nym-api/src/nym_nodes/handlers/mod.rs | 67 +++++++++++++++-- nym-api/src/support/caching/cache.rs | 13 +++- nym-api/src/support/cli/run.rs | 1 + nym-api/src/support/http/openapi.rs | 1 + nym-api/src/support/http/state.rs | 23 +++++- nym-node/Cargo.toml | 1 + nym-node/src/node/mod.rs | 41 ++++++++++- 15 files changed, 327 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc972ebc8e..7e90ec8c48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5906,6 +5906,7 @@ dependencies = [ "nym-sphinx-addressing", "nym-task", "nym-types", + "nym-validator-client", "nym-wireguard", "nym-wireguard-types", "rand", diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 071fb597bf..d0d64f4b93 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -25,12 +25,12 @@ use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated}; use nym_api_requests::nym_nodes::SkimmedNode; use nym_coconut_dkg_common::types::EpochId; use nym_http_api_client::UserAgent; +use nym_mixnet_contract_common::NymNodeDetails; use nym_network_defaults::NymNetworkDetails; use time::Date; use url::Url; pub use crate::nym_api::NymApiClientExt; -use nym_mixnet_contract_common::NymNodeDetails; pub use nym_mixnet_contract_common::{ mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId, }; @@ -330,10 +330,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) -> 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::() .expect("failed to build nym api client"); diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 13a1ca7671..a3ea3aea8a 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -12,7 +12,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; @@ -934,6 +934,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 + } + #[instrument(level = "debug", skip(self))] async fn epoch_credentials( &self, diff --git a/nym-api/nym-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs index 34c51dab91..c21fe448c7 100644 --- a/nym-api/nym-api-requests/src/models.rs +++ b/nym-api/nym-api-requests/src/models.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::helpers::unix_epoch; +use crate::helpers::PlaceholderJsonSchemaImpl; use crate::legacy::{ LegacyGatewayBondWithId, LegacyMixNodeBondWithLayer, LegacyMixNodeDetailsWithLayer, }; @@ -1143,6 +1144,67 @@ pub struct NoiseDetails { pub ip_addresses: Vec, } +#[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 { + 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::*; diff --git a/nym-api/src/ecash/tests/mod.rs b/nym-api/src/ecash/tests/mod.rs index 123c0a7930..3e81637322 100644 --- a/nym-api/src/ecash/tests/mod.rs +++ b/nym-api/src/ecash/tests/mod.rs @@ -1261,6 +1261,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()), diff --git a/nym-api/src/node_describe_cache/mod.rs b/nym-api/src/node_describe_cache/mod.rs index 8368e58a97..aa80d831ab 100644 --- a/nym-api/src/node_describe_cache/mod.rs +++ b/nym-api/src/node_describe_cache/mod.rs @@ -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, } +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, @@ -315,7 +353,11 @@ impl RefreshData { } } - async fn try_refresh(self) -> Option { + pub(crate) fn node_id(&self) -> NodeId { + self.node_id + } + + pub(crate) async fn try_refresh(self) -> Option { 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 = 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()) } } } diff --git a/nym-api/src/node_status_api/models.rs b/nym-api/src/node_status_api/models.rs index bce5d6eea6..7fd879982b 100644 --- a/nym-api/src/node_status_api/models.rs +++ b/nym-api/src/node_status_api/models.rs @@ -355,6 +355,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()), @@ -375,6 +382,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 for AxumErrorResponse { diff --git a/nym-api/src/nym_contract_cache/cache/mod.rs b/nym-api/src/nym_contract_cache/cache/mod.rs index ac7f89b0c5..2f08c26914 100644 --- a/nym-api/src/nym_contract_cache/cache/mod.rs +++ b/nym-api/src/nym_contract_cache/cache/mod.rs @@ -1,6 +1,7 @@ // Copyright 2023 - Nym Technologies SA // 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::ValidatorCacheData; @@ -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::{Interval, NodeId, NymNodeDetails, RewardedSet, RewardingParams}; use std::{ collections::HashSet, @@ -352,6 +354,48 @@ impl NymContractCache { self.legacy_mixnode_details(mix_id).await.1 } + pub async fn get_node_refresh_data( + &self, + node_identity: ed25519::PublicKey, + ) -> Option { + 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) } diff --git a/nym-api/src/nym_nodes/handlers/mod.rs b/nym-api/src/nym_nodes/handlers/mod.rs index 4ccb3c02de..47530a6c5c 100644 --- a/nym-api/src/nym_nodes/handlers/mod.rs +++ b/nym-api/src/nym_nodes/handlers/mod.rs @@ -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 { 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 { .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, + Json(request_body): Json, +) -> AxumResult> { + 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, diff --git a/nym-api/src/support/caching/cache.rs b/nym-api/src/support/caching/cache.rs index 706d2b342b..405f6022a3 100644 --- a/nym-api/src/support/caching/cache.rs +++ b/nym-api/src/support/caching/cache.rs @@ -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 SharedCache { RwLockReadGuard::try_map(guard, |a| a.inner.as_ref()).map_err(|_| UninitialisedCache) } + pub(crate) async fn write( + &self, + ) -> Result>, 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 Cache { 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) -> bool { let now = now.unwrap_or(OffsetDateTime::now_utc()); diff --git a/nym-api/src/support/cli/run.rs b/nym-api/src/support/cli/run.rs index 97faffa387..493def1c5a 100644 --- a/nym-api/src/support/cli/run.rs +++ b/nym-api/src/support/cli/run.rs @@ -188,6 +188,7 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::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>>, +} + +impl ForcedRefresh { + pub(crate) async fn last_refreshed(&self, node_id: NodeId) -> Option { + 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 diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 442b62a3b9..affd10f753 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -52,6 +52,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 } diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 5a7bc0c419..bd687ff0c3 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -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; @@ -740,6 +745,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 @@ -754,6 +791,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"))?; From a400aa8928457e96b9610e718b0bc184dd527615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 8 Nov 2024 09:33:30 +0000 Subject: [PATCH 09/27] bugfix: preserve as much as possible of the rewarded set during migration (#5103) --- contracts/mixnet/src/nodes/storage/helpers.rs | 25 ++-- contracts/mixnet/src/queued_migrations.rs | 129 ++++++++++++++++-- 2 files changed, 127 insertions(+), 27 deletions(-) diff --git a/contracts/mixnet/src/nodes/storage/helpers.rs b/contracts/mixnet/src/nodes/storage/helpers.rs index 1d15c6b03a..169a0d42bc 100644 --- a/contracts/mixnet/src/nodes/storage/helpers.rs +++ b/contracts/mixnet/src/nodes/storage/helpers.rs @@ -104,7 +104,10 @@ pub(crate) fn next_nymnode_id_counter(store: &mut dyn Storage) -> StdResult 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(()) } diff --git a/contracts/mixnet/src/queued_migrations.rs b/contracts/mixnet/src/queued_migrations.rs index 15cb9a6d43..007a2f376f 100644 --- a/contracts/mixnet/src/queued_migrations.rs +++ b/contracts/mixnet/src/queued_migrations.rs @@ -90,12 +90,16 @@ mod families_purge { mod nym_nodes_usage { use crate::constants::{CONTRACT_STATE_KEY, REWARDING_PARAMS_KEY}; + use crate::interval::storage::current_interval; use crate::mixnet_contract_settings::storage::CONTRACT_STATE; + use crate::nodes::storage::helpers::RoleStorageBucket; + use crate::nodes::storage::rewarded_set::{ACTIVE_ROLES_BUCKET, ROLES, ROLES_METADATA}; use crate::rewards::storage::RewardingStorage; use crate::support::helpers::ensure_epoch_in_progress_state; use cosmwasm_std::{Addr, Coin, DepsMut, Order, StdResult, Storage}; use cw_storage_plus::{Item, Map}; use mixnet_contract_common::error::MixnetContractError; + use mixnet_contract_common::nym_node::{RewardedSetMetadata, Role}; use mixnet_contract_common::reward_params::RewardedSetParams; use mixnet_contract_common::{ ContractState, ContractStateParams, IntervalRewardParams, MigrateMsg, NodeId, @@ -173,7 +177,9 @@ mod nym_nodes_usage { Ok(()) } - fn preassign_gateway_ids(storage: &mut dyn Storage) -> Result<(), MixnetContractError> { + fn preassign_gateway_ids( + storage: &mut dyn Storage, + ) -> Result<(Option, Option), MixnetContractError> { // that one is a big if. we have ~100 gateways so we **might** be able to fit it within migration. // if not, then we'll have to do it in batches/change our approach @@ -182,8 +188,15 @@ mod nym_nodes_usage { .map(|res| res.map(|row| row.1)) .collect::>>()?; + let mut start = None; + let mut end = None; for gateway in gateways { let id = crate::nodes::storage::next_nymnode_id_counter(storage)?; + if start.is_none() { + start = Some(id) + } + end = Some(id); + crate::gateways::storage::PREASSIGNED_LEGACY_IDS.save( storage, gateway.gateway.identity_key, @@ -191,10 +204,12 @@ mod nym_nodes_usage { )?; } - Ok(()) + Ok((start, end)) } - fn cleanup_legacy_storage(storage: &mut dyn Storage) -> Result<(), MixnetContractError> { + fn cleanup_legacy_storage( + storage: &mut dyn Storage, + ) -> Result, MixnetContractError> { #[derive(Copy, Clone, Default, Serialize, Deserialize)] pub struct LayerDistribution { pub layer1: u64, @@ -224,11 +239,11 @@ mod nym_nodes_usage { .keys(storage, None, None, Order::Ascending) .collect::, _>>()?; - for node_id in rewarded_ids { + for &node_id in &rewarded_ids { REWARDED_SET.remove(storage, node_id) } - Ok(()) + Ok(rewarded_ids) } fn migrate_rewarded_set_params(storage: &mut dyn Storage) -> Result<(), MixnetContractError> { @@ -268,6 +283,98 @@ mod nym_nodes_usage { Ok(()) } + fn assign_temporary_rewarded_set( + storage: &mut dyn Storage, + (min_available_gateway, max_available_gateway): (Option, Option), + current_rewarded_set_mixnodes: Vec, + ) -> Result<(), MixnetContractError> { + let epoch_id = current_interval(storage)?.current_epoch_absolute_id(); + + // in the previous step we explicitly set rewarded set to 120 mixnodes and 50 entry gateways + // note: we can't assign exit gateways because the contract itself doesn't know which might support it + + let active_bucket = RoleStorageBucket::default(); + let inactive_bucket = active_bucket.other(); + ACTIVE_ROLES_BUCKET.save(storage, &active_bucket)?; + + // ACTIVE BUCKET: + let mut active_metadata = RewardedSetMetadata::new(epoch_id); + + let mut current_rewarded_set_mixnodes = current_rewarded_set_mixnodes; + // ensure it's sorted. it should have already been, but better safe than sorry.. + current_rewarded_set_mixnodes.sort(); + + let mut layer1 = Vec::new(); + let mut layer2 = Vec::new(); + let mut layer3 = Vec::new(); + let mut entry = Vec::new(); + + for (i, mix_id) in current_rewarded_set_mixnodes + .into_iter() + .take(120) + .enumerate() + { + if i % 3 == 0 { + layer1.push(mix_id); + } else if i % 3 == 1 { + layer2.push(mix_id); + } else if i % 3 == 2 { + layer3.push(mix_id); + } + } + + if let (Some(min_id), Some(max_id)) = (min_available_gateway, max_available_gateway) { + // we can assign the gateway nodes + entry = (min_id..=max_id).take(50).collect(); + } + + // ACTIVE BUCKET: + active_metadata.fully_assigned = true; + + // layer1 + ROLES.save(storage, (active_bucket as u8, Role::Layer1), &layer1)?; + active_metadata.layer1_metadata.num_nodes = layer1.len() as u32; + active_metadata.layer1_metadata.highest_id = layer1.last().copied().unwrap_or_default(); + + // layer2 + ROLES.save(storage, (active_bucket as u8, Role::Layer2), &layer2)?; + active_metadata.layer2_metadata.num_nodes = layer2.len() as u32; + active_metadata.layer2_metadata.highest_id = layer2.last().copied().unwrap_or_default(); + + // layer3 + ROLES.save(storage, (active_bucket as u8, Role::Layer3), &layer3)?; + active_metadata.layer3_metadata.num_nodes = layer3.len() as u32; + active_metadata.layer3_metadata.highest_id = layer3.last().copied().unwrap_or_default(); + + // entry + ROLES.save(storage, (active_bucket as u8, Role::EntryGateway), &entry)?; + active_metadata.entry_gateway_metadata.num_nodes = entry.len() as u32; + active_metadata.entry_gateway_metadata.highest_id = + entry.last().copied().unwrap_or_default(); + + // nothing for exit or standby + ROLES.save(storage, (active_bucket as u8, Role::ExitGateway), &vec![])?; + ROLES.save(storage, (active_bucket as u8, Role::Standby), &vec![])?; + ROLES_METADATA.save(storage, active_bucket as u8, &active_metadata)?; + + // SECONDARY BUCKET + let roles = vec![ + Role::Layer1, + Role::Layer2, + Role::Layer3, + Role::EntryGateway, + Role::ExitGateway, + Role::Standby, + ]; + for role in roles { + ROLES.save(storage, (inactive_bucket as u8, role), &vec![])? + } + + ROLES_METADATA.save(storage, inactive_bucket as u8, &Default::default())?; + + Ok(()) + } + pub(crate) fn migrate_to_nym_nodes_usage( deps: DepsMut<'_>, _msg: &MigrateMsg, @@ -284,16 +391,18 @@ mod nym_nodes_usage { // pre-assign NodeId to all gateways (that will be applied during nym-node migration) // to simplify all other code during the intermediate period - preassign_gateway_ids(deps.storage)?; - - // initialise all the storage structures required by nym-nodes - crate::nodes::storage::initialise_storage(deps.storage)?; + let gateways = preassign_gateway_ids(deps.storage)?; // update the simple active/rewarded set sizes to actually contain the distribution of roles migrate_rewarded_set_params(deps.storage)?; // remove all redundant storage items - cleanup_legacy_storage(deps.storage)?; + let old_rewarded_set_mixnodes = cleanup_legacy_storage(deps.storage)?; + + // assign initial rewarded set + // and initialise all the storage structures required by nym-nodes + // based on the nodes that are in the contract right now + assign_temporary_rewarded_set(deps.storage, gateways, old_rewarded_set_mixnodes)?; Ok(()) } From ac77712cc08dbc93d043e46b135d027bc16dbc48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Mon, 11 Nov 2024 17:38:21 +0100 Subject: [PATCH 10/27] nym-credential-proxy-requests: reqwest use rustls-tls (#5116) * nym-credential-proxy-requests: reqwest use rustls-tls * nym-credential-proxy: reqwest default-features false --- nym-credential-proxy/Cargo.toml | 2 +- nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nym-credential-proxy/Cargo.toml b/nym-credential-proxy/Cargo.toml index b3ebb7f459..07e82599d0 100644 --- a/nym-credential-proxy/Cargo.toml +++ b/nym-credential-proxy/Cargo.toml @@ -37,7 +37,7 @@ futures = "0.3.30" humantime = "2.1.0" thiserror = "1.0.59" rand = "0.8.5" -reqwest = "0.12.4" +reqwest = { version = "0.12.4", default-features = false } schemars = "0.8.17" strum = "0.26.3" strum_macros = "0.26.4" diff --git a/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml b/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml index 462fa8a4c1..86e281eb8a 100644 --- a/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml +++ b/nym-credential-proxy/nym-credential-proxy-requests/Cargo.toml @@ -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: From 4fab7eac3f2e7d00336239b88d53ec439461aecb Mon Sep 17 00:00:00 2001 From: Tommy Verrall Date: Wed, 13 Nov 2024 10:56:19 +0100 Subject: [PATCH 11/27] temporarily disable playground and test my node in the wallet once we have time to fix these we will import these again --- .../src/pages/bonding/node-settings/NodeSettings.tsx | 10 +++++----- .../bonding/node-settings/node-settings.constant.tsx | 10 +++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx index dd86326130..bf7eb7a648 100644 --- a/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx @@ -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 = () => { > {value === 'General' && bondedNode && } - {value === 'Test my node' && } + {/* {value === 'Test my node' && } */} {value === 'Unbond' && bondedNode && ( )} - {value === 'Playground' && bondedNode && } + {/* {value === 'Playground' && bondedNode && } */} {confirmationDetails && confirmationDetails.status === 'success' && ( { 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' ; From 5b216e8d4039b38ab7e5d51acadeefe86363f6f2 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Wed, 13 Nov 2024 11:23:07 +0100 Subject: [PATCH 12/27] update versions --- Cargo.lock | 16 ++++++++-------- clients/native/Cargo.toml | 2 +- clients/socks5/Cargo.toml | 2 +- explorer-api/Cargo.toml | 2 +- nym-api/Cargo.toml | 2 +- nym-node/Cargo.toml | 2 +- service-providers/network-requester/Cargo.toml | 2 +- tools/nym-cli/Cargo.toml | 2 +- tools/nymvisor/Cargo.toml | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e90ec8c48..65a44d2f9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2428,7 +2428,7 @@ dependencies = [ [[package]] name = "explorer-api" -version = "1.1.41" +version = "1.1.42" dependencies = [ "chrono", "clap 4.5.18", @@ -4451,7 +4451,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "nym-api" -version = "1.1.45" +version = "1.1.46" dependencies = [ "anyhow", "async-trait", @@ -4692,7 +4692,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.43" +version = "1.1.44" dependencies = [ "anyhow", "base64 0.22.1", @@ -4773,7 +4773,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.42" +version = "1.1.43" dependencies = [ "bs58", "clap 4.5.18", @@ -5828,7 +5828,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.43" +version = "1.1.44" dependencies = [ "addr", "anyhow", @@ -5879,7 +5879,7 @@ dependencies = [ [[package]] name = "nym-node" -version = "1.1.9" +version = "1.1.10" dependencies = [ "anyhow", "bip39", @@ -6225,7 +6225,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.42" +version = "1.1.43" dependencies = [ "bs58", "clap 4.5.18", @@ -6770,7 +6770,7 @@ dependencies = [ [[package]] name = "nymvisor" -version = "0.1.8" +version = "0.1.9" dependencies = [ "anyhow", "bytes", diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index c9317bb5f3..9550c65b3f 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-client" -version = "1.1.42" +version = "1.1.43" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 9a22fd4355..5243206efa 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.42" +version = "1.1.43" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 8e59935476..11f8672f98 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.41" +version = "1.1.42" edition = "2021" license.workspace = true diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index b57d11e0e7..fa31615f70 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -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 diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index affd10f753..3b2bf0cb87 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -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 diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 6678d72c09..9ab6b58afd 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -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" diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index 8451a20d89..2a4bdf0186 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.43" +version = "1.1.44" authors.workspace = true edition = "2021" license.workspace = true diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml index fae37c502c..a1f0590c8d 100644 --- a/tools/nymvisor/Cargo.toml +++ b/tools/nymvisor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nymvisor" -version = "0.1.8" +version = "0.1.9" authors.workspace = true repository.workspace = true homepage.workspace = true From c0aadebf80fb693dc5816e0123ed35dcc787aa5b Mon Sep 17 00:00:00 2001 From: Fouad Date: Wed, 13 Nov 2024 12:53:57 +0000 Subject: [PATCH 13/27] Migrate node when events pending (#5125) * dont show node migration if there are vesting tokens * catch and set errors --- .../src/components/Bonding/BondedGateway.tsx | 1 + .../src/components/Bonding/BondedMixnode.tsx | 22 +++++++++----- nym-wallet/src/context/bonding.tsx | 30 ++++++++++++------- nym-wallet/src/pages/bonding/Bonding.tsx | 11 +++++-- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/nym-wallet/src/components/Bonding/BondedGateway.tsx b/nym-wallet/src/components/Bonding/BondedGateway.tsx index ab8ee84209..53d0a06b16 100644 --- a/nym-wallet/src/components/Bonding/BondedGateway.tsx +++ b/nym-wallet/src/components/Bonding/BondedGateway.tsx @@ -113,6 +113,7 @@ export const BondedGateway = ({ startIcon={} variant="contained" disableElevation + disabled={!!gateway.proxy} onClick={onShowMigrateToNymNodeModal} > Migrate to Nym Node diff --git a/nym-wallet/src/components/Bonding/BondedMixnode.tsx b/nym-wallet/src/components/Bonding/BondedMixnode.tsx index 5d0fc68e64..6b2977b48e 100644 --- a/nym-wallet/src/components/Bonding/BondedMixnode.tsx +++ b/nym-wallet/src/components/Bonding/BondedMixnode.tsx @@ -190,15 +190,21 @@ export const BondedMixnode = ({ - + + + + {nextEpoch instanceof Error ? null : ( diff --git a/nym-wallet/src/context/bonding.tsx b/nym-wallet/src/context/bonding.tsx index 19652b6cff..01c04fe813 100644 --- a/nym-wallet/src/context/bonding.tsx +++ b/nym-wallet/src/context/bonding.tsx @@ -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( diff --git a/nym-wallet/src/pages/bonding/Bonding.tsx b/nym-wallet/src/pages/bonding/Bonding.tsx index d618775e05..57dc05bbe4 100644 --- a/nym-wallet/src/pages/bonding/Bonding.tsx +++ b/nym-wallet/src/pages/bonding/Bonding.tsx @@ -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 refresh()} />; + return ( + refresh()} + /> + ); } return ( From 532c25c4f5553f4f201bd5a36775960f2a6377f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 14 Nov 2024 08:48:06 +0000 Subject: [PATCH 14/27] change: dont allow mixnodes bonded with vested tokens into the rewarded set (#5129) --- .../epoch_operations/rewarded_set_assignment.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/nym-api/src/epoch_operations/rewarded_set_assignment.rs b/nym-api/src/epoch_operations/rewarded_set_assignment.rs index 0d5c97eacd..d92cd1d7f5 100644 --- a/nym-api/src/epoch_operations/rewarded_set_assignment.rs +++ b/nym-api/src/epoch_operations/rewarded_set_assignment.rs @@ -66,6 +66,7 @@ struct IgnoredNodes { no_self_described: usize, not_nym_node_binary: usize, no_terms_and_conditions: usize, + use_vested_tokens: usize, } impl IgnoredNodes { @@ -75,6 +76,7 @@ impl IgnoredNodes { no_self_described: 0, not_nym_node_binary: 0, no_terms_and_conditions: 0, + use_vested_tokens: 0, } } @@ -82,6 +84,7 @@ impl IgnoredNodes { self.no_self_described == 0 && self.not_nym_node_binary == 0 && self.no_terms_and_conditions == 0 + && self.use_vested_tokens == 0 } fn maybe_log_summary(&self) { @@ -104,6 +107,12 @@ impl IgnoredNodes { self.no_terms_and_conditions, self.typ ) } + if self.use_vested_tokens != 0 { + warn!( + "{} {} operators bonded using vested tokens", + self.use_vested_tokens, self.typ + ) + } } } @@ -286,6 +295,11 @@ impl EpochAdvancer { continue; } + if mix.bond_information.proxy.is_some() { + legacy_mixnodes_info.use_vested_tokens += 1; + continue; + } + let performance = self .load_mixnode_performance(&interval, mix.mix_id()) .await From 46a33b5ef69c22447d3346784ea7a88e316fd12a Mon Sep 17 00:00:00 2001 From: Andrej Mihajlov Date: Thu, 14 Nov 2024 13:05:03 +0100 Subject: [PATCH 15/27] Add NYM_VPN_API to env files (#5099) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add missing NYM_VPN_API uri to environment files * Add trailing slashes --------- Co-authored-by: Jon Häggblad --- envs/canary.env | 5 +++-- envs/mainnet.env | 2 +- envs/qa.env | 5 +++-- envs/sandbox.env | 7 ++++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/envs/canary.env b/envs/canary.env index db70a19fd0..84aa65eefa 100644 --- a/envs/canary.env +++ b/envs/canary.env @@ -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/ diff --git a/envs/mainnet.env b/envs/mainnet.env index 419547bf97..8a2b1f61bf 100644 --- a/envs/mainnet.env +++ b/envs/mainnet.env @@ -25,4 +25,4 @@ NYXD=https://rpc.nymtech.net NYM_API=https://validator.nymtech.net/api/ NYXD_WS="wss://rpc.nymtech.net/websocket" EXPLORER_API=https://explorer.nymtech.net/api/ -NYM_VPN_API="https://nymvpn.com/api" +NYM_VPN_API="https://nymvpn.com/api/" diff --git a/envs/qa.env b/envs/qa.env index 34306a62ea..81adaf299c 100644 --- a/envs/qa.env +++ b/envs/qa.env @@ -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/ diff --git a/envs/sandbox.env b/envs/sandbox.env index 6310b8fa89..3033312368 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -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/ From 6809f7302e2cdf30682d121022db42ec9e8b7d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 14 Nov 2024 15:32:20 +0000 Subject: [PATCH 16/27] Pain/polyfill deprecated endpoints (#5131) * polyfilled contract cache endpoints * polyfilled legacy described endpoints --- nym-api/src/nym_contract_cache/handlers.rs | 158 ++++++++++++++++++--- nym-api/src/nym_nodes/handlers/legacy.rs | 98 +++++++++---- nym-api/src/support/legacy_helpers.rs | 85 +++++++++++ nym-api/src/support/mod.rs | 2 + 4 files changed, 295 insertions(+), 48 deletions(-) create mode 100644 nym-api/src/support/legacy_helpers.rs diff --git a/nym-api/src/nym_contract_cache/handlers.rs b/nym-api/src/nym_contract_cache/handlers.rs index d57f1317c4..00f02e1fe2 100644 --- a/nym-api/src/nym_contract_cache/handlers.rs +++ b/nym-api/src/nym_contract_cache/handlers.rs @@ -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 { )] #[deprecated] async fn get_mixnodes(State(state): State) -> Json> { - 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) -> Json) -> Json> { - Json( - state - .nym_contract_cache() - .legacy_gateways_filtered() - .await - .into_iter() - .map(Into::into) - .collect(), - ) + // legacy + let mut out: Vec = 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) -> Json> { - 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, diff --git a/nym-api/src/nym_nodes/handlers/legacy.rs b/nym-api/src/nym_nodes/handlers/legacy.rs index cae94d5254..3dd6a51f74 100644 --- a/nym-api/src/nym_nodes/handlers/legacy.rs +++ b/nym-api/src/nym_nodes/handlers/legacy.rs @@ -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> { 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 = contract_cache .legacy_mixnodes_filtered() .await .into_iter() .map(|m| m.bond_information) .collect::>(); - 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) } diff --git a/nym-api/src/support/legacy_helpers.rs b/nym-api/src/support/legacy_helpers.rs new file mode 100644 index 0000000000..d5135c7643 --- /dev/null +++ b/nym-api/src/support/legacy_helpers.rs @@ -0,0 +1,85 @@ +// Copyright 2024 - Nym Technologies SA +// 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, + } +} diff --git a/nym-api/src/support/mod.rs b/nym-api/src/support/mod.rs index 28abaf3b94..a1773f9804 100644 --- a/nym-api/src/support/mod.rs +++ b/nym-api/src/support/mod.rs @@ -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; From 94f247563b8edda297a6ffc56cb6c16c78bac166 Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Fri, 15 Nov 2024 17:45:26 +0000 Subject: [PATCH 17/27] nym-node-status-agent bump version --- Cargo.lock | 2 +- nym-node-status-agent/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65a44d2f9a..99d53669da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5981,7 +5981,7 @@ dependencies = [ [[package]] name = "nym-node-status-agent" -version = "0.1.5" +version = "0.1.6" dependencies = [ "anyhow", "clap 4.5.18", diff --git a/nym-node-status-agent/Cargo.toml b/nym-node-status-agent/Cargo.toml index 45363a8cb6..f7d8543a1f 100644 --- a/nym-node-status-agent/Cargo.toml +++ b/nym-node-status-agent/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "nym-node-status-agent" -version = "0.1.5" +version = "0.1.6" authors.workspace = true repository.workspace = true homepage.workspace = true From db20c2e2faba2b22b9675b0f7b9a82f663b71cbd Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Fri, 15 Nov 2024 17:55:07 +0000 Subject: [PATCH 18/27] node-status-agent: cherry-pick GH Actions pipeline and dockerfile from `9c680fd` --- .github/workflows/push-node-status-agent.yaml | 27 +++++++++++-------- nym-node-status-agent/Dockerfile | 14 +++++++++- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/.github/workflows/push-node-status-agent.yaml b/.github/workflows/push-node-status-agent.yaml index a66d4dee41..fb25f9dac0 100644 --- a/.github/workflows/push-node-status-agent.yaml +++ b/.github/workflows/push-node-status-agent.yaml @@ -2,6 +2,10 @@ name: Build and upload Node Status agent container to harbor.nymte.ch on: workflow_dispatch: + inputs: + gateway_probe_git_ref: + type: string + description: Which gateway probe git ref to build the image with env: WORKING_DIRECTORY: "nym-node-status-agent" @@ -32,25 +36,26 @@ jobs: with: cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml - - name: Check if tag exists + - name: cleanup-gateway-probe-ref + id: cleanup_gateway_probe_ref run: | - if git rev-parse ${{ steps.get_version.outputs.value }} >/dev/null 2>&1; then - echo "Tag ${{ steps.get_version.outputs.value }} already exists" - fi + GATEWAY_PROBE_GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} + GIT_REF_SLUG="${GATEWAY_PROBE_GIT_REF//\//-}" + echo "git_ref=${GIT_REF_SLUG}" >> $GITHUB_OUTPUT - name: Remove existing tag if exists run: | - if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} >/dev/null 2>&1; then - git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} - git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} + if git rev-parse ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }} >/dev/null 2>&1; then + git push --delete origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }} + git tag -d ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }} fi - name: Create tag run: | - git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} -m "Version ${{ steps.get_version.outputs.result }}" - git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }} + git tag -a ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }} -m "Version ${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" + git push origin ${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }} - name: BuildAndPushImageOnHarbor run: | - docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:${{ steps.get_version.outputs.result }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest - docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags + 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 \ No newline at end of file diff --git a/nym-node-status-agent/Dockerfile b/nym-node-status-agent/Dockerfile index 0280fc5687..b50ce8be3d 100644 --- a/nym-node-status-agent/Dockerfile +++ b/nym-node-status-agent/Dockerfile @@ -1,5 +1,7 @@ FROM rust:latest AS builder +ARG GIT_REF=main + RUN apt update && apt install -yy libdbus-1-dev pkg-config libclang-dev # Install go @@ -7,6 +9,7 @@ RUN wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz -O go.tar.gz RUN tar -xzvf go.tar.gz -C /usr/local RUN git clone https://github.com/nymtech/nym-vpn-client /usr/src/nym-vpn-client +RUN cd /usr/src/nym-vpn-client && git checkout $GIT_REF ENV PATH=/go/bin:/usr/local/go/bin:$PATH WORKDIR /usr/src/nym-vpn-client/nym-vpn-core RUN cargo build --release --package nym-gateway-probe @@ -15,6 +18,15 @@ COPY ./ /usr/src/nym WORKDIR /usr/src/nym/nym-node-status-agent RUN cargo build --release +#------------------------------------------------------------------- +# The following environment variables are required at runtime: +# +# NODE_STATUS_AGENT_SERVER_ADDRESS +# NODE_STATUS_AGENT_SERVER_PORT +# +# see https://github.com/nymtech/nym/blob/develop/nym-node-status-agent/src/cli.rs for details +#------------------------------------------------------------------- + FROM ubuntu:24.04 RUN apt-get update && apt-get install -y ca-certificates @@ -25,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" ] +ENTRYPOINT [ "/nym/nym-node-status-agent", "run-probe" ] \ No newline at end of file From e44a36e5b5447e6c9ea28f0c5da431743a953c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 18 Nov 2024 11:21:07 +0200 Subject: [PATCH 19/27] Respond to auth messages with same version (#5140) * Introduce traits for response * Ugly responde with same protocol version * Don't pull sdk crate unnecessarily --- common/authenticator-requests/src/error.rs | 3 + common/authenticator-requests/src/lib.rs | 1 + common/authenticator-requests/src/traits.rs | 269 +++++++++++ .../src/lib.rs | 2 +- service-providers/authenticator/src/error.rs | 3 + .../authenticator/src/mixnet_listener.rs | 422 +++++++++++++----- .../authenticator/src/peer_manager.rs | 9 +- 7 files changed, 593 insertions(+), 116 deletions(-) create mode 100644 common/authenticator-requests/src/traits.rs diff --git a/common/authenticator-requests/src/error.rs b/common/authenticator-requests/src/error.rs index fc9f8700d5..1c43da3cad 100644 --- a/common/authenticator-requests/src/error.rs +++ b/common/authenticator-requests/src/error.rs @@ -22,4 +22,7 @@ pub enum Error { #[error("conversion: {0}")] Conversion(String), + + #[error("failed to serialize response packet: {source}")] + FailedToSerializeResponsePacket { source: Box }, } diff --git a/common/authenticator-requests/src/lib.rs b/common/authenticator-requests/src/lib.rs index dd98cc9f7f..f2b2fb55ce 100644 --- a/common/authenticator-requests/src/lib.rs +++ b/common/authenticator-requests/src/lib.rs @@ -1,6 +1,7 @@ // Copyright 2024 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +pub mod traits; pub mod v1; pub mod v2; pub mod v3; diff --git a/common/authenticator-requests/src/traits.rs b/common/authenticator-requests/src/traits.rs new file mode 100644 index 0000000000..cb7cf37ee1 --- /dev/null +++ b/common/authenticator-requests/src/traits.rs @@ -0,0 +1,269 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::net::IpAddr; + +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, Error}; + +#[derive(Copy, Clone, Debug)] +pub enum AuthenticatorVersion { + V1, + V2, + V3, + UNKNOWN, +} + +impl From 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 { + 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 + } +} + +pub trait FinalMessage { + fn pub_key(&self) -> PeerPublicKey; + fn verify(&self, private_key: &PrivateKey, nonce: u64) -> Result<(), Error>; + fn private_ip(&self) -> IpAddr; + fn credential(&self) -> Option; +} + +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_ip(&self) -> IpAddr { + self.private_ip + } + + fn credential(&self) -> Option { + 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_ip(&self) -> IpAddr { + self.gateway_client.private_ip + } + + fn credential(&self) -> Option { + 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_ip(&self) -> IpAddr { + self.gateway_client.private_ip + } + + fn credential(&self) -> Option { + 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() + } +} + +pub enum AuthenticatorRequest { + Initial { + msg: Box, + protocol: Protocol, + reply_to: Recipient, + request_id: u64, + }, + Final { + msg: Box, + protocol: Protocol, + reply_to: Recipient, + request_id: u64, + }, + QueryBandwidth { + msg: Box, + protocol: Protocol, + reply_to: Recipient, + request_id: u64, + }, + TopUpBandwidth { + msg: Box, + protocol: Protocol, + reply_to: Recipient, + request_id: u64, + }, +} + +impl From 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 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 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, + } + } + } + } +} diff --git a/common/service-provider-requests-common/src/lib.rs b/common/service-provider-requests-common/src/lib.rs index d13a7156c9..f9f0564e1d 100644 --- a/common/service-provider-requests-common/src/lib.rs +++ b/common/service-provider-requests-common/src/lib.rs @@ -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, diff --git a/service-providers/authenticator/src/error.rs b/service-providers/authenticator/src/error.rs index bfc04c3fbc..6645369349 100644 --- a/service-providers/authenticator/src/error.rs +++ b/service-providers/authenticator/src/error.rs @@ -88,6 +88,9 @@ pub enum AuthenticatorError { #[error("storage should have the requested bandwidht entry")] MissingClientBandwidthEntry, + + #[error("unknown version number")] + UnknownVersion, } pub type Result = std::result::Result; diff --git a/service-providers/authenticator/src/mixnet_listener.rs b/service-providers/authenticator/src/mixnet_listener.rs index 8fc5c62582..606e38a259 100644 --- a/service-providers/authenticator/src/mixnet_listener.rs +++ b/service-providers/authenticator/src/mixnet_listener.rs @@ -11,15 +11,17 @@ use defguard_wireguard_rs::net::IpAddrMask; use defguard_wireguard_rs::{host::Peer, key::Key}; use futures::StreamExt; use nym_authenticator_requests::{ + traits::{ + AuthenticatorRequest, AuthenticatorVersion, FinalMessage, InitMessage, + QueryBandwidthMessage, TopUpMessage, + }, v1, v2, v3::{ self, registration::{ - FinalMessage, GatewayClient, InitMessage, PendingRegistrations, PrivateIPs, - RegistrationData, RegistredData, RemainingBandwidthData, + GatewayClient, PendingRegistrations, PrivateIPs, RegistrationData, + RemainingBandwidthData, }, - request::{AuthenticatorRequest, AuthenticatorRequestData}, - response::AuthenticatorResponse, }, CURRENT_VERSION, }; @@ -32,7 +34,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; @@ -43,7 +45,7 @@ use tokio_stream::wrappers::IntervalStream; use crate::{config::Config, error::*}; -type AuthenticatorHandleResult = Result; +type AuthenticatorHandleResult = Result<(Vec, Recipient)>; const DEFAULT_REGISTRATION_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute pub(crate) struct RegistredAndFree { @@ -153,22 +155,72 @@ impl MixnetListener { async fn on_initial_request( &mut self, - init_message: InitMessage, + init_message: Box, + 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_ip, + 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(), + 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?; @@ -178,15 +230,49 @@ impl MixnetListener { "private ip list should not be empty".to_string(), )); }; - return Ok(AuthenticatorResponse::new_registered( - RegistredData { - pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()), - private_ip: allowed_ip.ip, - 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_ip.ip, + 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_ip.ip, + 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_ip.ip, + 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 @@ -205,51 +291,98 @@ impl MixnetListener { ); 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_ip, + 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, + 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, + 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.gateway_client.private_ip, 32)); + .push(IpAddrMask::new(final_message.private_ip(), 32)); // 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) @@ -279,17 +412,45 @@ impl MixnetListener { } 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_ip: registration_data.gateway_data.private_ip, - 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_ip, + 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_ip, + 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_ip, + 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( @@ -325,22 +486,60 @@ impl MixnetListener { async fn on_query_bandwidth_request( &mut self, - peer_public_key: PeerPublicKey, + msg: Box, + 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, + 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, + protocol: Protocol, request_id: u64, reply_to: Recipient, ) -> AuthenticatorHandleResult { @@ -349,7 +548,7 @@ impl MixnetListener { }; 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 @@ -364,7 +563,7 @@ impl MixnetListener { 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(), @@ -376,13 +575,22 @@ impl MixnetListener { ); 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::V3 => v3::response::AuthenticatorResponse::new_topup_bandwidth( + 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( @@ -394,62 +602,52 @@ impl MixnetListener { 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, 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 @@ -473,8 +671,8 @@ impl MixnetListener { 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}"); } } @@ -506,7 +704,6 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result v1::request::AuthenticatorRequest::from_reconstructed_message(reconstructed) .map_err(|err| AuthenticatorError::FailedToDeserializeTaggedPacket { source: err }) - .map(Into::::into) .map(Into::into), [2, request_type] => { if request_type == ServiceProviderType::Authenticator as u8 { @@ -525,6 +722,7 @@ fn deserialize_request(reconstructed: &ReconstructedMessage) -> Result, ) -> Result> { - 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 From 35343b5220f84edfc4cc845d9c0a1228adaa6d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 18 Nov 2024 10:00:40 +0000 Subject: [PATCH 20/27] bugfix: make sure to assign correct node_id and identity during 'gateway_details' table migration (#5142) --- nym-api/src/support/storage/manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-api/src/support/storage/manager.rs b/nym-api/src/support/storage/manager.rs index 4731f90ac3..8a2959aed7 100644 --- a/nym-api/src/support/storage/manager.rs +++ b/nym-api/src/support/storage/manager.rs @@ -1329,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; "#, From 6dc9b79aceb2aacd045ee15cbb7d2b5cd92fba84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 18 Nov 2024 10:28:46 +0000 Subject: [PATCH 21/27] bugifx: assign 'node_id' when converting from 'GatewayDetails' to 'TestNode' (#5143) --- nym-api/src/support/storage/models.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-api/src/support/storage/models.rs b/nym-api/src/support/storage/models.rs index 6a7b120e66..1418cab9f8 100644 --- a/nym-api/src/support/storage/models.rs +++ b/nym-api/src/support/storage/models.rs @@ -87,7 +87,7 @@ pub struct GatewayDetails { impl From for TestNode { fn from(value: GatewayDetails) -> Self { TestNode { - node_id: None, + node_id: Some(value.node_id), identity_key: Some(value.identity), } } From 5ad11f2048f24b0c8902495359ac706b803cadc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 18 Nov 2024 13:33:19 +0200 Subject: [PATCH 22/27] Limit race probability (#5145) * Limit race probability * Actually assign value --- common/wireguard/src/peer_controller.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/wireguard/src/peer_controller.rs b/common/wireguard/src/peer_controller.rs index bc143745c4..8c7d94784a 100644 --- a/common/wireguard/src/peer_controller.rs +++ b/common/wireguard/src/peer_controller.rs @@ -194,6 +194,10 @@ impl PeerController { ); 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}"); From 086b4f6f54d6bf6baf1fc0bc7fe0b011bbc41531 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Mon, 18 Nov 2024 13:01:27 +0100 Subject: [PATCH 23/27] update changelog --- CHANGELOG.md | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fdec67361..5618fe59c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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]) From b5f1d674feda0b8407a8b521259995855903f346 Mon Sep 17 00:00:00 2001 From: benedettadavico Date: Mon, 18 Nov 2024 14:07:01 +0100 Subject: [PATCH 24/27] update wallet versions and changelog --- nym-wallet/CHANGELOG.md | 10 ++++++++++ nym-wallet/package.json | 2 +- nym-wallet/src-tauri/Cargo.toml | 2 +- nym-wallet/src-tauri/tauri.conf.json | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/nym-wallet/CHANGELOG.md b/nym-wallet/CHANGELOG.md index 146e6174ed..aba29abef0 100644 --- a/nym-wallet/CHANGELOG.md +++ b/nym-wallet/CHANGELOG.md @@ -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]) diff --git a/nym-wallet/package.json b/nym-wallet/package.json index 3d97eefd26..341da85fb1 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/nym-wallet-app", - "version": "1.2.14", + "version": "1.2.15", "license": "MIT", "main": "index.js", "scripts": { diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 88032e5efb..fcf0f6417e 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -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 = "" diff --git a/nym-wallet/src-tauri/tauri.conf.json b/nym-wallet/src-tauri/tauri.conf.json index 0067868c37..e493efdfed 100644 --- a/nym-wallet/src-tauri/tauri.conf.json +++ b/nym-wallet/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-wallet", - "version": "1.2.14" + "version": "1.2.15" }, "build": { "distDir": "../dist", From 62e0771236cc84da789afa68f8d455ad410d23b5 Mon Sep 17 00:00:00 2001 From: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:24:52 +0100 Subject: [PATCH 25/27] Update publish-nym-contracts.yml --- .github/workflows/publish-nym-contracts.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-nym-contracts.yml b/.github/workflows/publish-nym-contracts.yml index 85f1363b9b..ed1141ddf1 100644 --- a/.github/workflows/publish-nym-contracts.yml +++ b/.github/workflows/publish-nym-contracts.yml @@ -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 From b49ef643df86f0c670672429812c632fbbaf6cf1 Mon Sep 17 00:00:00 2001 From: Tommy Verrall <60836166+tommyv1987@users.noreply.github.com> Date: Mon, 18 Nov 2024 17:56:57 +0100 Subject: [PATCH 26/27] Update publish-nym-binaries.yml --- .github/workflows/publish-nym-binaries.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish-nym-binaries.yml b/.github/workflows/publish-nym-binaries.yml index e64825e425..b28f118ec7 100644 --- a/.github/workflows/publish-nym-binaries.yml +++ b/.github/workflows/publish-nym-binaries.yml @@ -55,6 +55,7 @@ jobs: uses: actions-rs/toolchain@v1 with: toolchain: stable + override: true - name: Build all binaries uses: actions-rs/cargo@v1 From 4d43728059749794e1cddadd27e2e03765309f4c Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 20 Nov 2024 09:44:59 +0000 Subject: [PATCH 27/27] fix linting --- nym-wallet/src/context/bonding.tsx | 12 ++++++++---- .../src/pages/bonding/node-settings/NodeSettings.tsx | 3 --- .../bonding/node-settings/node-settings.constant.tsx | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/nym-wallet/src/context/bonding.tsx b/nym-wallet/src/context/bonding.tsx index 01c04fe813..6ff1ca3393 100644 --- a/nym-wallet/src/context/bonding.tsx +++ b/nym-wallet/src/context/bonding.tsx @@ -136,13 +136,14 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen if (bondedNode && isNymNode(bondedNode)) tx = await unbondNymNodeRequest(fee?.fee); if (bondedNode && isMixnode(bondedNode) && !bondedNode.proxy) tx = await unbondMixnodeRequest(fee?.fee); if (bondedNode && isGateway(bondedNode) && !bondedNode.proxy) tx = await unbondGatewayRequest(fee?.fee); + return tx; } catch (e) { Console.warn(e); setError(`an error occurred: ${e as string}`); } finally { setIsLoading(false); } - return tx; + return undefined; }; const updateNymNodeConfig = async (data: NodeConfigUpdate) => { @@ -153,13 +154,14 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen if (clientDetails?.client_address) { await getNodeDetails(clientDetails?.client_address); } + return tx; } catch (e) { Console.warn(e); setError(`an error occurred: ${e}`); } finally { setIsLoading(false); } - return tx; + return undefined; }; const redeemRewards = async (fee?: FeeDetails) => { @@ -168,12 +170,13 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen try { if (bondedNode && !isNymNode(bondedNode)) tx = await vestingClaimOperatorReward(fee?.fee); else tx = await claimOperatorReward(fee?.fee); + return tx; } catch (e: any) { setError(`an error occurred: ${e}`); } finally { setIsLoading(false); } - return tx; + return undefined; }; const updateBondAmount = async (data: TUpdateBondArgs) => { @@ -225,6 +228,7 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen Console.error(e); setError(`an error occurred: ${e}`); } + return undefined; }; const migrateLegacyNode = async () => { @@ -243,8 +247,8 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen Console.error(e); setError(`an error occurred: ${e}`); } - setIsLoading(false); + return undefined; }; const memoizedValue = useMemo( diff --git a/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx index bf7eb7a648..09e11d37d5 100644 --- a/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/NodeSettings.tsx @@ -14,12 +14,9 @@ import { useBondingContext, BondingContextProvider } from 'src/context'; import { AppContext, urls } from 'src/context/main'; import { getIntervalAsDate } from 'src/utils'; -import { TBondedMixnode } from 'src/requests/mixnodeDetails'; import { NodeGeneralSettings } from './settings-pages/general-settings'; import { NodeUnbondPage } from './settings-pages/NodeUnbondPage'; import { NavItems, makeNavItems } from './node-settings.constant'; -import { ApyPlayground } from './apy-playground'; -import { NodeTestPage } from './node-test'; export const NodeSettings = () => { const theme = useTheme(); diff --git a/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx b/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx index 8daf8174ab..187ad2d473 100644 --- a/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx @@ -16,4 +16,4 @@ export const makeNavItems = (bondedNode: TBondedNode) => { // And these back in once fixed. // 'Playground' | 'Test my node' include in array at a later point -export type NavItems = 'General' | 'Unbond' ; +export type NavItems = 'General' | 'Unbond';