Compare commits

...

12 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacşu 5d5c402ff6 Apply 250GB/30 days to magura 2024-11-13 12:06:06 +02:00
Tommy Verrall 4fab7eac3f temporarily disable playground and test my node in the wallet
once we have time to fix these we will import these again
2024-11-13 10:56:19 +01:00
Jon Häggblad ac77712cc0 nym-credential-proxy-requests: reqwest use rustls-tls (#5116)
* nym-credential-proxy-requests: reqwest use rustls-tls

* nym-credential-proxy: reqwest default-features false
2024-11-11 17:38:21 +01:00
Jędrzej Stuczyński a400aa8928 bugfix: preserve as much as possible of the rewarded set during migration (#5103) 2024-11-08 09:33:30 +00:00
Jędrzej Stuczyński c001059af9 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
2024-11-06 09:17:44 +00:00
Jędrzej Stuczyński fd8dc63c88 fixed HistoricalUptimeUpdater (#5097) 2024-11-05 14:40:50 +00:00
Dinko Zdravac d03c5b3650 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
2024-11-05 15:36:16 +01:00
Bogdan-Ștefan Neacşu 69e97b3bbc Remove old use of 1GB constant (#5096)
* Remove old use of 1GB constant

* Fix clippy
2024-11-05 16:16:59 +02:00
Bogdan-Ștefan Neacşu 15ca24b848 Add more translations from v2 to v3 authenticator (#5091) 2024-11-05 15:30:00 +02:00
Fouad fa551b6d9d Nym node - Fix claim delegator rewards (#5090)
* update function param from mixId to nodeId

* fix claim operator rewards
2024-11-05 13:01:22 +00:00
Bogdan-Ștefan Neacşu c6959d3e2d Make 250 GB/30 days for free ride mode (#5083) 2024-11-05 11:14:43 +02:00
Jędrzej Stuczyński 2569deb080 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
2024-11-04 21:15:29 +00:00
43 changed files with 725 additions and 176 deletions
Generated
+3 -2
View File
@@ -5906,6 +5906,7 @@ dependencies = [
"nym-sphinx-addressing",
"nym-task",
"nym-types",
"nym-validator-client",
"nym-wireguard",
"nym-wireguard-types",
"rand",
@@ -5980,7 +5981,7 @@ dependencies = [
[[package]]
name = "nym-node-status-agent"
version = "0.1.4"
version = "0.1.5"
dependencies = [
"anyhow",
"clap 4.5.18",
@@ -5996,7 +5997,7 @@ dependencies = [
[[package]]
name = "nym-node-status-api"
version = "0.1.5"
version = "0.1.6"
dependencies = [
"anyhow",
"axum 0.7.7",
@@ -27,7 +27,7 @@ pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InitMessage {
@@ -98,6 +98,16 @@ impl TryFrom<v3::response::AuthenticatorResponse> for v2::response::Authenticato
}
}
impl From<v2::response::AuthenticatorResponse> for v3::response::AuthenticatorResponse {
fn from(value: v2::response::AuthenticatorResponse) -> Self {
Self {
protocol: value.protocol,
data: value.data.into(),
reply_to: value.reply_to,
}
}
}
impl TryFrom<v3::response::AuthenticatorResponseData> for v2::response::AuthenticatorResponseData {
type Error = crate::Error;
@@ -129,6 +139,22 @@ impl TryFrom<v3::response::AuthenticatorResponseData> for v2::response::Authenti
}
}
impl From<v2::response::AuthenticatorResponseData> for v3::response::AuthenticatorResponseData {
fn from(value: v2::response::AuthenticatorResponseData) -> Self {
match value {
v2::response::AuthenticatorResponseData::PendingRegistration(
pending_registration_response,
) => Self::PendingRegistration(pending_registration_response.into()),
v2::response::AuthenticatorResponseData::Registered(registered_response) => {
Self::Registered(registered_response.into())
}
v2::response::AuthenticatorResponseData::RemainingBandwidth(
remaining_bandwidth_response,
) => Self::RemainingBandwidth(remaining_bandwidth_response.into()),
}
}
}
impl From<v3::response::PendingRegistrationResponse> for v2::response::PendingRegistrationResponse {
fn from(value: v3::response::PendingRegistrationResponse) -> Self {
Self {
@@ -139,6 +165,16 @@ impl From<v3::response::PendingRegistrationResponse> for v2::response::PendingRe
}
}
impl From<v2::response::PendingRegistrationResponse> for v3::response::PendingRegistrationResponse {
fn from(value: v2::response::PendingRegistrationResponse) -> Self {
Self {
request_id: value.request_id,
reply_to: value.reply_to,
reply: value.reply.into(),
}
}
}
impl From<v3::response::RegisteredResponse> for v2::response::RegisteredResponse {
fn from(value: v3::response::RegisteredResponse) -> Self {
Self {
@@ -149,6 +185,16 @@ impl From<v3::response::RegisteredResponse> for v2::response::RegisteredResponse
}
}
impl From<v2::response::RegisteredResponse> for v3::response::RegisteredResponse {
fn from(value: v2::response::RegisteredResponse) -> Self {
Self {
request_id: value.request_id,
reply_to: value.reply_to,
reply: value.reply.into(),
}
}
}
impl From<v3::response::RemainingBandwidthResponse> for v2::response::RemainingBandwidthResponse {
fn from(value: v3::response::RemainingBandwidthResponse) -> Self {
Self {
@@ -159,6 +205,16 @@ impl From<v3::response::RemainingBandwidthResponse> for v2::response::RemainingB
}
}
impl From<v2::response::RemainingBandwidthResponse> for v3::response::RemainingBandwidthResponse {
fn from(value: v2::response::RemainingBandwidthResponse) -> Self {
Self {
request_id: value.request_id,
reply_to: value.reply_to,
reply: value.reply.map(Into::into),
}
}
}
impl From<v3::registration::RegistrationData> for v2::registration::RegistrationData {
fn from(value: v3::registration::RegistrationData) -> Self {
Self {
@@ -169,6 +225,16 @@ impl From<v3::registration::RegistrationData> for v2::registration::Registration
}
}
impl From<v2::registration::RegistrationData> for v3::registration::RegistrationData {
fn from(value: v2::registration::RegistrationData) -> Self {
Self {
nonce: value.nonce,
gateway_data: value.gateway_data.into(),
wg_port: value.wg_port,
}
}
}
impl From<v3::registration::RegistredData> for v2::registration::RegistredData {
fn from(value: v3::registration::RegistredData) -> Self {
Self {
@@ -179,6 +245,16 @@ impl From<v3::registration::RegistredData> for v2::registration::RegistredData {
}
}
impl From<v2::registration::RegistredData> for v3::registration::RegistredData {
fn from(value: v2::registration::RegistredData) -> Self {
Self {
pub_key: value.pub_key,
private_ip: value.private_ip,
wg_port: value.wg_port,
}
}
}
impl From<v3::registration::RemainingBandwidthData> for v2::registration::RemainingBandwidthData {
fn from(value: v3::registration::RemainingBandwidthData) -> Self {
Self {
@@ -186,3 +262,11 @@ impl From<v3::registration::RemainingBandwidthData> for v2::registration::Remain
}
}
}
impl From<v2::registration::RemainingBandwidthData> for v3::registration::RemainingBandwidthData {
fn from(value: v2::registration::RemainingBandwidthData) -> Self {
Self {
available_bandwidth: value.available_bandwidth,
}
}
}
@@ -27,7 +27,7 @@ pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
pub const BANDWIDTH_CAP_PER_DAY: u64 = 250 * 1024 * 1024 * 1024; // 250 GB
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct InitMessage {
@@ -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<UserAgent>) -> Self {
let nym_api = nym_api::Client::builder::<_, ValidatorClientError>(api_url)
.expect("invalid api url")
.with_user_agent(user_agent)
.with_user_agent(user_agent.into())
.build::<ValidatorClientError>()
.expect("failed to build nym api client");
@@ -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;
@@ -696,16 +696,32 @@ pub trait NymApiClientExt: ApiClient {
&self,
node_id: NodeId,
) -> Result<NodePerformanceResponse, NymAPIError> {
self.get_json_from(format!("/v1/nym-nodes/performance/{node_id}"))
.await
self.get_json(
&[
routes::API_VERSION,
"nym-nodes",
"performance",
&node_id.to_string(),
],
NO_PARAMS,
)
.await
}
async fn get_node_annotation(
&self,
node_id: NodeId,
) -> Result<AnnotationResponse, NymAPIError> {
self.get_json_from(format!("/v1/nym-nodes/annotation/{node_id}"))
.await
self.get_json(
&[
routes::API_VERSION,
"nym-nodes",
"annotation",
&node_id.to_string(),
],
NO_PARAMS,
)
.await
}
#[deprecated]
@@ -918,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,
@@ -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<NodeOwnershipResponse, NyxdError> {
async fn get_nymnode_details(&self, node_id: NodeId) -> Result<NodeDetailsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetNymNodeDetails { node_id })
.await
}
+1
View File
@@ -315,6 +315,7 @@ impl Client {
parse_response(res, true).await
}
#[instrument(level = "debug", skip_all)]
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
+9 -1
View File
@@ -3,7 +3,7 @@ use crate::deprecated::DelegationEvent;
use crate::error::TypesError;
use crate::mixnode::NodeCostParams;
use cosmwasm_std::Decimal;
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId};
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId, NodeRewarding};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -70,6 +70,14 @@ pub struct DelegationWithEverything {
pub mixnode_is_unbonding: Option<bool>,
}
pub struct NodeInformation {
pub owner: String,
pub mix_id: NodeId,
pub node_identity: String,
pub rewarding_details: NodeRewarding,
pub is_unbonding: bool,
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
+3 -3
View File
@@ -7,8 +7,8 @@ use defguard_wireguard_rs::{
WireguardInterfaceApi,
};
use futures::channel::oneshot;
use nym_authenticator_requests::{
latest::registration::RemainingBandwidthData, v1::registration::BANDWIDTH_CAP_PER_DAY,
use nym_authenticator_requests::latest::registration::{
RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY,
};
use nym_credential_verification::{
bandwidth_storage_manager::BandwidthStorageManager, BandwidthFlushingBehaviourConfig,
@@ -230,7 +230,7 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
// host information not updated yet
return Ok(None);
};
BANDWIDTH_CAP_PER_DAY.saturating_sub((peer.rx_bytes + peer.tx_bytes) as i64)
BANDWIDTH_CAP_PER_DAY.saturating_sub(peer.rx_bytes + peer.tx_bytes) as i64
};
Ok(Some(RemainingBandwidthData {
+3 -3
View File
@@ -6,7 +6,7 @@ use crate::peer_controller::PeerControlRequest;
use defguard_wireguard_rs::host::Peer;
use defguard_wireguard_rs::{host::Host, key::Key};
use futures::channel::oneshot;
use nym_authenticator_requests::v2::registration::BANDWIDTH_CAP_PER_DAY;
use nym_authenticator_requests::latest::registration::BANDWIDTH_CAP_PER_DAY;
use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager;
use nym_gateway_storage::models::WireguardPeer;
use nym_gateway_storage::Storage;
@@ -18,7 +18,7 @@ use tokio::sync::{mpsc, RwLock};
use tokio_stream::{wrappers::IntervalStream, StreamExt};
pub(crate) type SharedBandwidthStorageManager<St> = Arc<RwLock<BandwidthStorageManager<St>>>;
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24); // 24 hours
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 30); // 30 days
pub struct PeerHandle<St> {
storage: St,
@@ -98,7 +98,7 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
} else {
if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER {
log::debug!(
"Peer {} has been present for 24 hours, removing it",
"Peer {} has been present for 30 days, removing it",
self.public_key.to_string()
);
let success = self.remove_peer().await?;
+8 -17
View File
@@ -104,7 +104,10 @@ pub(crate) fn next_nymnode_id_counter(store: &mut dyn Storage) -> StdResult<Node
}
pub(crate) fn initialise_storage(storage: &mut dyn Storage) -> Result<(), MixnetContractError> {
ACTIVE_ROLES_BUCKET.save(storage, &RoleStorageBucket::default())?;
let active_bucket = RoleStorageBucket::default();
let inactive_bucket = active_bucket.other();
ACTIVE_ROLES_BUCKET.save(storage, &active_bucket)?;
let roles = vec![
Role::Layer1,
Role::Layer2,
@@ -114,24 +117,12 @@ pub(crate) fn initialise_storage(storage: &mut dyn Storage) -> Result<(), Mixnet
Role::Standby,
];
for role in roles {
ROLES.save(storage, (RoleStorageBucket::default() as u8, role), &vec![])?;
ROLES.save(
storage,
(RoleStorageBucket::default().other() as u8, role),
&vec![],
)?
ROLES.save(storage, (active_bucket as u8, role), &vec![])?;
ROLES.save(storage, (inactive_bucket as u8, role), &vec![])?
}
ROLES_METADATA.save(
storage,
RoleStorageBucket::default() as u8,
&Default::default(),
)?;
ROLES_METADATA.save(
storage,
RoleStorageBucket::default().other() as u8,
&Default::default(),
)?;
ROLES_METADATA.save(storage, active_bucket as u8, &Default::default())?;
ROLES_METADATA.save(storage, inactive_bucket as u8, &Default::default())?;
Ok(())
}
+119 -10
View File
@@ -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<NodeId>, Option<NodeId>), 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::<StdResult<Vec<_>>>()?;
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<Vec<NodeId>, 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::<Result<Vec<_>, _>>()?;
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<NodeId>, Option<NodeId>),
current_rewarded_set_mixnodes: Vec<NodeId>,
) -> 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(())
}
-1
View File
@@ -102,7 +102,6 @@ export const isLessThan = (a: number, b: number) => a < b;
*/
export const isBalanceEnough = (fee: string, tx: string = '0', balance: string = '0') => {
console.log('balance', balance, fee, tx);
try {
return Big(balance).gte(Big(fee).plus(Big(tx)));
} catch (e) {
+62
View File
@@ -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<IpAddr>,
}
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct NodeRefreshBody {
#[serde(with = "bs58_ed25519_pubkey")]
#[schemars(with = "String")]
pub node_identity: ed25519::PublicKey,
// a poor man's nonce
pub request_timestamp: i64,
#[schemars(with = "PlaceholderJsonSchemaImpl")]
pub signature: ed25519::Signature,
}
impl NodeRefreshBody {
pub fn plaintext(node_identity: ed25519::PublicKey, request_timestamp: i64) -> Vec<u8> {
node_identity
.to_bytes()
.into_iter()
.chain(request_timestamp.to_be_bytes())
.chain(b"describe-cache-refresh-request".iter().copied())
.collect()
}
pub fn new(private_key: &ed25519::PrivateKey) -> Self {
let node_identity = private_key.public_key();
let request_timestamp = OffsetDateTime::now_utc().unix_timestamp();
let signature = private_key.sign(Self::plaintext(node_identity, request_timestamp));
NodeRefreshBody {
node_identity,
request_timestamp,
signature,
}
}
pub fn verify_signature(&self) -> bool {
self.node_identity
.verify(
Self::plaintext(self.node_identity, self.request_timestamp),
&self.signature,
)
.is_ok()
}
pub fn is_stale(&self) -> bool {
let Ok(encoded) = OffsetDateTime::from_unix_timestamp(self.request_timestamp) else {
return true;
};
let now = OffsetDateTime::now_utc();
if encoded > now {
return true;
}
if (encoded + Duration::from_secs(30)) < now {
return true;
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
+1
View File
@@ -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()),
+49 -22
View File
@@ -9,9 +9,10 @@ use crate::support::config;
use crate::support::config::DEFAULT_NODE_DESCRIBE_BATCH_SIZE;
use async_trait::async_trait;
use futures::{stream, StreamExt};
use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer};
use nym_api_requests::models::{DescribedNodeType, NymNodeData, NymNodeDescription};
use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_mixnet_contract_common::{LegacyMixLayer, NodeId};
use nym_mixnet_contract_common::{LegacyMixLayer, NodeId, NymNodeDetails};
use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::MixnodeConversionError;
@@ -151,6 +152,10 @@ pub struct DescribedNodes {
}
impl DescribedNodes {
pub fn force_update(&mut self, node: NymNodeDescription) {
self.nodes.insert(node.node_id, node);
}
pub fn get_description(&self, node_id: &NodeId) -> Option<&NymNodeData> {
self.nodes.get(node_id).map(|n| &n.description)
}
@@ -292,7 +297,7 @@ async fn try_get_description(
}
#[derive(Debug)]
struct RefreshData {
pub(crate) struct RefreshData {
host: String,
node_id: NodeId,
node_type: DescribedNodeType,
@@ -300,6 +305,39 @@ struct RefreshData {
port: Option<u16>,
}
impl<'a> From<&'a LegacyMixNodeDetailsWithLayer> for RefreshData {
fn from(node: &'a LegacyMixNodeDetailsWithLayer) -> Self {
RefreshData::new(
&node.bond_information.mix_node.host,
DescribedNodeType::LegacyMixnode,
node.mix_id(),
Some(node.bond_information.mix_node.http_api_port),
)
}
}
impl<'a> From<&'a LegacyGatewayBondWithId> for RefreshData {
fn from(node: &'a LegacyGatewayBondWithId) -> Self {
RefreshData::new(
&node.bond.gateway.host,
DescribedNodeType::LegacyGateway,
node.node_id,
None,
)
}
}
impl<'a> From<&'a NymNodeDetails> for RefreshData {
fn from(node: &'a NymNodeDetails) -> Self {
RefreshData::new(
&node.bond_information.node.host,
DescribedNodeType::NymNode,
node.node_id(),
node.bond_information.node.custom_http_port,
)
}
}
impl RefreshData {
pub fn new(
host: impl Into<String>,
@@ -315,7 +353,11 @@ impl RefreshData {
}
}
async fn try_refresh(self) -> Option<NymNodeDescription> {
pub(crate) fn node_id(&self) -> NodeId {
self.node_id
}
pub(crate) async fn try_refresh(self) -> Option<NymNodeDescription> {
match try_get_description(self).await {
Ok(description) => Some(description),
Err(err) => {
@@ -341,18 +383,13 @@ impl CacheItemProvider for NodeDescriptionProvider {
// - legacy gateways (because they might already be running nym-nodes, but haven't updated contract info)
// - nym-nodes
let mut nodes_to_query = Vec::new();
let mut nodes_to_query: Vec<RefreshData> = Vec::new();
match self.contract_cache.all_cached_legacy_mixnodes().await {
None => error!("failed to obtain mixnodes information from the cache"),
Some(legacy_mixnodes) => {
for node in &**legacy_mixnodes {
nodes_to_query.push(RefreshData::new(
&node.bond_information.mix_node.host,
DescribedNodeType::LegacyMixnode,
node.mix_id(),
Some(node.bond_information.mix_node.http_api_port),
))
nodes_to_query.push(node.into())
}
}
}
@@ -361,12 +398,7 @@ impl CacheItemProvider for NodeDescriptionProvider {
None => error!("failed to obtain gateways information from the cache"),
Some(legacy_gateways) => {
for node in &**legacy_gateways {
nodes_to_query.push(RefreshData::new(
&node.bond.gateway.host,
DescribedNodeType::LegacyGateway,
node.node_id,
None,
))
nodes_to_query.push(node.into())
}
}
}
@@ -375,12 +407,7 @@ impl CacheItemProvider for NodeDescriptionProvider {
None => error!("failed to obtain nym-nodes information from the cache"),
Some(nym_nodes) => {
for node in &**nym_nodes {
nodes_to_query.push(RefreshData::new(
&node.bond_information.node.host,
DescribedNodeType::NymNode,
node.node_id(),
node.bond_information.node.custom_http_port,
))
nodes_to_query.push(node.into())
}
}
}
+14
View File
@@ -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<UninitialisedCache> for AxumErrorResponse {
+5 -11
View File
@@ -1,4 +1,4 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_status_api::models::{
@@ -9,7 +9,7 @@ use crate::storage::NymApiStorage;
use nym_task::{TaskClient, TaskManager};
use std::time::Duration;
use time::{OffsetDateTime, PrimitiveDateTime, Time};
use tokio::time::{interval, sleep};
use tokio::time::{interval_at, Instant};
use tracing::error;
use tracing::{info, trace, warn};
@@ -93,15 +93,8 @@ impl HistoricalUptimeUpdater {
"waiting until {update_datetime} to update the historical uptimes for the first time ({} seconds left)", time_left.as_secs()
);
tokio::select! {
biased;
_ = shutdown.recv() => {
trace!("UpdateHandler: Received shutdown");
}
_ = sleep(time_left) => {}
}
let mut interval = interval(ONE_DAY);
let start = Instant::now() + time_left;
let mut interval = interval_at(start, ONE_DAY);
while !shutdown.is_shutdown() {
tokio::select! {
biased;
@@ -109,6 +102,7 @@ impl HistoricalUptimeUpdater {
trace!("UpdateHandler: Received shutdown");
}
_ = interval.tick() => {
info!("updating historical uptimes of nodes");
// we don't want to have another select here; uptime update is relatively speedy
// and we don't want to exit while we're in the middle of database update
if let Err(err) = self.update_uptimes().await {
+44
View File
@@ -1,6 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node_describe_cache::RefreshData;
use crate::nym_contract_cache::cache::data::CachedContractsInfo;
use crate::support::caching::Cache;
use data::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<RefreshData> {
if !self.initialised() {
return None;
}
let inner = self.inner.read().await;
let encoded_identity = node_identity.to_base58_string();
// 1. check nymnodes
if let Some(nym_node) = inner
.nym_nodes
.iter()
.find(|n| n.bond_information.identity() == encoded_identity)
{
return Some(nym_node.into());
}
// 2. check legacy mixnodes
if let Some(mixnode) = inner
.legacy_mixnodes
.iter()
.find(|n| n.bond_information.identity() == encoded_identity)
{
return Some(mixnode.into());
}
// 3. check legacy gateways
if let Some(gateway) = inner
.legacy_gateways
.iter()
.find(|n| n.identity() == &encoded_identity)
{
return Some(gateway.into());
}
None
}
pub fn initialised(&self) -> bool {
self.initialised.load(Ordering::Relaxed)
}
+63 -4
View File
@@ -5,11 +5,11 @@ use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
use crate::support::http::helpers::{NodeIdParam, PaginationRequest};
use crate::support::http::state::AppState;
use axum::extract::{Path, Query, State};
use axum::routing::get;
use axum::routing::{get, post};
use axum::{Json, Router};
use nym_api_requests::models::{
AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NoiseDetails,
NymNodeDescription, PerformanceHistoryResponse, UptimeHistoryResponse,
AnnotationResponse, NodeDatePerformanceResponse, NodePerformanceResponse, NodeRefreshBody,
NoiseDetails, NymNodeDescription, PerformanceHistoryResponse, UptimeHistoryResponse,
};
use nym_api_requests::pagination::{PaginatedResponse, Pagination};
use nym_contracts_common::NaiveFloat;
@@ -17,7 +17,8 @@ use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::NymNodeDetails;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use time::Date;
use std::time::Duration;
use time::{Date, OffsetDateTime};
use utoipa::{IntoParams, ToSchema};
pub(crate) mod legacy;
@@ -25,6 +26,7 @@ pub(crate) mod unstable;
pub(crate) fn nym_node_routes() -> Router<AppState> {
Router::new()
.route("/refresh-described", post(refresh_described))
.route("/noise", get(nodes_noise))
.route("/bonded", get(get_bonded_nodes))
.route("/described", get(get_described_nodes))
@@ -42,6 +44,63 @@ pub(crate) fn nym_node_routes() -> Router<AppState> {
.route("/uptime-history/:node_id", get(get_node_uptime_history))
}
#[utoipa::path(
tag = "Nym Nodes",
post,
request_body = NodeRefreshBody,
path = "/refresh-described",
context_path = "/v1/nym-nodes",
)]
async fn refresh_described(
State(state): State<AppState>,
Json(request_body): Json<NodeRefreshBody>,
) -> AxumResult<Json<()>> {
let Some(refresh_data) = state
.nym_contract_cache()
.get_node_refresh_data(request_body.node_identity)
.await
else {
return Err(AxumErrorResponse::not_found(format!(
"node with identity {} does not seem to exist",
request_body.node_identity
)));
};
if !request_body.verify_signature() {
return Err(AxumErrorResponse::unauthorised("invalid request signature"));
}
if request_body.is_stale() {
return Err(AxumErrorResponse::bad_request("the request is stale"));
}
let node_id = refresh_data.node_id();
if let Some(last) = state.forced_refresh.last_refreshed(node_id).await {
// max 1 refresh a minute
let minute_ago = OffsetDateTime::now_utc() - Duration::from_secs(60);
if last > minute_ago {
return Err(AxumErrorResponse::too_many(
"already refreshed node in the last minute",
));
}
}
// to make sure you can't ddos the endpoint while a request is in progress
state.forced_refresh.set_last_refreshed(node_id).await;
if let Some(updated_data) = refresh_data.try_refresh().await {
let Ok(mut describe_cache) = state.described_nodes_cache.write().await else {
return Err(AxumErrorResponse::service_unavailable());
};
describe_cache.get_mut().force_update(updated_data)
} else {
return Err(AxumErrorResponse::unprocessable_entity(
"failed to refresh node description",
));
}
Ok(Json(()))
}
#[utoipa::path(
tag = "Nym Nodes",
get,
+12 -1
View File
@@ -6,7 +6,7 @@ use std::sync::Arc;
use std::time::Duration;
use thiserror::Error;
use time::OffsetDateTime;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::sync::{RwLock, RwLockMappedWriteGuard, RwLockReadGuard, RwLockWriteGuard};
#[derive(Debug, Error)]
#[error("the cache item has not been initialised")]
@@ -45,6 +45,13 @@ impl<T> SharedCache<T> {
RwLockReadGuard::try_map(guard, |a| a.inner.as_ref()).map_err(|_| UninitialisedCache)
}
pub(crate) async fn write(
&self,
) -> Result<RwLockMappedWriteGuard<'_, Cache<T>>, UninitialisedCache> {
let guard = self.0.write().await;
RwLockWriteGuard::try_map(guard, |a| a.inner.as_mut()).map_err(|_| UninitialisedCache)
}
// ignores expiration data
#[allow(dead_code)]
pub(crate) async fn unchecked_get_inner(
@@ -134,6 +141,10 @@ impl<T> Cache<T> {
self.as_at = OffsetDateTime::now_utc()
}
pub(crate) fn get_mut(&mut self) -> &mut T {
&mut self.value
}
#[allow(dead_code)]
pub fn has_expired(&self, ttl: Duration, now: Option<OffsetDateTime>) -> bool {
let now = now.unwrap_or(OffsetDateTime::now_utc());
+1
View File
@@ -188,6 +188,7 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
};
let router = router.with_state(AppState {
forced_refresh: Default::default(),
nym_contract_cache: nym_contract_cache_state.clone(),
node_status_cache: node_status_cache_state.clone(),
circulating_supply_cache: circulating_supply_cache.clone(),
+1
View File
@@ -20,6 +20,7 @@ use utoipauto::utoipauto;
models::CirculatingSupplyResponse,
models::CoinSchema,
nym_mixnet_contract_common::Interval,
nym_api_requests::models::NodeRefreshBody,
nym_api_requests::models::GatewayStatusReportResponse,
nym_api_requests::models::GatewayUptimeHistoryResponse,
nym_api_requests::models::GatewayCoreStatusResponse,
+22 -1
View File
@@ -15,7 +15,9 @@ use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeA
use nym_mixnet_contract_common::NodeId;
use nym_task::TaskManager;
use std::collections::HashMap;
use tokio::sync::RwLockReadGuard;
use std::sync::Arc;
use time::OffsetDateTime;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
@@ -70,6 +72,7 @@ type AxumJoinHandle = JoinHandle<std::io::Result<()>>;
#[derive(Clone)]
pub(crate) struct AppState {
pub(crate) forced_refresh: ForcedRefresh,
pub(crate) nym_contract_cache: NymContractCache,
pub(crate) node_status_cache: NodeStatusCache,
pub(crate) circulating_supply_cache: CirculatingSupplyCache,
@@ -79,6 +82,24 @@ pub(crate) struct AppState {
pub(crate) node_info_cache: unstable::NodeInfoCache,
}
#[derive(Clone, Default)]
pub(crate) struct ForcedRefresh {
pub(crate) refreshes: Arc<RwLock<HashMap<NodeId, OffsetDateTime>>>,
}
impl ForcedRefresh {
pub(crate) async fn last_refreshed(&self, node_id: NodeId) -> Option<OffsetDateTime> {
self.refreshes.read().await.get(&node_id).copied()
}
pub(crate) async fn set_last_refreshed(&self, node_id: NodeId) {
self.refreshes
.write()
.await
.insert(node_id, OffsetDateTime::now_utc());
}
}
impl AppState {
pub(crate) fn nym_contract_cache(&self) -> &NymContractCache {
&self.nym_contract_cache
+4 -3
View File
@@ -983,7 +983,8 @@ impl StorageManager {
since: i64,
until: i64,
) -> Result<Vec<ActiveGateway>, sqlx::Error> {
sqlx::query_as(
sqlx::query_as!(
ActiveGateway,
r#"
SELECT DISTINCT identity, node_id as "node_id: NodeId", id
FROM gateway_details
@@ -993,9 +994,9 @@ impl StorageManager {
SELECT 1 FROM gateway_status WHERE timestamp > ? AND timestamp < ?
)
"#,
since,
until
)
.bind(since)
.bind(until)
.fetch_all(&self.connection_pool)
.await
}
+1 -1
View File
@@ -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"
@@ -18,7 +18,7 @@ serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
time = { workspace = true, features = ["serde", "formatting", "parsing"] }
tsify = { workspace = true, optional = true }
reqwest = { workspace = true, features = ["json"] }
reqwest = { workspace = true, features = ["json", "rustls-tls"] }
wasm-bindgen = { workspace = true, optional = true }
## openapi:
+1 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-node-status-agent"
version = "0.1.4"
version = "0.1.5"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+25 -12
View File
@@ -1,3 +1,4 @@
use anyhow::bail;
use clap::{Parser, Subcommand};
use nym_bin_common::bin_info;
use nym_common_models::ns_api::TestrunAssignment;
@@ -51,11 +52,13 @@ impl Args {
let version = probe.version().await;
tracing::info!("Probe version:\n{}", version);
let testrun = request_testrun(&server_address).await?;
if let Some(testrun) = request_testrun(&server_address).await? {
let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key));
let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key));
submit_results(&server_address, testrun.testrun_id, log).await?;
submit_results(&server_address, testrun.testrun_id, log).await?;
} else {
tracing::info!("No testruns available, exiting")
}
Ok(())
}
@@ -64,16 +67,26 @@ impl Args {
const URL_BASE: &str = "internal/testruns";
#[instrument(level = "debug", skip_all)]
async fn request_testrun(server_addr: &str) -> anyhow::Result<TestrunAssignment> {
async fn request_testrun(server_addr: &str) -> anyhow::Result<Option<TestrunAssignment>> {
let target_url = format!("{}/{}", server_addr, URL_BASE);
let client = reqwest::Client::new();
let res = client
.get(target_url)
.send()
.await
.and_then(|response| response.error_for_status())?;
res.json()
.await
let res = client.get(target_url).send().await?;
let status = res.status();
let response_text = res.text().await?;
if status.is_client_error() {
bail!("{}: {}", status, response_text);
} else if status.is_server_error() {
if matches!(status, reqwest::StatusCode::SERVICE_UNAVAILABLE)
&& response_text.contains("No testruns available")
{
return Ok(None);
} else {
bail!("{}: {}", status, response_text);
}
}
serde_json::from_str(&response_text)
.map(|testrun| {
tracing::info!("Received testrun assignment: {:?}", testrun);
testrun
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node-status-api"
version = "0.1.5"
version = "0.1.6"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+2 -8
View File
@@ -1,10 +1,7 @@
use anyhow::anyhow;
use axum::{response::Redirect, Router};
use tokio::net::ToSocketAddrs;
use tower_http::{
cors::CorsLayer,
trace::{DefaultOnResponse, TraceLayer},
};
use tower_http::{cors::CorsLayer, trace::TraceLayer};
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
@@ -61,10 +58,7 @@ impl RouterBuilder {
// CORS layer needs to wrap other API layers
.layer(setup_cors())
// logger should be outermost layer
.layer(
TraceLayer::new_for_http()
.on_response(DefaultOnResponse::new().level(tracing::Level::DEBUG)),
)
.layer(TraceLayer::new_for_http())
}
}
+2 -1
View File
@@ -49,7 +49,8 @@ async fn request_testrun(State(state): State<AppState>) -> HttpResult<Json<Testr
);
Ok(Json(testrun))
} else {
Err(HttpError::no_available_testruns())
tracing::debug!("No testruns available for agent");
Err(HttpError::no_testruns_available())
}
}
Err(err) => Err(HttpError::internal_with_logging(err)),
+2 -2
View File
@@ -27,9 +27,9 @@ impl HttpError {
}
}
pub(crate) fn no_available_testruns() -> Self {
pub(crate) fn no_testruns_available() -> Self {
Self {
message: serde_json::json!({"message": "No available testruns"}).to_string(),
message: serde_json::json!({"message": "No testruns available"}).to_string(),
status: axum::http::StatusCode::SERVICE_UNAVAILABLE,
}
}
+1
View File
@@ -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 }
+40 -1
View File
@@ -32,13 +32,18 @@ use nym_node_http_api::{NymNodeHTTPServer, NymNodeRouter};
use nym_sphinx_acknowledgements::AckKey;
use nym_sphinx_addressing::Recipient;
use nym_task::{TaskClient, TaskManager};
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::models::NodeRefreshBody;
use nym_validator_client::NymApiClient;
use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData};
use rand::rngs::OsRng;
use rand::{CryptoRng, RngCore};
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc;
use tracing::{debug, error, info, trace};
use tokio::time::timeout;
use tracing::{debug, error, info, trace, warn};
use zeroize::Zeroizing;
use self::helpers::load_x25519_wireguard_keypair;
@@ -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"))?;
@@ -7,7 +7,9 @@ use crate::vesting::delegate::vesting_undelegate_from_mixnode;
use nym_mixnet_contract_common::mixnode::MixStakeSaturationResponse;
use nym_mixnet_contract_common::NodeId;
use nym_types::currency::DecCoin;
use nym_types::delegation::{Delegation, DelegationWithEverything, DelegationsSummaryResponse};
use nym_types::delegation::{
Delegation, DelegationWithEverything, DelegationsSummaryResponse, NodeInformation,
};
use nym_types::deprecated::{
convert_to_delegation_events, DelegationEvent, WrappedDelegationEvent,
};
@@ -19,6 +21,7 @@ use nym_validator_client::nyxd::contract_traits::{
MixnetQueryClient, MixnetSigningClient, NymContractsProvider, PagedMixnetQueryClient,
};
use nym_validator_client::nyxd::Fee;
use nym_validator_client::DirectSigningHttpRpcValidatorClient;
use tap::TapFallible;
#[tauri::command]
@@ -141,6 +144,58 @@ pub async fn undelegate_all_from_mixnode(
Ok(res)
}
pub(crate) async fn get_node_information(
client: &DirectSigningHttpRpcValidatorClient,
node_id: NodeId,
error_strings: &mut Vec<String>,
) -> Result<Option<NodeInformation>, BackendError> {
let native_nymnode = client
.nyxd
.get_nymnode_details(node_id)
.await
.inspect_err(|err| {
let str_err =
format!("Failed to get nymnode details for node_id = {node_id}. Error: {err}");
log::error!(" <<< {str_err}",);
error_strings.push(str_err);
})?;
if let Some(native) = native_nymnode.details {
return Ok(Some(NodeInformation {
is_unbonding: native.is_unbonding(),
owner: native.bond_information.owner.to_string(),
mix_id: node_id,
node_identity: native.bond_information.node.identity_key,
rewarding_details: native.rewarding_details,
}));
}
let legacy_mixnode = client
.nyxd
.get_mixnode_details(node_id)
.await
.inspect_err(|err| {
let str_err = format!(
"Failed to get legacy mixnode details for node_id = {node_id}. Error: {err}",
);
log::error!(" <<< {}", str_err);
error_strings.push(str_err);
})?
.mixnode_details;
if let Some(legacy) = legacy_mixnode {
return Ok(Some(NodeInformation {
is_unbonding: legacy.is_unbonding(),
owner: legacy.bond_information.owner.to_string(),
mix_id: node_id,
node_identity: legacy.bond_information.mix_node.identity_key,
rewarding_details: legacy.rewarding_details,
}));
}
Ok(None)
}
// TODO: fix later (yeah...)
#[allow(deprecated)]
#[tauri::command]
@@ -208,21 +263,9 @@ pub async fn get_all_mix_delegations(
d.amount
);
let mixnode = client
.nyxd
.get_mixnode_details(d.mix_id)
.await
.tap_err(|err| {
let str_err = format!(
"Failed to get mixnode details for mix_id = {}. Error: {}",
d.mix_id, err
);
log::error!(" <<< {}", str_err);
error_strings.push(str_err);
})?
.mixnode_details;
let node_details = get_node_information(client, d.mix_id, &mut error_strings).await?;
let accumulated_by_operator = mixnode
let accumulated_by_operator = node_details
.as_ref()
.map(|m| {
guard.display_coin_from_base_decimal(&base_mix_denom, m.rewarding_details.operator)
@@ -238,10 +281,13 @@ pub async fn get_all_mix_delegations(
})
.unwrap_or_default();
let accumulated_by_delegates = mixnode
let accumulated_by_delegates = node_details
.as_ref()
.map(|m| {
guard.display_coin_from_base_decimal(&base_mix_denom, m.rewarding_details.delegates)
reg.attempt_create_display_coin_from_base_dec_amount(
&base_mix_denom,
m.rewarding_details.delegates,
)
})
.transpose()
.tap_err(|err| {
@@ -254,7 +300,7 @@ pub async fn get_all_mix_delegations(
})
.unwrap_or_default();
let cost_params = mixnode
let cost_params = node_details
.as_ref()
.map(|m| {
NodeCostParams::from_mixnet_contract_mixnode_cost_params(
@@ -335,21 +381,26 @@ pub async fn get_all_mix_delegations(
" >>> Get average uptime percentage: mix_iid = {}",
d.mix_id
);
let avg_uptime_percent = client
let current_performance = client
.nym_api
.get_mixnode_avg_uptime(d.mix_id)
.get_current_node_performance(d.mix_id)
.await
.tap_err(|err| {
.inspect_err(|err| {
let str_err = format!(
"Failed to get average uptime percentage for mix_id = {}. Error: {}",
d.mix_id, err
"Failed to get current node performance for node_id = {}. Error: {err}",
d.mix_id
);
log::error!(" <<< {}", str_err);
error_strings.push(str_err);
})
.ok()
.map(|r| r.avg_uptime);
log::trace!(" <<< {:?}", avg_uptime_percent);
.and_then(|r| r.performance);
// convert to old u8
let current_uptime = current_performance.map(|p| (p * 100.) as u8);
log::trace!(" <<< {:?}", current_uptime);
log::trace!(
" >>> Convert delegated on block height to timestamp: block_height = {}",
@@ -377,9 +428,9 @@ pub async fn get_all_mix_delegations(
pending_events.len()
);
let mixnode_is_unbonding = mixnode.as_ref().map(|m| m.is_unbonding());
let mixnode_is_unbonding = node_details.as_ref().map(|m| m.is_unbonding);
log::trace!(
" >>> mixnode with mix_id: {} is unbonding: {:?}",
" >>> node with mix_id: {} is unbonding: {:?}",
d.mix_id,
mixnode_is_unbonding
);
@@ -387,16 +438,14 @@ pub async fn get_all_mix_delegations(
with_everything.push(DelegationWithEverything {
owner: d.owner,
mix_id: d.mix_id,
node_identity: mixnode
.map(|m| m.bond_information.mix_node.identity_key)
.unwrap_or_default(),
node_identity: node_details.map(|m| m.node_identity).unwrap_or_default(),
amount: d.amount,
block_height: d.height,
uses_vesting_contract_tokens,
delegated_on_iso_datetime,
stake_saturation: stake_saturation.uncapped_saturation,
accumulated_by_operator,
avg_uptime_percent,
avg_uptime_percent: current_uptime,
accumulated_by_delegates,
cost_params,
unclaimed_rewards: accumulated_rewards,
@@ -4,11 +4,10 @@ import { ModalListItem } from 'src/components/Modals/ModalListItem';
import { SimpleModal } from 'src/components/Modals/SimpleModal';
import { ModalFee } from 'src/components/Modals/ModalFee';
import { useGetFee } from 'src/hooks/useGetFee';
import { simulateClaimOperatorReward, simulateVestingClaimOperatorReward } from 'src/requests';
import { AppContext } from 'src/context';
import { BalanceWarning } from 'src/components/FeeWarning';
import { Box } from '@mui/material';
import { TBondedMixnode } from 'src/requests/mixnodeDetails';
import { TBondedNymNode } from 'src/requests/nymNodeDetails';
export const RedeemRewardsModal = ({
node,
@@ -16,23 +15,18 @@ export const RedeemRewardsModal = ({
onError,
onClose,
}: {
node: TBondedMixnode;
node: TBondedNymNode;
onConfirm: (fee?: FeeDetails) => Promise<void>;
onError: (err: string) => void;
onClose: () => void;
}) => {
const { fee, getFee, isFeeLoading, feeError } = useGetFee();
const { fee, isFeeLoading, feeError } = useGetFee();
const { userBalance } = useContext(AppContext);
useEffect(() => {
if (feeError) onError(feeError);
}, [feeError]);
useEffect(() => {
if (node.proxy) getFee(simulateVestingClaimOperatorReward, {});
else getFee(simulateClaimOperatorReward, {});
}, []);
const handleOnOK = async () => onConfirm(fee);
return (
+1 -1
View File
@@ -292,7 +292,7 @@ export const Bonding = () => {
/>
)}
{showModal === 'redeem' && bondedNode && isMixnode(bondedNode) && (
{showModal === 'redeem' && bondedNode && isNymNode(bondedNode) && (
<RedeemRewardsModal
node={bondedNode}
onClose={() => setShowModal(undefined)}
@@ -39,9 +39,9 @@ export const NodeSettings = () => {
if (location.state === 'unbond') {
setValue('Unbond');
}
if (location.state === 'test-node') {
setValue('Test my node');
}
// if (location.state === 'test-node') {
// setValue('Test my node');
// }
}, [location]);
const handleUnbond = async (fee?: FeeDetails) => {
@@ -129,11 +129,11 @@ export const NodeSettings = () => {
>
<Divider />
{value === 'General' && bondedNode && <NodeGeneralSettings bondedNode={bondedNode} />}
{value === 'Test my node' && <NodeTestPage />}
{/* {value === 'Test my node' && <NodeTestPage />} */}
{value === 'Unbond' && bondedNode && (
<NodeUnbondPage bondedNode={bondedNode} onConfirm={handleUnbond} onError={handleError} />
)}
{value === 'Playground' && bondedNode && <ApyPlayground bondedNode={bondedNode as TBondedMixnode} />}
{/* {value === 'Playground' && bondedNode && <ApyPlayground bondedNode={bondedNode as TBondedMixnode} />} */}
{confirmationDetails && confirmationDetails.status === 'success' && (
<ConfirmationDetailsModal
title={confirmationDetails.title}
@@ -5,11 +5,15 @@ export const makeNavItems = (bondedNode: TBondedNode) => {
const navItems: NavItems[] = ['Unbond'];
if (isNymNode(bondedNode)) {
// add these items to the beginning of the array "General", "Test my node", "Playground"
navItems.unshift('General', 'Test my node', 'Playground');
// Add these items to the beginning of the array "General", "Test my node", "Playground"
// Temporarily removed , 'Test my node due to wasm issues which we need to fix
// 'Playground' due to freezing issues
navItems.unshift('General');
}
return navItems;
};
export type NavItems = 'General' | 'Unbond' | 'Test my node' | 'Playground';
// And these back in once fixed.
// 'Playground' | 'Test my node' include in array at a later point
export type NavItems = 'General' | 'Unbond' ;
+1 -1
View File
@@ -7,7 +7,7 @@ import {
NodeConfigUpdate,
GatewayConfigUpdate,
} from '@nymproject/types';
import { TBondGatewayArgs, TBondGatewaySignatureArgs, TNodeConfigUpdateArgs } from '../types';
import { TBondGatewayArgs, TBondGatewaySignatureArgs } from '../types';
import { invokeWrapper } from './wrapper';
export const bondGateway = async (args: TBondGatewayArgs) =>
+1 -1
View File
@@ -6,7 +6,7 @@ export const claimOperatorReward = async (fee?: Fee) =>
export const claimDelegatorRewards = async (mixId: number, fee?: FeeDetails) =>
invokeWrapper<TransactionExecuteResult[]>('claim_locked_and_unlocked_delegator_reward', {
mixId,
nodeId: mixId,
fee: fee?.fee,
});