Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d5c402ff6 | |||
| 4fab7eac3f | |||
| ac77712cc0 | |||
| a400aa8928 | |||
| c001059af9 | |||
| fd8dc63c88 | |||
| d03c5b3650 | |||
| 69e97b3bbc | |||
| 15ca24b848 | |||
| fa551b6d9d | |||
| c6959d3e2d | |||
| 2569deb080 |
Generated
+3
-2
@@ -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
|
||||
}
|
||||
|
||||
@@ -315,6 +315,7 @@ impl Client {
|
||||
parse_response(res, true).await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::deprecated::DelegationEvent;
|
||||
use crate::error::TypesError;
|
||||
use crate::mixnode::NodeCostParams;
|
||||
use cosmwasm_std::Decimal;
|
||||
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId};
|
||||
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId, NodeRewarding};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -70,6 +70,14 @@ pub struct DelegationWithEverything {
|
||||
pub mixnode_is_unbonding: Option<bool>,
|
||||
}
|
||||
|
||||
pub struct NodeInformation {
|
||||
pub owner: String,
|
||||
pub mix_id: NodeId,
|
||||
pub node_identity: String,
|
||||
pub rewarding_details: NodeRewarding,
|
||||
pub is_unbonding: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
|
||||
@@ -7,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 {
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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' ;
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user