Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c48fa6b8c | |||
| 689cb3a9c2 | |||
| 60870fc458 | |||
| b44997f412 | |||
| 34aaa60a44 | |||
| dfa4563d22 | |||
| f30cc7a8a1 | |||
| 4f08161315 | |||
| 23d4c75597 | |||
| c24ca29212 | |||
| 6cff49caf6 | |||
| ea31aa6bcf | |||
| 410683dae6 | |||
| 043fb31c09 | |||
| e36573463c | |||
| c79471e9ad | |||
| 1cd8713d26 | |||
| edb797da68 | |||
| 72e1290d8f | |||
| 905d8ed7de | |||
| 4e5ccf7926 | |||
| 0a8eb940bb | |||
| 34fb67602c | |||
| 766ae8dd8e | |||
| bd1fd73ba0 |
Generated
+127
-257
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -57,7 +57,7 @@ members = [
|
||||
"common/gateway-requests",
|
||||
"common/gateway-stats-storage",
|
||||
"common/gateway-storage",
|
||||
"common/http-api-client",
|
||||
"common/http-api-client", "common/http-api-client-macro",
|
||||
"common/http-api-common",
|
||||
"common/inclusion-probability",
|
||||
"common/ip-packet-requests",
|
||||
@@ -95,7 +95,7 @@ members = [
|
||||
"common/ticketbooks-merkle",
|
||||
"common/topology",
|
||||
"common/tun",
|
||||
"common/types", "common/upgrade-mode-check",
|
||||
"common/types",
|
||||
"common/verloc",
|
||||
"common/wasm/client-core",
|
||||
"common/wasm/storage",
|
||||
@@ -122,7 +122,7 @@ members = [
|
||||
"nym-node-status-api/nym-node-status-client",
|
||||
"nym-node/nym-node-metrics",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-outfox", "nym-signers-monitor",
|
||||
"nym-outfox",
|
||||
"nym-statistics-api",
|
||||
"nym-validator-rewarder",
|
||||
"nyx-chain-watcher",
|
||||
@@ -271,10 +271,10 @@ hyper = "1.6.0"
|
||||
hyper-util = "0.1"
|
||||
indicatif = "0.18.0"
|
||||
inquire = "0.6.2"
|
||||
inventory = "0.3.21"
|
||||
ip_network = "0.4.1"
|
||||
ipnetwork = "0.20"
|
||||
itertools = "0.14.0"
|
||||
jwt-simple = { version = "0.12.12", default-features = false, features = ["pure-rust"] }
|
||||
k256 = "0.13"
|
||||
lazy_static = "1.5.0"
|
||||
ledger-transport = "0.10.0"
|
||||
@@ -318,6 +318,7 @@ serde_json_path = "0.7.2"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
serde_plain = "1.0.2"
|
||||
sha2 = "0.10.9"
|
||||
si-scale = "0.2.3"
|
||||
snow = "0.9.6"
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::error::BandwidthControllerError;
|
||||
use crate::utils::{
|
||||
get_aggregate_verification_key, get_coin_index_signatures, get_expiration_date_signatures,
|
||||
};
|
||||
use log::info;
|
||||
use log::{error, info};
|
||||
use nym_credential_storage::storage::Storage;
|
||||
use nym_credentials::ecash::bandwidth::IssuanceTicketBook;
|
||||
use nym_credentials::ecash::utils::obtain_aggregate_wallet;
|
||||
@@ -42,7 +42,8 @@ where
|
||||
deposit_amount.into(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.inspect_err(|err| error!("ticketbook deposit fail with: {err}"))?;
|
||||
|
||||
let deposit_id = result.parse_singleton_u32_contract_data()?;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use nym_credentials_interface::{
|
||||
};
|
||||
use nym_ecash_time::Date;
|
||||
use nym_validator_client::coconut::all_ecash_api_clients;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::prelude::SliceRandom;
|
||||
|
||||
@@ -53,6 +53,7 @@ nym-client-core-config-types = { path = "./config-types", features = [
|
||||
nym-client-core-surb-storage = { path = "./surb-storage" }
|
||||
nym-client-core-gateways-storage = { path = "./gateways-storage" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
|
||||
|
||||
@@ -57,7 +57,7 @@ use nym_task::{TaskClient, TaskHandle};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::HardcodedTopologyProvider;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, NymApiClient, UserAgent};
|
||||
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, UserAgent};
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::thread_rng;
|
||||
@@ -566,7 +566,7 @@ where
|
||||
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
config_topology: config::Topology,
|
||||
nym_api_urls: Vec<Url>,
|
||||
nym_api_client: NymApiClient,
|
||||
nym_api_client: nym_http_api_client::Client,
|
||||
) -> Box<dyn TopologyProvider + Send + Sync> {
|
||||
// if no custom provider was ... provided ..., create one using nym-api
|
||||
custom_provider.unwrap_or_else(|| {
|
||||
@@ -749,21 +749,31 @@ where
|
||||
setup_gateway(setup_method, key_store, details_store).await
|
||||
}
|
||||
|
||||
fn construct_nym_api_client(config: &Config, user_agent: Option<UserAgent>) -> NymApiClient {
|
||||
fn construct_nym_api_client(
|
||||
config: &Config,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Result<nym_http_api_client::Client, ClientCoreError> {
|
||||
let mut nym_api_urls = config.get_nym_api_endpoints();
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
let mut builder = nym_http_api_client::Client::builder(nym_api_urls[0].clone())
|
||||
.map_err(|source| ClientCoreError::NymApiQueryFailure { source })?;
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
NymApiClient::new_with_user_agent(nym_api_urls[0].clone(), user_agent)
|
||||
} else {
|
||||
NymApiClient::new(nym_api_urls[0].clone())
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
builder = builder.with_bincode();
|
||||
|
||||
builder
|
||||
.build()
|
||||
.map_err(|source| ClientCoreError::NymApiQueryFailure { source })
|
||||
}
|
||||
|
||||
async fn determine_key_rotation_state(
|
||||
client: &NymApiClient,
|
||||
client: &nym_http_api_client::Client,
|
||||
) -> Result<KeyRotationConfig, ClientCoreError> {
|
||||
Ok(client.nym_api.get_key_rotation_info().await?.into())
|
||||
Ok(client.get_key_rotation_info().await?.into())
|
||||
}
|
||||
|
||||
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
|
||||
@@ -830,7 +840,7 @@ where
|
||||
.dkg_query_client
|
||||
.map(|client| BandwidthController::new(credential_store, client));
|
||||
|
||||
let nym_api_client = Self::construct_nym_api_client(&self.config, self.user_agent.clone());
|
||||
let nym_api_client = Self::construct_nym_api_client(&self.config, self.user_agent.clone())?;
|
||||
let key_rotation_config = Self::determine_key_rotation_state(&nym_api_client).await?;
|
||||
|
||||
let topology_provider = Self::setup_topology_provider(
|
||||
|
||||
@@ -175,7 +175,6 @@ impl MixTrafficController {
|
||||
},
|
||||
None => {
|
||||
tracing::trace!("MixTrafficController, client request channel closed");
|
||||
break
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::helpers::get_time_now;
|
||||
use crate::client::replies::{
|
||||
reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys,
|
||||
};
|
||||
@@ -23,7 +22,7 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C
|
||||
use nym_task::TaskClient;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::*;
|
||||
|
||||
// The interval at which we check for stale buffers
|
||||
@@ -55,7 +54,7 @@ struct ReceivedMessagesBufferInner<R: MessageReceiver> {
|
||||
stats_tx: ClientStatsSender,
|
||||
|
||||
// Periodically check for stale buffers to clean up
|
||||
last_stale_check: crate::client::helpers::Instant,
|
||||
last_stale_check: Instant,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
@@ -155,7 +154,7 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
}
|
||||
|
||||
fn cleanup_stale_buffers(&mut self) {
|
||||
let now = get_time_now();
|
||||
let now = Instant::now();
|
||||
if now - self.last_stale_check > STALE_BUFFER_CHECK_INTERVAL {
|
||||
self.last_stale_check = now;
|
||||
self.message_receiver
|
||||
@@ -191,7 +190,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
message_sender: None,
|
||||
recently_reconstructed: HashSet::new(),
|
||||
stats_tx,
|
||||
last_stale_check: get_time_now(),
|
||||
last_stale_check: Instant::now(),
|
||||
})),
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
use nym_topology::provider_trait::{ToTopologyMetadata, TopologyProvider};
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::cmp::min;
|
||||
@@ -39,30 +41,43 @@ impl Config {
|
||||
pub struct NymApiTopologyProvider {
|
||||
config: Config,
|
||||
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
validator_client: nym_http_api_client::Client,
|
||||
nym_api_urls: Vec<Url>,
|
||||
currently_used_api: usize,
|
||||
use_bincode: bool,
|
||||
}
|
||||
|
||||
impl NymApiTopologyProvider {
|
||||
pub fn new(
|
||||
config: impl Into<Config>,
|
||||
mut nym_api_urls: Vec<Url>,
|
||||
mut validator_client: nym_validator_client::client::NymApiClient,
|
||||
validator_client: nym_http_api_client::Client,
|
||||
) -> Self {
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
validator_client.change_nym_api(nym_api_urls[0].clone());
|
||||
|
||||
NymApiTopologyProvider {
|
||||
let mut provider = NymApiTopologyProvider {
|
||||
config: config.into(),
|
||||
validator_client,
|
||||
nym_api_urls,
|
||||
currently_used_api: 0,
|
||||
}
|
||||
use_bincode: true,
|
||||
};
|
||||
// Set all API URLs - the client will try them in order with automatic failover
|
||||
provider.validator_client.change_base_urls(
|
||||
provider
|
||||
.nym_api_urls
|
||||
.iter()
|
||||
.map(|u| u.clone().into())
|
||||
.collect(),
|
||||
);
|
||||
provider
|
||||
}
|
||||
|
||||
pub fn disable_bincode(&mut self) {
|
||||
self.validator_client.use_bincode = false;
|
||||
self.use_bincode = false;
|
||||
// Note: The unified client doesn't support toggling bincode after creation.
|
||||
// This would require recreating the client without bincode.
|
||||
// For now, we'll track the preference but it won't take effect.
|
||||
warn!("Disabling bincode on existing client is not currently supported");
|
||||
}
|
||||
|
||||
fn use_next_nym_api(&mut self) {
|
||||
@@ -72,8 +87,19 @@ impl NymApiTopologyProvider {
|
||||
}
|
||||
|
||||
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
|
||||
self.validator_client
|
||||
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
|
||||
|
||||
// Provide all URLs starting from the next one in rotation order
|
||||
// This enables automatic failover to other endpoints
|
||||
let rotated_urls: Vec<_> = self
|
||||
.nym_api_urls
|
||||
.iter()
|
||||
.cycle()
|
||||
.skip(self.currently_used_api)
|
||||
.take(self.nym_api_urls.len())
|
||||
.map(|u| u.clone().into())
|
||||
.collect();
|
||||
|
||||
self.validator_client.change_base_urls(rotated_urls)
|
||||
}
|
||||
|
||||
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
|
||||
@@ -99,8 +125,13 @@ impl NymApiTopologyProvider {
|
||||
.filter(|n| n.performance.round_to_integer() >= self.config.min_node_performance())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
|
||||
.with_skimmed_nodes(&nodes_filtered)
|
||||
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
|
||||
NymTopology::new(
|
||||
metadata.to_topology_metadata(),
|
||||
epoch_rewarded_set,
|
||||
Vec::new(),
|
||||
)
|
||||
.with_skimmed_nodes(&nodes_filtered)
|
||||
} else {
|
||||
// if we're not using extended topology, we're only getting active set mixnodes and gateways
|
||||
|
||||
@@ -148,8 +179,13 @@ impl NymApiTopologyProvider {
|
||||
}
|
||||
}
|
||||
|
||||
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
|
||||
.with_skimmed_nodes(&nodes)
|
||||
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
|
||||
NymTopology::new(
|
||||
metadata.to_topology_metadata(),
|
||||
epoch_rewarded_set,
|
||||
Vec::new(),
|
||||
)
|
||||
.with_skimmed_nodes(&nodes)
|
||||
};
|
||||
|
||||
if !topology.is_minimally_routable() {
|
||||
|
||||
@@ -7,7 +7,8 @@ use futures::{SinkExt, StreamExt};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKeyRef;
|
||||
use nym_validator_client::client::{IdentityKeyRef, NymApiClientExt};
|
||||
use nym_validator_client::nym_nodes::SkimmedNodesWithMetadata;
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
#[cfg(unix)]
|
||||
@@ -83,6 +84,48 @@ struct GatewayWithLatency<'a, G: ConnectableGateway> {
|
||||
latency: Duration,
|
||||
}
|
||||
|
||||
// Helper to collect all pages of entry nodes - replicates NymApiClient's convenience method
|
||||
async fn get_all_basic_entry_nodes_with_metadata(
|
||||
client: &nym_http_api_client::Client,
|
||||
use_bincode: bool,
|
||||
) -> Result<SkimmedNodesWithMetadata, ClientCoreError> {
|
||||
// Get first page to obtain metadata
|
||||
let mut page = 0;
|
||||
let res = client
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
// Collect remaining pages
|
||||
loop {
|
||||
let mut res = client
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
return Err(ClientCoreError::ValidatorClientError(
|
||||
nym_validator_client::ValidatorClientError::InconsistentPagedMetadata,
|
||||
));
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
fn new(gateway: &'a G, latency: Duration) -> Self {
|
||||
GatewayWithLatency { gateway, latency }
|
||||
@@ -99,16 +142,30 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
let client = if let Some(user_agent) = user_agent {
|
||||
nym_validator_client::client::NymApiClient::new_with_user_agent(nym_api.clone(), user_agent)
|
||||
} else {
|
||||
nym_validator_client::client::NymApiClient::new(nym_api.clone())
|
||||
};
|
||||
|
||||
// Use the unified HTTP client directly with optional user agent
|
||||
let mut builder = nym_http_api_client::Client::builder(nym_api.clone())
|
||||
.map_err(|e| {
|
||||
ClientCoreError::ValidatorClientError(
|
||||
nym_validator_client::ValidatorClientError::NymAPIError { source: e },
|
||||
)
|
||||
})?
|
||||
.with_bincode(); // Use bincode for better performance
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
let client = builder.build().map_err(|e| {
|
||||
ClientCoreError::ValidatorClientError(
|
||||
nym_validator_client::ValidatorClientError::NymAPIError { source: e },
|
||||
)
|
||||
})?;
|
||||
|
||||
tracing::debug!("Fetching list of gateways from: {nym_api}");
|
||||
|
||||
let gateways = client
|
||||
.get_all_basic_entry_assigned_nodes_with_metadata()
|
||||
// Use our helper to handle pagination
|
||||
let gateways = get_all_basic_entry_nodes_with_metadata(&client, true)
|
||||
.await?
|
||||
.nodes;
|
||||
info!("nym api reports {} gateways", gateways.len());
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::nyxd::{self, NyxdClient};
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use crate::signing::signer::{NoSigner, OfflineSigner};
|
||||
use crate::{
|
||||
nym_api, DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient,
|
||||
ReqwestRpcClient, ValidatorClientError,
|
||||
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient,
|
||||
ValidatorClientError,
|
||||
};
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -153,7 +153,7 @@ impl Config {
|
||||
pub struct Client<C, S = NoSigner> {
|
||||
// ideally they would have been read-only, but unfortunately rust doesn't have such features
|
||||
// #[deprecated(note = "please use `nym_api_client` instead")]
|
||||
pub nym_api: nym_api::Client,
|
||||
pub nym_api: nym_http_api_client::Client,
|
||||
// pub nym_api_client: NymApiClient,
|
||||
pub nyxd: NyxdClient<C, S>,
|
||||
}
|
||||
@@ -214,7 +214,7 @@ impl Client<ReqwestRpcClient> {
|
||||
|
||||
impl<C> Client<C> {
|
||||
pub fn new_with_rpc_client(config: Config, rpc_client: C) -> Self {
|
||||
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
|
||||
let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
|
||||
|
||||
Client {
|
||||
nym_api: nym_api_client,
|
||||
@@ -228,7 +228,7 @@ impl<C, S> Client<C, S> {
|
||||
where
|
||||
S: OfflineSigner,
|
||||
{
|
||||
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
|
||||
let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
|
||||
|
||||
Client {
|
||||
nym_api: nym_api_client,
|
||||
@@ -385,38 +385,25 @@ impl<C, S> Client<C, S> {
|
||||
}
|
||||
}
|
||||
|
||||
/// DEPRECATED: Use nym_http_api_client::Client with from_network() or with_bincode() instead
|
||||
#[deprecated(
|
||||
since = "1.2.0",
|
||||
note = "Use nym_http_api_client::Client::from_network() or ClientBuilder::with_bincode() instead"
|
||||
)]
|
||||
#[derive(Clone)]
|
||||
pub struct NymApiClient {
|
||||
pub use_bincode: bool,
|
||||
pub nym_api: nym_api::Client,
|
||||
pub nym_api: nym_http_api_client::Client,
|
||||
// TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
|
||||
// we could re-implement the communication with the REST API on port 1317
|
||||
}
|
||||
|
||||
impl From<nym_api::Client> for NymApiClient {
|
||||
fn from(nym_api: nym_api::Client) -> Self {
|
||||
NymApiClient {
|
||||
use_bincode: false,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
|
||||
#[allow(deprecated)]
|
||||
impl NymApiClient {
|
||||
pub fn new(api_url: Url) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, None);
|
||||
|
||||
NymApiClient {
|
||||
use_bincode: true,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, Some(timeout));
|
||||
let nym_api = nym_http_api_client::Client::new(api_url, Some(timeout));
|
||||
|
||||
NymApiClient {
|
||||
use_bincode: true,
|
||||
@@ -431,10 +418,10 @@ impl NymApiClient {
|
||||
}
|
||||
|
||||
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)
|
||||
let nym_api = nym_http_api_client::Client::builder(api_url)
|
||||
.expect("invalid api url")
|
||||
.with_user_agent(user_agent.into())
|
||||
.build::<ValidatorClientError>()
|
||||
.build()
|
||||
.expect("failed to build nym api client");
|
||||
|
||||
NymApiClient {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::NymApiClient;
|
||||
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
|
||||
use nym_coconut_dkg_common::verification_key::ContractVKShare;
|
||||
use nym_compact_ecash::error::CompactEcashError;
|
||||
@@ -15,7 +14,7 @@ use url::Url;
|
||||
// TODO: it really doesn't feel like this should live in this crate.
|
||||
#[derive(Clone)]
|
||||
pub struct EcashApiClient {
|
||||
pub api_client: NymApiClient,
|
||||
pub api_client: nym_http_api_client::Client,
|
||||
pub verification_key: VerificationKeyAuth,
|
||||
pub node_id: NodeIndex,
|
||||
pub cosmos_address: cosmrs::AccountId,
|
||||
@@ -25,10 +24,15 @@ impl Display for EcashApiClient {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[id: {}] {} @ {}",
|
||||
"[id: {}] {} @ ({})",
|
||||
self.node_id,
|
||||
self.cosmos_address,
|
||||
self.api_client.api_url()
|
||||
self.api_client
|
||||
.base_urls()
|
||||
.iter()
|
||||
.map(|url| url.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -60,6 +64,9 @@ pub enum EcashApiError {
|
||||
source: CompactEcashError,
|
||||
},
|
||||
|
||||
#[error("failed to create API client: {0}")]
|
||||
ClientError(String),
|
||||
|
||||
#[error("the provided account address is malformed: {source}")]
|
||||
MalformedAccountAddress {
|
||||
#[from]
|
||||
@@ -89,8 +96,13 @@ impl TryFrom<ContractVKShare> for EcashApiClient {
|
||||
// In non-client applications this resolver can cause warning logs about H2 connection
|
||||
// failure. This indicates that the long lived https connection was closed by the remote
|
||||
// peer and the resolver will have to reconnect. It should not impact actual functionality
|
||||
let api_client = nym_http_api_client::Client::builder(url_address)
|
||||
.map_err(|e| EcashApiError::ClientError(e.to_string()))?
|
||||
.build()
|
||||
.map_err(|e| EcashApiError::ClientError(e.to_string()))?;
|
||||
|
||||
Ok(EcashApiClient {
|
||||
api_client: NymApiClient::new(url_address),
|
||||
api_client,
|
||||
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
|
||||
node_id: share.node_index,
|
||||
cosmos_address: share.owner.as_str().parse()?,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::nym_api::NymApiClientExt;
|
||||
use crate::nyxd::contract_traits::MixnetQueryClient;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::Config as ClientConfig;
|
||||
use crate::{NymApiClient, QueryHttpRpcNyxdClient, ValidatorClientError};
|
||||
use crate::{QueryHttpRpcNyxdClient, ValidatorClientError};
|
||||
use colored::Colorize;
|
||||
use core::fmt;
|
||||
use itertools::Itertools;
|
||||
@@ -87,8 +88,17 @@ fn setup_connection_tests<H: BuildHasher + 'static>(
|
||||
}
|
||||
});
|
||||
|
||||
let api_connection_test_clients = api_urls.map(|(network, url)| {
|
||||
ClientForConnectionTest::Api(network, url.clone(), NymApiClient::new(url))
|
||||
let api_connection_test_clients = api_urls.filter_map(|(network, url)| {
|
||||
match nym_http_api_client::Client::builder(url.clone()).and_then(|b| b.build()) {
|
||||
Ok(client) => Some(ClientForConnectionTest::Api(network, url, client)),
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"Failed to create API client for {}: {err}",
|
||||
network.network_name
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
nyxd_connection_test_clients.chain(api_connection_test_clients)
|
||||
@@ -160,7 +170,7 @@ async fn test_nyxd_connection(
|
||||
async fn test_nym_api_connection(
|
||||
network: NymNetworkDetails,
|
||||
url: &Url,
|
||||
client: &NymApiClient,
|
||||
client: &nym_http_api_client::Client,
|
||||
) -> ConnectionResult {
|
||||
let result = match timeout(
|
||||
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
|
||||
@@ -186,7 +196,7 @@ async fn test_nym_api_connection(
|
||||
|
||||
enum ClientForConnectionTest {
|
||||
Nyxd(NymNetworkDetails, Url, Box<QueryHttpRpcNyxdClient>),
|
||||
Api(NymNetworkDetails, Url, NymApiClient),
|
||||
Api(NymNetworkDetails, Url, nym_http_api_client::Client),
|
||||
}
|
||||
|
||||
impl ClientForConnectionTest {
|
||||
|
||||
@@ -14,7 +14,6 @@ pub mod signing;
|
||||
pub use crate::error::ValidatorClientError;
|
||||
pub use crate::rpc::reqwest::ReqwestRpcClient;
|
||||
pub use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
pub use client::NymApiClient;
|
||||
pub use client::{Client, Config, EcashApiClient};
|
||||
pub use nym_api_requests::*;
|
||||
pub use nym_http_api_client::UserAgent;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_api_requests::models::RequestError;
|
||||
use nym_http_api_client::HttpClientError;
|
||||
|
||||
pub type NymAPIError = HttpClientError<RequestError>;
|
||||
pub type NymAPIError = HttpClientError;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::nym_api::error::NymAPIError;
|
||||
use crate::nym_api::routes::{ecash, CORE_STATUS_COUNT, SINCE_ARG};
|
||||
use crate::nym_nodes::SkimmedNodesWithMetadata;
|
||||
use async_trait::async_trait;
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -37,7 +38,7 @@ pub use nym_api_requests::{
|
||||
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
},
|
||||
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SkimmedNode},
|
||||
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata, SkimmedNode},
|
||||
NymNetworkDetailsResponse,
|
||||
};
|
||||
use nym_contracts_common::IdentityKey;
|
||||
@@ -49,8 +50,8 @@ use time::format_description::BorrowedFormatItem;
|
||||
use time::Date;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::ValidatorClientError;
|
||||
pub use nym_coconut_dkg_common::types::EpochId;
|
||||
pub use nym_http_api_client::Client;
|
||||
|
||||
pub mod error;
|
||||
pub mod routes;
|
||||
@@ -62,6 +63,9 @@ pub fn rfc_3339_date() -> Vec<BorrowedFormatItem<'static>> {
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait NymApiClientExt: ApiClient {
|
||||
/// Get the current API URL being used by the client
|
||||
fn api_url(&self) -> &url::Url;
|
||||
|
||||
async fn health(&self) -> Result<ApiHealthResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
@@ -241,6 +245,156 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_current_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
|
||||
self.get_rewarded_set().await
|
||||
}
|
||||
|
||||
async fn get_all_basic_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
|
||||
// unroll first loop iteration in order to obtain the metadata
|
||||
let mut page = 0;
|
||||
let res = self
|
||||
.get_basic_nodes_v2(false, Some(page), None, true)
|
||||
.await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.get_basic_nodes_v2(false, Some(page), None, true)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
// Create a custom error for inconsistent metadata
|
||||
return Err(NymAPIError::InternalResponseInconsistency {
|
||||
url: self.api_url().clone(),
|
||||
details: "Inconsistent paged metadata".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() >= res.nodes.pagination.total {
|
||||
break;
|
||||
} else {
|
||||
page += 1
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
async fn get_all_basic_active_mixing_assigned_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
|
||||
// Get all mixing nodes that are in the active/rewarded set
|
||||
let mut page = 0;
|
||||
let res = self
|
||||
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
let metadata = res.metadata;
|
||||
let mut nodes = res.nodes.data;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let res = self
|
||||
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
return Err(NymAPIError::InternalResponseInconsistency {
|
||||
url: self.api_url().clone(),
|
||||
details: "Inconsistent paged metadata".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data.clone());
|
||||
|
||||
// Check if we've got all nodes
|
||||
if nodes.len() >= res.nodes.pagination.total {
|
||||
break;
|
||||
} else {
|
||||
page += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
async fn get_all_basic_entry_assigned_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
|
||||
// Get all nodes that can act as entry gateways
|
||||
let mut page = 0;
|
||||
let res = self
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
let metadata = res.metadata;
|
||||
let mut nodes = res.nodes.data;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let res = self
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
return Err(NymAPIError::InternalResponseInconsistency {
|
||||
url: self.api_url().clone(),
|
||||
details: "Inconsistent paged metadata".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data.clone());
|
||||
|
||||
// Check if we've got all nodes
|
||||
if nodes.len() >= res.nodes.pagination.total {
|
||||
break;
|
||||
} else {
|
||||
page += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
async fn get_all_described_nodes(&self) -> Result<Vec<NymNodeDescription>, NymAPIError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut descriptions = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self.get_nodes_described(Some(page), None).await?;
|
||||
|
||||
descriptions.append(&mut res.data);
|
||||
if descriptions.len() < res.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(descriptions)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_nym_nodes(
|
||||
&self,
|
||||
@@ -268,6 +422,25 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_all_bonded_nym_nodes(&self) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut bonds = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self.get_nym_nodes(Some(page), None).await?;
|
||||
|
||||
bonds.append(&mut res.data);
|
||||
if bonds.len() < res.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bonds)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
@@ -1371,8 +1544,49 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Method to change the base API URLs being used by the client
|
||||
fn change_base_urls(&mut self, urls: Vec<url::Url>);
|
||||
|
||||
/// Retrieve expanded information for all bonded nodes on the network
|
||||
async fn get_all_expanded_nodes(&self) -> Result<SemiSkimmedNodesWithMetadata, NymAPIError> {
|
||||
// Unroll the first iteration to get the metadata
|
||||
let mut page = 0;
|
||||
|
||||
let res = self.get_expanded_nodes(false, Some(page), None).await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let mut res = self.get_expanded_nodes(false, Some(page), None).await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
}
|
||||
|
||||
// Client is already nym_http_api_client::Client (re-exported above), so just one impl needed
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl NymApiClientExt for Client {}
|
||||
impl NymApiClientExt for nym_http_api_client::Client {
|
||||
fn api_url(&self) -> &url::Url {
|
||||
self.current_url().as_ref()
|
||||
}
|
||||
|
||||
fn change_base_urls(&mut self, urls: Vec<url::Url>) {
|
||||
self.change_base_urls(urls.into_iter().map(|u| u.into()).collect());
|
||||
}
|
||||
}
|
||||
|
||||
+14
-3
@@ -28,7 +28,7 @@ use cosmrs::proto::cosmwasm::wasm::v1::{
|
||||
QueryRawContractStateResponse, QuerySmartContractStateRequest, QuerySmartContractStateResponse,
|
||||
};
|
||||
use cosmrs::tendermint::{block, chain, Hash};
|
||||
use cosmrs::{AccountId, Coin as CosmosCoin};
|
||||
use cosmrs::{AccountId, Coin as CosmosCoin, Tx};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -556,12 +556,23 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
Ok(serde_json::from_slice(&res.data)?)
|
||||
}
|
||||
|
||||
async fn query_simulate(&self, tx_bytes: Vec<u8>) -> Result<SimulateResponse, NyxdError> {
|
||||
// deprecation warning is due to the fact the protobuf files built were based on cosmos-sdk 0.44,
|
||||
// where they prefer using tx_bytes directly. However, in 0.42, which we are using at the time
|
||||
// of writing this, the option does not work
|
||||
// TODO: we should really stop using the `tx` argument here and use `tx_bytes` exlusively,
|
||||
// however, at the time of writing this update, while our QA and mainnet networks do support it,
|
||||
// sandbox is still running old version of wasmd that lacks support for `tx_bytes`
|
||||
#[allow(deprecated)]
|
||||
async fn query_simulate(
|
||||
&self,
|
||||
tx: Option<Tx>,
|
||||
tx_bytes: Vec<u8>,
|
||||
) -> Result<SimulateResponse, NyxdError> {
|
||||
let path = Some("/cosmos.tx.v1beta1.Service/Simulate".to_owned());
|
||||
|
||||
let req = SimulateRequest {
|
||||
tx: tx.map(Into::into),
|
||||
tx_bytes,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let res = self
|
||||
|
||||
+10
-7
@@ -81,14 +81,17 @@ where
|
||||
auth_info: single_unspecified_signer_auth(public_key, sequence_response.sequence),
|
||||
signatures: vec![Vec::new()],
|
||||
};
|
||||
self.query_simulate(Some(partial_tx), Vec::new()).await
|
||||
|
||||
let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
|
||||
body_bytes: partial_tx.body.into_bytes()?,
|
||||
auth_info_bytes: partial_tx.auth_info.into_bytes()?,
|
||||
signatures: partial_tx.signatures,
|
||||
}
|
||||
.into();
|
||||
self.query_simulate(tx_raw.to_bytes()?).await
|
||||
// for completion sake, once we're able to transition into using `tx_bytes`,
|
||||
// we might want to use something like this instead:
|
||||
// let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
|
||||
// body_bytes: partial_tx.body.into_bytes().unwrap(),
|
||||
// auth_info_bytes: partial_tx.auth_info.into_bytes().unwrap(),
|
||||
// signatures: partial_tx.signatures,
|
||||
// }
|
||||
// .into();
|
||||
// self.query_simulate(None, tx_raw.to_bytes().unwrap()).await
|
||||
}
|
||||
|
||||
async fn upload(
|
||||
|
||||
@@ -38,6 +38,7 @@ cosmrs = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::errors::ContextError;
|
||||
pub use nym_http_api_client::Client as NymApiClient;
|
||||
use nym_network_defaults::{
|
||||
setup_env,
|
||||
var_names::{MIXNET_CONTRACT_ADDRESS, NYM_API, NYXD, VESTING_CONTRACT_ADDRESS},
|
||||
NymNetworkDetails,
|
||||
};
|
||||
pub use nym_validator_client::nym_api::Client as NymApiClient;
|
||||
use nym_validator_client::nyxd::{self, AccountId, NyxdClient};
|
||||
use nym_validator_client::{
|
||||
DirectSigningHttpRpcNyxdClient, DirectSigningHttpRpcValidatorClient, QueryHttpRpcNyxdClient,
|
||||
|
||||
@@ -14,7 +14,7 @@ use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketB
|
||||
use nym_credentials_interface::Bandwidth;
|
||||
use nym_credentials_interface::{ClientTicket, TicketType};
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
|
||||
};
|
||||
@@ -354,7 +354,7 @@ impl CredentialHandler {
|
||||
Err(err) => {
|
||||
error!("failed to send ticket {ticket_id} for verification to ecash signer '{client}': {err}. if we don't reach quorum, we'll retry later");
|
||||
Err(EcashTicketError::ApiFailure(EcashApiError::NymApi {
|
||||
source: err,
|
||||
source: nym_validator_client::ValidatorClientError::NymAPIError { source: err },
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ nym-ecash-time = { path = "../ecash-time", features = ["expiration"] }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
|
||||
@@ -15,7 +15,7 @@ use nym_credentials_interface::{
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_ecash_time::{ecash_default_expiration_date, ecash_today, EcashTime};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::Date;
|
||||
|
||||
@@ -116,7 +116,7 @@ impl IssuanceTicketBook {
|
||||
|
||||
pub async fn obtain_blinded_credential(
|
||||
&self,
|
||||
client: &nym_validator_client::client::NymApiClient,
|
||||
client: &nym_http_api_client::Client,
|
||||
request_body: &BlindSignRequestBody,
|
||||
) -> Result<BlindedSignature, Error> {
|
||||
let server_response = client.blind_sign(request_body).await?;
|
||||
@@ -179,7 +179,7 @@ impl IssuanceTicketBook {
|
||||
// ideally this would have been generic over credential type, but we really don't need secp256k1 keys for bandwidth vouchers
|
||||
pub async fn obtain_partial_ticketbook_credential(
|
||||
&self,
|
||||
client: &nym_validator_client::client::NymApiClient,
|
||||
client: &nym_http_api_client::Client,
|
||||
signer_index: u64,
|
||||
validator_vk: &VerificationKeyAuth,
|
||||
signing_data: CredentialSigningData,
|
||||
|
||||
@@ -10,6 +10,7 @@ use nym_credentials_interface::{
|
||||
VerificationKeyAuth, WalletSignatures,
|
||||
};
|
||||
use nym_validator_client::client::EcashApiClient;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
|
||||
// so we wouldn't break all the existing imports
|
||||
pub use nym_ecash_time::{cred_exp_date, ecash_date_offset, ecash_today, EcashTime};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::ecash::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
|
||||
use nym_credentials_interface::CompactEcashError;
|
||||
use nym_crypto::asymmetric::x25519::KeyRecoveryError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use nym_validator_client::{nym_api::error::NymAPIError, ValidatorClientError};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -37,6 +37,9 @@ pub enum Error {
|
||||
#[error("Ran into a validator client error - {0}")]
|
||||
ValidatorClientError(#[from] ValidatorClientError),
|
||||
|
||||
#[error("Nym API request failed - {0}")]
|
||||
NymAPIError(#[from] NymAPIError),
|
||||
|
||||
#[error("Bandwidth operation overflowed. {0}")]
|
||||
BandwidthOverflow(String),
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ digest = { workspace = true, optional = true }
|
||||
generic-array = { workspace = true, optional = true }
|
||||
hkdf = { workspace = true, optional = true }
|
||||
hmac = { workspace = true, optional = true }
|
||||
jwt-simple = { workspace = true, optional = true }
|
||||
cipher = { workspace = true, optional = true }
|
||||
x25519-dalek = { workspace = true, features = ["static_secrets"], optional = true }
|
||||
ed25519-dalek = { workspace = true, features = ["rand_core"], optional = true }
|
||||
@@ -40,7 +39,6 @@ rand_chacha = { workspace = true }
|
||||
[features]
|
||||
default = []
|
||||
aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"]
|
||||
naive_jwt = ["asymmetric", "jwt-simple"]
|
||||
serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
|
||||
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"]
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use ed25519_dalek::SignatureError;
|
||||
use ed25519_dalek::{SecretKey, Signer, SigningKey};
|
||||
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||
|
||||
use ed25519_dalek::Signer;
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
@@ -14,9 +13,6 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod serde_helpers;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
pub use serde_helpers::*;
|
||||
|
||||
#[cfg(feature = "sphinx")]
|
||||
use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
|
||||
|
||||
@@ -85,8 +81,8 @@ impl KeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_secret(secret: ed25519_dalek::SecretKey, index: u32) -> Self {
|
||||
let ed25519_signing_key = ed25519_dalek::SigningKey::from(secret);
|
||||
pub fn from_secret(secret: SecretKey, index: u32) -> Self {
|
||||
let ed25519_signing_key = SigningKey::from(secret);
|
||||
|
||||
KeyPair {
|
||||
private_key: PrivateKey(ed25519_signing_key.to_bytes()),
|
||||
@@ -280,7 +276,7 @@ impl Display for PrivateKey {
|
||||
|
||||
impl<'a> From<&'a PrivateKey> for PublicKey {
|
||||
fn from(pk: &'a PrivateKey) -> Self {
|
||||
PublicKey(ed25519_dalek::SigningKey::from_bytes(&pk.0).verifying_key())
|
||||
PublicKey(SigningKey::from_bytes(&pk.0).verifying_key())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +320,7 @@ impl PrivateKey {
|
||||
}
|
||||
|
||||
pub fn sign<M: AsRef<[u8]>>(&self, message: M) -> Signature {
|
||||
let signing_key: ed25519_dalek::SigningKey = self.0.into();
|
||||
let signing_key: SigningKey = self.0.into();
|
||||
let sig = signing_key.sign(message.as_ref());
|
||||
Signature(sig)
|
||||
}
|
||||
@@ -429,57 +425,9 @@ impl<'d> Deserialize<'d> for Signature {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl PublicKey {
|
||||
pub fn to_jwt_compatible_key(&self) -> jwt_simple::algorithms::Ed25519PublicKey {
|
||||
(*self).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl From<PublicKey> for jwt_simple::algorithms::Ed25519PublicKey {
|
||||
fn from(value: PublicKey) -> Self {
|
||||
// SAFETY: we have a valid ed25519 pubkey, we're just changing to a different library wrapper
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_simple::algorithms::Ed25519PublicKey::from_bytes(&value.to_bytes()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl PrivateKey {
|
||||
pub fn to_jwt_compatible_keys(&self) -> jwt_simple::algorithms::Ed25519KeyPair {
|
||||
let pub_key = self.public_key();
|
||||
let mut bytes = zeroize::Zeroizing::new([0u8; 64]);
|
||||
|
||||
bytes[..SECRET_KEY_LENGTH]
|
||||
.copy_from_slice(zeroize::Zeroizing::new(self.to_bytes()).as_ref());
|
||||
bytes[SECRET_KEY_LENGTH..].copy_from_slice(&pub_key.to_bytes());
|
||||
|
||||
// SAFETY: we have a valid ed25519 keys, we're just changing to a different library wrapper
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_simple::algorithms::Ed25519KeyPair::from_bytes(bytes.as_ref()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl KeyPair {
|
||||
pub fn to_jwt_compatible_keys(&self) -> jwt_simple::algorithms::Ed25519KeyPair {
|
||||
let mut bytes = zeroize::Zeroizing::new([0u8; 64]);
|
||||
|
||||
bytes[..SECRET_KEY_LENGTH]
|
||||
.copy_from_slice(zeroize::Zeroizing::new(self.private_key.to_bytes()).as_ref());
|
||||
bytes[SECRET_KEY_LENGTH..].copy_from_slice(&self.public_key.to_bytes());
|
||||
|
||||
// SAFETY: we have a valid ed25519 keys, we're just changing to a different library wrapper
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_simple::algorithms::Ed25519KeyPair::from_bytes(bytes.as_ref()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::thread_rng;
|
||||
|
||||
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||
|
||||
@@ -490,29 +438,4 @@ mod tests {
|
||||
assert_zeroize::<PrivateKey>();
|
||||
assert_zeroize_on_drop::<PrivateKey>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "naive_jwt", feature = "rand"))]
|
||||
fn check_jwt_key_compat_conversion() {
|
||||
let mut rng = thread_rng();
|
||||
let keys = KeyPair::new(&mut rng);
|
||||
let jwt_keys = keys.to_jwt_compatible_keys();
|
||||
|
||||
// internally they're represented by hidden `Edwards25519KeyPair` (plus key_id)
|
||||
// which has way nicer API for assertions
|
||||
let jwt_keys_inner =
|
||||
jwt_simple::algorithms::Edwards25519KeyPair::from_bytes(&jwt_keys.to_bytes()).unwrap();
|
||||
|
||||
let compact_ed25519 = jwt_keys_inner.as_ref();
|
||||
assert!(compact_ed25519
|
||||
.sk
|
||||
.validate_public_key(&compact_ed25519.pk)
|
||||
.is_ok());
|
||||
|
||||
let dummy_message = "hello world";
|
||||
let sig1 = keys.private_key.sign(dummy_message).to_bytes();
|
||||
let sig2 = compact_ed25519.sk.sign(dummy_message, None).to_vec();
|
||||
|
||||
assert_eq!(sig1.to_vec(), sig2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,14 +157,6 @@ impl<LS, TS, LC, TC> SignerResult<LS, TS, LC, TC> {
|
||||
pub fn malformed_details(&self) -> bool {
|
||||
self.information.parse().is_err()
|
||||
}
|
||||
|
||||
pub fn try_get_test_result(&self) -> Option<&SignerTestResult<LS, TS, LC, TC>> {
|
||||
if let SignerStatus::Tested { result } = &self.status {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<LS, TS, LC, TC> SignerResult<LS, TS, LC, TC>
|
||||
|
||||
@@ -22,6 +22,7 @@ url = { workspace = true }
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-ecash-signer-check-types = { path = "../ecash-signer-check-types" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{LocalChainStatus, SigningStatus, TypedSignerResult};
|
||||
use crate::{LocalChainStatus, SignerCheckError, SigningStatus, TypedSignerResult};
|
||||
use nym_ecash_signer_check_types::dealer_information::RawDealerInformation;
|
||||
use nym_ecash_signer_check_types::status::{SignerStatus, SignerTestResult};
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::models::BinaryBuildInformationOwned;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::{
|
||||
ContractVKShare, DealerDetails,
|
||||
};
|
||||
use nym_validator_client::NymApiClient;
|
||||
use std::time::Duration;
|
||||
use tracing::{error, warn};
|
||||
use url::Url;
|
||||
@@ -32,37 +31,38 @@ pub(crate) mod signing_status {
|
||||
}
|
||||
|
||||
struct ClientUnderTest {
|
||||
api_client: NymApiClient,
|
||||
api_client: nym_http_api_client::Client,
|
||||
build_info: Option<BinaryBuildInformationOwned>,
|
||||
}
|
||||
|
||||
impl ClientUnderTest {
|
||||
pub(crate) fn new(api_url: &Url) -> Self {
|
||||
ClientUnderTest {
|
||||
api_client: NymApiClient::new(api_url.clone()),
|
||||
pub(crate) fn new(api_url: &Url) -> Result<Self, SignerCheckError> {
|
||||
// The builder should not fail with a valid URL that's already parsed
|
||||
// If it does fail, it's an internal error that we can't recover from
|
||||
let api_client = nym_http_api_client::Client::builder(api_url.clone())?.build()?;
|
||||
|
||||
Ok(ClientUnderTest {
|
||||
api_client,
|
||||
build_info: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn try_retrieve_build_information(&mut self) -> bool {
|
||||
match tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
self.api_client.nym_api.build_information(),
|
||||
)
|
||||
.await
|
||||
match tokio::time::timeout(Duration::from_secs(5), self.api_client.build_information())
|
||||
.await
|
||||
{
|
||||
Ok(Ok(build_information)) => {
|
||||
self.build_info = Some(build_information);
|
||||
true
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.api_url());
|
||||
warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.current_url());
|
||||
false
|
||||
}
|
||||
Err(_timeout) => {
|
||||
warn!(
|
||||
"{}: timed out while attempting to retrieve build information",
|
||||
self.api_client.api_url()
|
||||
self.api_client.current_url()
|
||||
);
|
||||
false
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl ClientUnderTest {
|
||||
.inspect_err(|err| {
|
||||
error!(
|
||||
"ecash signer '{}' reports invalid version {}: {err}",
|
||||
self.api_client.api_url(),
|
||||
self.api_client.current_url(),
|
||||
build_info.build_version
|
||||
)
|
||||
})
|
||||
@@ -121,14 +121,14 @@ impl ClientUnderTest {
|
||||
|
||||
// check if it supports the current query
|
||||
if self.supports_chain_status_query() {
|
||||
return match self.api_client.nym_api.get_chain_blocks_status().await {
|
||||
return match self.api_client.get_chain_blocks_status().await {
|
||||
Ok(status) => LocalChainStatus::Reachable {
|
||||
response: Box::new(status),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve local chain status: {err}",
|
||||
self.api_client.api_url()
|
||||
self.api_client.current_url()
|
||||
);
|
||||
LocalChainStatus::Unreachable
|
||||
}
|
||||
@@ -136,14 +136,14 @@ impl ClientUnderTest {
|
||||
}
|
||||
|
||||
// fallback to the legacy query
|
||||
match self.api_client.nym_api.get_chain_status().await {
|
||||
match self.api_client.get_chain_status().await {
|
||||
Ok(status) => LocalChainStatus::ReachableLegacy {
|
||||
response: Box::new(status),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve [legacy] local chain status: {err}",
|
||||
self.api_client.api_url()
|
||||
self.api_client.current_url()
|
||||
);
|
||||
LocalChainStatus::Unreachable
|
||||
}
|
||||
@@ -158,14 +158,14 @@ impl ClientUnderTest {
|
||||
|
||||
// check if it supports the current query
|
||||
if self.supports_signing_status_query() {
|
||||
return match self.api_client.nym_api.get_signer_status().await {
|
||||
return match self.api_client.get_signer_status().await {
|
||||
Ok(response) => SigningStatus::Reachable {
|
||||
response: Box::new(response),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve signer chain status: {err}",
|
||||
self.api_client.api_url()
|
||||
self.api_client.current_url()
|
||||
);
|
||||
SigningStatus::Unreachable
|
||||
}
|
||||
@@ -173,14 +173,14 @@ impl ClientUnderTest {
|
||||
}
|
||||
|
||||
// fallback to the legacy query
|
||||
match self.api_client.nym_api.get_signer_information().await {
|
||||
match self.api_client.get_signer_information().await {
|
||||
Ok(status) => SigningStatus::ReachableLegacy {
|
||||
response: Box::new(status),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve [legacy] signer chain status: {err}",
|
||||
self.api_client.api_url()
|
||||
self.api_client.current_url()
|
||||
);
|
||||
// NOTE: this might equally mean the signing is disabled
|
||||
SigningStatus::Unreachable
|
||||
@@ -201,7 +201,13 @@ pub(crate) async fn check_client(
|
||||
return SignerStatus::ProvidedInvalidDetails.with_details(dealer_information, dkg_epoch);
|
||||
};
|
||||
|
||||
let mut client = ClientUnderTest::new(&parsed_information.announce_address);
|
||||
let mut client = match ClientUnderTest::new(&parsed_information.announce_address) {
|
||||
Ok(client) => client,
|
||||
Err(err) => {
|
||||
error!("failed to create client instance: {err}");
|
||||
return SignerStatus::Unreachable.with_details(dealer_information, dkg_epoch);
|
||||
}
|
||||
};
|
||||
|
||||
// 8. check basic connection status - can you retrieve build information?
|
||||
if !client.try_retrieve_build_information().await {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_http_api_client::HttpClientError;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -11,6 +12,12 @@ pub enum SignerCheckError {
|
||||
|
||||
#[error("failed to query the DKG contract: {source}")]
|
||||
DKGContractQueryFailure { source: NyxdError },
|
||||
|
||||
#[error("failed to build client: {source}")]
|
||||
HttpClient {
|
||||
#[from]
|
||||
source: HttpClientError,
|
||||
},
|
||||
}
|
||||
|
||||
impl SignerCheckError {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "nym-http-api-client-macro"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1"
|
||||
syn = { workspace = true, features = ["full"] }
|
||||
quote = "1.0.40"
|
||||
proc-macro-crate = "3"
|
||||
|
||||
[dev-dependencies]
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
reqwest = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,311 @@
|
||||
//! Proc-macros for configuring HTTP clients globally via the `inventory` crate.
|
||||
//!
|
||||
//! This crate provides macros that allow any crate in the workspace to contribute
|
||||
//! configuration modifications to `reqwest::ClientBuilder` instances through a
|
||||
//! compile-time registry pattern.
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! The macros work by:
|
||||
//! 1. Collecting configuration functions from across all crates at compile time
|
||||
//! 2. Sorting them by priority (lower numbers run first)
|
||||
//! 3. Applying them sequentially to build HTTP clients with consistent settings
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ## Basic Usage with `client_defaults!`
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use nym_http_api_client_macro::client_defaults;
|
||||
//!
|
||||
//! // Register default configurations with priority
|
||||
//! client_defaults!(
|
||||
//! priority = 10; // Optional, defaults to 0
|
||||
//! timeout = std::time::Duration::from_secs(30),
|
||||
//! gzip = true,
|
||||
//! user_agent = "MyApp/1.0"
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
//! ## Using `client_cfg!` for one-off configurations
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use nym_http_api_client_macro::client_cfg;
|
||||
//!
|
||||
//! let configure = client_cfg!(
|
||||
//! timeout = std::time::Duration::from_secs(60),
|
||||
//! default_headers {
|
||||
//! "X-Custom-Header" => "value",
|
||||
//! "Authorization" => auth_token
|
||||
//! }
|
||||
//! );
|
||||
//!
|
||||
//! let builder = reqwest::ClientBuilder::new();
|
||||
//! let configured = configure(builder);
|
||||
//! ```
|
||||
//!
|
||||
//! # DSL Reference
|
||||
//!
|
||||
//! The macro DSL supports several patterns:
|
||||
//! - `key = value` - Calls `builder.key(value)`
|
||||
//! - `key(arg1, arg2)` - Calls `builder.key(arg1, arg2)`
|
||||
//! - `flag` - Calls `builder.flag()` with no arguments
|
||||
//! - `default_headers { "name" => "value", ... }` - Sets default headers
|
||||
//!
|
||||
//! # Priority System
|
||||
//!
|
||||
//! Configurations are applied in priority order (lower numbers first):
|
||||
//! - Negative priorities: Early configuration (e.g., -100 for base settings)
|
||||
//! - Zero (default): Standard configuration
|
||||
//! - Positive priorities: Late configuration (e.g., 100 for overrides)
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
braced,
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
token, Expr, Ident, LitInt, Result, Token,
|
||||
};
|
||||
|
||||
// ------------------ core crate path resolution ------------------
|
||||
|
||||
fn core_path() -> TokenStream2 {
|
||||
match crate_name("nym-http-api-client") {
|
||||
Ok(FoundCrate::Itself) => quote!(crate),
|
||||
Ok(FoundCrate::Name(name)) => {
|
||||
let ident = Ident::new(&name, Span::call_site());
|
||||
quote!( ::#ident )
|
||||
}
|
||||
Err(_) => {
|
||||
// Fallback if the crate is not found by name (unlikely if deps set up correctly)
|
||||
quote!(::nym_http_api_client)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------ DSL parsing ------------------
|
||||
|
||||
struct Items(Punctuated<Item, Token![,]>);
|
||||
impl Parse for Items {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
Ok(Self(Punctuated::parse_terminated(input)?))
|
||||
}
|
||||
}
|
||||
|
||||
enum Item {
|
||||
Assign {
|
||||
key: Ident,
|
||||
_eq: Token![=],
|
||||
value: Expr,
|
||||
}, // foo = EXPR
|
||||
Call {
|
||||
key: Ident,
|
||||
args: Punctuated<Expr, Token![,]>,
|
||||
_p: token::Paren,
|
||||
}, // foo(a,b)
|
||||
DefaultHeaders {
|
||||
_key: Ident,
|
||||
map: HeaderMapInit,
|
||||
}, // default_headers { ... }
|
||||
Flag {
|
||||
key: Ident,
|
||||
}, // foo
|
||||
}
|
||||
|
||||
impl Parse for Item {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let key: Ident = input.parse()?;
|
||||
|
||||
if input.peek(Token![=]) {
|
||||
let _eq: Token![=] = input.parse()?;
|
||||
let value: Expr = input.parse()?;
|
||||
return Ok(Self::Assign { key, _eq, value });
|
||||
}
|
||||
if input.peek(token::Paren) {
|
||||
let content;
|
||||
let _p = syn::parenthesized!(content in input);
|
||||
let args = Punctuated::<Expr, Token![,]>::parse_terminated(&content)?;
|
||||
return Ok(Self::Call { key, args, _p });
|
||||
}
|
||||
if input.peek(token::Brace) && key == format_ident!("default_headers") {
|
||||
let map = input.parse::<HeaderMapInit>()?;
|
||||
return Ok(Self::DefaultHeaders { _key: key, map });
|
||||
}
|
||||
Ok(Self::Flag { key })
|
||||
}
|
||||
}
|
||||
|
||||
struct HeaderPair {
|
||||
k: Expr,
|
||||
_arrow: Token![=>],
|
||||
v: Expr,
|
||||
}
|
||||
impl Parse for HeaderPair {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
k: input.parse()?,
|
||||
_arrow: input.parse()?,
|
||||
v: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct HeaderMapInit {
|
||||
_brace: token::Brace,
|
||||
pairs: Punctuated<HeaderPair, Token![,]>,
|
||||
}
|
||||
impl Parse for HeaderMapInit {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let content;
|
||||
let _brace = braced!(content in input);
|
||||
let pairs = Punctuated::<HeaderPair, Token![,]>::parse_terminated(&content)?;
|
||||
Ok(Self { _brace, pairs })
|
||||
}
|
||||
}
|
||||
|
||||
// Generate statements that mutate a builder named `b` using the resolved core path.
|
||||
fn to_stmts(items: Items, core: &TokenStream2) -> TokenStream2 {
|
||||
let mut stmts = Vec::new();
|
||||
for it in items.0 {
|
||||
match it {
|
||||
Item::Assign { key, value, .. } => {
|
||||
let m = key;
|
||||
stmts.push(quote! { b = b.#m(#value); });
|
||||
}
|
||||
Item::Call { key, args, .. } => {
|
||||
let m = key;
|
||||
let args = args.iter();
|
||||
stmts.push(quote! { b = b.#m( #( #args ),* ); });
|
||||
}
|
||||
Item::DefaultHeaders { map, .. } => {
|
||||
let (ks, vs): (Vec<_>, Vec<_>) = map.pairs.into_iter().map(|p| (p.k, p.v)).unzip();
|
||||
stmts.push(quote! {
|
||||
let mut __cm = #core::reqwest::header::HeaderMap::new();
|
||||
#(
|
||||
{
|
||||
use #core::reqwest::header::{HeaderName, HeaderValue};
|
||||
let __k = HeaderName::try_from(#ks)
|
||||
.unwrap_or_else(|e| panic!("Invalid header name: {}", e));
|
||||
let __v = HeaderValue::try_from(#vs)
|
||||
.unwrap_or_else(|e| panic!("Invalid header value: {}", e));
|
||||
__cm.insert(__k, __v);
|
||||
}
|
||||
)*
|
||||
b = b.default_headers(__cm);
|
||||
});
|
||||
}
|
||||
Item::Flag { key } => {
|
||||
let m = key;
|
||||
stmts.push(quote! { b = b.#m(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
quote! { #(#stmts)* }
|
||||
}
|
||||
|
||||
// ------------------ client_cfg! ------------------
|
||||
|
||||
/// Creates a closure that configures a `ReqwestClientBuilder`.
|
||||
///
|
||||
/// This macro generates a closure that can be used to configure a single
|
||||
/// `reqwest::ClientBuilder` instance without affecting global defaults.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// let config = client_cfg!(
|
||||
/// timeout = Duration::from_secs(30),
|
||||
/// gzip = true
|
||||
/// );
|
||||
/// let client = config(reqwest::ClientBuilder::new()).build()?;
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn client_cfg(input: TokenStream) -> TokenStream {
|
||||
let items = parse_macro_input!(input as Items);
|
||||
let core = core_path();
|
||||
let body = to_stmts(items, &core);
|
||||
let out = quote! {
|
||||
|mut b: #core::ReqwestClientBuilder| { #body b }
|
||||
};
|
||||
out.into()
|
||||
}
|
||||
|
||||
// ------------------ client_defaults! with optional priority header ------------------
|
||||
|
||||
struct MaybePrioritized {
|
||||
priority: i32,
|
||||
items: Items,
|
||||
}
|
||||
impl Parse for MaybePrioritized {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
// Optional header: `priority = <int> ;`
|
||||
let fork = input.fork();
|
||||
let mut priority = 0i32;
|
||||
if fork.peek(Ident) && fork.parse::<Ident>()? == "priority" && fork.peek(Token![=]) {
|
||||
// commit
|
||||
let _ = input.parse::<Ident>()?; // priority
|
||||
let _ = input.parse::<Token![=]>()?;
|
||||
let lit: LitInt = input.parse()?;
|
||||
priority = lit.base10_parse()?;
|
||||
let _ = input.parse::<Token![;]>()?;
|
||||
}
|
||||
let items = input.parse::<Items>()?;
|
||||
Ok(Self { priority, items })
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers global default configurations for HTTP clients.
|
||||
///
|
||||
/// This macro submits a configuration record to the global registry that will
|
||||
/// be applied to all HTTP clients created with `default_builder()`.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `priority` (optional): Integer priority for ordering (lower runs first, default: 0)
|
||||
/// - Configuration items: Any valid `reqwest::ClientBuilder` method calls
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// client_defaults!(
|
||||
/// priority = -50; // Run early in the configuration chain
|
||||
/// connect_timeout = Duration::from_secs(10),
|
||||
/// pool_max_idle_per_host = 32,
|
||||
/// default_headers {
|
||||
/// "User-Agent" => "MyApp/1.0",
|
||||
/// "Accept" => "application/json"
|
||||
/// }
|
||||
/// );
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn client_defaults(input: TokenStream) -> TokenStream {
|
||||
let MaybePrioritized { priority, items } = parse_macro_input!(input as MaybePrioritized);
|
||||
let core = core_path();
|
||||
let body = to_stmts(items, &core);
|
||||
|
||||
let out = quote! {
|
||||
#[allow(non_snake_case)]
|
||||
mod __client_defaults {
|
||||
use super::*;
|
||||
#[allow(unused)]
|
||||
pub fn __cfg(
|
||||
mut b: #core::ReqwestClientBuilder
|
||||
) -> #core::ReqwestClientBuilder {
|
||||
#body
|
||||
b
|
||||
}
|
||||
|
||||
#core::inventory::submit! {
|
||||
#core::registry::ConfigRecord {
|
||||
priority: #priority,
|
||||
apply: __cfg,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
out.into()
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use nym_http_api_client_macro::{client_cfg, client_defaults};
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_client_cfg_basic() {
|
||||
// Test that the macro compiles with basic configuration
|
||||
let _config = client_cfg!(timeout = Duration::from_secs(30), gzip = true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_cfg_with_headers() {
|
||||
// Test that the macro compiles with default headers
|
||||
let _config = client_cfg!(
|
||||
timeout = Duration::from_secs(30),
|
||||
default_headers {
|
||||
"User-Agent" => "TestApp/1.0",
|
||||
"Accept" => "application/json"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_cfg_with_method_calls() {
|
||||
// Test that the macro compiles with method calls
|
||||
let _config = client_cfg!(
|
||||
pool_max_idle_per_host = 32,
|
||||
tcp_nodelay = true,
|
||||
danger_accept_invalid_certs = true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_defaults_with_priority() {
|
||||
// Test that client_defaults macro compiles with priority
|
||||
client_defaults!(
|
||||
priority = -100;
|
||||
gzip = true,
|
||||
deflate = true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_defaults_without_priority() {
|
||||
// Test that client_defaults macro compiles without priority (defaults to 0)
|
||||
client_defaults!(brotli = true, zstd = true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_client_cfg() {
|
||||
// Test that empty configuration compiles
|
||||
let _config = client_cfg!();
|
||||
}
|
||||
|
||||
// Integration test to verify the closure actually works
|
||||
#[test]
|
||||
fn test_client_cfg_closure_application() {
|
||||
let config = client_cfg!(gzip = true);
|
||||
|
||||
// Apply the configuration to a new builder
|
||||
let builder = reqwest::ClientBuilder::new();
|
||||
let _configured_builder = config(builder);
|
||||
// Note: We can't easily test the internal state of the builder,
|
||||
// but we verify it compiles and runs without panic
|
||||
}
|
||||
@@ -13,6 +13,7 @@ license.workspace = true
|
||||
[features]
|
||||
default=["tunneling"]
|
||||
tunneling=[]
|
||||
network-defaults = ["dep:nym-network-defaults"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
@@ -23,9 +24,12 @@ url = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true}
|
||||
serde_plain = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
|
||||
# used for decoding text responses (they were already implicitly included)
|
||||
bytes = { workspace = true }
|
||||
@@ -34,6 +38,8 @@ mime = { workspace = true }
|
||||
|
||||
nym-http-api-common = { path = "../http-api-common", default-features = false }
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
nym-network-defaults = { path = "../network-defaults", optional = true }
|
||||
nym-http-api-client-macro = { path = "../http-api-client-macro" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
hickory-resolver = { workspace = true, features = ["https-ring", "tls-ring", "webpki-roots"] }
|
||||
|
||||
@@ -54,10 +54,14 @@ impl Front {
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
#[cfg(feature = "tunneling")]
|
||||
/// Policy for when to use domain fronting for HTTP requests.
|
||||
pub enum FrontPolicy {
|
||||
/// Always use domain fronting for all requests.
|
||||
Always,
|
||||
/// Only use domain fronting when retrying failed requests.
|
||||
OnRetry,
|
||||
#[default]
|
||||
/// Never use domain fronting.
|
||||
Off,
|
||||
}
|
||||
|
||||
@@ -96,14 +100,14 @@ mod tests {
|
||||
// Some(vec!["https://cdn77.com"]),
|
||||
// ).unwrap(); // cdn77
|
||||
|
||||
let client = ClientBuilder::new::<_, &str>(url1)
|
||||
let client = ClientBuilder::new(url1)
|
||||
.expect("bad url")
|
||||
.with_fronting(FrontPolicy::Always)
|
||||
.build::<&str>()
|
||||
.build()
|
||||
.expect("failed to build client");
|
||||
|
||||
let response = client
|
||||
.send_request::<_, (), &str, &str, &str>(
|
||||
.send_request::<_, (), &str, &str>(
|
||||
reqwest::Method::GET,
|
||||
&["api", "v1", "network", "details"],
|
||||
NO_PARAMS,
|
||||
|
||||
+418
-158
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
//! Global registry for HTTP client configurations.
|
||||
//!
|
||||
//! This module provides a compile-time registry system that allows any crate
|
||||
//! in the workspace to contribute configuration modifications to HTTP clients.
|
||||
|
||||
use crate::ReqwestClientBuilder;
|
||||
|
||||
/// A configuration record that modifies a `ReqwestClientBuilder`.
|
||||
///
|
||||
/// Records are collected at compile-time via the `inventory` crate and
|
||||
/// applied in priority order when building HTTP clients.
|
||||
pub struct ConfigRecord {
|
||||
/// Lower numbers run earlier.
|
||||
pub priority: i32,
|
||||
/// A function that takes a builder and returns a mutated builder.
|
||||
pub apply: fn(ReqwestClientBuilder) -> ReqwestClientBuilder,
|
||||
}
|
||||
|
||||
inventory::collect!(ConfigRecord);
|
||||
|
||||
/// Returns the default builder with all registered configurations applied.
|
||||
pub fn default_builder() -> ReqwestClientBuilder {
|
||||
let mut b = ReqwestClientBuilder::new();
|
||||
let mut records: Vec<&'static ConfigRecord> =
|
||||
inventory::iter::<ConfigRecord>.into_iter().collect();
|
||||
records.sort_by_key(|r| r.priority); // lower runs first
|
||||
for r in records {
|
||||
b = (r.apply)(b);
|
||||
}
|
||||
b
|
||||
}
|
||||
|
||||
/// Builds a client using the default builder with all registered configurations.
|
||||
pub fn build_client() -> reqwest::Result<reqwest::Client> {
|
||||
default_builder().build()
|
||||
}
|
||||
|
||||
/// Debug function to inspect registered configurations.
|
||||
/// Returns a vector of (priority, function_pointer) tuples for debugging.
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn inspect_registered_configs() -> Vec<(i32, usize)> {
|
||||
let mut configs: Vec<(i32, usize)> = inventory::iter::<ConfigRecord>
|
||||
.into_iter()
|
||||
.map(|record| (record.priority, record.apply as usize))
|
||||
.collect();
|
||||
configs.sort_by_key(|(priority, _)| *priority);
|
||||
configs
|
||||
}
|
||||
|
||||
/// Returns the count of registered configuration records.
|
||||
pub fn registered_config_count() -> usize {
|
||||
inventory::iter::<ConfigRecord>.into_iter().count()
|
||||
}
|
||||
@@ -95,10 +95,10 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
"http://example.com/".parse()?,
|
||||
])
|
||||
.with_retries(3)
|
||||
.build::<HttpClientError>()?;
|
||||
.build()?;
|
||||
|
||||
let req = client.create_get_request(&["/"], NO_PARAMS);
|
||||
let resp = client.send::<HttpClientError>(req).await?;
|
||||
let req = client.create_get_request(&["/"], NO_PARAMS).unwrap();
|
||||
let resp = client.send(req).await?;
|
||||
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
@@ -111,10 +111,7 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[test]
|
||||
fn host_updating() {
|
||||
let url = Url::new("http://example.com", None).unwrap();
|
||||
let mut client = ClientBuilder::new::<_, &str>(url)
|
||||
.unwrap()
|
||||
.build::<&str>()
|
||||
.unwrap();
|
||||
let mut client = ClientBuilder::new(url).unwrap().build().unwrap();
|
||||
|
||||
// check that the url is set correctly
|
||||
let current_url = client.current_url();
|
||||
@@ -171,10 +168,10 @@ fn host_updating() {
|
||||
#[cfg(feature = "tunneling")]
|
||||
fn fronted_host_updating() {
|
||||
let url = Url::new("http://example.com", Some(vec!["http://front1.com"])).unwrap();
|
||||
let mut client = ClientBuilder::new::<_, &str>(url)
|
||||
let mut client = ClientBuilder::new(url)
|
||||
.unwrap()
|
||||
.with_fronting(crate::fronted::FrontPolicy::Always)
|
||||
.build::<&str>()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// check that the url is set correctly
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
use nym_http_api_client::registry;
|
||||
|
||||
// Create separate modules to avoid name conflicts
|
||||
mod config_early {
|
||||
use nym_http_api_client_macro::client_defaults;
|
||||
|
||||
client_defaults!(
|
||||
priority = -200;
|
||||
tcp_nodelay = true
|
||||
);
|
||||
}
|
||||
|
||||
mod config_late {
|
||||
use nym_http_api_client_macro::client_defaults;
|
||||
|
||||
client_defaults!(
|
||||
priority = 100;
|
||||
pool_idle_timeout = std::time::Duration::from_secs(90)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registry_collects_configs() {
|
||||
// Verify that configurations are being registered
|
||||
let count = registry::registered_config_count();
|
||||
// Should have at least the ones we registered above plus the default from lib.rs
|
||||
assert!(
|
||||
count >= 3,
|
||||
"Expected at least 3 registered configs, got {}",
|
||||
count
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_builder_applies_configs() {
|
||||
// Test that default_builder returns a configured builder
|
||||
let _builder = registry::default_builder();
|
||||
// The builder should have all configurations applied
|
||||
// We can't easily inspect the internals, but we verify it doesn't panic
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_client_works() {
|
||||
// Test that we can successfully build a client with all configurations
|
||||
let result = registry::build_client();
|
||||
assert!(result.is_ok(), "Failed to build client: {:?}", result.err());
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
fn test_inspect_configs() {
|
||||
// In debug mode, test that we can inspect registered configurations
|
||||
let configs = registry::inspect_registered_configs();
|
||||
|
||||
// Verify configs are sorted by priority
|
||||
for window in configs.windows(2) {
|
||||
assert!(window[0].0 <= window[1].0, "Configs not sorted by priority");
|
||||
}
|
||||
|
||||
// Verify we have configs at different priority levels
|
||||
let priorities: Vec<i32> = configs.iter().map(|(p, _)| *p).collect();
|
||||
assert!(
|
||||
priorities.iter().any(|&p| p < 0),
|
||||
"Expected negative priority configs"
|
||||
);
|
||||
assert!(
|
||||
priorities.iter().any(|&p| p >= 0),
|
||||
"Expected non-negative priority configs"
|
||||
);
|
||||
}
|
||||
@@ -55,6 +55,7 @@ pub struct ApiUrl {
|
||||
pub front_hosts: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ApiUrlConst<'a> {
|
||||
pub url: &'a str,
|
||||
pub front_hosts: Option<&'a [&'a str]>,
|
||||
@@ -188,8 +189,14 @@ impl NymNetworkDetails {
|
||||
),
|
||||
},
|
||||
nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API),
|
||||
nym_api_urls: None,
|
||||
nym_vpn_api_urls: None,
|
||||
nym_api_urls: Some(mainnet::NYM_APIS.iter().copied().map(Into::into).collect()),
|
||||
nym_vpn_api_urls: Some(
|
||||
mainnet::NYM_VPN_APIS
|
||||
.iter()
|
||||
.copied()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
[package]
|
||||
name = "nym-upgrade-mode-check"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
jwt-simple = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["rustls-tls"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
time = { workspace = true, features = ["serde"] }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-http-api-client = { path = "../http-api-client", default-features = false }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde", "naive_jwt"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
time = { workspace = true, features = ["macros"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,123 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::UpgradeModeCheckError;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_http_api_client::generate_user_agent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
pub struct UpgradeModeAttestation {
|
||||
#[serde(flatten)]
|
||||
pub content: UpgradeModeAttestationContent,
|
||||
|
||||
#[serde(with = "ed25519::bs58_ed25519_signature")]
|
||||
pub signature: ed25519::Signature,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename = "upgrade_mode")]
|
||||
pub struct UpgradeModeAttestationContent {
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub starting_time: OffsetDateTime,
|
||||
|
||||
#[serde(with = "ed25519::bs58_ed25519_pubkey")]
|
||||
pub attester_public_key: ed25519::PublicKey,
|
||||
}
|
||||
|
||||
impl UpgradeModeAttestation {
|
||||
pub fn verify(&self) -> bool {
|
||||
self.content
|
||||
.attester_public_key
|
||||
.verify(self.content.as_json(), &self.signature)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeAttestationContent {
|
||||
pub fn as_json(&self) -> String {
|
||||
// SAFETY: Serialize impl is valid and we have no non-string map keys
|
||||
#[allow(clippy::unwrap_used)]
|
||||
serde_json::to_string(&self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_new_attestation(key: &ed25519::PrivateKey) -> UpgradeModeAttestation {
|
||||
generate_new_attestation_with_starting_time(key, OffsetDateTime::now_utc())
|
||||
}
|
||||
|
||||
pub fn generate_new_attestation_with_starting_time(
|
||||
key: &ed25519::PrivateKey,
|
||||
starting_time: OffsetDateTime,
|
||||
) -> UpgradeModeAttestation {
|
||||
let content = UpgradeModeAttestationContent {
|
||||
starting_time,
|
||||
attester_public_key: key.into(),
|
||||
};
|
||||
UpgradeModeAttestation {
|
||||
signature: key.sign(content.as_json()),
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn attempt_retrieve(
|
||||
url: &str,
|
||||
) -> Result<Option<UpgradeModeAttestation>, UpgradeModeCheckError> {
|
||||
let retrieval_failure = |source| UpgradeModeCheckError::AttestationRetrievalFailure {
|
||||
url: url.to_string(),
|
||||
source,
|
||||
};
|
||||
|
||||
let attestation = reqwest::ClientBuilder::new()
|
||||
.user_agent(generate_user_agent!())
|
||||
.timeout(Duration::from_secs(5))
|
||||
.build()
|
||||
.map_err(retrieval_failure)?
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(retrieval_failure)?
|
||||
.json::<Option<UpgradeModeAttestation>>()
|
||||
.await
|
||||
.map_err(retrieval_failure)?;
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn upgrade_mode_attestation_serde_json() -> anyhow::Result<()> {
|
||||
// unix timestamp: 1629720000
|
||||
let starting_time = time::macros::datetime!(2021-08-23 12:00 UTC);
|
||||
|
||||
let key = ed25519::PrivateKey::from_bytes(&[
|
||||
108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248,
|
||||
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
|
||||
])?;
|
||||
|
||||
let attestation = generate_new_attestation_with_starting_time(&key, starting_time);
|
||||
|
||||
let attestation_json = serde_json::to_string(&attestation)?;
|
||||
let attestation_content_json = attestation.content.as_json();
|
||||
|
||||
let expected_attestation = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt","signature":"5rWUr2ypaDTtrMKegMP3tQkkZGFAuhNTnEVCVe5Azv6QqvLzoGdQiMkFmeyhDd1XSfoXpL9fFM58rsdA1kf4GYMM"}"#;
|
||||
let expected_content = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt"}"#;
|
||||
|
||||
assert_eq!(attestation_content_json, expected_content);
|
||||
assert_eq!(attestation_json, expected_attestation);
|
||||
|
||||
let recovered_attestation = serde_json::from_str(&attestation_json)?;
|
||||
assert_eq!(attestation, recovered_attestation);
|
||||
|
||||
let recovered_content = serde_json::from_str(&attestation_content_json)?;
|
||||
assert_eq!(attestation.content, recovered_content);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UpgradeModeCheckError {
|
||||
#[error("failed to decode jwt metadata")]
|
||||
TokenMetadataDecodeFailure { source: jwt_simple::Error },
|
||||
|
||||
#[error("the jwt metadata didn't contain explicit public key")]
|
||||
MissingTokenPublicKey,
|
||||
|
||||
#[error("the attached public key was not valid ed25519 public key")]
|
||||
MalformedEd25519PublicKey { source: Ed25519RecoveryError },
|
||||
|
||||
#[error("failed to verify the jwt: {source}")]
|
||||
JwtVerificationFailure { source: jwt_simple::Error },
|
||||
|
||||
#[error("failed to retrieve attestation from {url}:{source}")]
|
||||
AttestationRetrievalFailure { url: String, source: reqwest::Error },
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{UpgradeModeAttestation, UpgradeModeCheckError};
|
||||
use jwt_simple::claims::Claims;
|
||||
use jwt_simple::common::{KeyMetadata, VerificationOptions};
|
||||
use jwt_simple::prelude::{EdDSAKeyPairLike, EdDSAPublicKeyLike};
|
||||
use jwt_simple::token::Token;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
|
||||
// for now use static issuer such as "nym-credential-proxy"
|
||||
pub fn generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation: UpgradeModeAttestation,
|
||||
validity: Duration,
|
||||
keys: &ed25519::KeyPair,
|
||||
issuer: Option<&'static str>,
|
||||
) -> String {
|
||||
let claim = Claims::with_custom_claims(attestation, validity.into());
|
||||
let mut claim = if let Some(issuer) = issuer {
|
||||
claim.with_issuer(issuer)
|
||||
} else {
|
||||
claim
|
||||
};
|
||||
claim.create_nonce();
|
||||
|
||||
let md = KeyMetadata::default().with_public_key(keys.public_key().to_base58_string());
|
||||
|
||||
let mut jwt_keys = keys.to_jwt_compatible_keys();
|
||||
// SAFETY: trait impl for EdDSA is infallible
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_keys.attach_metadata(md).unwrap();
|
||||
|
||||
// SAFETY: our construction of the jwt is valid
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_keys.sign(claim).unwrap()
|
||||
}
|
||||
|
||||
pub fn validate_upgrade_mode_jwt(
|
||||
token: &str,
|
||||
expected_issuer: Option<&'static str>,
|
||||
) -> Result<UpgradeModeAttestation, UpgradeModeCheckError> {
|
||||
// for now, we completely ignore the validity of the pubkey (I know, I know).
|
||||
// that will be changed later on
|
||||
// so as a bypass we have to extract the claimed issuer from the jwt to verify against it
|
||||
let metadata = Token::decode_metadata(token)
|
||||
.map_err(|source| UpgradeModeCheckError::TokenMetadataDecodeFailure { source })?;
|
||||
|
||||
let pub_key = metadata
|
||||
.public_key()
|
||||
.ok_or(UpgradeModeCheckError::MissingTokenPublicKey)?;
|
||||
|
||||
let ed25519_pub_key = ed25519::PublicKey::from_base58_string(pub_key)
|
||||
.map_err(|source| UpgradeModeCheckError::MalformedEd25519PublicKey { source })?;
|
||||
|
||||
let mut opts = VerificationOptions::default();
|
||||
if let Some(issuer) = expected_issuer {
|
||||
opts.allowed_issuers = Some(HashSet::from_iter(vec![issuer.to_string()]));
|
||||
}
|
||||
|
||||
let attestation = ed25519_pub_key
|
||||
.to_jwt_compatible_key()
|
||||
.verify_token::<UpgradeModeAttestation>(token, Some(opts))
|
||||
.map_err(|source| UpgradeModeCheckError::JwtVerificationFailure { source })?
|
||||
.custom;
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::generate_new_attestation;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
|
||||
#[test]
|
||||
fn generate_and_validate_jwt() {
|
||||
let attestation_key = ed25519::PrivateKey::from_bytes(&[
|
||||
108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248,
|
||||
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
|
||||
])
|
||||
.unwrap();
|
||||
let jwt_key = ed25519::PrivateKey::from_bytes(&[
|
||||
152, 17, 144, 255, 213, 219, 246, 208, 109, 33, 100, 73, 1, 141, 32, 63, 141, 89, 167,
|
||||
2, 52, 215, 241, 219, 200, 18, 159, 241, 76, 111, 42, 32,
|
||||
])
|
||||
.unwrap();
|
||||
let keys = ed25519::KeyPair::from(jwt_key);
|
||||
|
||||
let attestation = generate_new_attestation(&attestation_key);
|
||||
let jwt_issuer = generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation,
|
||||
Duration::from_secs(60 * 60),
|
||||
&keys,
|
||||
Some("nym-credential-proxy"),
|
||||
);
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("nym-credential-proxy")).is_ok());
|
||||
|
||||
// we don't care about issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, None).is_ok());
|
||||
|
||||
// we expect another-issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("another-issuer")).is_err());
|
||||
|
||||
let jwt_no_issuer = generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation,
|
||||
Duration::from_secs(60 * 60),
|
||||
&keys,
|
||||
None,
|
||||
);
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, Some("nym-credential-proxy")).is_err());
|
||||
|
||||
// we don't care about issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, None).is_ok());
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) mod attestation;
|
||||
pub(crate) mod error;
|
||||
pub(crate) mod jwt;
|
||||
|
||||
pub use attestation::{
|
||||
attempt_retrieve, generate_new_attestation, generate_new_attestation_with_starting_time,
|
||||
UpgradeModeAttestation,
|
||||
};
|
||||
pub use error::UpgradeModeCheckError;
|
||||
pub use jwt::{generate_jwt_for_upgrade_mode_attestation, validate_upgrade_mode_jwt};
|
||||
@@ -25,3 +25,5 @@ url = { workspace = true }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
nym-task = { path = "../task" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
|
||||
@@ -10,7 +10,7 @@ use futures::StreamExt;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_validator_client::models::NymNodeDescription;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::net::SocketAddr;
|
||||
@@ -135,10 +135,15 @@ impl VerlocMeasurer {
|
||||
let mut api_endpoints = self.config.nym_api_urls.clone();
|
||||
api_endpoints.shuffle(&mut thread_rng());
|
||||
for api_endpoint in api_endpoints {
|
||||
let client = NymApiClient::new_with_user_agent(
|
||||
api_endpoint.clone(),
|
||||
self.config.user_agent.clone(),
|
||||
);
|
||||
let client = match nym_http_api_client::Client::builder(api_endpoint.clone())
|
||||
.and_then(|b| b.with_user_agent(self.config.user_agent.clone()).build())
|
||||
{
|
||||
Ok(c) => c,
|
||||
Err(err) => {
|
||||
warn!("failed to create client for {api_endpoint}: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match client.get_all_described_nodes().await {
|
||||
Ok(res) => return Some(res),
|
||||
Err(err) => {
|
||||
|
||||
@@ -34,6 +34,7 @@ nym-statistics-common = { path = "../../statistics" }
|
||||
nym-task = { path = "../../task" }
|
||||
nym-topology = { path = "../../topology", features = ["wasm-serde-types"] }
|
||||
nym-validator-client = { path = "../../client-libs/validator-client", default-features = false }
|
||||
nym-http-api-client = { path = "../../http-api-client" }
|
||||
wasm-utils = { path = "../utils" }
|
||||
wasm-storage = { path = "../storage" }
|
||||
|
||||
|
||||
@@ -37,6 +37,12 @@ pub enum WasmCoreError {
|
||||
source: ValidatorClientError,
|
||||
},
|
||||
|
||||
#[error("failed to query nym api: {source}")]
|
||||
NymApiQueryError {
|
||||
#[from]
|
||||
source: nym_validator_client::nym_api::error::NymAPIError,
|
||||
},
|
||||
|
||||
#[error("The provided wasm topology was invalid: {source}")]
|
||||
WasmTopologyError {
|
||||
#[from]
|
||||
|
||||
@@ -19,9 +19,9 @@ use nym_client_core::init::{
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_topology::wasm_helpers::WasmFriendlyNymTopology;
|
||||
use nym_topology::{NymTopology, RoutingNode};
|
||||
use nym_topology::{EpochRewardedSet, NymTopology, RoutingNode};
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use nym_validator_client::{NymApiClient, UserAgent};
|
||||
use nym_validator_client::{nym_api::NymApiClientExt, UserAgent};
|
||||
use rand::thread_rng;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
@@ -72,7 +72,16 @@ pub async fn current_network_topology_async(
|
||||
}
|
||||
};
|
||||
|
||||
let api_client = NymApiClient::new(url);
|
||||
let api_client = nym_http_api_client::Client::builder(url.clone())
|
||||
.map_err(|_err| WasmCoreError::MalformedUrl {
|
||||
raw: nym_api_url.to_string(),
|
||||
source: url::ParseError::EmptyHost,
|
||||
})?
|
||||
.build()
|
||||
.map_err(|_err| WasmCoreError::MalformedUrl {
|
||||
raw: nym_api_url.to_string(),
|
||||
source: url::ParseError::EmptyHost,
|
||||
})?;
|
||||
let rewarded_set = api_client.get_current_rewarded_set().await?;
|
||||
let mixnodes_res = api_client
|
||||
.get_all_basic_active_mixing_assigned_nodes_with_metadata()
|
||||
@@ -90,9 +99,14 @@ pub async fn current_network_topology_async(
|
||||
|
||||
let gateways = gateways_res.nodes;
|
||||
|
||||
let topology = NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
|
||||
.with_skimmed_nodes(&mixnodes)
|
||||
.with_skimmed_nodes(&gateways);
|
||||
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
|
||||
let topology = NymTopology::new(
|
||||
metadata.to_topology_metadata(),
|
||||
epoch_rewarded_set,
|
||||
Vec::new(),
|
||||
)
|
||||
.with_skimmed_nodes(&mixnodes)
|
||||
.with_skimmed_nodes(&gateways);
|
||||
|
||||
Ok(topology.into())
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ pub use nym_client_core::{
|
||||
pub use nym_gateway_client::{
|
||||
error::GatewayClientError, GatewayClient, GatewayClientConfig, GatewayConfig,
|
||||
};
|
||||
pub use nym_http_api_client::Client as ApiClient;
|
||||
pub use nym_sphinx::{
|
||||
addressing::{clients::Recipient, nodes::NodeIdentity},
|
||||
params::PacketType,
|
||||
@@ -29,7 +30,6 @@ pub use nym_sphinx::{
|
||||
pub use nym_statistics_common::clients::ClientStatsSender;
|
||||
pub use nym_task;
|
||||
pub use nym_topology::{HardcodedTopologyProvider, MixLayer, NymTopology, TopologyProvider};
|
||||
pub use nym_validator_client::nym_api::Client as ApiClient;
|
||||
pub use nym_validator_client::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient};
|
||||
// TODO: that's a very nasty import path. it should come from contracts instead!
|
||||
pub use nym_validator_client::client::IdentityKey;
|
||||
|
||||
@@ -7,16 +7,14 @@ use tracing::instrument;
|
||||
use nym_http_api_client::{ApiClient, Client, HttpClientError, NO_PARAMS};
|
||||
|
||||
use nym_wireguard_private_metadata_shared::{
|
||||
routes, Version, {ErrorResponse, Request, Response},
|
||||
routes, Version, {Request, Response},
|
||||
};
|
||||
|
||||
pub type WireguardMetadataApiClientError = HttpClientError<ErrorResponse>;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait WireguardMetadataApiClient: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn version(&self) -> Result<Version, WireguardMetadataApiClientError> {
|
||||
async fn version(&self) -> Result<Version, HttpClientError> {
|
||||
let version: u64 = self
|
||||
.get_json(
|
||||
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::VERSION],
|
||||
@@ -30,7 +28,7 @@ pub trait WireguardMetadataApiClient: ApiClient {
|
||||
async fn available_bandwidth(
|
||||
&self,
|
||||
request_body: &Request,
|
||||
) -> Result<Response, WireguardMetadataApiClientError> {
|
||||
) -> Result<Response, HttpClientError> {
|
||||
self.post_json(
|
||||
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::AVAILABLE],
|
||||
NO_PARAMS,
|
||||
@@ -40,10 +38,7 @@ pub trait WireguardMetadataApiClient: ApiClient {
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, request_body))]
|
||||
async fn topup_bandwidth(
|
||||
&self,
|
||||
request_body: &Request,
|
||||
) -> Result<Response, WireguardMetadataApiClientError> {
|
||||
async fn topup_bandwidth(&self, request_body: &Request) -> Result<Response, HttpClientError> {
|
||||
self.post_json(
|
||||
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::TOPUP],
|
||||
NO_PARAMS,
|
||||
|
||||
@@ -13,7 +13,7 @@ mod tests {
|
||||
use nym_wireguard_private_metadata_server::{
|
||||
AppState, PeerControllerTransceiver, RouterBuilder,
|
||||
};
|
||||
use nym_wireguard_private_metadata_shared::{latest, v0, v1, ErrorResponse};
|
||||
use nym_wireguard_private_metadata_shared::{latest, v0, v1};
|
||||
use tokio::{net::TcpListener, sync::mpsc};
|
||||
|
||||
pub(crate) const VERIFIER_AVAILABLE_BANDWIDTH: i64 = 42;
|
||||
@@ -140,7 +140,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
|
||||
Client::new_url(addr.to_string(), None).unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -15,7 +15,6 @@ pub(crate) mod test {
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_wireguard::{peer_controller::PeerControlRequest, CONTROL_CHANNEL_SIZE};
|
||||
use nym_wireguard_private_metadata_server::PeerControllerTransceiver;
|
||||
use nym_wireguard_private_metadata_shared::ErrorResponse;
|
||||
use nym_wireguard_private_metadata_shared::{
|
||||
v0 as latest, AxumErrorResponse, AxumResult, Construct, Extract, Request, Response,
|
||||
};
|
||||
@@ -141,6 +140,6 @@ pub(crate) mod test {
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
|
||||
Client::new_url(addr.to_string(), None).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
//! ```
|
||||
|
||||
use crate::error::ZulipClientError;
|
||||
use crate::message::{DirectMessage, SendMessageResponse, SendableMessage, StreamMessage};
|
||||
use crate::message::{SendMessageResponse, SendableMessage};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use reqwest::{header, Method, RequestBuilder};
|
||||
@@ -92,20 +92,6 @@ impl Client {
|
||||
.map_err(|source| ZulipClientError::RequestDecodeFailure { source })
|
||||
}
|
||||
|
||||
pub async fn send_direct_message(
|
||||
&self,
|
||||
msg: impl Into<DirectMessage>,
|
||||
) -> Result<SendMessageResponse, ZulipClientError> {
|
||||
self.send_message(msg.into()).await
|
||||
}
|
||||
|
||||
pub async fn send_channel_message(
|
||||
&self,
|
||||
msg: impl Into<StreamMessage>,
|
||||
) -> Result<SendMessageResponse, ZulipClientError> {
|
||||
self.send_message(msg.into()).await
|
||||
}
|
||||
|
||||
fn build_request(&self, method: Method, endpoint: &'static str) -> RequestBuilder {
|
||||
let url = format!("{}{endpoint}", self.server_url);
|
||||
trace!("posting to {url}");
|
||||
|
||||
@@ -22,7 +22,7 @@ pub enum SendMessageResponse {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum SendableMessageContent {
|
||||
@@ -40,7 +40,7 @@ pub enum SendableMessageContent {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct SendableMessage {
|
||||
#[serde(flatten)]
|
||||
@@ -117,17 +117,17 @@ impl StreamMessage {
|
||||
pub fn new(
|
||||
to: impl Into<ToChannel>,
|
||||
content: impl Into<String>,
|
||||
topic: impl IntoMaybeTopic,
|
||||
topic: Option<String>,
|
||||
) -> Self {
|
||||
StreamMessage {
|
||||
to: to.into().to_string(),
|
||||
topic: topic.into_maybe_topic(),
|
||||
topic,
|
||||
content: content.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_topic(to: impl Into<ToChannel>, content: impl Into<String>) -> Self {
|
||||
Self::new(to, content, None::<String>)
|
||||
Self::new(to, content, None)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -194,74 +194,22 @@ impl From<StreamMessage> for SendableMessageContent {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, U> From<(T, S, U)> for StreamMessage
|
||||
impl<T, S> From<(T, S, Option<S>)> for StreamMessage
|
||||
where
|
||||
T: Into<ToChannel>,
|
||||
S: Into<String>,
|
||||
U: IntoMaybeTopic,
|
||||
{
|
||||
fn from((to, content, topic): (T, S, U)) -> Self {
|
||||
StreamMessage::new(to, content, topic)
|
||||
fn from((to, content, topic): (T, S, Option<S>)) -> Self {
|
||||
StreamMessage::new(to, content, topic.map(Into::into))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> From<(T, S)> for StreamMessage
|
||||
impl<T, S> From<(T, S, Option<S>)> for SendableMessage
|
||||
where
|
||||
T: Into<ToChannel>,
|
||||
S: Into<String>,
|
||||
{
|
||||
fn from((to, content): (T, S)) -> Self {
|
||||
StreamMessage::no_topic(to, content)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, U> From<(T, S, U)> for SendableMessage
|
||||
where
|
||||
T: Into<ToChannel>,
|
||||
S: Into<String>,
|
||||
U: IntoMaybeTopic,
|
||||
{
|
||||
fn from(inner: (T, S, U)) -> Self {
|
||||
fn from(inner: (T, S, Option<S>)) -> Self {
|
||||
StreamMessage::from(inner).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoMaybeTopic {
|
||||
fn into_maybe_topic(self) -> Option<String>;
|
||||
}
|
||||
|
||||
impl<S> IntoMaybeTopic for &Option<S>
|
||||
where
|
||||
S: Into<String> + Clone,
|
||||
{
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
self.clone().map(|s| s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> IntoMaybeTopic for Option<S>
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
self.map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoMaybeTopic for String {
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoMaybeTopic for &String {
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoMaybeTopic for &str {
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
Some(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1
-1
@@ -744,7 +744,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -52,7 +52,7 @@ impl TestSetup {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_ctx(&self) -> QueryCtx {
|
||||
pub fn query_ctx(&self) -> QueryCtx<'_> {
|
||||
QueryCtx::from((self.deps.as_ref(), self.env.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,13 +73,13 @@ impl TestSetupSimple {
|
||||
message_info(&admin, &[])
|
||||
}
|
||||
|
||||
pub fn execute_ctx(&mut self, sender: MessageInfo) -> ExecCtx {
|
||||
pub fn execute_ctx(&mut self, sender: MessageInfo) -> ExecCtx<'_> {
|
||||
let env = self.env.clone();
|
||||
ExecCtx::from((self.deps.as_mut(), env, sender))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn query_ctx(&self) -> QueryCtx {
|
||||
pub fn query_ctx(&self) -> QueryCtx<'_> {
|
||||
QueryCtx::from((self.deps.as_ref(), self.env.clone()))
|
||||
}
|
||||
|
||||
|
||||
@@ -139,9 +139,9 @@ mod tests {
|
||||
|
||||
fn add_dummy_mixes_with_delegations(test: &mut TestSetup, delegators: usize, mixes: usize) {
|
||||
for i in 0..mixes {
|
||||
let mix_id = test.add_legacy_mixnode(&test.make_addr(format!("mix-owner{}", i)), None);
|
||||
let mix_id = test.add_legacy_mixnode(&test.make_addr(format!("mix-owner{i}")), None);
|
||||
for delegator in 0..delegators {
|
||||
let name = &test.make_addr(format!("delegator{}", delegator));
|
||||
let name = &test.make_addr(format!("delegator{delegator}"));
|
||||
test.add_immediate_delegation(name, 100_000_000u32, mix_id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ mod tests {
|
||||
|
||||
let amount1 = coin(100_000_000, TEST_COIN_DENOM);
|
||||
|
||||
let sender1 = message_info(owner, &[amount1.clone()]);
|
||||
let sender1 = message_info(owner, std::slice::from_ref(&amount1));
|
||||
|
||||
try_delegate_to_node(test.deps_mut(), env.clone(), sender1, mix_id).unwrap();
|
||||
|
||||
|
||||
@@ -503,8 +503,8 @@ pub(crate) mod tests {
|
||||
storage,
|
||||
id,
|
||||
&UnbondedMixnode {
|
||||
identity_key: format!("dummy{}", id),
|
||||
owner: Addr::unchecked(format!("dummy{}", id)),
|
||||
identity_key: format!("dummy{id}"),
|
||||
owner: Addr::unchecked(format!("dummy{id}")),
|
||||
proxy: None,
|
||||
unbonding_height: 123,
|
||||
},
|
||||
@@ -570,7 +570,7 @@ pub(crate) mod tests {
|
||||
storage,
|
||||
id,
|
||||
&UnbondedMixnode {
|
||||
identity_key: format!("dummy{}", id),
|
||||
identity_key: format!("dummy{id}"),
|
||||
owner: owner.clone(),
|
||||
proxy: None,
|
||||
unbonding_height: 123,
|
||||
@@ -817,7 +817,7 @@ pub(crate) mod tests {
|
||||
id,
|
||||
&UnbondedMixnode {
|
||||
identity_key: identity.to_string(),
|
||||
owner: Addr::unchecked(format!("dummy{}", id)),
|
||||
owner: Addr::unchecked(format!("dummy{id}")),
|
||||
proxy: None,
|
||||
unbonding_height: 123,
|
||||
},
|
||||
|
||||
@@ -165,9 +165,9 @@ pub mod test_helpers {
|
||||
#[track_caller]
|
||||
pub fn assert_eq_with_leeway(a: Uint128, b: Uint128, leeway: Uint128) {
|
||||
if a > b {
|
||||
assert!(a - b <= leeway, "{} != {}", a, b)
|
||||
assert!(a - b <= leeway, "{a} != {b}")
|
||||
} else {
|
||||
assert!(b - a <= leeway, "{} != {}", a, b)
|
||||
assert!(b - a <= leeway, "{a} != {b}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,9 +175,9 @@ pub mod test_helpers {
|
||||
pub fn assert_decimals(a: Decimal, b: Decimal) {
|
||||
let epsilon = Decimal::from_ratio(1u128, 100_000_000u128);
|
||||
if a > b {
|
||||
assert!(a - b < epsilon, "{} != {}", a, b)
|
||||
assert!(a - b < epsilon, "{a} != {b}")
|
||||
} else {
|
||||
assert!(b - a < epsilon, "{} != {}", a, b)
|
||||
assert!(b - a < epsilon, "{a} != {b}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1699,7 +1699,7 @@ pub mod test_helpers {
|
||||
deps.branch(),
|
||||
&env,
|
||||
env.block.height,
|
||||
Addr::unchecked(format!("owner{}", i)),
|
||||
Addr::unchecked(format!("owner{i}")),
|
||||
mix_id,
|
||||
tests::fixtures::good_mixnode_pledge().pop().unwrap(),
|
||||
)
|
||||
@@ -1713,7 +1713,7 @@ pub mod test_helpers {
|
||||
n: usize,
|
||||
) {
|
||||
for i in 0..n {
|
||||
add_unbonded_mixnode(&mut rng, deps.branch(), None, &addr(format!("owner{}", i)));
|
||||
add_unbonded_mixnode(&mut rng, deps.branch(), None, &addr(format!("owner{i}")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1765,7 +1765,7 @@ pub mod test_helpers {
|
||||
id,
|
||||
&UnbondedMixnode {
|
||||
identity_key: identity_key
|
||||
.unwrap_or(&*format!("identity{}", id))
|
||||
.unwrap_or(&*format!("identity{id}"))
|
||||
.to_string(),
|
||||
owner: Addr::unchecked(owner),
|
||||
proxy: None,
|
||||
|
||||
@@ -131,7 +131,7 @@ impl PreInitContract {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deps(&self) -> Deps {
|
||||
pub(crate) fn deps(&self) -> Deps<'_> {
|
||||
Deps {
|
||||
storage: &self.storage,
|
||||
api: &self.api,
|
||||
@@ -139,7 +139,7 @@ impl PreInitContract {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deps_mut(&mut self) -> DepsMut {
|
||||
pub(crate) fn deps_mut(&mut self) -> DepsMut<'_> {
|
||||
DepsMut {
|
||||
storage: &mut self.storage,
|
||||
api: &self.api,
|
||||
@@ -147,7 +147,7 @@ impl PreInitContract {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn querier(&self) -> QuerierWrapper {
|
||||
pub(crate) fn querier(&self) -> QuerierWrapper<'_> {
|
||||
self.tester_builder.querier()
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ mod tests {
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
message_info(&admin, &[amount.clone()]),
|
||||
message_info(&admin, std::slice::from_ref(&amount)),
|
||||
msg,
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"mixmining_reserve": {
|
||||
"denom": "unym",
|
||||
"amount": "182883243257647"
|
||||
"amount": "184339094131786"
|
||||
},
|
||||
"vesting_tokens": {
|
||||
"denom": "unym",
|
||||
@@ -13,6 +13,6 @@
|
||||
},
|
||||
"circulating_supply": {
|
||||
"denom": "unym",
|
||||
"amount": "817116756742353"
|
||||
"amount": "815660905868214"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
817_116_756
|
||||
815_660_905
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
5_080
|
||||
5_120
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.77%
|
||||
0.74%
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
37.305
|
||||
38.479
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
250_000
|
||||
1_040_817
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
7.34%
|
||||
30.63%
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
60_000_000
|
||||
249_796_152
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
60_000_000
|
||||
249_796_152
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
| **Item** | **Description** | **Amount in NYM** |
|
||||
|:-------------------|:------------------------------------------------------|--------------------:|
|
||||
| Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 182_883_243 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 184_339_094 |
|
||||
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 817_116_756 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 250_000 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 815_660_905 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_040_817 |
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"interval": {
|
||||
"reward_pool": "182883243257647.891553460395608456",
|
||||
"staking_supply": "60000000000000",
|
||||
"staking_supply_scale_factor": "0.07342892",
|
||||
"epoch_reward_budget": "5080090090.490219209818344322",
|
||||
"stake_saturation_point": "250000000000",
|
||||
"reward_pool": "184339094131786.145886263466367605",
|
||||
"staking_supply": "249796152422140.492822331813424919",
|
||||
"staking_supply_scale_factor": "0.30625",
|
||||
"epoch_reward_budget": "5120530392.54961516350731851",
|
||||
"stake_saturation_point": "1040817301758.918720093049222603",
|
||||
"sybil_resistance": "0.3",
|
||||
"active_set_work_factor": "10",
|
||||
"interval_pool_emission": "0.02"
|
||||
},
|
||||
"rewarded_set": {
|
||||
"entry_gateways": 80,
|
||||
"exit_gateways": 100,
|
||||
"mixnodes": 60,
|
||||
"entry_gateways": 50,
|
||||
"exit_gateways": 70,
|
||||
"mixnodes": 120,
|
||||
"standby": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
Thursday, September 4th 2025, 09:12:25 UTC
|
||||
Friday, August 22nd 2025, 10:15:08 UTC
|
||||
@@ -58,8 +58,8 @@ Options:
|
||||
Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false]
|
||||
--wireguard-bind-address <WIREGUARD_BIND_ADDRESS>
|
||||
Socket address this node will use for binding its wireguard interface. default: `[::]:51822` [env: NYMNODE_WG_BIND_ADDRESS=]
|
||||
--wireguard-announced-port <WIREGUARD_ANNOUNCED_PORT>
|
||||
Port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
|
||||
--wireguard-tunnel-announced-port <WIREGUARD_TUNNEL_ANNOUNCED_PORT>
|
||||
Tunnel port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
|
||||
--wireguard-private-network-prefix <WIREGUARD_PRIVATE_NETWORK_PREFIX>
|
||||
The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=]
|
||||
--verloc-bind-address <VERLOC_BIND_ADDRESS>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
| [AmeriNoc](https://www.amerinoc.com) | USA | Yes | nan | nan | 07/2025 |
|
||||
| [BitLaunch](https://bitlaunch.io) | Canada, USA, UK | No | Yes | Expensive. Digial Ocean through BitLanch has IPv6 | 05/2024 |
|
||||
| [Cherry Servers](https://www.cherryservers.com) | Lithuania, Netherlands, USA, Singapore | No | Yes | Issued IP doesn’t match the location offered by the provider. | 05/2024 |
|
||||
| [Colocall](https://www.colocall.net/) | Ukraine | Yes | nan | nan | 07/2025 |
|
||||
| [Colocall](https://www.colocall.net/) | Ukraine | Yes | nan | 07/2025 | nan |
|
||||
| [DataPacket](https://www.datapacket.com/pricing) | NL, GR, SK, BE, RO, HU, DK, IE, DE, UA, PT, GB, ES, FR, IT, NO, CZ, BG, SE, AT, PL, HR, CH, USA, CO, AR, PE, MX, CL, TR, ZA, NG, IL, HK, AU, SG, JP | Yes | nan | nan | 07/2025 |
|
||||
| [Dataclub](https://www.dataclub.eu/) | Latvia, Sweden, Netherlands | Yes | nan | nan | 07/2027 |
|
||||
| [Flokinet](https://flokinet.is) | Netherlands, Iceland, Romania,France | Yes, needs a ticket and custom setup | yes, including XMR | Very slow customer support | 05/2024 |
|
||||
@@ -13,7 +13,6 @@
|
||||
| [HostSailor](https://hostsailor.com) | USA | Yes, based on ticket | Yes | The IPv6 setup needs custom research and is not documented | 05/2024 |
|
||||
| [Hostiko](https://hostiko.com.ua) | Ukraine, Germany | Yes, on by default | Yes | Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket. | 07/2024 |
|
||||
| [Hostinger](https://hostinger.com) | France, Lithuania, India, USA, Brazil | Yes, out of the box | Yes | Not fast enough, Crypto payments must be done per each server monthly or annually. | 07/2025 |
|
||||
| [Hostraha](https://hostraha.com) | Kenya and other African countries | No, but advertised otherwise | Yes, USDT TRC20 | Don't recommend. Unresponsive technical and billing support, never provided IPv6 even though advertised and paid for. When VPS cancelled, company still tried to bill the credit card on file multiple times. | 08/2025 |
|
||||
| [Hostroyale](https://hostroyale.com/hosting/dedicated-server/) | Various countries with different pricing | nan | Yes | nan | 07/2025 |
|
||||
| [Hostslick](https://hostslick.com) | Netherlands, Germany | Yes, on by default | Yes | Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node | 07/2024 |
|
||||
| [Incognet](https://incognet.io) | Netherlands and USA | Yes, on by default | Yes | They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits | 07/2024 |
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
[Zenlayer](https://www.zenlayer.com/bare-metal/), [advertised over 50 locations](50+ https://www.zenlayer.com/global-network),,,,07/2025
|
||||
[PrivateLayer](https://privatelayer.com),Swiss,Yes,Yes,Slow customer response,07/2025
|
||||
[AmeriNoc](https://www.amerinoc.com),USA,Yes,,,07/2025
|
||||
[Colocall](https://www.colocall.net/),Ukraine,Yes,,,07/2025
|
||||
[Colocall](https://www.colocall.net/),Ukraine,Yes,,07/2025,
|
||||
[Incognet](https://incognet.io/kansas-city-dedicated-servers),"USA, Netherlands",Yes,,,07/2025
|
||||
[FranTech](https://my.frantech.ca),USA,Yes,,,07/2025
|
||||
[Psychz](https://www.psychz.net),"US, UK, Brazil, Japan, Russia, South Africa and many more",Yes,,,07/2025
|
||||
@@ -41,4 +41,4 @@
|
||||
[Dataclub](https://www.dataclub.eu/),"Latvia, Sweden, Netherlands",Yes,,,07/2027
|
||||
[Privex](https://www.privex.io/tor-exit-policy/),"USA, Germany, Sweden",Yes,Yes,,07/2025
|
||||
[Svea](https://svea.net/vps),Sweden,Yes,,,07/2025
|
||||
[Hostraha](https://hostraha.com),Kenya and other African countries,"No, but advertised otherwise","Yes, USDT TRC20","Don't recommend. Unresponsive technical and billing support, never provided IPv6 even though advertised and paid for. When VPS cancelled, company still tried to bill the credit card on file multiple times.",08/2025
|
||||
[Hostraha](https://hostraha.com),"Kenya and other African countries", "No, but advertised otherwise", "Yes, USDT TRC20", "Don't recommend. Unresponsive technical and billing support, never provided IPv6 even though advertised and paid for. When VPS cancelled, company still tried to bill the credit card on file multiple times.", 08/2025
|
||||
|
||||
|
+8
-2
@@ -20,16 +20,22 @@ use nym_sdk::mixnet;
|
||||
use nym_sdk::mixnet::MixnetMessageSender;
|
||||
use nym_topology::provider_trait::{async_trait, TopologyProvider};
|
||||
use nym_topology::{nym_topology_from_detailed, NymTopology};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use url::Url;
|
||||
|
||||
struct MyTopologyProvider {
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
validator_client: nym_http_api_client::Client,
|
||||
}
|
||||
|
||||
impl MyTopologyProvider {
|
||||
fn new(nym_api_url: Url) -> MyTopologyProvider {
|
||||
let validator_client = nym_http_api_client::Client::builder::<_, nym_validator_client::models::RequestError>(nym_api_url)
|
||||
.expect("Failed to create API client builder")
|
||||
.build::<nym_validator_client::models::RequestError>()
|
||||
.expect("Failed to build API client");
|
||||
|
||||
MyTopologyProvider {
|
||||
validator_client: nym_validator_client::client::NymApiClient::new(nym_api_url),
|
||||
validator_client,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ nym-task = { path = "../common/task" }
|
||||
nym-topology = { path = "../common/topology" }
|
||||
nym-api-requests = { path = "nym-api-requests" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
nym-http-api-client = { path = "../common/http-api-client" }
|
||||
nym-bin-common = { path = "../common/bin-common", features = ["output_format", "openapi", "basic_tracing"] }
|
||||
nym-node-tester-utils = { path = "../common/node-tester-utils" }
|
||||
nym-node-requests = { path = "../nym-node/nym-node-requests" }
|
||||
|
||||
@@ -8,7 +8,9 @@ use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_mixnet_contract_common::reward_params::Performance;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT};
|
||||
use nym_network_defaults::{
|
||||
DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT, WG_TUNNEL_PORT,
|
||||
};
|
||||
use nym_node_requests::api::v1::authenticator::models::Authenticator;
|
||||
use nym_node_requests::api::v1::gateway::models::Wireguard;
|
||||
use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
|
||||
@@ -313,11 +315,20 @@ impl From<Authenticator> for AuthenticatorDetails {
|
||||
pub struct WireguardDetails {
|
||||
// NOTE: the port field is deprecated in favour of tunnel_port
|
||||
pub port: u16,
|
||||
#[serde(default = "default_tunnel_port")]
|
||||
pub tunnel_port: u16,
|
||||
#[serde(default = "default_metadata_port")]
|
||||
pub metadata_port: u16,
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
fn default_tunnel_port() -> u16 {
|
||||
WG_TUNNEL_PORT
|
||||
}
|
||||
fn default_metadata_port() -> u16 {
|
||||
WG_METADATA_PORT
|
||||
}
|
||||
|
||||
// works for current simple case.
|
||||
impl From<Wireguard> for WireguardDetails {
|
||||
fn from(value: Wireguard) -> Self {
|
||||
|
||||
@@ -13,6 +13,7 @@ use nym_dkg::Threshold;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITLE;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use thiserror::Error;
|
||||
@@ -46,6 +47,9 @@ pub enum EcashError {
|
||||
#[error("coconut api query failure: {0}")]
|
||||
CoconutApiError(#[from] EcashApiError),
|
||||
|
||||
#[error("nym api query failure: {0}")]
|
||||
NymApiError(#[from] NymAPIError),
|
||||
|
||||
#[error(transparent)]
|
||||
SerdeJsonError(#[from] serde_json::Error),
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITL
|
||||
use nym_ecash_time::{ecash_default_expiration_date, ecash_today_date};
|
||||
use nym_task::TaskClient;
|
||||
use nym_ticketbooks_merkle::{IssuedTicketbook, IssuedTicketbooksFullMerkleProof, MerkleLeaf};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::{thread_rng, RngCore};
|
||||
|
||||
@@ -67,7 +67,7 @@ use nym_validator_client::nym_api::routes::{
|
||||
use nym_validator_client::nyxd::cosmwasm_client::logs::Log;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
use nym_validator_client::nyxd::{AccountId, ExecTxResult, Fee, Hash, TxResponse};
|
||||
use nym_validator_client::{EcashApiClient, NymApiClient};
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
@@ -1148,7 +1148,10 @@ impl DummyCommunicationChannel {
|
||||
cosmos_address: AccountId,
|
||||
) -> Self {
|
||||
let client = EcashApiClient {
|
||||
api_client: NymApiClient::new("http://localhost:1234".parse().unwrap()),
|
||||
api_client: nym_http_api_client::Client::new(
|
||||
"http://localhost:1234".parse().unwrap(),
|
||||
None,
|
||||
),
|
||||
verification_key: aggregated_verification_key,
|
||||
node_id: 1,
|
||||
cosmos_address,
|
||||
|
||||
@@ -26,7 +26,10 @@ pub fn new_client(
|
||||
base_url: impl IntoUrl,
|
||||
bearer_token: impl Into<String>,
|
||||
) -> Result<VpnApiClient, VpnApiClientError> {
|
||||
let url = base_url.into_url()?;
|
||||
let raw = base_url.as_str().to_string();
|
||||
let url = base_url
|
||||
.into_url()
|
||||
.map_err(|source| VpnApiClientError::MalformedUrl { raw, source })?;
|
||||
Ok(VpnApiClient {
|
||||
inner: Client::builder(url)?
|
||||
.with_user_agent(format!(
|
||||
@@ -97,9 +100,14 @@ impl NymVpnApiClient for VpnApiClient {
|
||||
{
|
||||
let req = self
|
||||
.inner
|
||||
.create_get_request(path, NO_PARAMS)
|
||||
.create_get_request(path, NO_PARAMS)?
|
||||
.bearer_auth(&self.bearer_token)
|
||||
.send();
|
||||
.build()
|
||||
.map_err(VpnApiClientError::reqwest_client_build_error)?;
|
||||
|
||||
let url = req.url().clone();
|
||||
|
||||
let req = reqwest::Client::new().execute(req);
|
||||
|
||||
// the only reason for that target lock is so that I could call this method from an ephemeral test
|
||||
// running in non-wasm mode (since I wanted to use tokio)
|
||||
@@ -110,8 +118,9 @@ impl NymVpnApiClient for VpnApiClient {
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = req.await?;
|
||||
|
||||
let res = req
|
||||
.await
|
||||
.map_err(|source| VpnApiClientError::request_send_error(url, source))?;
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
@@ -129,9 +138,14 @@ impl NymVpnApiClient for VpnApiClient {
|
||||
{
|
||||
let req = self
|
||||
.inner
|
||||
.create_post_request(path, params, json_body)
|
||||
.create_post_request(path, params, json_body)?
|
||||
.bearer_auth(&self.bearer_token)
|
||||
.send();
|
||||
.build()
|
||||
.map_err(VpnApiClientError::reqwest_client_build_error)?;
|
||||
|
||||
let url = req.url().clone();
|
||||
|
||||
let req = reqwest::Client::new().execute(req);
|
||||
|
||||
// the only reason for that target lock is so that I could call this method from an ephemeral test
|
||||
// running in non-wasm mode (since I wanted to use tokio)
|
||||
@@ -142,7 +156,9 @@ impl NymVpnApiClient for VpnApiClient {
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = req.await?;
|
||||
let res = req
|
||||
.await
|
||||
.map_err(|source| VpnApiClientError::request_send_error(url, source))?;
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
};
|
||||
use nym_credentials_interface::Base58;
|
||||
use nym_validator_client::ecash::BlindSignRequestBody;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use nym_ecash_signer_check::SignerCheckError;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nym_api::{error::NymAPIError, EpochId};
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
@@ -71,6 +71,12 @@ pub enum CredentialProxyError {
|
||||
source: EcashApiError,
|
||||
},
|
||||
|
||||
#[error("Nym API request failed: {source}")]
|
||||
NymApiFailure {
|
||||
#[from]
|
||||
source: NymAPIError,
|
||||
},
|
||||
|
||||
#[error("Compact ecash internal error: {0}")]
|
||||
CompactEcashInternalError(#[from] nym_compact_ecash::error::CompactEcashError),
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
DkgQueryClient, NymContractsProvider, PagedDkgQueryClient,
|
||||
|
||||
@@ -40,3 +40,5 @@ nym-sphinx = { path = "../common/nymsphinx" }
|
||||
nym-topology = { path = "../common/topology" }
|
||||
nym-types = { path = "../common/types" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
nym-http-api-client = { path = "../common/http-api-client" }
|
||||
nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
|
||||
@@ -6,12 +6,15 @@ use log::{info, warn};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_client_core::config::ForgetMe;
|
||||
use nym_crypto::asymmetric::ed25519::PrivateKey;
|
||||
use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
use nym_network_defaults::setup_env;
|
||||
use nym_network_defaults::var_names::NYM_API;
|
||||
use nym_sdk::mixnet::{self, MixnetClient};
|
||||
use nym_sphinx::chunking::monitoring;
|
||||
use nym_topology::provider_trait::ToTopologyMetadata;
|
||||
use nym_topology::{HardcodedTopologyProvider, NymTopology};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::UserAgent;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::sync::LazyLock;
|
||||
@@ -160,10 +163,9 @@ async fn nym_topology_from_env() -> anyhow::Result<NymTopology> {
|
||||
let api_url = std::env::var(NYM_API)?;
|
||||
|
||||
info!("Generating topology from {api_url}");
|
||||
let client = nym_validator_client::client::NymApiClient::new_with_user_agent(
|
||||
api_url.parse()?,
|
||||
bin_info!(),
|
||||
);
|
||||
let client = nym_http_api_client::Client::builder(api_url)?
|
||||
.with_user_agent(UserAgent::from(bin_info!()))
|
||||
.build()?;
|
||||
|
||||
let rewarded_set = client.get_current_rewarded_set().await?;
|
||||
|
||||
@@ -172,10 +174,15 @@ async fn nym_topology_from_env() -> anyhow::Result<NymTopology> {
|
||||
let nodes = nodes_response.nodes;
|
||||
let metadata = nodes_response.metadata;
|
||||
|
||||
Ok(
|
||||
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
|
||||
.with_skimmed_nodes(&nodes),
|
||||
// Convert RewardedSetResponse to EpochRewardedSet which can then be converted to CachedEpochRewardedSet
|
||||
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
|
||||
|
||||
Ok(NymTopology::new(
|
||||
metadata.to_topology_metadata(),
|
||||
epoch_rewarded_set,
|
||||
Vec::new(),
|
||||
)
|
||||
.with_skimmed_nodes(&nodes))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n date_utc as \"date_utc!\",\n SUM(total_stake) as \"total_stake!: i64\",\n SUM(packets_received) as \"total_packets_received!: i64\",\n SUM(packets_sent) as \"total_packets_sent!: i64\",\n SUM(packets_dropped) as \"total_packets_dropped!: i64\"\n FROM (\n SELECT\n date_utc,\n n.total_stake,\n n.packets_received,\n n.packets_sent,\n n.packets_dropped\n FROM nym_node_daily_mixing_stats n\n UNION ALL\n SELECT\n m.date_utc,\n m.total_stake,\n m.packets_received,\n m.packets_sent,\n m.packets_dropped\n FROM mixnode_daily_stats m\n LEFT JOIN nym_node_daily_mixing_stats ON m.mix_id = nym_node_daily_mixing_stats.node_id\n WHERE nym_node_daily_mixing_stats.node_id IS NULL\n )\n GROUP BY date_utc\n ORDER BY date_utc ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "date_utc!",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "total_stake!: i64",
|
||||
"type_info": "Numeric"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "total_packets_received!: i64",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "total_packets_sent!: i64",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "total_packets_dropped!: i64",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "124d45b9604439584650f401607c46bdbd162c7c689f74fe9ddfdfd48f5ddc07"
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n date_utc as \"date_utc!\",\n SUM(total_stake)::BIGINT as \"total_stake!: i64\",\n SUM(packets_received)::BIGINT as \"total_packets_received!: i64\",\n SUM(packets_sent)::BIGINT as \"total_packets_sent!: i64\",\n SUM(packets_dropped)::BIGINT as \"total_packets_dropped!: i64\"\n FROM (\n SELECT\n date_utc,\n n.total_stake,\n n.packets_received,\n n.packets_sent,\n n.packets_dropped\n FROM nym_node_daily_mixing_stats n\n UNION ALL\n SELECT\n m.date_utc,\n m.total_stake,\n m.packets_received,\n m.packets_sent,\n m.packets_dropped\n FROM mixnode_daily_stats m\n LEFT JOIN nym_node_daily_mixing_stats ON m.mix_id = nym_node_daily_mixing_stats.node_id\n WHERE nym_node_daily_mixing_stats.node_id IS NULL\n )\n GROUP BY date_utc\n ORDER BY date_utc ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "date_utc!",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "total_stake!: i64",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "total_packets_received!: i64",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "total_packets_sent!: i64",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "total_packets_dropped!: i64",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "a19da2073bc691fe5cd2119a9527c16d6a8806ba50dcb759ac705b1a19288ca9"
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-api"
|
||||
version = "3.3.2"
|
||||
version = "3.3.1"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -108,10 +108,10 @@ pub(crate) async fn get_daily_stats(pool: &DbPool) -> anyhow::Result<Vec<DailySt
|
||||
r#"
|
||||
SELECT
|
||||
date_utc as "date_utc!",
|
||||
SUM(total_stake)::BIGINT as "total_stake!: i64",
|
||||
SUM(packets_received)::BIGINT as "total_packets_received!: i64",
|
||||
SUM(packets_sent)::BIGINT as "total_packets_sent!: i64",
|
||||
SUM(packets_dropped)::BIGINT as "total_packets_dropped!: i64"
|
||||
SUM(total_stake) as "total_stake!: i64",
|
||||
SUM(packets_received) as "total_packets_received!: i64",
|
||||
SUM(packets_sent) as "total_packets_sent!: i64",
|
||||
SUM(packets_dropped) as "total_packets_dropped!: i64"
|
||||
FROM (
|
||||
SELECT
|
||||
date_utc,
|
||||
|
||||
@@ -5,11 +5,11 @@ use nym_node_requests::api::{client::NymNodeApiClientExt, v1::metrics::models::S
|
||||
use nym_validator_client::{
|
||||
client::{NodeId, NymNodeDetails},
|
||||
models::{DescribedNodeType, NymNodeDescription},
|
||||
NymApiClient,
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use nym_statistics_common::types::SessionType;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use std::collections::HashMap;
|
||||
use tokio::time::Duration;
|
||||
use tracing::instrument;
|
||||
@@ -60,13 +60,11 @@ async fn run(
|
||||
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
||||
.no_hickory_dns()
|
||||
.with_timeout(nym_api_client_timeout)
|
||||
.build::<&str>()?;
|
||||
|
||||
let api_client = NymApiClient::from(nym_api);
|
||||
.build()?;
|
||||
|
||||
//SW TBC what nodes exactly need to be scraped, the skimmed node endpoint seems to return more nodes
|
||||
let bonded_nodes = api_client.get_all_bonded_nym_nodes().await?;
|
||||
let all_nodes = api_client.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet
|
||||
let bonded_nodes = nym_api.get_all_bonded_nym_nodes().await?;
|
||||
let all_nodes = nym_api.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet
|
||||
tracing::debug!("Fetched {} total nodes", all_nodes.len());
|
||||
|
||||
let mut nodes_to_scrape: HashMap<NodeId, MetricsScrapingData> = bonded_nodes
|
||||
|
||||
@@ -19,7 +19,7 @@ use nym_validator_client::{
|
||||
use nym_validator_client::{
|
||||
nym_nodes::{NodeRole, SkimmedNode},
|
||||
nyxd::{contract_traits::PagedMixnetQueryClient, AccountId},
|
||||
NymApiClient, QueryHttpRpcNyxdClient,
|
||||
QueryHttpRpcNyxdClient,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
@@ -109,11 +109,9 @@ impl Monitor {
|
||||
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
||||
.no_hickory_dns()
|
||||
.with_timeout(self.nym_api_client_timeout)
|
||||
.build::<&str>()?;
|
||||
.build()?;
|
||||
|
||||
let api_client = NymApiClient::from(nym_api);
|
||||
|
||||
let described_nodes = api_client
|
||||
let described_nodes = nym_api
|
||||
.get_all_described_nodes()
|
||||
.await
|
||||
.log_error("get_all_described_nodes")?
|
||||
@@ -135,7 +133,7 @@ impl Monitor {
|
||||
|
||||
tracing::info!("🟣 🚪 gateway nodes: {}", gateways.len());
|
||||
|
||||
let bonded_nym_nodes = api_client
|
||||
let bonded_nym_nodes = nym_api
|
||||
.get_all_bonded_nym_nodes()
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -146,10 +144,11 @@ impl Monitor {
|
||||
tracing::info!("🟣 bonded_nodes: {}", bonded_nym_nodes.len());
|
||||
|
||||
// returns only bonded nodes
|
||||
let nym_nodes = api_client
|
||||
.get_all_basic_nodes()
|
||||
let nym_nodes = nym_api
|
||||
.get_all_basic_nodes_with_metadata()
|
||||
.await
|
||||
.log_error("get_all_basic_nodes")?;
|
||||
.log_error("get_all_basic_nodes")?
|
||||
.nodes;
|
||||
|
||||
let nym_node_count = nym_nodes.len();
|
||||
tracing::info!("🟣 get_all_basic_nodes: {}", nym_node_count);
|
||||
@@ -167,8 +166,7 @@ impl Monitor {
|
||||
self.location_cached(node_description).await;
|
||||
}
|
||||
|
||||
let mixnodes_detailed = api_client
|
||||
.nym_api
|
||||
let mixnodes_detailed = nym_api
|
||||
.get_mixnodes_detailed_unfiltered()
|
||||
.await
|
||||
.log_error("get_mixnodes_detailed_unfiltered")?;
|
||||
@@ -190,15 +188,13 @@ impl Monitor {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mixnodes_described = api_client
|
||||
.nym_api
|
||||
let mixnodes_described = nym_api
|
||||
.get_mixnodes_described()
|
||||
.await
|
||||
.log_error("get_mixnodes_described")?;
|
||||
|
||||
tracing::info!("🟣 mixnodes_described: {}", mixnodes_described.len());
|
||||
let mixing_assigned_nodes = api_client
|
||||
.nym_api
|
||||
let mixing_assigned_nodes = nym_api
|
||||
.get_basic_active_mixing_assigned_nodes(false, None, None, false)
|
||||
.await
|
||||
.log_error("get_basic_active_mixing_assigned_nodes")?
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::api::v1::gateway::models::WebSockets;
|
||||
use crate::api::v1::node::models::{
|
||||
AuxiliaryDetails, NodeDescription, NodeRoles, SignedHostInformation,
|
||||
};
|
||||
use crate::api::ErrorResponse;
|
||||
use crate::routes;
|
||||
use async_trait::async_trait;
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
@@ -21,7 +20,7 @@ use crate::api::v1::network_requester::models::NetworkRequester;
|
||||
use crate::api::v1::node_load::models::NodeLoad;
|
||||
pub use nym_http_api_client::Client;
|
||||
|
||||
pub type NymNodeApiClientError = HttpClientError<ErrorResponse>;
|
||||
pub type NymNodeApiClientError = HttpClientError;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user