Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e202b83463 | |||
| 9cbf8e20b4 | |||
| 33716f693b | |||
| 293cf278c6 | |||
| 4e9153b31c | |||
| eebc9baf15 | |||
| ce0cbd552d | |||
| 9c6df6fe5b | |||
| c250b6ad78 |
Generated
+78
@@ -4863,6 +4863,7 @@ dependencies = [
|
||||
"nym-ecash-signer-check",
|
||||
"nym-ecash-time",
|
||||
"nym-gateway-client",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-node-requests",
|
||||
@@ -5074,6 +5075,7 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-ecash-time",
|
||||
"nym-http-api-client",
|
||||
"nym-id",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
@@ -5162,6 +5164,7 @@ dependencies = [
|
||||
"nym-http-api-client",
|
||||
"nym-id",
|
||||
"nym-mixnet-client",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-nonexhaustive-delayqueue",
|
||||
"nym-pemstore",
|
||||
@@ -5510,6 +5513,7 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-ecash-time",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-serde-helpers",
|
||||
"nym-validator-client",
|
||||
@@ -5609,6 +5613,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"nym-ecash-signer-check-types",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-validator-client",
|
||||
"semver 1.0.26",
|
||||
@@ -5718,6 +5723,7 @@ dependencies = [
|
||||
"nym-types",
|
||||
"nym-validator-client",
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-private-metadata-server",
|
||||
"nym-wireguard-types",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
@@ -5878,6 +5884,7 @@ dependencies = [
|
||||
"mime",
|
||||
"nym-bin-common",
|
||||
"nym-http-api-common",
|
||||
"nym-network-defaults",
|
||||
"once_cell",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
@@ -6138,6 +6145,8 @@ dependencies = [
|
||||
"nym-client-core",
|
||||
"nym-crypto",
|
||||
"nym-gateway-requests",
|
||||
"nym-http-api-client",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-sdk",
|
||||
"nym-sphinx",
|
||||
@@ -6605,6 +6614,7 @@ dependencies = [
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-gateway-requests",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-ordered-buffer",
|
||||
"nym-service-providers-common",
|
||||
@@ -7178,6 +7188,7 @@ dependencies = [
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ecash-time",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-pemstore",
|
||||
"nym-serde-helpers",
|
||||
@@ -7207,7 +7218,9 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"humantime",
|
||||
"nym-api-requests",
|
||||
"nym-crypto",
|
||||
"nym-http-api-client",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
@@ -7310,6 +7323,68 @@ dependencies = [
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wireguard-private-metadata-client"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"nym-http-api-client",
|
||||
"nym-wireguard-private-metadata-shared",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wireguard-private-metadata-server"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"axum 0.7.9",
|
||||
"futures",
|
||||
"nym-credential-verification",
|
||||
"nym-credentials-interface",
|
||||
"nym-http-api-common",
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-private-metadata-shared",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-http 0.5.2",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wireguard-private-metadata-shared"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"bincode",
|
||||
"nym-credentials-interface",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wireguard-private-metadata-tests"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum 0.7.9",
|
||||
"nym-credential-verification",
|
||||
"nym-credentials-interface",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-private-metadata-client",
|
||||
"nym-wireguard-private-metadata-server",
|
||||
"nym-wireguard-private-metadata-shared",
|
||||
"tokio",
|
||||
"tower-http 0.5.2",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-wireguard-types"
|
||||
version = "0.1.0"
|
||||
@@ -10125,6 +10200,7 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-group-contract-common",
|
||||
"nym-http-api-client",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"nym-pemstore",
|
||||
@@ -11247,6 +11323,7 @@ dependencies = [
|
||||
"clap",
|
||||
"comfy-table",
|
||||
"nym-bin-common",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-validator-client",
|
||||
"serde",
|
||||
@@ -11477,6 +11554,7 @@ dependencies = [
|
||||
"nym-credential-storage",
|
||||
"nym-crypto",
|
||||
"nym-gateway-client",
|
||||
"nym-http-api-client",
|
||||
"nym-sphinx",
|
||||
"nym-sphinx-acknowledgements",
|
||||
"nym-statistics-common",
|
||||
|
||||
@@ -102,6 +102,10 @@ members = [
|
||||
"common/wasm/storage",
|
||||
"common/wasm/utils",
|
||||
"common/wireguard",
|
||||
"common/wireguard-private-metadata/client",
|
||||
"common/wireguard-private-metadata/server",
|
||||
"common/wireguard-private-metadata/shared",
|
||||
"common/wireguard-private-metadata/tests",
|
||||
"common/wireguard-types",
|
||||
"common/zulip-client",
|
||||
"documentation/autodoc",
|
||||
|
||||
@@ -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,42 @@ 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_validator_client::models::RequestError,
|
||||
>(nym_api_urls[0].clone())
|
||||
.map_err(|e| ClientCoreError::NymApiQueryFailure {
|
||||
source: nym_validator_client::nym_api::error::NymAPIError::GenericRequestFailure(
|
||||
e.to_string(),
|
||||
),
|
||||
})?;
|
||||
|
||||
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::<nym_validator_client::models::RequestError>()
|
||||
.map_err(|e| ClientCoreError::NymApiQueryFailure {
|
||||
source: nym_validator_client::nym_api::error::NymAPIError::GenericRequestFailure(
|
||||
e.to_string(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
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 +851,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(
|
||||
|
||||
@@ -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,32 @@ 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::<nym_validator_client::models::RequestError>()
|
||||
.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,7 +418,7 @@ 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::<_, ValidatorClientError>(api_url)
|
||||
.expect("invalid api url")
|
||||
.with_user_agent(user_agent.into())
|
||||
.build::<ValidatorClientError>()
|
||||
|
||||
@@ -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,10 @@ 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -60,6 +59,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 +91,16 @@ 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::<
|
||||
_,
|
||||
nym_api_requests::models::RequestError,
|
||||
>(url_address)
|
||||
.map_err(|e| EcashApiError::ClientError(e.to_string()))?
|
||||
.build::<nym_api_requests::models::RequestError>()
|
||||
.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,19 @@ 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::<nym_api_requests::models::RequestError>())
|
||||
{
|
||||
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 +172,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 +198,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;
|
||||
|
||||
@@ -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,162 @@ 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::EndpointFailure {
|
||||
status: reqwest::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
error: nym_api_requests::models::RequestError::new(
|
||||
"Inconsistent paged metadata",
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
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::EndpointFailure {
|
||||
status: reqwest::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
error: nym_api_requests::models::RequestError::new(
|
||||
"Inconsistent paged metadata",
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
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::EndpointFailure {
|
||||
status: reqwest::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
error: nym_api_requests::models::RequestError::new(
|
||||
"Inconsistent paged metadata",
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
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 +428,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 +1550,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
+4
-4
@@ -110,14 +110,14 @@ FROM ecash_ticketbook;
|
||||
|
||||
-- 6. finally swap out the old tables
|
||||
-- drop old tables
|
||||
DROP TABLE expiration_date_signatures;
|
||||
DROP TABLE pending_issuance;
|
||||
DROP TABLE ecash_ticketbook;
|
||||
DROP TABLE expiration_date_signatures;
|
||||
|
||||
-- rename new tables
|
||||
ALTER TABLE expiration_date_signatures_new
|
||||
RENAME TO expiration_date_signatures;
|
||||
ALTER TABLE pending_issuance_new
|
||||
RENAME TO pending_issuance;
|
||||
ALTER TABLE ecash_ticketbook_new
|
||||
RENAME TO ecash_ticketbook;
|
||||
RENAME TO ecash_ticketbook;
|
||||
ALTER TABLE expiration_date_signatures_new
|
||||
RENAME TO expiration_date_signatures;
|
||||
|
||||
@@ -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 },
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::ecash::traits::EcashManager;
|
||||
use async_trait::async_trait;
|
||||
use bandwidth_storage_manager::BandwidthStorageManager;
|
||||
use nym_credentials::ecash::utils::{cred_exp_date, ecash_today, EcashTime};
|
||||
use nym_credentials_interface::{Bandwidth, ClientTicket, TicketType};
|
||||
@@ -139,3 +140,18 @@ impl CredentialVerifier {
|
||||
.await)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait TicketVerifier {
|
||||
/// Verify that the ticket is valid and cryptographically correct.
|
||||
/// If the verification succeeds, also increase the bandwidth with the ticket's
|
||||
/// amount and return the latest available bandwidth
|
||||
async fn verify(&mut self) -> Result<i64>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TicketVerifier for CredentialVerifier {
|
||||
async fn verify(&mut self) -> Result<i64> {
|
||||
self.verify().await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -13,6 +13,7 @@ license.workspace = true
|
||||
[features]
|
||||
default=["tunneling"]
|
||||
tunneling=[]
|
||||
network-defaults = ["dep:nym-network-defaults"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
@@ -34,6 +35,7 @@ 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 }
|
||||
|
||||
[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,
|
||||
}
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@
|
||||
//! ```
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub use reqwest::ClientBuilder as ReqwestClientBuilder;
|
||||
pub use reqwest::StatusCode;
|
||||
|
||||
use crate::path::RequestPath;
|
||||
@@ -161,6 +162,8 @@ use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "tunneling")]
|
||||
mod fronted;
|
||||
#[cfg(feature = "tunneling")]
|
||||
pub use fronted::FrontPolicy;
|
||||
mod url;
|
||||
pub use url::{IntoUrl, Url};
|
||||
mod user_agent;
|
||||
@@ -191,6 +194,15 @@ pub type Params<'a, K, V> = &'a [(K, V)];
|
||||
/// Empty collection of HTTP Request Parameters.
|
||||
pub const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
|
||||
|
||||
/// Serialization format for API requests and responses
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SerializationFormat {
|
||||
/// Use JSON serialization (default, always works)
|
||||
Json,
|
||||
/// Use bincode serialization (must be explicitly opted into)
|
||||
Bincode,
|
||||
}
|
||||
|
||||
/// The Errors that may occur when creating or using an HTTP client.
|
||||
#[derive(Debug, Error)]
|
||||
#[allow(missing_docs)]
|
||||
@@ -370,6 +382,7 @@ pub struct ClientBuilder {
|
||||
front: Option<fronted::Front>,
|
||||
|
||||
retry_limit: usize,
|
||||
serialization: SerializationFormat,
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
@@ -395,6 +408,50 @@ impl ClientBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a client builder from network details with sensible defaults
|
||||
#[cfg(feature = "network-defaults")]
|
||||
pub fn from_network(
|
||||
network: &nym_network_defaults::NymNetworkDetails,
|
||||
) -> Result<Self, HttpClientError> {
|
||||
let urls = network
|
||||
.nym_api_urls
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
HttpClientError::GenericRequestFailure(
|
||||
"No API URLs configured in network details".to_string(),
|
||||
)
|
||||
})?
|
||||
.iter()
|
||||
.map(|api_url| {
|
||||
// Convert ApiUrl to our Url type with fronting support
|
||||
let mut url = Url::parse(&api_url.url)?;
|
||||
|
||||
// Add fronting domains if available
|
||||
#[cfg(feature = "tunneling")]
|
||||
if let Some(ref front_hosts) = api_url.front_hosts {
|
||||
let fronts: Vec<String> = front_hosts
|
||||
.iter()
|
||||
.map(|host| format!("https://{}", host))
|
||||
.collect();
|
||||
url = Url::new(api_url.url.clone(), Some(fronts))
|
||||
.map_err(|e| HttpClientError::GenericRequestFailure(e.to_string()))?;
|
||||
}
|
||||
|
||||
Ok(url)
|
||||
})
|
||||
.collect::<Result<Vec<_>, HttpClientError>>()?;
|
||||
|
||||
let mut builder = Self::new_with_urls(urls);
|
||||
|
||||
// Enable domain fronting by default (on retry)
|
||||
#[cfg(feature = "tunneling")]
|
||||
{
|
||||
builder = builder.with_fronting(FrontPolicy::OnRetry);
|
||||
}
|
||||
|
||||
Ok(builder)
|
||||
}
|
||||
|
||||
/// Constructs a new http `ClientBuilder` from a valid url.
|
||||
pub fn new_with_urls(urls: Vec<Url>) -> Self {
|
||||
let urls = Self::check_urls(urls);
|
||||
@@ -428,6 +485,7 @@ impl ClientBuilder {
|
||||
front: None,
|
||||
|
||||
retry_limit: 0,
|
||||
serialization: SerializationFormat::Json,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,6 +558,17 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the serialization format for API requests and responses
|
||||
pub fn with_serialization(mut self, format: SerializationFormat) -> Self {
|
||||
self.serialization = format;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure the client to use bincode serialization
|
||||
pub fn with_bincode(self) -> Self {
|
||||
self.with_serialization(SerializationFormat::Bincode)
|
||||
}
|
||||
|
||||
/// Returns a Client that uses this ClientBuilder configuration.
|
||||
pub fn build<E>(self) -> Result<Client, HttpClientError<E>>
|
||||
where
|
||||
@@ -541,6 +610,7 @@ impl ClientBuilder {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
request_timeout: self.timeout.unwrap_or(DEFAULT_TIMEOUT),
|
||||
retry_limit: self.retry_limit,
|
||||
serialization: self.serialization,
|
||||
};
|
||||
|
||||
Ok(client)
|
||||
@@ -561,6 +631,7 @@ pub struct Client {
|
||||
request_timeout: Duration,
|
||||
|
||||
retry_limit: usize,
|
||||
serialization: SerializationFormat,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@@ -618,6 +689,7 @@ impl Client {
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
request_timeout: self.request_timeout,
|
||||
serialization: self.serialization,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,26 +765,37 @@ impl Client {
|
||||
/// this method. For example, if the client is configured to rotate hosts after each error, this
|
||||
/// method should be called after the host has been updated -- i.e. as part of the subsequent
|
||||
/// send.
|
||||
fn apply_hosts_to_req(&self, r: &mut reqwest::Request) {
|
||||
fn apply_hosts_to_req(&self, r: &mut reqwest::Request) -> (&str, Option<&str>) {
|
||||
let url = self.current_url();
|
||||
r.url_mut().set_host(url.host_str()).unwrap();
|
||||
|
||||
#[cfg(feature = "tunneling")]
|
||||
if let Some(ref front) = self.front {
|
||||
if front.is_enabled() {
|
||||
// this should never fail as we are transplanting the host from one url to another
|
||||
r.url_mut().set_host(url.front_str()).unwrap();
|
||||
let front_host = url.front_str().unwrap_or("");
|
||||
let actual_host = url.host_str().unwrap_or("");
|
||||
|
||||
let actual_host: HeaderValue = url
|
||||
.host_str()
|
||||
.unwrap_or("")
|
||||
.parse()
|
||||
.unwrap_or(HeaderValue::from_static(""));
|
||||
tracing::debug!(
|
||||
"Domain fronting enabled: routing via CDN {} to actual host {}",
|
||||
front_host,
|
||||
actual_host
|
||||
);
|
||||
|
||||
// this should never fail as we are transplanting the host from one url to another
|
||||
r.url_mut().set_host(Some(front_host)).unwrap();
|
||||
|
||||
let actual_host_header: HeaderValue =
|
||||
actual_host.parse().unwrap_or(HeaderValue::from_static(""));
|
||||
// If the map did have this key present, the new value is associated with the key
|
||||
// and all previous values are removed. (reqwest HeaderMap docs)
|
||||
_ = r.headers_mut().insert(reqwest::header::HOST, actual_host);
|
||||
_ = r
|
||||
.headers_mut()
|
||||
.insert(reqwest::header::HOST, actual_host_header);
|
||||
|
||||
return (url.as_str(), url.front_str());
|
||||
}
|
||||
}
|
||||
(url.as_str(), None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,6 +825,13 @@ impl ApiClientCore for Client {
|
||||
|
||||
let mut rb = RequestBuilder::from_parts(self.reqwest_client.clone(), req);
|
||||
|
||||
// Set Accept header based on serialization preference
|
||||
let accept_header = match self.serialization {
|
||||
SerializationFormat::Json => "application/json",
|
||||
SerializationFormat::Bincode => "application/bincode",
|
||||
};
|
||||
rb = rb.header(reqwest::header::ACCEPT, accept_header);
|
||||
|
||||
if let Some(body) = json_body {
|
||||
rb = rb.json(body);
|
||||
}
|
||||
@@ -790,7 +880,14 @@ impl ApiClientCore for Client {
|
||||
if let Some(ref front) = self.front {
|
||||
// If fronting is set to be enabled on error, enable domain fronting as we
|
||||
// have encountered an error.
|
||||
let was_enabled = front.is_enabled();
|
||||
front.retry_enable();
|
||||
if !was_enabled && front.is_enabled() {
|
||||
tracing::info!(
|
||||
"Domain fronting activated after connection failure: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if attempts < self.retry_limit {
|
||||
|
||||
@@ -47,7 +47,8 @@ pub mod nyx {
|
||||
pub mod wireguard {
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
pub const WG_PORT: u16 = 51822;
|
||||
pub const WG_TUNNEL_PORT: u16 = 51822;
|
||||
pub const WG_METADATA_PORT: u16 = 51830;
|
||||
|
||||
// The interface used to route traffic
|
||||
pub const WG_TUN_BASE_NAME: &str = "nymwg";
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,17 @@ 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::<nym_api_requests::models::RequestError>()
|
||||
}) {
|
||||
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,19 @@ pub async fn current_network_topology_async(
|
||||
}
|
||||
};
|
||||
|
||||
let api_client = NymApiClient::new(url);
|
||||
let api_client = nym_http_api_client::Client::builder::<
|
||||
_,
|
||||
nym_validator_client::models::RequestError,
|
||||
>(url.clone())
|
||||
.map_err(|_err| WasmCoreError::MalformedUrl {
|
||||
raw: nym_api_url.to_string(),
|
||||
source: url::ParseError::EmptyHost,
|
||||
})?
|
||||
.build::<nym_validator_client::models::RequestError>()
|
||||
.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 +102,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;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "nym-wireguard-private-metadata-client"
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
nym-http-api-client = { path = "../../http-api-client" }
|
||||
nym-wireguard-private-metadata-shared = { path = "../shared" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tracing::instrument;
|
||||
|
||||
use nym_http_api_client::{ApiClient, Client, HttpClientError, NO_PARAMS};
|
||||
|
||||
use nym_wireguard_private_metadata_shared::{
|
||||
routes, Version, {ErrorResponse, 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> {
|
||||
let version: u64 = self
|
||||
.get_json(
|
||||
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::VERSION],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await?;
|
||||
Ok(version.into())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn available_bandwidth(
|
||||
&self,
|
||||
request_body: &Request,
|
||||
) -> Result<Response, WireguardMetadataApiClientError> {
|
||||
self.post_json(
|
||||
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::AVAILABLE],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, request_body))]
|
||||
async fn topup_bandwidth(
|
||||
&self,
|
||||
request_body: &Request,
|
||||
) -> Result<Response, WireguardMetadataApiClientError> {
|
||||
self.post_json(
|
||||
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::TOPUP],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl WireguardMetadataApiClient for Client {}
|
||||
@@ -0,0 +1,43 @@
|
||||
[package]
|
||||
name = "nym-wireguard-private-metadata-server"
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
axum = { workspace = true, features = ["tokio", "macros"] }
|
||||
futures = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
|
||||
tokio-util = { workspace = true }
|
||||
tower-http = { workspace = true, features = [
|
||||
"cors",
|
||||
"trace",
|
||||
"compression-br",
|
||||
"compression-deflate",
|
||||
"compression-gzip",
|
||||
"compression-zstd",
|
||||
] }
|
||||
utoipa = { workspace = true, features = ["axum_extras", "time"] }
|
||||
utoipa-swagger-ui = { workspace = true, features = ["axum"] }
|
||||
|
||||
nym-credentials-interface = { path = "../../credentials-interface" }
|
||||
nym-credential-verification = { path = "../../credential-verification" }
|
||||
nym-http-api-common = { path = "../../http-api-common", features = [
|
||||
"middleware",
|
||||
"utoipa",
|
||||
"output",
|
||||
] }
|
||||
nym-wireguard = { path = "../../wireguard" }
|
||||
nym-wireguard-private-metadata-shared = { path = "../shared" }
|
||||
|
||||
[dev-dependencies]
|
||||
async-trait = { workspace = true }
|
||||
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use nym_wireguard::WgApiWrapper;
|
||||
|
||||
pub(crate) mod openapi;
|
||||
pub(crate) mod router;
|
||||
pub(crate) mod state;
|
||||
|
||||
/// Shutdown goes 2 directions:
|
||||
/// 1. signal background tasks to gracefully finish
|
||||
/// 2. signal server itself
|
||||
///
|
||||
/// These are done through separate shutdown handles. Of course, shut down server
|
||||
/// AFTER you have shut down BG tasks (or past their grace period).
|
||||
#[allow(unused)]
|
||||
pub struct ShutdownHandles {
|
||||
axum_shutdown_button: CancellationToken,
|
||||
/// Tokio JoinHandle for axum server's task
|
||||
axum_join_handle: AxumJoinHandle,
|
||||
/// Wireguard API for kernel interactions
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
}
|
||||
|
||||
impl ShutdownHandles {
|
||||
/// Cancellation token is given to Axum server constructor. When the token
|
||||
/// receives a shutdown signal, Axum server will shut down gracefully.
|
||||
pub fn new(
|
||||
axum_join_handle: AxumJoinHandle,
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
axum_shutdown_button: CancellationToken,
|
||||
) -> Self {
|
||||
Self {
|
||||
axum_shutdown_button,
|
||||
axum_join_handle,
|
||||
wg_api,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type AxumJoinHandle = JoinHandle<std::io::Result<()>>;
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use utoipa::OpenApi;
|
||||
|
||||
use nym_wireguard_private_metadata_shared::{Request, Response};
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
info(title = "Nym Wireguard Private Metadata"),
|
||||
tags(),
|
||||
components(schemas(Request, Response))
|
||||
)]
|
||||
pub(crate) struct ApiDoc;
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use anyhow::anyhow;
|
||||
use axum::response::Redirect;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use core::net::SocketAddr;
|
||||
use nym_http_api_common::middleware::logging::log_request_info;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_util::sync::WaitForCancellationFutureOwned;
|
||||
use tower_http::cors::CorsLayer;
|
||||
use utoipa::OpenApi;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
use crate::http::openapi::ApiDoc;
|
||||
use crate::http::state::AppState;
|
||||
use crate::network::bandwidth_routes;
|
||||
|
||||
/// Wrapper around `axum::Router` which ensures correct [order of layers][order].
|
||||
/// Add new routes as if you were working directly with `axum`.
|
||||
///
|
||||
/// Why? Middleware like logger, CORS, TLS which need to handle request before other
|
||||
/// layers should be added last. Using this builder pattern ensures that.
|
||||
///
|
||||
/// [order]: https://docs.rs/axum/latest/axum/middleware/index.html#ordering
|
||||
pub struct RouterBuilder {
|
||||
unfinished_router: Router<AppState>,
|
||||
}
|
||||
|
||||
impl RouterBuilder {
|
||||
/// All routes should be, if possible, added here. Exceptions are e.g.
|
||||
/// routes which are added conditionally in other places based on some `if`.
|
||||
pub fn with_default_routes() -> Self {
|
||||
let default_routes = Router::new()
|
||||
.merge(SwaggerUi::new("/swagger").url("/api-docs/openapi.json", ApiDoc::openapi()))
|
||||
.route("/", get(|| async { Redirect::to("/swagger") }))
|
||||
.nest("/v1", Router::new().nest("/bandwidth", bandwidth_routes()));
|
||||
Self {
|
||||
unfinished_router: default_routes,
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke this as late as possible before constructing HTTP server
|
||||
/// (after all routes were added).
|
||||
pub fn with_state(self, state: AppState) -> RouterWithState {
|
||||
RouterWithState {
|
||||
router: self.finalize_routes().with_state(state),
|
||||
}
|
||||
}
|
||||
|
||||
/// Middleware added here intercepts the request before it gets to other routes.
|
||||
fn finalize_routes(self) -> Router<AppState> {
|
||||
self.unfinished_router
|
||||
.layer(setup_cors())
|
||||
.layer(axum::middleware::from_fn(log_request_info))
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_cors() -> CorsLayer {
|
||||
CorsLayer::new()
|
||||
.allow_origin(tower_http::cors::Any)
|
||||
.allow_methods([axum::http::Method::GET, axum::http::Method::POST])
|
||||
.allow_headers(tower_http::cors::Any)
|
||||
.allow_credentials(false)
|
||||
}
|
||||
|
||||
pub struct RouterWithState {
|
||||
pub router: Router,
|
||||
}
|
||||
|
||||
impl RouterWithState {
|
||||
pub async fn build_server(self, bind_address: &SocketAddr) -> anyhow::Result<ApiHttpServer> {
|
||||
let listener = tokio::net::TcpListener::bind(bind_address)
|
||||
.await
|
||||
.map_err(|err| anyhow!("Couldn't bind to address {} due to {}", bind_address, err))?;
|
||||
|
||||
Ok(ApiHttpServer {
|
||||
router: self.router,
|
||||
listener,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ApiHttpServer {
|
||||
router: Router,
|
||||
listener: TcpListener,
|
||||
}
|
||||
|
||||
impl ApiHttpServer {
|
||||
pub async fn run(self, receiver: WaitForCancellationFutureOwned) -> Result<(), std::io::Error> {
|
||||
// into_make_service_with_connect_info allows us to see client ip address
|
||||
axum::serve(
|
||||
self.listener,
|
||||
self.router
|
||||
.into_make_service_with_connect_info::<SocketAddr>(),
|
||||
)
|
||||
.with_graceful_shutdown(receiver)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::net::IpAddr;
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
|
||||
use crate::transceiver::PeerControllerTransceiver;
|
||||
use nym_wireguard_private_metadata_shared::error::MetadataError;
|
||||
|
||||
#[derive(Clone, axum::extract::FromRef)]
|
||||
pub struct AppState {
|
||||
transceiver: PeerControllerTransceiver,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(transceiver: PeerControllerTransceiver) -> Self {
|
||||
Self { transceiver }
|
||||
}
|
||||
|
||||
pub async fn available_bandwidth(&self, ip: IpAddr) -> Result<i64, MetadataError> {
|
||||
self.transceiver.query_bandwidth(ip).await
|
||||
}
|
||||
|
||||
// Top up with a credential and return the afterwards available bandwidth
|
||||
pub async fn topup_bandwidth(
|
||||
&self,
|
||||
ip: IpAddr,
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<i64, MetadataError> {
|
||||
self.transceiver
|
||||
.topup_bandwidth(ip, Box::new(credential))
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod http;
|
||||
mod network;
|
||||
mod transceiver;
|
||||
|
||||
pub use http::{
|
||||
router::{ApiHttpServer, RouterBuilder, RouterWithState},
|
||||
state::AppState,
|
||||
ShutdownHandles,
|
||||
};
|
||||
pub use transceiver::PeerControllerTransceiver;
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use axum::{
|
||||
extract::{ConnectInfo, Query, State},
|
||||
Json, Router,
|
||||
};
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_wireguard_private_metadata_shared::{
|
||||
interface::{RequestData, ResponseData},
|
||||
latest, AxumErrorResponse, AxumResult, Construct, Extract, Request, Response,
|
||||
};
|
||||
use tower_http::compression::CompressionLayer;
|
||||
|
||||
use crate::http::state::AppState;
|
||||
|
||||
pub(crate) fn bandwidth_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/version", axum::routing::get(version))
|
||||
.route("/available", axum::routing::post(available_bandwidth))
|
||||
.route("/topup", axum::routing::post(topup_bandwidth))
|
||||
.layer(CompressionLayer::new())
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "bandwidth",
|
||||
get,
|
||||
path = "/v1/bandwidth/version",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
(Response = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
async fn version(Query(output): Query<OutputParams>) -> AxumResult<FormattedResponse<u64>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
Ok(output.to_response(latest::VERSION.into()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "bandwidth",
|
||||
post,
|
||||
request_body = Request,
|
||||
path = "/v1/bandwidth/available",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
(Response = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
async fn available_bandwidth(
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
Json(request): Json<Request>,
|
||||
) -> AxumResult<FormattedResponse<Response>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let (RequestData::AvailableBandwidth(_), version) =
|
||||
request.extract().map_err(AxumErrorResponse::bad_request)?
|
||||
else {
|
||||
return Err(AxumErrorResponse::bad_request("incorrect request type"));
|
||||
};
|
||||
let available_bandwidth = state
|
||||
.available_bandwidth(addr.ip())
|
||||
.await
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
let response = Response::construct(
|
||||
ResponseData::AvailableBandwidth(available_bandwidth),
|
||||
version,
|
||||
)
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
|
||||
Ok(output.to_response(response))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "bandwidth",
|
||||
post,
|
||||
request_body = Request,
|
||||
path = "/v1/bandwidth/topup",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
(Response = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
async fn topup_bandwidth(
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
Query(output): Query<OutputParams>,
|
||||
State(state): State<AppState>,
|
||||
Json(request): Json<Request>,
|
||||
) -> AxumResult<FormattedResponse<Response>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let (RequestData::TopUpBandwidth(credential), version) =
|
||||
request.extract().map_err(AxumErrorResponse::bad_request)?
|
||||
else {
|
||||
return Err(AxumErrorResponse::bad_request("incorrect request type"));
|
||||
};
|
||||
let available_bandwidth = state
|
||||
.topup_bandwidth(addr.ip(), *credential)
|
||||
.await
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
let response = Response::construct(ResponseData::TopUpBandwidth(available_bandwidth), version)
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
|
||||
Ok(output.to_response(response))
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::net::IpAddr;
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use nym_credential_verification::ClientBandwidth;
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_wireguard::peer_controller::PeerControlRequest;
|
||||
use nym_wireguard_private_metadata_shared::error::MetadataError;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PeerControllerTransceiver {
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
}
|
||||
|
||||
impl PeerControllerTransceiver {
|
||||
pub fn new(request_tx: mpsc::Sender<PeerControlRequest>) -> Self {
|
||||
Self { request_tx }
|
||||
}
|
||||
|
||||
async fn get_client_bandwidth(&self, ip: IpAddr) -> Result<ClientBandwidth, MetadataError> {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
let msg = PeerControlRequest::GetClientBandwidthByIp { ip, response_tx };
|
||||
self.request_tx
|
||||
.send(msg)
|
||||
.await
|
||||
.map_err(|_| MetadataError::PeerInteractionStopped)?;
|
||||
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| MetadataError::NoResponse)?
|
||||
.map_err(|err| MetadataError::Unsuccessful {
|
||||
reason: err.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn query_bandwidth(&self, ip: IpAddr) -> Result<i64, MetadataError> {
|
||||
Ok(self.get_client_bandwidth(ip).await?.available().await)
|
||||
}
|
||||
|
||||
// Top up with a credential and return the afterwards available bandwidth
|
||||
pub(crate) async fn topup_bandwidth(
|
||||
&self,
|
||||
ip: IpAddr,
|
||||
credential: Box<CredentialSpendingData>,
|
||||
) -> Result<i64, MetadataError> {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
let msg = PeerControlRequest::GetVerifierByIp {
|
||||
ip,
|
||||
credential,
|
||||
response_tx,
|
||||
};
|
||||
self.request_tx
|
||||
.send(msg)
|
||||
.await
|
||||
.map_err(|_| MetadataError::PeerInteractionStopped)?;
|
||||
|
||||
let mut verifier = response_rx
|
||||
.await
|
||||
.map_err(|_| MetadataError::NoResponse)?
|
||||
.map_err(|err| MetadataError::Unsuccessful {
|
||||
reason: err.to_string(),
|
||||
})?;
|
||||
let available_bandwidth =
|
||||
verifier
|
||||
.verify()
|
||||
.await
|
||||
.map_err(|err| MetadataError::CredentialVerification {
|
||||
message: err.to_string(),
|
||||
})?;
|
||||
|
||||
Ok(available_bandwidth)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use nym_credential_verification::TicketVerifier;
|
||||
use nym_wireguard::CONTROL_CHANNEL_SIZE;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(crate) const CREDENTIAL_BYTES: [u8; 1245] = [
|
||||
0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254,
|
||||
16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139,
|
||||
154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123,
|
||||
35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1,
|
||||
151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43,
|
||||
90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193,
|
||||
39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81,
|
||||
184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195,
|
||||
196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48,
|
||||
92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48,
|
||||
166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186,
|
||||
19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38,
|
||||
228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85,
|
||||
88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241,
|
||||
85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112,
|
||||
48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31,
|
||||
97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203,
|
||||
71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107,
|
||||
213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180,
|
||||
178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1,
|
||||
96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166,
|
||||
30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212,
|
||||
207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71,
|
||||
223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22,
|
||||
119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131,
|
||||
59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119,
|
||||
240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202,
|
||||
58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144,
|
||||
189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33,
|
||||
30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150,
|
||||
74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6,
|
||||
227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85,
|
||||
15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38,
|
||||
161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20,
|
||||
47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48,
|
||||
135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170,
|
||||
150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109,
|
||||
55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249,
|
||||
87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144,
|
||||
178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32,
|
||||
203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184,
|
||||
96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169,
|
||||
24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61,
|
||||
130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82,
|
||||
108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1,
|
||||
32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234,
|
||||
150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241,
|
||||
203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217,
|
||||
160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52,
|
||||
147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81,
|
||||
70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233,
|
||||
178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98,
|
||||
110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69,
|
||||
131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251,
|
||||
197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233,
|
||||
162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106,
|
||||
83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145,
|
||||
192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124,
|
||||
55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0,
|
||||
0, 0, 0, 0, 0, 1,
|
||||
];
|
||||
|
||||
pub(crate) struct MockVerifier {
|
||||
ret: i64,
|
||||
}
|
||||
|
||||
impl MockVerifier {
|
||||
pub(crate) fn new(ret: i64) -> MockVerifier {
|
||||
Self { ret }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl TicketVerifier for MockVerifier {
|
||||
async fn verify(&mut self) -> nym_credential_verification::Result<i64> {
|
||||
Ok(self.ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_bandwidth() {
|
||||
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
|
||||
let transceiver = PeerControllerTransceiver::new(request_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
match request_rx.recv().await.unwrap() {
|
||||
PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx } => {
|
||||
response_tx
|
||||
.send(Ok(ClientBandwidth::new(Default::default())))
|
||||
.ok();
|
||||
}
|
||||
_ => panic!("Not expected"),
|
||||
}
|
||||
});
|
||||
|
||||
let bw = transceiver
|
||||
.query_bandwidth("10.0.0.42".parse().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(bw, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stop_peer() {
|
||||
let (request_tx, request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
|
||||
let transceiver = PeerControllerTransceiver::new(request_tx);
|
||||
|
||||
drop(request_rx);
|
||||
let err = transceiver
|
||||
.query_bandwidth("10.0.0.42".parse().unwrap())
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(err, MetadataError::PeerInteractionStopped);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unresponsive_peer() {
|
||||
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
|
||||
let transceiver = PeerControllerTransceiver::new(request_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
match request_rx.recv().await.unwrap() {
|
||||
PeerControlRequest::GetClientBandwidthByIp {
|
||||
ip: _,
|
||||
response_tx: _,
|
||||
} => {}
|
||||
_ => panic!("Not expected"),
|
||||
}
|
||||
});
|
||||
|
||||
let err = transceiver
|
||||
.query_bandwidth("10.0.0.42".parse().unwrap())
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(err, MetadataError::NoResponse);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unsuccessful_query_bandwidth() {
|
||||
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
|
||||
let transceiver = PeerControllerTransceiver::new(request_tx);
|
||||
|
||||
tokio::spawn(async move {
|
||||
match request_rx.recv().await.unwrap() {
|
||||
PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx } => {
|
||||
response_tx
|
||||
.send(Err(nym_wireguard::error::Error::Internal(
|
||||
"testing".to_owned(),
|
||||
)))
|
||||
.ok();
|
||||
}
|
||||
_ => panic!("Not expected"),
|
||||
}
|
||||
});
|
||||
|
||||
let ret = transceiver
|
||||
.query_bandwidth("10.0.0.42".parse().unwrap())
|
||||
.await;
|
||||
assert!(ret.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn topup() {
|
||||
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
|
||||
let transceiver = PeerControllerTransceiver::new(request_tx);
|
||||
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
|
||||
let verifier_bw = 42;
|
||||
|
||||
tokio::spawn(async move {
|
||||
match request_rx.recv().await.unwrap() {
|
||||
PeerControlRequest::GetVerifierByIp {
|
||||
ip: _,
|
||||
credential: _,
|
||||
response_tx,
|
||||
} => {
|
||||
response_tx
|
||||
.send(Ok(Box::new(MockVerifier::new(verifier_bw))))
|
||||
.ok();
|
||||
}
|
||||
_ => panic!("Not expected"),
|
||||
}
|
||||
});
|
||||
|
||||
let bw = transceiver
|
||||
.topup_bandwidth("10.0.0.42".parse().unwrap(), Box::new(credential))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(bw, verifier_bw);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unsuccessful_topup() {
|
||||
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
|
||||
let transceiver = PeerControllerTransceiver::new(request_tx);
|
||||
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
|
||||
|
||||
tokio::spawn(async move {
|
||||
match request_rx.recv().await.unwrap() {
|
||||
PeerControlRequest::GetVerifierByIp {
|
||||
ip: _,
|
||||
credential: _,
|
||||
response_tx,
|
||||
} => {
|
||||
response_tx
|
||||
.send(Err(nym_wireguard::error::Error::Internal(
|
||||
"testing".to_owned(),
|
||||
)))
|
||||
.ok();
|
||||
}
|
||||
_ => panic!("Not expected"),
|
||||
}
|
||||
});
|
||||
|
||||
let ret = transceiver
|
||||
.topup_bandwidth("10.0.0.42".parse().unwrap(), Box::new(credential))
|
||||
.await;
|
||||
assert!(ret.is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "nym-wireguard-private-metadata-shared"
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
axum = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
schemars = { workspace = true, features = ["preserve_order"] }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
utoipa = { workspace = true }
|
||||
|
||||
nym-credentials-interface = { path = "../../credentials-interface" }
|
||||
|
||||
[features]
|
||||
testing = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum MetadataError {
|
||||
#[error("peers can't be interacted with anymore")]
|
||||
PeerInteractionStopped,
|
||||
|
||||
#[error("no response received")]
|
||||
NoResponse,
|
||||
|
||||
#[error("query was not successful: {reason}")]
|
||||
Unsuccessful { reason: String },
|
||||
|
||||
#[error("Models error: {message}")]
|
||||
Models { message: String },
|
||||
|
||||
#[error("Credential verification error: {message}")]
|
||||
CredentialVerification { message: String },
|
||||
}
|
||||
|
||||
impl From<crate::models::error::Error> for MetadataError {
|
||||
fn from(value: crate::models::error::Error) -> Self {
|
||||
Self::Models {
|
||||
message: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod error;
|
||||
mod models;
|
||||
pub mod routes;
|
||||
|
||||
#[cfg(feature = "testing")]
|
||||
pub use models::v0;
|
||||
pub use models::{
|
||||
error::Error as ModelError, interface, latest, v1, AxumErrorResponse, AxumResult, Construct,
|
||||
ErrorResponse, Extract, Request, Response, Version,
|
||||
};
|
||||
|
||||
fn make_bincode_serializer() -> impl bincode::Options {
|
||||
use bincode::Options;
|
||||
bincode::DefaultOptions::new()
|
||||
.with_big_endian()
|
||||
.with_varint_encoding()
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::models::Version;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Bincode(#[from] bincode::Error),
|
||||
|
||||
#[error("trying to deserialize from version {source_version:?} into {target_version:?}")]
|
||||
InvalidVersion {
|
||||
source_version: Version,
|
||||
target_version: Version,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"trying to deserialize from query type {source_query_type} query type {target_query_type}"
|
||||
)]
|
||||
InvalidQueryType {
|
||||
source_query_type: String,
|
||||
target_query_type: String,
|
||||
},
|
||||
|
||||
#[error("update not possible from {from:?} to {to:?}")]
|
||||
UpdateNotPossible { from: Version, to: Version },
|
||||
|
||||
#[error("downgrade not possible from {from:?} to {to:?}")]
|
||||
DowngradeNotPossible { from: Version, to: Version },
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
|
||||
#[cfg(feature = "testing")]
|
||||
use crate::models::v0;
|
||||
use crate::models::{v1, Construct, Extract, Request, Response, Version};
|
||||
|
||||
pub enum RequestData {
|
||||
AvailableBandwidth(()),
|
||||
TopUpBandwidth(Box<CredentialSpendingData>),
|
||||
}
|
||||
|
||||
impl From<super::latest::interface::RequestData> for RequestData {
|
||||
fn from(value: super::latest::interface::RequestData) -> Self {
|
||||
match value {
|
||||
super::latest::interface::RequestData::AvailableBandwidth(inner) => {
|
||||
Self::AvailableBandwidth(inner)
|
||||
}
|
||||
super::latest::interface::RequestData::TopUpBandwidth(credential_spending_data) => {
|
||||
Self::TopUpBandwidth(credential_spending_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequestData> for super::latest::interface::RequestData {
|
||||
fn from(value: RequestData) -> Self {
|
||||
match value {
|
||||
RequestData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner),
|
||||
RequestData::TopUpBandwidth(credential_spending_data) => {
|
||||
Self::TopUpBandwidth(credential_spending_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<super::latest::interface::ResponseData> for ResponseData {
|
||||
fn from(value: super::latest::interface::ResponseData) -> Self {
|
||||
match value {
|
||||
super::latest::interface::ResponseData::AvailableBandwidth(inner) => {
|
||||
Self::AvailableBandwidth(inner)
|
||||
}
|
||||
super::latest::interface::ResponseData::TopUpBandwidth(credential_spending_data) => {
|
||||
Self::TopUpBandwidth(credential_spending_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResponseData> for super::latest::interface::ResponseData {
|
||||
fn from(value: ResponseData) -> Self {
|
||||
match value {
|
||||
ResponseData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner),
|
||||
ResponseData::TopUpBandwidth(credential_spending_data) => {
|
||||
Self::TopUpBandwidth(credential_spending_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Construct<RequestData> for Request {
|
||||
fn construct(info: RequestData, version: Version) -> Result<Self, super::error::Error> {
|
||||
match version {
|
||||
#[cfg(feature = "testing")]
|
||||
Version::V0 => {
|
||||
let translate_info = super::latest::interface::RequestData::from(info);
|
||||
let downgrade_info = v0::interface::RequestData::try_from(translate_info)?;
|
||||
let versioned_request = v0::VersionedRequest::construct(downgrade_info, version)?;
|
||||
Ok(versioned_request.try_into()?)
|
||||
}
|
||||
Version::V1 => {
|
||||
let versioned_request = v1::VersionedRequest::construct(info.into(), version)?;
|
||||
Ok(versioned_request.try_into()?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Extract<RequestData> for Request {
|
||||
fn extract(&self) -> Result<(RequestData, Version), crate::models::Error> {
|
||||
match self.version {
|
||||
#[cfg(feature = "testing")]
|
||||
super::Version::V0 => {
|
||||
let versioned_request = v0::VersionedRequest::try_from(self.clone())?;
|
||||
let (request, version) = versioned_request.extract()?;
|
||||
|
||||
let upgrade_request = super::latest::interface::RequestData::try_from(request)?;
|
||||
|
||||
Ok((upgrade_request.into(), version))
|
||||
}
|
||||
super::Version::V1 => {
|
||||
let versioned_request = v1::VersionedRequest::try_from(self.clone())?;
|
||||
let (extracted, version) = versioned_request.extract()?;
|
||||
Ok((extracted.into(), version))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResponseData {
|
||||
AvailableBandwidth(i64),
|
||||
TopUpBandwidth(i64),
|
||||
}
|
||||
|
||||
impl Construct<ResponseData> for Response {
|
||||
fn construct(info: ResponseData, version: Version) -> Result<Self, super::error::Error> {
|
||||
match version {
|
||||
#[cfg(feature = "testing")]
|
||||
super::Version::V0 => {
|
||||
let translate_response = super::latest::interface::ResponseData::from(info);
|
||||
let downgrade_response = v0::interface::ResponseData::try_from(translate_response)?;
|
||||
let versioned_response =
|
||||
v0::VersionedResponse::construct(downgrade_response, version)?;
|
||||
Ok(versioned_response.try_into()?)
|
||||
}
|
||||
Version::V1 => {
|
||||
let versioned_response = v1::VersionedResponse::construct(info.into(), version)?;
|
||||
Ok(versioned_response.try_into()?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Extract<ResponseData> for Response {
|
||||
fn extract(&self) -> Result<(ResponseData, Version), super::error::Error> {
|
||||
match self.version {
|
||||
#[cfg(feature = "testing")]
|
||||
super::Version::V0 => {
|
||||
let versioned_response = v0::VersionedResponse::try_from(self.clone())?;
|
||||
let (response, version) = versioned_response.extract()?;
|
||||
|
||||
let upgrade_response = super::latest::interface::ResponseData::try_from(response)?;
|
||||
|
||||
Ok((upgrade_response.into(), version))
|
||||
}
|
||||
super::Version::V1 => {
|
||||
let versioned_response = v1::VersionedResponse::try_from(self.clone())?;
|
||||
let (extracted, version) = versioned_response.extract()?;
|
||||
Ok((extracted.into(), version))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use axum::http::StatusCode;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub(crate) mod error;
|
||||
pub mod interface;
|
||||
#[cfg(feature = "testing")]
|
||||
pub mod v0; // dummy version, only for filling boilerplate code for update/downgrade and testing
|
||||
pub mod v1;
|
||||
|
||||
pub use v1 as latest;
|
||||
|
||||
use crate::models::error::Error;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub enum Version {
|
||||
#[cfg(feature = "testing")]
|
||||
/// only used for testing purposes, don't include it in your matching arms
|
||||
V0,
|
||||
V1,
|
||||
}
|
||||
|
||||
impl From<u64> for Version {
|
||||
fn from(value: u64) -> Self {
|
||||
#[cfg(feature = "testing")]
|
||||
let zero_version = Version::V0;
|
||||
#[cfg(not(feature = "testing"))]
|
||||
let zero_version = latest::VERSION;
|
||||
match value {
|
||||
0 => zero_version,
|
||||
1 => Version::V1,
|
||||
_ => latest::VERSION, // if unknown, it means we're behind, so we can use the latest we know about
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Version> for u64 {
|
||||
fn from(value: Version) -> Self {
|
||||
// remember to modify the above match if you're bumping the version
|
||||
match value {
|
||||
#[cfg(feature = "testing")]
|
||||
Version::V0 => 0,
|
||||
Version::V1 => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct Request {
|
||||
pub version: Version,
|
||||
pub(crate) inner: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct Response {
|
||||
pub version: Version,
|
||||
pub(crate) inner: Vec<u8>,
|
||||
}
|
||||
|
||||
pub trait Extract<T> {
|
||||
fn extract(&self) -> Result<(T, Version), Error>;
|
||||
}
|
||||
|
||||
pub trait Construct<T>: Sized {
|
||||
fn construct(info: T, version: Version) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
pub type AxumResult<T> = Result<T, AxumErrorResponse>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
pub struct ErrorResponse {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl Display for ErrorResponse {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.message.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AxumErrorResponse {
|
||||
message: ErrorResponse,
|
||||
status: StatusCode,
|
||||
}
|
||||
|
||||
impl AxumErrorResponse {
|
||||
pub fn bad_request(msg: impl Display) -> Self {
|
||||
Self {
|
||||
message: ErrorResponse {
|
||||
message: msg.to_string(),
|
||||
},
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl axum::response::IntoResponse for AxumErrorResponse {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
(self.status, self.message.message.to_string()).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
mod tests {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const CREDENTIAL_BYTES: [u8; 1245] = [
|
||||
0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254,
|
||||
16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139,
|
||||
154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123,
|
||||
35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1,
|
||||
151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43,
|
||||
90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193,
|
||||
39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81,
|
||||
184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195,
|
||||
196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48,
|
||||
92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48,
|
||||
166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186,
|
||||
19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38,
|
||||
228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85,
|
||||
88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241,
|
||||
85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112,
|
||||
48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31,
|
||||
97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203,
|
||||
71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107,
|
||||
213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180,
|
||||
178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1,
|
||||
96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166,
|
||||
30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212,
|
||||
207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71,
|
||||
223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22,
|
||||
119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131,
|
||||
59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119,
|
||||
240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202,
|
||||
58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144,
|
||||
189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33,
|
||||
30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150,
|
||||
74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6,
|
||||
227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85,
|
||||
15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38,
|
||||
161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20,
|
||||
47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48,
|
||||
135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170,
|
||||
150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109,
|
||||
55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249,
|
||||
87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144,
|
||||
178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32,
|
||||
203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184,
|
||||
96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169,
|
||||
24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61,
|
||||
130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82,
|
||||
108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1,
|
||||
32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234,
|
||||
150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241,
|
||||
203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217,
|
||||
160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52,
|
||||
147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81,
|
||||
70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233,
|
||||
178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98,
|
||||
110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69,
|
||||
131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251,
|
||||
197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233,
|
||||
162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106,
|
||||
83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145,
|
||||
192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124,
|
||||
55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0,
|
||||
0, 0, 0, 0, 0, 1,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bincode::Options;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, models::Request};
|
||||
|
||||
use super::super::{Error, QueryType, VersionedRequest};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct InnerAvailableBandwidthRequest {}
|
||||
|
||||
impl TryFrom<VersionedRequest> for InnerAvailableBandwidthRequest {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
|
||||
match value.query_type {
|
||||
QueryType::AvailableBandwidth => {
|
||||
Ok(make_bincode_serializer().deserialize(&value.inner)?)
|
||||
}
|
||||
QueryType::TopupBandwidth => Err(Error::InvalidQueryType {
|
||||
source_query_type: value.query_type.to_string(),
|
||||
target_query_type: QueryType::AvailableBandwidth.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerAvailableBandwidthRequest> for VersionedRequest {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Request> for InnerAvailableBandwidthRequest {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: Request) -> Result<Self, Self::Error> {
|
||||
VersionedRequest::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerAvailableBandwidthRequest> for Request {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
|
||||
VersionedRequest::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
let req = InnerAvailableBandwidthRequest {};
|
||||
let ser = VersionedRequest::try_from(req).unwrap();
|
||||
assert_eq!(QueryType::AvailableBandwidth, ser.query_type);
|
||||
let de = InnerAvailableBandwidthRequest::try_from(ser).unwrap();
|
||||
assert_eq!(req, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_content() {
|
||||
let future_req = VersionedRequest {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerAvailableBandwidthRequest::try_from(future_req).is_ok());
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bincode::Options;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, models::Response};
|
||||
|
||||
use super::super::{Error, QueryType, VersionedResponse};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct InnerAvailableBandwidthResponse {}
|
||||
|
||||
impl TryFrom<VersionedResponse> for InnerAvailableBandwidthResponse {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
|
||||
match value.query_type {
|
||||
QueryType::AvailableBandwidth => {
|
||||
Ok(make_bincode_serializer().deserialize(&value.inner)?)
|
||||
}
|
||||
QueryType::TopupBandwidth => Err(Error::InvalidQueryType {
|
||||
source_query_type: value.query_type.to_string(),
|
||||
target_query_type: QueryType::AvailableBandwidth.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerAvailableBandwidthResponse> for VersionedResponse {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Response> for InnerAvailableBandwidthResponse {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: Response) -> Result<Self, Self::Error> {
|
||||
VersionedResponse::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerAvailableBandwidthResponse> for Response {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
|
||||
VersionedResponse::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
let resp = InnerAvailableBandwidthResponse {};
|
||||
let ser = VersionedResponse::try_from(resp).unwrap();
|
||||
assert_eq!(QueryType::AvailableBandwidth, ser.query_type);
|
||||
let de = InnerAvailableBandwidthResponse::try_from(ser).unwrap();
|
||||
assert_eq!(resp, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_content() {
|
||||
let future_resp = VersionedResponse {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerAvailableBandwidthResponse::try_from(future_resp).is_ok());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::{
|
||||
available_bandwidth::{
|
||||
request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse,
|
||||
},
|
||||
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
|
||||
QueryType, VersionedRequest, VersionedResponse, VERSION,
|
||||
};
|
||||
use crate::models::{error::Error, Construct, Extract, Version};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RequestData {
|
||||
AvailableBandwidth(()),
|
||||
TopUpBandwidth(()),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ResponseData {
|
||||
AvailableBandwidth(()),
|
||||
TopUpBandwidth(()),
|
||||
}
|
||||
|
||||
impl Construct<RequestData> for VersionedRequest {
|
||||
fn construct(info: RequestData, _version: Version) -> Result<Self, Error> {
|
||||
match info {
|
||||
RequestData::AvailableBandwidth(_) => Ok(InnerAvailableBandwidthRequest {}.try_into()?),
|
||||
RequestData::TopUpBandwidth(_) => Ok(InnerTopUpRequest {}.try_into()?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Extract<RequestData> for VersionedRequest {
|
||||
fn extract(&self) -> Result<(RequestData, Version), Error> {
|
||||
match self.query_type {
|
||||
QueryType::AvailableBandwidth => {
|
||||
let _req = InnerAvailableBandwidthRequest::try_from(self.clone())?;
|
||||
Ok((RequestData::AvailableBandwidth(()), VERSION))
|
||||
}
|
||||
QueryType::TopupBandwidth => {
|
||||
let _req = InnerTopUpRequest::try_from(self.clone())?;
|
||||
Ok((RequestData::TopUpBandwidth(()), VERSION))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Construct<ResponseData> for VersionedResponse {
|
||||
fn construct(info: ResponseData, _version: Version) -> Result<Self, Error> {
|
||||
match info {
|
||||
ResponseData::AvailableBandwidth(()) => {
|
||||
Ok(InnerAvailableBandwidthResponse {}.try_into()?)
|
||||
}
|
||||
ResponseData::TopUpBandwidth(()) => Ok(InnerTopUpResponse {}.try_into()?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Extract<ResponseData> for VersionedResponse {
|
||||
fn extract(&self) -> Result<(ResponseData, Version), Error> {
|
||||
match self.query_type {
|
||||
QueryType::AvailableBandwidth => {
|
||||
let _resp = InnerAvailableBandwidthResponse::try_from(self.clone())?;
|
||||
Ok((ResponseData::AvailableBandwidth(()), VERSION))
|
||||
}
|
||||
QueryType::TopupBandwidth => {
|
||||
let _resp = InnerTopUpResponse::try_from(self.clone())?;
|
||||
Ok((ResponseData::TopUpBandwidth(()), VERSION))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use bincode::Options;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use super::error::Error;
|
||||
use crate::{
|
||||
make_bincode_serializer,
|
||||
models::{Request, Response, Version},
|
||||
};
|
||||
|
||||
pub(crate) mod available_bandwidth;
|
||||
pub mod interface;
|
||||
pub(crate) mod topup_bandwidth;
|
||||
|
||||
pub const VERSION: Version = Version::V0;
|
||||
|
||||
pub use available_bandwidth::{
|
||||
request::InnerAvailableBandwidthRequest as AvailableBandwidthRequest,
|
||||
response::InnerAvailableBandwidthResponse as AvailableBandwidthResponse,
|
||||
};
|
||||
pub use topup_bandwidth::{
|
||||
request::InnerTopUpRequest as TopUpRequest, response::InnerTopUpResponse as TopUpResponse,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub enum QueryType {
|
||||
AvailableBandwidth,
|
||||
TopupBandwidth,
|
||||
}
|
||||
|
||||
impl Display for QueryType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct VersionedRequest {
|
||||
query_type: QueryType,
|
||||
inner: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TryFrom<VersionedRequest> for Request {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
|
||||
Ok(Request {
|
||||
version: VERSION,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Request> for VersionedRequest {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Request) -> Result<Self, Self::Error> {
|
||||
if value.version != VERSION {
|
||||
return Err(Error::InvalidVersion {
|
||||
source_version: value.version,
|
||||
target_version: VERSION,
|
||||
});
|
||||
}
|
||||
Ok(make_bincode_serializer().deserialize(&value.inner)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct VersionedResponse {
|
||||
query_type: QueryType,
|
||||
inner: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TryFrom<VersionedResponse> for Response {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
|
||||
Ok(Response {
|
||||
version: VERSION,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Response> for VersionedResponse {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Response) -> Result<Self, Self::Error> {
|
||||
if value.version != VERSION {
|
||||
return Err(Error::InvalidVersion {
|
||||
source_version: value.version,
|
||||
target_version: VERSION,
|
||||
});
|
||||
}
|
||||
Ok(make_bincode_serializer().deserialize(&value.inner)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use self::{
|
||||
available_bandwidth::{
|
||||
request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse,
|
||||
},
|
||||
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde_request_av_bw() {
|
||||
let req = VersionedRequest {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerAvailableBandwidthRequest {})
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let ser = Request::try_from(req.clone()).unwrap();
|
||||
assert_eq!(VERSION, ser.version);
|
||||
let de = VersionedRequest::try_from(ser).unwrap();
|
||||
assert_eq!(req, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_response_av_bw() {
|
||||
let resp = VersionedResponse {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerAvailableBandwidthResponse {})
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let ser = Response::try_from(resp.clone()).unwrap();
|
||||
assert_eq!(VERSION, ser.version);
|
||||
let de = VersionedResponse::try_from(ser).unwrap();
|
||||
assert_eq!(resp, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_request_topup() {
|
||||
let req = VersionedRequest {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerTopUpRequest {})
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let ser = Request::try_from(req.clone()).unwrap();
|
||||
assert_eq!(VERSION, ser.version);
|
||||
let de = VersionedRequest::try_from(ser).unwrap();
|
||||
assert_eq!(req, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_response_topup() {
|
||||
let resp = VersionedResponse {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerTopUpResponse {})
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let ser = Response::try_from(resp.clone()).unwrap();
|
||||
assert_eq!(VERSION, ser.version);
|
||||
let de = VersionedResponse::try_from(ser).unwrap();
|
||||
assert_eq!(resp, de);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bincode::Options;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, models::Request};
|
||||
|
||||
use super::super::{Error, QueryType, VersionedRequest};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct InnerTopUpRequest {}
|
||||
|
||||
impl TryFrom<VersionedRequest> for InnerTopUpRequest {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
|
||||
match value.query_type {
|
||||
QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?),
|
||||
QueryType::AvailableBandwidth => Err(Error::InvalidQueryType {
|
||||
source_query_type: value.query_type.to_string(),
|
||||
target_query_type: QueryType::TopupBandwidth.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerTopUpRequest> for VersionedRequest {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Request> for InnerTopUpRequest {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: Request) -> Result<Self, Self::Error> {
|
||||
VersionedRequest::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerTopUpRequest> for Request {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
|
||||
VersionedRequest::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
let req = InnerTopUpRequest {};
|
||||
let ser = VersionedRequest::try_from(req.clone()).unwrap();
|
||||
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
|
||||
let de = InnerTopUpRequest::try_from(ser).unwrap();
|
||||
assert_eq!(req, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_content() {
|
||||
let future_req = VersionedRequest {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerTopUpRequest::try_from(future_req).is_ok());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bincode::Options;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, models::Response};
|
||||
|
||||
use super::super::{Error, QueryType, VersionedResponse};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct InnerTopUpResponse {}
|
||||
|
||||
impl TryFrom<VersionedResponse> for InnerTopUpResponse {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
|
||||
match value.query_type {
|
||||
QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?),
|
||||
QueryType::AvailableBandwidth => Err(Error::InvalidQueryType {
|
||||
source_query_type: value.query_type.to_string(),
|
||||
target_query_type: QueryType::TopupBandwidth.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerTopUpResponse> for VersionedResponse {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Response> for InnerTopUpResponse {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: Response) -> Result<Self, Self::Error> {
|
||||
VersionedResponse::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerTopUpResponse> for Response {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
|
||||
VersionedResponse::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
let resp = InnerTopUpResponse {};
|
||||
let ser = VersionedResponse::try_from(resp.clone()).unwrap();
|
||||
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
|
||||
let de = InnerTopUpResponse::try_from(ser).unwrap();
|
||||
assert_eq!(resp, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_content() {
|
||||
let future_resp = VersionedResponse {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerTopUpResponse::try_from(future_resp).is_ok());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bincode::Options;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, models::Request};
|
||||
|
||||
use super::super::{Error, QueryType, VersionedRequest};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct InnerAvailableBandwidthRequest {}
|
||||
|
||||
impl TryFrom<VersionedRequest> for InnerAvailableBandwidthRequest {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
|
||||
match value.query_type {
|
||||
QueryType::AvailableBandwidth => {
|
||||
Ok(make_bincode_serializer().deserialize(&value.inner)?)
|
||||
}
|
||||
QueryType::TopupBandwidth => Err(Error::InvalidQueryType {
|
||||
source_query_type: value.query_type.to_string(),
|
||||
target_query_type: QueryType::AvailableBandwidth.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerAvailableBandwidthRequest> for VersionedRequest {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Request> for InnerAvailableBandwidthRequest {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: Request) -> Result<Self, Self::Error> {
|
||||
VersionedRequest::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerAvailableBandwidthRequest> for Request {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: InnerAvailableBandwidthRequest) -> Result<Self, Self::Error> {
|
||||
VersionedRequest::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
let req = InnerAvailableBandwidthRequest {};
|
||||
let ser = VersionedRequest::try_from(req).unwrap();
|
||||
assert_eq!(QueryType::AvailableBandwidth, ser.query_type);
|
||||
let de = InnerAvailableBandwidthRequest::try_from(ser).unwrap();
|
||||
assert_eq!(req, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_content() {
|
||||
let future_req = VersionedRequest {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerAvailableBandwidthRequest::try_from(future_req).is_ok());
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bincode::Options;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, models::Response};
|
||||
|
||||
use super::super::{Error, QueryType, VersionedResponse};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct InnerAvailableBandwidthResponse {
|
||||
pub available_bandwidth: i64,
|
||||
}
|
||||
|
||||
impl TryFrom<VersionedResponse> for InnerAvailableBandwidthResponse {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
|
||||
match value.query_type {
|
||||
QueryType::AvailableBandwidth => {
|
||||
Ok(make_bincode_serializer().deserialize(&value.inner)?)
|
||||
}
|
||||
QueryType::TopupBandwidth => Err(Error::InvalidQueryType {
|
||||
source_query_type: value.query_type.to_string(),
|
||||
target_query_type: QueryType::AvailableBandwidth.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerAvailableBandwidthResponse> for VersionedResponse {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Response> for InnerAvailableBandwidthResponse {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: Response) -> Result<Self, Self::Error> {
|
||||
VersionedResponse::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerAvailableBandwidthResponse> for Response {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: InnerAvailableBandwidthResponse) -> Result<Self, Self::Error> {
|
||||
VersionedResponse::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
let resp = InnerAvailableBandwidthResponse {
|
||||
available_bandwidth: 42,
|
||||
};
|
||||
let ser = VersionedResponse::try_from(resp).unwrap();
|
||||
assert_eq!(QueryType::AvailableBandwidth, ser.query_type);
|
||||
let de = InnerAvailableBandwidthResponse::try_from(ser).unwrap();
|
||||
assert_eq!(resp, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_content() {
|
||||
let future_resp = VersionedResponse {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerAvailableBandwidthResponse::try_from(future_resp).is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
|
||||
#[cfg(feature = "testing")]
|
||||
use super::super::v0 as previous;
|
||||
|
||||
use super::{
|
||||
available_bandwidth::{
|
||||
request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse,
|
||||
},
|
||||
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
|
||||
QueryType, VersionedRequest, VersionedResponse, VERSION,
|
||||
};
|
||||
use crate::models::{error::Error, Construct, Extract, Version};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RequestData {
|
||||
AvailableBandwidth(()),
|
||||
TopUpBandwidth(Box<CredentialSpendingData>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ResponseData {
|
||||
AvailableBandwidth(i64),
|
||||
TopUpBandwidth(i64),
|
||||
}
|
||||
|
||||
impl Construct<RequestData> for VersionedRequest {
|
||||
fn construct(info: RequestData, _version: Version) -> Result<Self, Error> {
|
||||
match info {
|
||||
RequestData::AvailableBandwidth(_) => Ok(InnerAvailableBandwidthRequest {}.try_into()?),
|
||||
RequestData::TopUpBandwidth(credential) => Ok(InnerTopUpRequest {
|
||||
credential: *credential,
|
||||
}
|
||||
.try_into()?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Extract<RequestData> for VersionedRequest {
|
||||
fn extract(&self) -> Result<(RequestData, Version), Error> {
|
||||
match self.query_type {
|
||||
QueryType::AvailableBandwidth => {
|
||||
let _req = InnerAvailableBandwidthRequest::try_from(self.clone())?;
|
||||
Ok((RequestData::AvailableBandwidth(()), VERSION))
|
||||
}
|
||||
QueryType::TopupBandwidth => {
|
||||
let req = InnerTopUpRequest::try_from(self.clone())?;
|
||||
Ok((
|
||||
RequestData::TopUpBandwidth(Box::new(req.credential)),
|
||||
VERSION,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Construct<ResponseData> for VersionedResponse {
|
||||
fn construct(info: ResponseData, _version: Version) -> Result<Self, Error> {
|
||||
match info {
|
||||
ResponseData::AvailableBandwidth(available_bandwidth) => {
|
||||
Ok(InnerAvailableBandwidthResponse {
|
||||
available_bandwidth,
|
||||
}
|
||||
.try_into()?)
|
||||
}
|
||||
ResponseData::TopUpBandwidth(available_bandwidth) => Ok(InnerTopUpResponse {
|
||||
available_bandwidth,
|
||||
}
|
||||
.try_into()?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Extract<ResponseData> for VersionedResponse {
|
||||
fn extract(&self) -> Result<(ResponseData, Version), Error> {
|
||||
match self.query_type {
|
||||
QueryType::AvailableBandwidth => {
|
||||
let resp = InnerAvailableBandwidthResponse::try_from(self.clone())?;
|
||||
Ok((
|
||||
ResponseData::AvailableBandwidth(resp.available_bandwidth),
|
||||
VERSION,
|
||||
))
|
||||
}
|
||||
QueryType::TopupBandwidth => {
|
||||
let resp = InnerTopUpResponse::try_from(self.clone())?;
|
||||
Ok((
|
||||
ResponseData::TopUpBandwidth(resp.available_bandwidth),
|
||||
VERSION,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions
|
||||
#[cfg(feature = "testing")]
|
||||
impl TryFrom<previous::interface::RequestData> for RequestData {
|
||||
type Error = super::Error;
|
||||
|
||||
fn try_from(value: previous::interface::RequestData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
previous::interface::RequestData::AvailableBandwidth(inner) => {
|
||||
Ok(Self::AvailableBandwidth(inner))
|
||||
}
|
||||
previous::interface::RequestData::TopUpBandwidth(_) => {
|
||||
Err(super::Error::UpdateNotPossible {
|
||||
from: previous::VERSION,
|
||||
to: VERSION,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions
|
||||
#[cfg(feature = "testing")]
|
||||
impl TryFrom<RequestData> for previous::interface::RequestData {
|
||||
type Error = super::Error;
|
||||
|
||||
fn try_from(value: RequestData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
RequestData::AvailableBandwidth(inner) => Ok(Self::AvailableBandwidth(inner)),
|
||||
RequestData::TopUpBandwidth(_) => Ok(Self::TopUpBandwidth(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions
|
||||
#[cfg(feature = "testing")]
|
||||
impl TryFrom<previous::interface::ResponseData> for ResponseData {
|
||||
type Error = super::Error;
|
||||
|
||||
fn try_from(value: previous::interface::ResponseData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
previous::interface::ResponseData::AvailableBandwidth(_) => {
|
||||
Err(super::Error::UpdateNotPossible {
|
||||
from: previous::VERSION,
|
||||
to: VERSION,
|
||||
})
|
||||
}
|
||||
previous::interface::ResponseData::TopUpBandwidth(_) => {
|
||||
Err(super::Error::UpdateNotPossible {
|
||||
from: previous::VERSION,
|
||||
to: VERSION,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this should be with #[cfg(feature = "testing")] only coming from v0, don't copy this for future versions
|
||||
#[cfg(feature = "testing")]
|
||||
impl TryFrom<ResponseData> for previous::interface::ResponseData {
|
||||
type Error = super::Error;
|
||||
|
||||
fn try_from(value: ResponseData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
ResponseData::AvailableBandwidth(_) => Ok(Self::AvailableBandwidth(())),
|
||||
ResponseData::TopUpBandwidth(_) => Ok(Self::TopUpBandwidth(())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::models::tests::CREDENTIAL_BYTES;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn request_upgrade() {
|
||||
assert_eq!(
|
||||
RequestData::try_from(previous::interface::RequestData::AvailableBandwidth(()))
|
||||
.unwrap(),
|
||||
RequestData::AvailableBandwidth(())
|
||||
);
|
||||
assert!(
|
||||
RequestData::try_from(previous::interface::RequestData::TopUpBandwidth(())).is_err(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_upgrade() {
|
||||
assert!(
|
||||
ResponseData::try_from(previous::interface::ResponseData::AvailableBandwidth(()))
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
ResponseData::try_from(previous::interface::ResponseData::TopUpBandwidth(())).is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_downgrade() {
|
||||
assert_eq!(
|
||||
previous::interface::RequestData::try_from(RequestData::AvailableBandwidth(()))
|
||||
.unwrap(),
|
||||
previous::interface::RequestData::AvailableBandwidth(())
|
||||
);
|
||||
assert_eq!(
|
||||
previous::interface::RequestData::try_from(RequestData::TopUpBandwidth(Box::new(
|
||||
CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap()
|
||||
)))
|
||||
.unwrap(),
|
||||
previous::interface::RequestData::TopUpBandwidth(())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn response_downgrade() {
|
||||
assert_eq!(
|
||||
previous::interface::ResponseData::try_from(ResponseData::AvailableBandwidth(42))
|
||||
.unwrap(),
|
||||
previous::interface::ResponseData::AvailableBandwidth(())
|
||||
);
|
||||
assert_eq!(
|
||||
previous::interface::ResponseData::try_from(ResponseData::TopUpBandwidth(42)).unwrap(),
|
||||
previous::interface::ResponseData::TopUpBandwidth(())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use bincode::Options;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use super::error::Error;
|
||||
use crate::{
|
||||
make_bincode_serializer,
|
||||
models::{Request, Response, Version},
|
||||
};
|
||||
|
||||
pub use available_bandwidth::{
|
||||
request::InnerAvailableBandwidthRequest as AvailableBandwidthRequest,
|
||||
response::InnerAvailableBandwidthResponse as AvailableBandwidthResponse,
|
||||
};
|
||||
pub use topup_bandwidth::{
|
||||
request::InnerTopUpRequest as TopUpRequest, response::InnerTopUpResponse as TopUpResponse,
|
||||
};
|
||||
|
||||
pub(crate) mod available_bandwidth;
|
||||
pub mod interface;
|
||||
pub(crate) mod topup_bandwidth;
|
||||
|
||||
pub const VERSION: Version = Version::V1;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub enum QueryType {
|
||||
AvailableBandwidth,
|
||||
TopupBandwidth,
|
||||
}
|
||||
|
||||
impl Display for QueryType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct VersionedRequest {
|
||||
query_type: QueryType,
|
||||
inner: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TryFrom<VersionedRequest> for Request {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
|
||||
Ok(Request {
|
||||
version: VERSION,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Request> for VersionedRequest {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Request) -> Result<Self, Self::Error> {
|
||||
if value.version != VERSION {
|
||||
return Err(Error::InvalidVersion {
|
||||
source_version: value.version,
|
||||
target_version: VERSION,
|
||||
});
|
||||
}
|
||||
Ok(make_bincode_serializer().deserialize(&value.inner)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct VersionedResponse {
|
||||
query_type: QueryType,
|
||||
inner: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TryFrom<VersionedResponse> for Response {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
|
||||
Ok(Response {
|
||||
version: VERSION,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Response> for VersionedResponse {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Response) -> Result<Self, Self::Error> {
|
||||
if value.version != VERSION {
|
||||
return Err(Error::InvalidVersion {
|
||||
source_version: value.version,
|
||||
target_version: VERSION,
|
||||
});
|
||||
}
|
||||
Ok(make_bincode_serializer().deserialize(&value.inner)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
|
||||
use crate::models::tests::CREDENTIAL_BYTES;
|
||||
|
||||
use self::{
|
||||
available_bandwidth::{
|
||||
request::InnerAvailableBandwidthRequest, response::InnerAvailableBandwidthResponse,
|
||||
},
|
||||
topup_bandwidth::{request::InnerTopUpRequest, response::InnerTopUpResponse},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mismatched_request_version() {
|
||||
let version = Version::V0;
|
||||
let future_bw = Request {
|
||||
version,
|
||||
inner: vec![],
|
||||
};
|
||||
if let Err(Error::InvalidVersion {
|
||||
source_version,
|
||||
target_version,
|
||||
}) = VersionedRequest::try_from(future_bw)
|
||||
{
|
||||
assert_eq!(source_version, version);
|
||||
assert_eq!(target_version, VERSION);
|
||||
} else {
|
||||
panic!("failed");
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatched_response_version() {
|
||||
let version = Version::V0;
|
||||
let future_bw = Response {
|
||||
version,
|
||||
inner: vec![],
|
||||
};
|
||||
if let Err(Error::InvalidVersion {
|
||||
source_version,
|
||||
target_version,
|
||||
}) = VersionedResponse::try_from(future_bw)
|
||||
{
|
||||
assert_eq!(source_version, version);
|
||||
assert_eq!(target_version, VERSION);
|
||||
} else {
|
||||
panic!("failed");
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_request_av_bw() {
|
||||
let req = VersionedRequest {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerAvailableBandwidthResponse {
|
||||
available_bandwidth: 42,
|
||||
})
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let ser = Request::try_from(req.clone()).unwrap();
|
||||
assert_eq!(VERSION, ser.version);
|
||||
let de = VersionedRequest::try_from(ser).unwrap();
|
||||
assert_eq!(req, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_response_av_bw() {
|
||||
let resp = VersionedResponse {
|
||||
query_type: QueryType::AvailableBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerAvailableBandwidthRequest {})
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let ser = Response::try_from(resp.clone()).unwrap();
|
||||
assert_eq!(VERSION, ser.version);
|
||||
let de = VersionedResponse::try_from(ser).unwrap();
|
||||
assert_eq!(resp, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_request_topup() {
|
||||
let req = VersionedRequest {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerTopUpRequest {
|
||||
credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(),
|
||||
})
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let ser = Request::try_from(req.clone()).unwrap();
|
||||
assert_eq!(VERSION, ser.version);
|
||||
let de = VersionedRequest::try_from(ser).unwrap();
|
||||
assert_eq!(req, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_response_topup() {
|
||||
let resp = VersionedResponse {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: make_bincode_serializer()
|
||||
.serialize(&InnerTopUpResponse {
|
||||
available_bandwidth: 42,
|
||||
})
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let ser = Response::try_from(resp.clone()).unwrap();
|
||||
assert_eq!(VERSION, ser.version);
|
||||
let de = VersionedResponse::try_from(ser).unwrap();
|
||||
assert_eq!(resp, de);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bincode::Options;
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, models::Request};
|
||||
|
||||
use super::super::{Error, QueryType, VersionedRequest};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct InnerTopUpRequest {
|
||||
/// Ecash credential
|
||||
pub credential: CredentialSpendingData,
|
||||
}
|
||||
|
||||
impl TryFrom<VersionedRequest> for InnerTopUpRequest {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedRequest) -> Result<Self, Self::Error> {
|
||||
match value.query_type {
|
||||
QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?),
|
||||
QueryType::AvailableBandwidth => Err(Error::InvalidQueryType {
|
||||
source_query_type: value.query_type.to_string(),
|
||||
target_query_type: QueryType::TopupBandwidth.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerTopUpRequest> for VersionedRequest {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Request> for InnerTopUpRequest {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: Request) -> Result<Self, Self::Error> {
|
||||
VersionedRequest::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerTopUpRequest> for Request {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: InnerTopUpRequest) -> Result<Self, Self::Error> {
|
||||
VersionedRequest::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::tests::CREDENTIAL_BYTES;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
let req = InnerTopUpRequest {
|
||||
credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(),
|
||||
};
|
||||
let ser = VersionedRequest::try_from(req.clone()).unwrap();
|
||||
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
|
||||
let de = InnerTopUpRequest::try_from(ser).unwrap();
|
||||
assert_eq!(req, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_content() {
|
||||
let future_req = VersionedRequest {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerTopUpRequest::try_from(future_req).is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bincode::Options;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{make_bincode_serializer, models::Response};
|
||||
|
||||
use super::super::{Error, QueryType, VersionedResponse};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct InnerTopUpResponse {
|
||||
pub available_bandwidth: i64,
|
||||
}
|
||||
|
||||
impl TryFrom<VersionedResponse> for InnerTopUpResponse {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: VersionedResponse) -> Result<Self, Self::Error> {
|
||||
match value.query_type {
|
||||
QueryType::TopupBandwidth => Ok(make_bincode_serializer().deserialize(&value.inner)?),
|
||||
QueryType::AvailableBandwidth => Err(Error::InvalidQueryType {
|
||||
source_query_type: value.query_type.to_string(),
|
||||
target_query_type: QueryType::TopupBandwidth.to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerTopUpResponse> for VersionedResponse {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: make_bincode_serializer().serialize(&value)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Response> for InnerTopUpResponse {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: Response) -> Result<Self, Self::Error> {
|
||||
VersionedResponse::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<InnerTopUpResponse> for Response {
|
||||
type Error = crate::error::MetadataError;
|
||||
|
||||
fn try_from(value: InnerTopUpResponse) -> Result<Self, Self::Error> {
|
||||
VersionedResponse::try_from(value)?
|
||||
.try_into()
|
||||
.map_err(|err: Error| crate::error::MetadataError::Models {
|
||||
message: err.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serde() {
|
||||
let resp = InnerTopUpResponse {
|
||||
available_bandwidth: 42,
|
||||
};
|
||||
let ser = VersionedResponse::try_from(resp.clone()).unwrap();
|
||||
assert_eq!(QueryType::TopupBandwidth, ser.query_type);
|
||||
let de = InnerTopUpResponse::try_from(ser).unwrap();
|
||||
assert_eq!(resp, de);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_content() {
|
||||
let future_resp = VersionedResponse {
|
||||
query_type: QueryType::TopupBandwidth,
|
||||
inner: vec![],
|
||||
};
|
||||
assert!(InnerTopUpResponse::try_from(future_resp).is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub const V1_API_VERSION: &str = "v1";
|
||||
|
||||
pub const BANDWIDTH: &str = "bandwidth";
|
||||
|
||||
pub const VERSION: &str = "version";
|
||||
pub const AVAILABLE: &str = "available";
|
||||
pub const TOPUP: &str = "topup";
|
||||
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "nym-wireguard-private-metadata-tests"
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
axum = { workspace = true, features = ["tokio", "macros"] }
|
||||
nym-credential-verification = { path = "../../credential-verification" }
|
||||
nym-credentials-interface = { path = "../../credentials-interface" }
|
||||
nym-http-api-client = { path = "../../http-api-client" }
|
||||
nym-http-api-common = { path = "../../http-api-common", features = [
|
||||
"middleware",
|
||||
"utoipa",
|
||||
"output",
|
||||
] }
|
||||
nym-wireguard = { path = "../../wireguard" }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
|
||||
tower-http = { workspace = true, features = [
|
||||
"cors",
|
||||
"trace",
|
||||
"compression-br",
|
||||
"compression-deflate",
|
||||
"compression-gzip",
|
||||
"compression-zstd",
|
||||
] }
|
||||
utoipa = { workspace = true, features = ["axum_extras", "time"] }
|
||||
|
||||
nym-wireguard-private-metadata-client = { path = "../client" }
|
||||
nym-wireguard-private-metadata-shared = { path = "../shared", features = [
|
||||
"testing",
|
||||
] }
|
||||
nym-wireguard-private-metadata-server = { path = "../server" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,217 @@
|
||||
#[cfg(test)]
|
||||
mod v0;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use nym_credential_verification::{ClientBandwidth, TicketVerifier};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_http_api_client::Client;
|
||||
use nym_wireguard::{peer_controller::PeerControlRequest, CONTROL_CHANNEL_SIZE};
|
||||
use nym_wireguard_private_metadata_client::WireguardMetadataApiClient;
|
||||
use nym_wireguard_private_metadata_server::{
|
||||
AppState, PeerControllerTransceiver, RouterBuilder,
|
||||
};
|
||||
use nym_wireguard_private_metadata_shared::{latest, v0, v1, ErrorResponse};
|
||||
use tokio::{net::TcpListener, sync::mpsc};
|
||||
|
||||
pub(crate) const VERIFIER_AVAILABLE_BANDWIDTH: i64 = 42;
|
||||
pub(crate) const CREDENTIAL_BYTES: [u8; 1245] = [
|
||||
0, 0, 4, 133, 96, 179, 223, 185, 136, 23, 213, 166, 59, 203, 66, 69, 209, 181, 227, 254,
|
||||
16, 102, 98, 237, 59, 119, 170, 111, 31, 194, 51, 59, 120, 17, 115, 229, 79, 91, 11, 139,
|
||||
154, 2, 212, 23, 68, 70, 167, 3, 240, 54, 224, 171, 221, 1, 69, 48, 60, 118, 119, 249, 123,
|
||||
35, 172, 227, 131, 96, 232, 209, 187, 123, 4, 197, 102, 90, 96, 45, 125, 135, 140, 99, 1,
|
||||
151, 17, 131, 143, 157, 97, 107, 139, 232, 212, 87, 14, 115, 253, 255, 166, 167, 186, 43,
|
||||
90, 96, 173, 105, 120, 40, 10, 163, 250, 224, 214, 200, 178, 4, 160, 16, 130, 59, 76, 193,
|
||||
39, 240, 3, 101, 141, 209, 183, 226, 186, 207, 56, 210, 187, 7, 164, 240, 164, 205, 37, 81,
|
||||
184, 214, 193, 195, 90, 205, 238, 225, 195, 104, 12, 123, 203, 57, 233, 243, 215, 145, 195,
|
||||
196, 57, 38, 125, 172, 18, 47, 63, 165, 110, 219, 180, 40, 58, 116, 92, 254, 160, 98, 48,
|
||||
92, 254, 232, 107, 184, 80, 234, 60, 160, 235, 249, 76, 41, 38, 165, 28, 40, 136, 74, 48,
|
||||
166, 50, 245, 23, 201, 140, 101, 79, 93, 235, 128, 186, 146, 126, 180, 134, 43, 13, 186,
|
||||
19, 195, 48, 168, 201, 29, 216, 95, 176, 198, 132, 188, 64, 39, 212, 150, 32, 52, 53, 38,
|
||||
228, 199, 122, 226, 217, 75, 40, 191, 151, 48, 164, 242, 177, 79, 14, 122, 105, 151, 85,
|
||||
88, 199, 162, 17, 96, 103, 83, 178, 128, 9, 24, 30, 74, 108, 241, 85, 240, 166, 97, 241,
|
||||
85, 199, 11, 198, 226, 234, 70, 107, 145, 28, 208, 114, 51, 12, 234, 108, 101, 202, 112,
|
||||
48, 185, 22, 159, 67, 109, 49, 27, 149, 90, 109, 32, 226, 112, 7, 201, 208, 209, 104, 31,
|
||||
97, 134, 204, 145, 27, 181, 206, 181, 106, 32, 110, 136, 115, 249, 201, 111, 5, 245, 203,
|
||||
71, 121, 169, 126, 151, 178, 236, 59, 221, 195, 48, 135, 115, 6, 50, 227, 74, 97, 107, 107,
|
||||
213, 90, 2, 203, 154, 138, 47, 128, 52, 134, 128, 224, 51, 65, 240, 90, 8, 55, 175, 180,
|
||||
178, 204, 206, 168, 110, 51, 57, 189, 169, 48, 169, 136, 121, 99, 51, 170, 178, 214, 74, 1,
|
||||
96, 151, 167, 25, 173, 180, 171, 155, 10, 55, 142, 234, 190, 113, 90, 79, 80, 244, 71, 166,
|
||||
30, 235, 113, 150, 133, 1, 218, 17, 109, 111, 223, 24, 216, 177, 41, 2, 204, 65, 221, 212,
|
||||
207, 236, 144, 6, 65, 224, 55, 42, 1, 1, 161, 134, 118, 127, 111, 220, 110, 127, 240, 71,
|
||||
223, 129, 12, 93, 20, 220, 60, 56, 71, 146, 184, 95, 132, 69, 28, 56, 53, 192, 213, 22,
|
||||
119, 230, 152, 225, 182, 188, 163, 219, 37, 175, 247, 73, 14, 247, 38, 72, 243, 1, 48, 131,
|
||||
59, 8, 13, 96, 143, 185, 127, 241, 161, 217, 24, 149, 193, 40, 16, 30, 202, 151, 28, 119,
|
||||
240, 153, 101, 156, 61, 193, 72, 245, 199, 181, 12, 231, 65, 166, 67, 142, 121, 207, 202,
|
||||
58, 197, 113, 188, 248, 42, 124, 105, 48, 161, 241, 55, 209, 36, 194, 27, 63, 233, 144,
|
||||
189, 85, 117, 234, 9, 139, 46, 31, 206, 114, 95, 131, 29, 240, 13, 81, 142, 140, 133, 33,
|
||||
30, 41, 141, 37, 80, 217, 95, 221, 76, 115, 86, 201, 165, 51, 252, 9, 28, 209, 1, 48, 150,
|
||||
74, 248, 212, 187, 222, 66, 210, 3, 200, 19, 217, 171, 184, 42, 148, 53, 150, 57, 50, 6,
|
||||
227, 227, 62, 49, 42, 148, 148, 157, 82, 191, 58, 24, 34, 56, 98, 120, 89, 105, 176, 85,
|
||||
15, 253, 241, 41, 153, 195, 136, 1, 48, 142, 126, 213, 101, 223, 79, 133, 230, 105, 38,
|
||||
161, 149, 2, 21, 136, 150, 42, 72, 218, 85, 146, 63, 223, 58, 108, 186, 183, 248, 62, 20,
|
||||
47, 34, 113, 160, 177, 204, 181, 16, 24, 212, 224, 35, 84, 51, 168, 56, 136, 11, 1, 48,
|
||||
135, 242, 62, 149, 230, 178, 32, 224, 119, 26, 234, 163, 237, 224, 114, 95, 112, 140, 170,
|
||||
150, 96, 125, 136, 221, 180, 78, 18, 11, 12, 184, 2, 198, 217, 119, 43, 69, 4, 172, 109,
|
||||
55, 183, 40, 131, 172, 161, 88, 183, 101, 1, 48, 173, 216, 22, 73, 42, 255, 211, 93, 249,
|
||||
87, 159, 115, 61, 91, 55, 130, 17, 216, 60, 34, 122, 55, 8, 244, 244, 153, 151, 57, 5, 144,
|
||||
178, 55, 249, 64, 211, 168, 34, 148, 56, 89, 92, 203, 70, 124, 219, 152, 253, 165, 0, 32,
|
||||
203, 116, 63, 7, 240, 222, 82, 86, 11, 149, 167, 72, 224, 55, 190, 66, 201, 65, 168, 184,
|
||||
96, 47, 194, 241, 168, 124, 7, 74, 214, 250, 37, 76, 32, 218, 69, 122, 103, 215, 145, 169,
|
||||
24, 212, 229, 168, 106, 10, 144, 31, 13, 25, 178, 242, 250, 106, 159, 40, 48, 163, 165, 61,
|
||||
130, 57, 146, 4, 73, 32, 254, 233, 125, 135, 212, 29, 111, 4, 177, 114, 15, 210, 170, 82,
|
||||
108, 110, 62, 166, 81, 209, 106, 176, 156, 14, 133, 242, 60, 127, 120, 242, 28, 97, 0, 1,
|
||||
32, 103, 93, 109, 89, 240, 91, 1, 84, 150, 50, 206, 157, 203, 49, 220, 120, 234, 175, 234,
|
||||
150, 126, 225, 94, 163, 164, 199, 138, 114, 62, 99, 106, 112, 1, 32, 171, 40, 220, 82, 241,
|
||||
203, 76, 146, 111, 139, 182, 179, 237, 182, 115, 75, 128, 201, 107, 43, 214, 0, 135, 217,
|
||||
160, 68, 150, 232, 144, 114, 237, 98, 32, 30, 134, 232, 59, 93, 163, 253, 244, 13, 202, 52,
|
||||
147, 168, 83, 121, 123, 95, 21, 210, 209, 225, 223, 143, 49, 10, 205, 238, 1, 22, 83, 81,
|
||||
70, 1, 32, 26, 76, 6, 234, 160, 50, 139, 102, 161, 232, 155, 106, 130, 171, 226, 210, 233,
|
||||
178, 85, 247, 71, 123, 55, 53, 46, 67, 148, 137, 156, 207, 208, 107, 1, 32, 102, 31, 4, 98,
|
||||
110, 156, 144, 61, 229, 140, 198, 84, 196, 238, 128, 35, 131, 182, 137, 125, 241, 95, 69,
|
||||
131, 170, 27, 2, 144, 75, 72, 242, 102, 3, 32, 121, 80, 45, 173, 56, 65, 218, 27, 40, 251,
|
||||
197, 32, 169, 104, 123, 110, 90, 78, 153, 166, 38, 9, 129, 228, 99, 8, 1, 116, 142, 233,
|
||||
162, 69, 32, 216, 169, 159, 116, 95, 12, 63, 176, 195, 6, 183, 123, 135, 75, 61, 112, 106,
|
||||
83, 235, 176, 41, 27, 248, 48, 71, 165, 170, 12, 92, 103, 103, 81, 32, 58, 74, 75, 145,
|
||||
192, 94, 153, 69, 80, 128, 241, 3, 16, 117, 192, 86, 161, 103, 44, 174, 211, 196, 182, 124,
|
||||
55, 11, 107, 142, 49, 88, 6, 41, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 37, 139, 240, 0, 0,
|
||||
0, 0, 0, 0, 0, 1,
|
||||
];
|
||||
|
||||
pub(crate) struct MockVerifier {
|
||||
ret: i64,
|
||||
}
|
||||
|
||||
impl MockVerifier {
|
||||
pub(crate) fn new(ret: i64) -> MockVerifier {
|
||||
Self { ret }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl TicketVerifier for MockVerifier {
|
||||
async fn verify(&mut self) -> nym_credential_verification::Result<i64> {
|
||||
Ok(self.ret)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn spawn_server_and_create_client() -> Client {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
|
||||
let router = RouterBuilder::with_default_routes()
|
||||
.with_state(AppState::new(PeerControllerTransceiver::new(request_tx)))
|
||||
.router;
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match request_rx.recv().await {
|
||||
Some(PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx }) => {
|
||||
response_tx
|
||||
.send(Ok(ClientBandwidth::new(Default::default())))
|
||||
.ok();
|
||||
}
|
||||
Some(PeerControlRequest::GetVerifierByIp {
|
||||
ip: _,
|
||||
credential: _,
|
||||
response_tx,
|
||||
}) => {
|
||||
response_tx
|
||||
.send(Ok(Box::new(MockVerifier::new(
|
||||
VERIFIER_AVAILABLE_BANDWIDTH,
|
||||
))))
|
||||
.ok();
|
||||
}
|
||||
None => break,
|
||||
_ => panic!("Not expected"),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
axum::serve(
|
||||
listener,
|
||||
router.into_make_service_with_connect_info::<SocketAddr>(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn query_latest_version() {
|
||||
let client = spawn_server_and_create_client().await;
|
||||
let version = client.version().await.unwrap();
|
||||
assert_eq!(version, latest::VERSION);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn query_against_server_v0() {
|
||||
let client = super::v0::network::test::spawn_server_and_create_client().await;
|
||||
|
||||
// version check
|
||||
let version = client.version().await.unwrap();
|
||||
assert_eq!(version, v0::VERSION);
|
||||
|
||||
// v0 reqwests
|
||||
let request = v0::AvailableBandwidthRequest {}.try_into().unwrap();
|
||||
let response = client.available_bandwidth(&request).await.unwrap();
|
||||
v0::AvailableBandwidthResponse::try_from(response).unwrap();
|
||||
|
||||
let request = v0::TopUpRequest {}.try_into().unwrap();
|
||||
let response = client.topup_bandwidth(&request).await.unwrap();
|
||||
v0::TopUpResponse::try_from(response).unwrap();
|
||||
|
||||
// v1 reqwests
|
||||
let request = v1::AvailableBandwidthRequest {}.try_into().unwrap();
|
||||
assert!(client.available_bandwidth(&request).await.is_err());
|
||||
|
||||
let request = v1::TopUpRequest {
|
||||
credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(),
|
||||
}
|
||||
.try_into()
|
||||
.unwrap();
|
||||
assert!(client.topup_bandwidth(&request).await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn query_against_server_v1() {
|
||||
let client = spawn_server_and_create_client().await;
|
||||
|
||||
// version check
|
||||
let version = client.version().await.unwrap();
|
||||
assert_eq!(version, v1::VERSION);
|
||||
|
||||
// v0 reqwests
|
||||
let request = v0::AvailableBandwidthRequest {}.try_into().unwrap();
|
||||
let response = client.available_bandwidth(&request).await.unwrap();
|
||||
v0::AvailableBandwidthResponse::try_from(response).unwrap();
|
||||
|
||||
let request = v0::TopUpRequest {}.try_into().unwrap();
|
||||
assert!(client.topup_bandwidth(&request).await.is_err());
|
||||
|
||||
// v1 reqwests
|
||||
let request = v1::AvailableBandwidthRequest {}.try_into().unwrap();
|
||||
let response = client.available_bandwidth(&request).await.unwrap();
|
||||
let available_bandwidth = v1::AvailableBandwidthResponse::try_from(response)
|
||||
.unwrap()
|
||||
.available_bandwidth;
|
||||
assert_eq!(available_bandwidth, 0);
|
||||
|
||||
let request = v1::TopUpRequest {
|
||||
credential: CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap(),
|
||||
}
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let response = client.topup_bandwidth(&request).await.unwrap();
|
||||
let available_bandwidth = v1::TopUpResponse::try_from(response)
|
||||
.unwrap()
|
||||
.available_bandwidth;
|
||||
assert_eq!(available_bandwidth, VERIFIER_AVAILABLE_BANDWIDTH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_wireguard_private_metadata_shared::{
|
||||
v0 as latest, Construct, Extract, Request, Response, Version,
|
||||
};
|
||||
|
||||
pub enum RequestData {
|
||||
AvailableBandwidth(()),
|
||||
TopUpBandwidth(()),
|
||||
}
|
||||
|
||||
impl From<latest::interface::RequestData> for RequestData {
|
||||
fn from(value: latest::interface::RequestData) -> Self {
|
||||
match value {
|
||||
latest::interface::RequestData::AvailableBandwidth(inner) => {
|
||||
Self::AvailableBandwidth(inner)
|
||||
}
|
||||
latest::interface::RequestData::TopUpBandwidth(credential_spending_data) => {
|
||||
Self::TopUpBandwidth(credential_spending_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequestData> for latest::interface::RequestData {
|
||||
fn from(value: RequestData) -> Self {
|
||||
match value {
|
||||
RequestData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner),
|
||||
RequestData::TopUpBandwidth(credential_spending_data) => {
|
||||
Self::TopUpBandwidth(credential_spending_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<latest::interface::ResponseData> for ResponseData {
|
||||
fn from(value: latest::interface::ResponseData) -> Self {
|
||||
match value {
|
||||
latest::interface::ResponseData::AvailableBandwidth(inner) => {
|
||||
Self::AvailableBandwidth(inner)
|
||||
}
|
||||
latest::interface::ResponseData::TopUpBandwidth(credential_spending_data) => {
|
||||
Self::TopUpBandwidth(credential_spending_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResponseData> for latest::interface::ResponseData {
|
||||
fn from(value: ResponseData) -> Self {
|
||||
match value {
|
||||
ResponseData::AvailableBandwidth(inner) => Self::AvailableBandwidth(inner),
|
||||
ResponseData::TopUpBandwidth(credential_spending_data) => {
|
||||
Self::TopUpBandwidth(credential_spending_data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Construct<RequestData> for Request {
|
||||
fn construct(
|
||||
info: RequestData,
|
||||
version: Version,
|
||||
) -> Result<Self, nym_wireguard_private_metadata_shared::ModelError> {
|
||||
match version {
|
||||
Version::V0 => {
|
||||
let translate_info = latest::interface::RequestData::from(info);
|
||||
let versioned_request =
|
||||
latest::VersionedRequest::construct(translate_info, latest::VERSION)?;
|
||||
Ok(versioned_request.try_into()?)
|
||||
}
|
||||
_ => Err(
|
||||
nym_wireguard_private_metadata_shared::ModelError::DowngradeNotPossible {
|
||||
from: version,
|
||||
to: Version::V0,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Extract<RequestData> for Request {
|
||||
fn extract(
|
||||
&self,
|
||||
) -> Result<(RequestData, Version), nym_wireguard_private_metadata_shared::ModelError> {
|
||||
match self.version {
|
||||
Version::V0 => {
|
||||
let versioned_request = latest::VersionedRequest::try_from(self.clone())?;
|
||||
let (request, version) = versioned_request.extract()?;
|
||||
|
||||
Ok((request.into(), version))
|
||||
}
|
||||
_ => Err(
|
||||
nym_wireguard_private_metadata_shared::ModelError::UpdateNotPossible {
|
||||
from: self.version,
|
||||
to: Version::V0,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResponseData {
|
||||
AvailableBandwidth(()),
|
||||
TopUpBandwidth(()),
|
||||
}
|
||||
|
||||
impl Construct<ResponseData> for Response {
|
||||
fn construct(
|
||||
info: ResponseData,
|
||||
version: Version,
|
||||
) -> Result<Self, nym_wireguard_private_metadata_shared::ModelError> {
|
||||
match version {
|
||||
Version::V0 => {
|
||||
let translate_response = latest::interface::ResponseData::from(info);
|
||||
let versioned_response =
|
||||
latest::VersionedResponse::construct(translate_response, version)?;
|
||||
Ok(versioned_response.try_into()?)
|
||||
}
|
||||
_ => Err(
|
||||
nym_wireguard_private_metadata_shared::ModelError::DowngradeNotPossible {
|
||||
from: version,
|
||||
to: Version::V0,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Extract<ResponseData> for Response {
|
||||
fn extract(
|
||||
&self,
|
||||
) -> Result<(ResponseData, Version), nym_wireguard_private_metadata_shared::ModelError> {
|
||||
match self.version {
|
||||
Version::V0 => {
|
||||
let versioned_response = latest::VersionedResponse::try_from(self.clone())?;
|
||||
let (response, version) = versioned_response.extract()?;
|
||||
|
||||
Ok((response.into(), version))
|
||||
}
|
||||
_ => Err(
|
||||
nym_wireguard_private_metadata_shared::ModelError::UpdateNotPossible {
|
||||
from: self.version,
|
||||
to: Version::V0,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub(crate) mod interface;
|
||||
pub(crate) mod network;
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test {
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use crate::{
|
||||
tests::{MockVerifier, VERIFIER_AVAILABLE_BANDWIDTH},
|
||||
v0::interface::{RequestData, ResponseData},
|
||||
};
|
||||
use axum::{extract::Query, Json, Router};
|
||||
use nym_credential_verification::ClientBandwidth;
|
||||
use nym_http_api_client::Client;
|
||||
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,
|
||||
};
|
||||
use tokio::{net::TcpListener, sync::mpsc};
|
||||
use tower_http::compression::CompressionLayer;
|
||||
|
||||
use nym_wireguard_private_metadata_server::AppState;
|
||||
|
||||
fn bandwidth_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/version", axum::routing::get(version))
|
||||
.route("/available", axum::routing::post(available_bandwidth))
|
||||
.route("/topup", axum::routing::post(topup_bandwidth))
|
||||
.layer(CompressionLayer::new())
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "bandwidth",
|
||||
get,
|
||||
path = "/v1/bandwidth/version",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
(Response = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
async fn version(Query(output): Query<OutputParams>) -> AxumResult<FormattedResponse<u64>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
Ok(output.to_response(latest::VERSION.into()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "bandwidth",
|
||||
post,
|
||||
request_body = Request,
|
||||
path = "/v1/bandwidth/available",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
(Response = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
async fn available_bandwidth(
|
||||
Query(output): Query<OutputParams>,
|
||||
Json(request): Json<Request>,
|
||||
) -> AxumResult<FormattedResponse<Response>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let (RequestData::AvailableBandwidth(_), version) =
|
||||
request.extract().map_err(AxumErrorResponse::bad_request)?
|
||||
else {
|
||||
return Err(AxumErrorResponse::bad_request("incorrect request type"));
|
||||
};
|
||||
let response = Response::construct(ResponseData::AvailableBandwidth(()), version)
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
|
||||
Ok(output.to_response(response))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "bandwidth",
|
||||
post,
|
||||
request_body = Request,
|
||||
path = "/v1/bandwidth/topup",
|
||||
responses(
|
||||
(status = 200, content(
|
||||
(Response = "application/bincode")
|
||||
))
|
||||
),
|
||||
)]
|
||||
async fn topup_bandwidth(
|
||||
Query(output): Query<OutputParams>,
|
||||
Json(request): Json<Request>,
|
||||
) -> AxumResult<FormattedResponse<Response>> {
|
||||
let output = output.output.unwrap_or_default();
|
||||
|
||||
let (RequestData::TopUpBandwidth(_), version) =
|
||||
request.extract().map_err(AxumErrorResponse::bad_request)?
|
||||
else {
|
||||
return Err(AxumErrorResponse::bad_request("incorrect request type"));
|
||||
};
|
||||
let response = Response::construct(ResponseData::TopUpBandwidth(()), version)
|
||||
.map_err(AxumErrorResponse::bad_request)?;
|
||||
|
||||
Ok(output.to_response(response))
|
||||
}
|
||||
|
||||
pub(crate) async fn spawn_server_and_create_client() -> Client {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let (request_tx, mut request_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
|
||||
let router = Router::new()
|
||||
.nest("/v1", Router::new().nest("/bandwidth", bandwidth_routes()))
|
||||
.with_state(AppState::new(PeerControllerTransceiver::new(request_tx)));
|
||||
|
||||
tokio::spawn(async move {
|
||||
match request_rx.recv().await.unwrap() {
|
||||
PeerControlRequest::GetClientBandwidthByIp { ip: _, response_tx } => {
|
||||
response_tx
|
||||
.send(Ok(ClientBandwidth::new(Default::default())))
|
||||
.ok();
|
||||
}
|
||||
PeerControlRequest::GetVerifierByIp {
|
||||
ip: _,
|
||||
credential: _,
|
||||
response_tx,
|
||||
} => {
|
||||
response_tx
|
||||
.send(Ok(Box::new(MockVerifier::new(
|
||||
VERIFIER_AVAILABLE_BANDWIDTH,
|
||||
))))
|
||||
.ok();
|
||||
}
|
||||
_ => panic!("Not expected"),
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
axum::serve(
|
||||
listener,
|
||||
router.into_make_service_with_connect_info::<SocketAddr>(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,13 @@ pub struct Config {
|
||||
/// default: `fc01::1`
|
||||
pub private_ipv6: Ipv6Addr,
|
||||
|
||||
/// Port announced to external clients wishing to connect to the wireguard interface.
|
||||
/// Tunnel port announced to external clients wishing to connect to the wireguard interface.
|
||||
/// Useful in the instances where the node is behind a proxy.
|
||||
pub announced_port: u16,
|
||||
pub announced_tunnel_port: u16,
|
||||
|
||||
/// Metadata port announced to external clients wishing to connect to the endpoint.
|
||||
/// Useful in the instances where the node is behind a proxy.
|
||||
pub announced_metadata_port: u16,
|
||||
|
||||
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
|
||||
/// The maximum value for IPv4 is 32
|
||||
|
||||
@@ -18,11 +18,13 @@ use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
#[cfg(target_os = "linux")]
|
||||
use nym_network_defaults::constants::WG_TUN_BASE_NAME;
|
||||
|
||||
pub(crate) mod error;
|
||||
pub mod error;
|
||||
pub mod peer_controller;
|
||||
pub mod peer_handle;
|
||||
pub mod peer_storage_manager;
|
||||
|
||||
pub const CONTROL_CHANNEL_SIZE: usize = 256;
|
||||
|
||||
pub struct WgApiWrapper {
|
||||
inner: WGApi,
|
||||
}
|
||||
@@ -126,7 +128,7 @@ pub struct WireguardGatewayData {
|
||||
|
||||
impl WireguardGatewayData {
|
||||
pub fn new(config: Config, keypair: Arc<KeyPair>) -> (Self, Receiver<PeerControlRequest>) {
|
||||
let (peer_tx, peer_rx) = mpsc::channel(1);
|
||||
let (peer_tx, peer_rx) = mpsc::channel(CONTROL_CHANNEL_SIZE);
|
||||
(
|
||||
WireguardGatewayData {
|
||||
config,
|
||||
@@ -178,10 +180,16 @@ pub async fn start_wireguard(
|
||||
let mut peer_bandwidth_managers = HashMap::with_capacity(peers.len());
|
||||
|
||||
for peer in peers.iter() {
|
||||
let bandwidth_manager = Arc::new(RwLock::new(
|
||||
PeerController::generate_bandwidth_manager(ecash_manager.storage(), &peer.public_key)
|
||||
let bandwidth_manager = peer_handle::SharedBandwidthStorageManager::new(
|
||||
Arc::new(RwLock::new(
|
||||
PeerController::generate_bandwidth_manager(
|
||||
ecash_manager.storage(),
|
||||
&peer.public_key,
|
||||
)
|
||||
.await?,
|
||||
));
|
||||
)),
|
||||
peer.allowed_ips.clone(),
|
||||
);
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone()));
|
||||
}
|
||||
|
||||
@@ -190,7 +198,7 @@ pub async fn start_wireguard(
|
||||
name: ifname.clone(),
|
||||
prvkey: BASE64_STANDARD.encode(wireguard_data.inner.keypair().private_key().to_bytes()),
|
||||
address: wireguard_data.inner.config().private_ipv4.to_string(),
|
||||
port: wireguard_data.inner.config().announced_port as u32,
|
||||
port: wireguard_data.inner.config().announced_tunnel_port as u32,
|
||||
peers,
|
||||
mtu: None,
|
||||
};
|
||||
@@ -233,6 +241,7 @@ pub async fn start_wireguard(
|
||||
|
||||
let host = wg_api.read_interface_data()?;
|
||||
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
|
||||
|
||||
let mut controller = PeerController::new(
|
||||
ecash_manager,
|
||||
metrics,
|
||||
|
||||
@@ -10,15 +10,18 @@ use futures::channel::oneshot;
|
||||
use log::info;
|
||||
use nym_credential_verification::{
|
||||
bandwidth_storage_manager::BandwidthStorageManager, ecash::traits::EcashManager,
|
||||
BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier,
|
||||
BandwidthFlushingBehaviourConfig, ClientBandwidth, CredentialVerifier, TicketVerifier,
|
||||
};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_gateway_requests::models::CredentialSpendingRequest;
|
||||
use nym_gateway_storage::traits::BandwidthGatewayStorage;
|
||||
use nym_node_metrics::NymNodeMetrics;
|
||||
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{
|
||||
net::IpAddr,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
@@ -41,22 +44,31 @@ pub enum PeerControlRequest {
|
||||
key: Key,
|
||||
response_tx: oneshot::Sender<QueryPeerControlResponse>,
|
||||
},
|
||||
GetClientBandwidth {
|
||||
GetClientBandwidthByKey {
|
||||
key: Key,
|
||||
response_tx: oneshot::Sender<GetClientBandwidthControlResponse>,
|
||||
},
|
||||
GetVerifier {
|
||||
GetClientBandwidthByIp {
|
||||
ip: IpAddr,
|
||||
response_tx: oneshot::Sender<GetClientBandwidthControlResponse>,
|
||||
},
|
||||
GetVerifierByKey {
|
||||
key: Key,
|
||||
credential: Box<CredentialSpendingData>,
|
||||
response_tx: oneshot::Sender<QueryVerifierControlResponse>,
|
||||
},
|
||||
GetVerifierByIp {
|
||||
ip: IpAddr,
|
||||
credential: Box<CredentialSpendingData>,
|
||||
response_tx: oneshot::Sender<QueryVerifierControlResponse>,
|
||||
},
|
||||
}
|
||||
|
||||
pub type AddPeerControlResponse = Result<()>;
|
||||
pub type RemovePeerControlResponse = Result<()>;
|
||||
pub type QueryPeerControlResponse = Result<Option<Peer>>;
|
||||
pub type GetClientBandwidthControlResponse = Result<ClientBandwidth>;
|
||||
pub type QueryVerifierControlResponse = Result<CredentialVerifier>;
|
||||
pub type QueryVerifierControlResponse = Result<Box<dyn TicketVerifier + Send + Sync>>;
|
||||
|
||||
pub struct PeerController {
|
||||
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
|
||||
@@ -77,7 +89,7 @@ pub struct PeerController {
|
||||
|
||||
impl PeerController {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
|
||||
metrics: NymNodeMetrics,
|
||||
wg_api: Arc<dyn WireguardInterfaceApi + Send + Sync>,
|
||||
@@ -165,10 +177,13 @@ impl PeerController {
|
||||
|
||||
async fn handle_add_request(&mut self, peer: &Peer) -> Result<()> {
|
||||
self.wg_api.configure_peer(peer)?;
|
||||
let bandwidth_storage_manager = Arc::new(RwLock::new(
|
||||
Self::generate_bandwidth_manager(self.ecash_verifier.storage(), &peer.public_key)
|
||||
.await?,
|
||||
));
|
||||
let bandwidth_storage_manager = SharedBandwidthStorageManager::new(
|
||||
Arc::new(RwLock::new(
|
||||
Self::generate_bandwidth_manager(self.ecash_verifier.storage(), &peer.public_key)
|
||||
.await?,
|
||||
)),
|
||||
peer.allowed_ips.clone(),
|
||||
);
|
||||
let cached_peer_manager = CachedPeerManager::new(peer);
|
||||
let mut handle = PeerHandle::new(
|
||||
peer.public_key.clone(),
|
||||
@@ -192,7 +207,20 @@ impl PeerController {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_query_peer(&self, key: &Key) -> Result<Option<Peer>> {
|
||||
async fn ip_to_key(&self, ip: IpAddr) -> Result<Option<Key>> {
|
||||
Ok(self
|
||||
.bw_storage_managers
|
||||
.iter()
|
||||
.find_map(|(key, bw_manager)| {
|
||||
bw_manager
|
||||
.allowed_ips()
|
||||
.iter()
|
||||
.find(|ip_mask| ip_mask.ip == ip)
|
||||
.and(Some(key.clone()))
|
||||
}))
|
||||
}
|
||||
|
||||
async fn handle_query_peer_by_key(&self, key: &Key) -> Result<Option<Peer>> {
|
||||
Ok(self
|
||||
.ecash_verifier
|
||||
.storage()
|
||||
@@ -202,20 +230,32 @@ impl PeerController {
|
||||
.transpose()?)
|
||||
}
|
||||
|
||||
async fn handle_get_client_bandwidth(&self, key: &Key) -> Result<ClientBandwidth> {
|
||||
async fn handle_get_client_bandwidth_by_key(&self, key: &Key) -> Result<ClientBandwidth> {
|
||||
let bandwidth_storage_manager = self
|
||||
.bw_storage_managers
|
||||
.get(key)
|
||||
.ok_or(Error::MissingClientBandwidthEntry)?;
|
||||
|
||||
Ok(bandwidth_storage_manager.read().await.client_bandwidth())
|
||||
Ok(bandwidth_storage_manager
|
||||
.inner()
|
||||
.read()
|
||||
.await
|
||||
.client_bandwidth())
|
||||
}
|
||||
|
||||
async fn handle_query_verifier(
|
||||
async fn handle_get_client_bandwidth_by_ip(&self, ip: IpAddr) -> Result<ClientBandwidth> {
|
||||
let Some(key) = self.ip_to_key(ip).await? else {
|
||||
return Err(Error::MissingClientKernelEntry(ip.to_string()));
|
||||
};
|
||||
|
||||
self.handle_get_client_bandwidth_by_key(&key).await
|
||||
}
|
||||
|
||||
async fn handle_query_verifier_by_key(
|
||||
&self,
|
||||
key: &Key,
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<CredentialVerifier> {
|
||||
) -> Result<Box<dyn TicketVerifier + Send + Sync>> {
|
||||
let storage = self.ecash_verifier.storage();
|
||||
let client_id = storage
|
||||
.get_wireguard_peer(&key.to_string())
|
||||
@@ -225,7 +265,11 @@ impl PeerController {
|
||||
let Some(bandwidth_storage_manager) = self.bw_storage_managers.get(key) else {
|
||||
return Err(Error::MissingClientBandwidthEntry);
|
||||
};
|
||||
let client_bandwidth = bandwidth_storage_manager.read().await.client_bandwidth();
|
||||
let client_bandwidth = bandwidth_storage_manager
|
||||
.inner()
|
||||
.read()
|
||||
.await
|
||||
.client_bandwidth();
|
||||
let verifier = CredentialVerifier::new(
|
||||
CredentialSpendingRequest::new(credential),
|
||||
self.ecash_verifier.clone(),
|
||||
@@ -237,7 +281,19 @@ impl PeerController {
|
||||
true,
|
||||
),
|
||||
);
|
||||
Ok(verifier)
|
||||
Ok(Box::new(verifier))
|
||||
}
|
||||
|
||||
async fn handle_query_verifier_by_ip(
|
||||
&self,
|
||||
ip: IpAddr,
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<Box<dyn TicketVerifier + Send + Sync>> {
|
||||
let Some(key) = self.ip_to_key(ip).await? else {
|
||||
return Err(Error::MissingClientKernelEntry(ip.to_string()));
|
||||
};
|
||||
|
||||
self.handle_query_verifier_by_key(&key, credential).await
|
||||
}
|
||||
|
||||
async fn update_metrics(&self, new_host: &Host) {
|
||||
@@ -340,18 +396,23 @@ impl PeerController {
|
||||
response_tx.send(self.remove_peer(&key).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::QueryPeer { key, response_tx }) => {
|
||||
response_tx.send(self.handle_query_peer(&key).await).ok();
|
||||
response_tx.send(self.handle_query_peer_by_key(&key).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::GetClientBandwidth { key, response_tx }) => {
|
||||
response_tx.send(self.handle_get_client_bandwidth(&key).await).ok();
|
||||
Some(PeerControlRequest::GetClientBandwidthByKey { key, response_tx }) => {
|
||||
response_tx.send(self.handle_get_client_bandwidth_by_key(&key).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::GetVerifier { key, credential, response_tx }) => {
|
||||
response_tx.send(self.handle_query_verifier(&key, *credential).await).ok();
|
||||
Some(PeerControlRequest::GetClientBandwidthByIp { ip, response_tx }) => {
|
||||
response_tx.send(self.handle_get_client_bandwidth_by_ip(ip).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::GetVerifierByKey { key, credential, response_tx }) => {
|
||||
response_tx.send(self.handle_query_verifier_by_key(&key, *credential).await).ok();
|
||||
}
|
||||
Some(PeerControlRequest::GetVerifierByIp { ip, credential, response_tx }) => {
|
||||
response_tx.send(self.handle_query_verifier_by_ip(ip, *credential).await).ok();
|
||||
}
|
||||
None => {
|
||||
log::trace!("PeerController [main loop]: stopping since channel closed");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::error::Error;
|
||||
use crate::peer_controller::PeerControlRequest;
|
||||
use crate::peer_storage_manager::{CachedPeerManager, PeerInformation};
|
||||
use defguard_wireguard_rs::{host::Host, key::Key};
|
||||
use defguard_wireguard_rs::{host::Host, key::Key, net::IpAddrMask};
|
||||
use futures::channel::oneshot;
|
||||
use nym_credential_verification::bandwidth_storage_manager::BandwidthStorageManager;
|
||||
use nym_task::TaskClient;
|
||||
@@ -13,7 +13,28 @@ use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
pub(crate) type SharedBandwidthStorageManager = Arc<RwLock<BandwidthStorageManager>>;
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SharedBandwidthStorageManager {
|
||||
inner: Arc<RwLock<BandwidthStorageManager>>,
|
||||
allowed_ips: Vec<IpAddrMask>,
|
||||
}
|
||||
|
||||
impl SharedBandwidthStorageManager {
|
||||
pub(crate) fn new(
|
||||
inner: Arc<RwLock<BandwidthStorageManager>>,
|
||||
allowed_ips: Vec<IpAddrMask>,
|
||||
) -> Self {
|
||||
Self { inner, allowed_ips }
|
||||
}
|
||||
|
||||
pub(crate) fn inner(&self) -> &RwLock<BandwidthStorageManager> {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub(crate) fn allowed_ips(&self) -> &[IpAddrMask] {
|
||||
&self.allowed_ips
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PeerHandle {
|
||||
public_key: Key,
|
||||
@@ -26,7 +47,7 @@ pub struct PeerHandle {
|
||||
}
|
||||
|
||||
impl PeerHandle {
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
public_key: Key,
|
||||
host_information: Arc<RwLock<Host>>,
|
||||
cached_peer: CachedPeerManager,
|
||||
@@ -120,6 +141,7 @@ impl PeerHandle {
|
||||
if spent_bandwidth > 0
|
||||
&& self
|
||||
.bandwidth_storage_manager
|
||||
.inner()
|
||||
.write()
|
||||
.await
|
||||
.try_use_bandwidth(spent_bandwidth)
|
||||
@@ -182,7 +204,7 @@ impl PeerHandle {
|
||||
|
||||
_ = self.task_client.recv() => {
|
||||
log::trace!("PeerHandle: Received shutdown");
|
||||
if let Err(e) = self.bandwidth_storage_manager.write().await.sync_storage_bandwidth().await {
|
||||
if let Err(e) = self.bandwidth_storage_manager.inner().write().await.sync_storage_bandwidth().await {
|
||||
log::error!("Storage sync failed - {e}, unaccounted bandwidth might have been consumed");
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
+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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ nym-ip-packet-router = { path = "../service-providers/ip-packet-router" }
|
||||
nym-node-metrics = { path = "../nym-node/nym-node-metrics" }
|
||||
|
||||
nym-wireguard = { path = "../common/wireguard" }
|
||||
nym-wireguard-private-metadata-server = { path = "../common/wireguard-private-metadata/server" }
|
||||
nym-wireguard-types = { path = "../common/wireguard-types", default-features = false }
|
||||
|
||||
nym-authenticator-requests = { path = "../common/authenticator-requests" }
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_network_defaults::{
|
||||
WG_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6, WG_TUN_DEVICE_NETMASK_V4,
|
||||
WG_TUN_DEVICE_NETMASK_V6,
|
||||
WG_METADATA_PORT, WG_TUNNEL_PORT, WG_TUN_DEVICE_IP_ADDRESS_V4, WG_TUN_DEVICE_IP_ADDRESS_V6,
|
||||
WG_TUN_DEVICE_NETMASK_V4, WG_TUN_DEVICE_NETMASK_V6,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
@@ -46,9 +46,9 @@ pub struct Authenticator {
|
||||
/// default: `fc01::1`
|
||||
pub private_ipv6: Ipv6Addr,
|
||||
|
||||
/// Port announced to external clients wishing to connect to the wireguard interface.
|
||||
/// Tunnel port announced to external clients wishing to connect to the wireguard interface.
|
||||
/// Useful in the instances where the node is behind a proxy.
|
||||
pub announced_port: u16,
|
||||
pub tunnel_announced_port: u16,
|
||||
|
||||
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
|
||||
/// The maximum value for IPv4 is 32
|
||||
@@ -62,10 +62,10 @@ pub struct Authenticator {
|
||||
impl Default for Authenticator {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT),
|
||||
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_TUNNEL_PORT),
|
||||
private_ipv4: WG_TUN_DEVICE_IP_ADDRESS_V4,
|
||||
private_ipv6: WG_TUN_DEVICE_IP_ADDRESS_V6,
|
||||
announced_port: WG_PORT,
|
||||
tunnel_announced_port: WG_TUNNEL_PORT,
|
||||
private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4,
|
||||
private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6,
|
||||
}
|
||||
@@ -78,7 +78,8 @@ impl From<Authenticator> for nym_wireguard_types::Config {
|
||||
bind_address: value.bind_address,
|
||||
private_ipv4: value.private_ipv4,
|
||||
private_ipv6: value.private_ipv6,
|
||||
announced_port: value.announced_port,
|
||||
announced_tunnel_port: value.tunnel_announced_port,
|
||||
announced_metadata_port: WG_METADATA_PORT,
|
||||
private_network_prefix_v4: value.private_network_prefix_v4,
|
||||
private_network_prefix_v6: value.private_network_prefix_v6,
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ impl MixnetListener {
|
||||
v1::registration::RegistredData {
|
||||
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
|
||||
private_ip: allowed_ipv4.into(),
|
||||
wg_port: self.config.authenticator.announced_port,
|
||||
wg_port: self.config.authenticator.tunnel_announced_port,
|
||||
},
|
||||
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
|
||||
request_id,
|
||||
@@ -304,7 +304,7 @@ impl MixnetListener {
|
||||
v2::registration::RegistredData {
|
||||
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
|
||||
private_ip: allowed_ipv4.into(),
|
||||
wg_port: self.config.authenticator.announced_port,
|
||||
wg_port: self.config.authenticator.tunnel_announced_port,
|
||||
},
|
||||
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
|
||||
request_id,
|
||||
@@ -317,7 +317,7 @@ impl MixnetListener {
|
||||
v3::registration::RegistredData {
|
||||
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
|
||||
private_ip: allowed_ipv4.into(),
|
||||
wg_port: self.config.authenticator.announced_port,
|
||||
wg_port: self.config.authenticator.tunnel_announced_port,
|
||||
},
|
||||
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
|
||||
request_id,
|
||||
@@ -330,7 +330,7 @@ impl MixnetListener {
|
||||
v4::registration::RegistredData {
|
||||
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
|
||||
private_ips: (allowed_ipv4, allowed_ipv6).into(),
|
||||
wg_port: self.config.authenticator.announced_port,
|
||||
wg_port: self.config.authenticator.tunnel_announced_port,
|
||||
},
|
||||
reply_to.ok_or(AuthenticatorError::MissingReplyToForOldClient)?,
|
||||
request_id,
|
||||
@@ -343,7 +343,7 @@ impl MixnetListener {
|
||||
v5::registration::RegistredData {
|
||||
pub_key: PeerPublicKey::new(self.keypair().public_key().to_bytes().into()),
|
||||
private_ips: (allowed_ipv4, allowed_ipv6).into(),
|
||||
wg_port: self.config.authenticator.announced_port,
|
||||
wg_port: self.config.authenticator.tunnel_announced_port,
|
||||
},
|
||||
request_id,
|
||||
)
|
||||
@@ -374,7 +374,7 @@ impl MixnetListener {
|
||||
let registration_data = RegistrationData {
|
||||
nonce,
|
||||
gateway_data: gateway_data.clone(),
|
||||
wg_port: self.config.authenticator.announced_port,
|
||||
wg_port: self.config.authenticator.tunnel_announced_port,
|
||||
};
|
||||
registred_and_free
|
||||
.registration_in_progres
|
||||
@@ -691,7 +691,7 @@ impl MixnetListener {
|
||||
} else {
|
||||
let mut verifier = self
|
||||
.peer_manager
|
||||
.query_verifier(msg.pub_key(), msg.credential())
|
||||
.query_verifier_by_key(msg.pub_key(), msg.credential())
|
||||
.await?;
|
||||
let available_bandwidth = verifier.verify().await?;
|
||||
self.seen_credential_cache
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::node::internal_service_providers::authenticator::error::AuthenticatorError;
|
||||
use defguard_wireguard_rs::{host::Peer, key::Key};
|
||||
use futures::channel::oneshot;
|
||||
use nym_credential_verification::{ClientBandwidth, CredentialVerifier};
|
||||
use nym_credential_verification::{ClientBandwidth, TicketVerifier};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData};
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
@@ -99,7 +99,7 @@ impl PeerManager {
|
||||
) -> Result<ClientBandwidth, AuthenticatorError> {
|
||||
let key = Key::new(key.to_bytes());
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
let msg = PeerControlRequest::GetClientBandwidth { key, response_tx };
|
||||
let msg = PeerControlRequest::GetClientBandwidthByKey { key, response_tx };
|
||||
self.wireguard_gateway_data
|
||||
.peer_tx()
|
||||
.send(msg)
|
||||
@@ -120,14 +120,14 @@ impl PeerManager {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn query_verifier(
|
||||
pub async fn query_verifier_by_key(
|
||||
&mut self,
|
||||
key: PeerPublicKey,
|
||||
credential: CredentialSpendingData,
|
||||
) -> Result<CredentialVerifier, AuthenticatorError> {
|
||||
) -> Result<Box<dyn TicketVerifier + Send + Sync>, AuthenticatorError> {
|
||||
let key = Key::new(key.to_bytes());
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
let msg = PeerControlRequest::GetVerifier {
|
||||
let msg = PeerControlRequest::GetVerifierByKey {
|
||||
key,
|
||||
credential: Box::new(credential),
|
||||
response_tx,
|
||||
@@ -398,13 +398,13 @@ mod tests {
|
||||
let credential = CredentialSpendingData::try_from_bytes(&CREDENTIAL_BYTES).unwrap();
|
||||
|
||||
assert!(peer_manager
|
||||
.query_verifier(public_key, credential.clone())
|
||||
.query_verifier_by_key(public_key, credential.clone())
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
helper_add_peer(&storage, &mut peer_manager).await;
|
||||
peer_manager
|
||||
.query_verifier(public_key, credential)
|
||||
.query_verifier_by_key(public_key, credential)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
+55
-17
@@ -19,7 +19,7 @@ use nym_network_defaults::NymNetworkDetails;
|
||||
use nym_network_requester::NRServiceProviderBuilder;
|
||||
use nym_node_metrics::events::MetricEventsSender;
|
||||
use nym_node_metrics::NymNodeMetrics;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::{ShutdownToken, TaskClient};
|
||||
use nym_topology::TopologyProvider;
|
||||
use nym_validator_client::nyxd::{Coin, CosmWasmClient};
|
||||
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
|
||||
@@ -91,7 +91,9 @@ pub struct GatewayTasksBuilder {
|
||||
|
||||
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
|
||||
|
||||
shutdown: TaskClient,
|
||||
legacy_task_client: TaskClient,
|
||||
|
||||
shutdown_token: ShutdownToken,
|
||||
|
||||
// populated and cached as necessary
|
||||
ecash_manager: Option<Arc<EcashManager>>,
|
||||
@@ -105,7 +107,7 @@ impl Drop for GatewayTasksBuilder {
|
||||
fn drop(&mut self) {
|
||||
// disarm the shutdown as it was already used to construct relevant tasks and we don't want the builder
|
||||
// to cause shutdown
|
||||
self.shutdown.disarm();
|
||||
self.legacy_task_client.disarm();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +121,8 @@ impl GatewayTasksBuilder {
|
||||
metrics_sender: MetricEventsSender,
|
||||
metrics: NymNodeMetrics,
|
||||
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
|
||||
shutdown: TaskClient,
|
||||
legacy_task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> GatewayTasksBuilder {
|
||||
GatewayTasksBuilder {
|
||||
config,
|
||||
@@ -133,7 +136,8 @@ impl GatewayTasksBuilder {
|
||||
metrics_sender,
|
||||
metrics,
|
||||
mnemonic,
|
||||
shutdown,
|
||||
legacy_task_client,
|
||||
shutdown_token,
|
||||
ecash_manager: None,
|
||||
wireguard_peers: None,
|
||||
wireguard_networks: None,
|
||||
@@ -228,7 +232,7 @@ impl GatewayTasksBuilder {
|
||||
handler_config,
|
||||
nyxd_client,
|
||||
self.identity_keypair.public_key().to_bytes(),
|
||||
self.shutdown.fork("ecash_manager"),
|
||||
self.legacy_task_client.fork("ecash_manager"),
|
||||
self.storage.clone(),
|
||||
)
|
||||
.await?,
|
||||
@@ -270,7 +274,7 @@ impl GatewayTasksBuilder {
|
||||
self.config.gateway.websocket_bind_address,
|
||||
self.config.debug.maximum_open_connections,
|
||||
shared_state,
|
||||
self.shutdown.fork("websocket"),
|
||||
self.legacy_task_client.fork("websocket"),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -286,13 +290,14 @@ impl GatewayTasksBuilder {
|
||||
let mut message_router_builder = SpMessageRouterBuilder::new(
|
||||
*self.identity_keypair.public_key(),
|
||||
self.mix_packet_sender.clone(),
|
||||
self.shutdown.fork("network_requester_message_router"),
|
||||
self.legacy_task_client
|
||||
.fork("network_requester_message_router"),
|
||||
);
|
||||
let transceiver = message_router_builder.gateway_transceiver();
|
||||
|
||||
let (on_start_tx, on_start_rx) = oneshot::channel();
|
||||
let mut nr_builder = NRServiceProviderBuilder::new(nr_opts.config.clone())
|
||||
.with_shutdown(self.shutdown.fork("network_requester_sp"))
|
||||
.with_shutdown(self.legacy_task_client.fork("network_requester_sp"))
|
||||
.with_custom_gateway_transceiver(transceiver)
|
||||
.with_wait_for_gateway(true)
|
||||
.with_minimum_gateway_performance(0)
|
||||
@@ -321,13 +326,13 @@ impl GatewayTasksBuilder {
|
||||
let mut message_router_builder = SpMessageRouterBuilder::new(
|
||||
*self.identity_keypair.public_key(),
|
||||
self.mix_packet_sender.clone(),
|
||||
self.shutdown.fork("ipr_message_router"),
|
||||
self.legacy_task_client.fork("ipr_message_router"),
|
||||
);
|
||||
let transceiver = message_router_builder.gateway_transceiver();
|
||||
|
||||
let (on_start_tx, on_start_rx) = oneshot::channel();
|
||||
let mut ip_packet_router = IpPacketRouter::new(ip_opts.config.clone())
|
||||
.with_shutdown(self.shutdown.fork("ipr_sp"))
|
||||
.with_shutdown(self.legacy_task_client.fork("ipr_sp"))
|
||||
.with_custom_gateway_transceiver(Box::new(transceiver))
|
||||
.with_wait_for_gateway(true)
|
||||
.with_minimum_gateway_performance(0)
|
||||
@@ -427,7 +432,7 @@ impl GatewayTasksBuilder {
|
||||
let mut message_router_builder = SpMessageRouterBuilder::new(
|
||||
*self.identity_keypair.public_key(),
|
||||
self.mix_packet_sender.clone(),
|
||||
self.shutdown.fork("authenticator_message_router"),
|
||||
self.legacy_task_client.fork("authenticator_message_router"),
|
||||
);
|
||||
let transceiver = message_router_builder.gateway_transceiver();
|
||||
|
||||
@@ -440,7 +445,7 @@ impl GatewayTasksBuilder {
|
||||
ecash_manager,
|
||||
)
|
||||
.with_custom_gateway_transceiver(transceiver)
|
||||
.with_shutdown(self.shutdown.fork("authenticator_sp"))
|
||||
.with_shutdown(self.legacy_task_client.fork("authenticator_sp"))
|
||||
.with_wait_for_gateway(true)
|
||||
.with_minimum_gateway_performance(0)
|
||||
.with_custom_topology_provider(topology_provider)
|
||||
@@ -460,7 +465,7 @@ impl GatewayTasksBuilder {
|
||||
pub fn build_stale_messages_cleaner(&self) -> StaleMessagesCleaner {
|
||||
StaleMessagesCleaner::new(
|
||||
&self.storage,
|
||||
self.shutdown.fork("stale_messages_cleaner"),
|
||||
self.legacy_task_client.fork("stale_messages_cleaner"),
|
||||
self.config.debug.stale_messages_max_age,
|
||||
self.config.debug.stale_messages_cleaner_run_interval,
|
||||
)
|
||||
@@ -471,13 +476,17 @@ impl GatewayTasksBuilder {
|
||||
&mut self,
|
||||
) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let _ = self.metrics.clone();
|
||||
let _ = self.shutdown_token.clone();
|
||||
unimplemented!("wireguard is not supported on this platform")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn try_start_wireguard(
|
||||
&mut self,
|
||||
) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
) -> Result<
|
||||
nym_wireguard_private_metadata_server::ShutdownHandles,
|
||||
Box<dyn std::error::Error + Send + Sync>,
|
||||
> {
|
||||
let all_peers = self.get_wireguard_peers().await?;
|
||||
|
||||
let Some(wireguard_data) = self.wireguard_data.take() else {
|
||||
@@ -492,14 +501,43 @@ impl GatewayTasksBuilder {
|
||||
);
|
||||
};
|
||||
|
||||
let router = nym_wireguard_private_metadata_server::RouterBuilder::with_default_routes();
|
||||
let router = router.with_state(nym_wireguard_private_metadata_server::AppState::new(
|
||||
nym_wireguard_private_metadata_server::PeerControllerTransceiver::new(
|
||||
wireguard_data.inner.peer_tx().clone(),
|
||||
),
|
||||
));
|
||||
|
||||
let bind_address = std::net::SocketAddr::new(
|
||||
wireguard_data.inner.config().private_ipv4.into(),
|
||||
wireguard_data.inner.config().announced_metadata_port,
|
||||
);
|
||||
|
||||
let wg_handle = nym_wireguard::start_wireguard(
|
||||
ecash_manager,
|
||||
self.metrics.clone(),
|
||||
all_peers,
|
||||
self.shutdown.fork("wireguard"),
|
||||
self.legacy_task_client.fork("wireguard"),
|
||||
wireguard_data,
|
||||
)
|
||||
.await?;
|
||||
Ok(wg_handle)
|
||||
|
||||
let server = router.build_server(&bind_address).await?;
|
||||
let cancel_token: tokio_util::sync::CancellationToken = (*self.shutdown_token).clone();
|
||||
let axum_shutdown_receiver = cancel_token.clone().cancelled_owned();
|
||||
let server_handle = tokio::spawn(async move {
|
||||
{
|
||||
info!("Started Wireguard Axum HTTP V2 server on {bind_address}");
|
||||
server.run(axum_shutdown_receiver).await
|
||||
}
|
||||
});
|
||||
|
||||
let shutdown_handles = nym_wireguard_private_metadata_server::ShutdownHandles::new(
|
||||
server_handle,
|
||||
wg_handle,
|
||||
cancel_token,
|
||||
);
|
||||
|
||||
Ok(shutdown_handles)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -311,7 +311,10 @@ impl From<Authenticator> for AuthenticatorDetails {
|
||||
}
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
pub struct WireguardDetails {
|
||||
// NOTE: the port field is deprecated in favour of tunnel_port
|
||||
pub port: u16,
|
||||
pub tunnel_port: u16,
|
||||
pub metadata_port: u16,
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
@@ -320,6 +323,8 @@ impl From<Wireguard> for WireguardDetails {
|
||||
fn from(value: Wireguard) -> Self {
|
||||
WireguardDetails {
|
||||
port: value.port,
|
||||
tunnel_port: value.tunnel_port,
|
||||
metadata_port: value.metadata_port,
|
||||
public_key: value.public_key,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -15,6 +15,7 @@ use nym_credentials::IssuanceTicketBook;
|
||||
use nym_credentials_interface::Base58;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_validator_client::ecash::BlindSignRequestBody;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use rand::rngs::OsRng;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
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;
|
||||
@@ -70,6 +70,12 @@ pub enum VpnApiError {
|
||||
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),
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ use nym_credentials_interface::VerificationKeyAuth;
|
||||
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, EcashQueryClient, 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,12 @@ 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::<
|
||||
_,
|
||||
nym_validator_client::models::RequestError,
|
||||
>(api_url)?
|
||||
.with_user_agent(UserAgent::from(bin_info!()))
|
||||
.build::<nym_validator_client::models::RequestError>()?;
|
||||
|
||||
let rewarded_set = client.get_current_rewarded_set().await?;
|
||||
|
||||
@@ -172,10 +177,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]
|
||||
|
||||
@@ -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;
|
||||
@@ -62,11 +62,9 @@ async fn run(
|
||||
.with_timeout(nym_api_client_timeout)
|
||||
.build::<&str>()?;
|
||||
|
||||
let api_client = NymApiClient::from(nym_api);
|
||||
|
||||
//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},
|
||||
@@ -111,9 +111,7 @@ impl Monitor {
|
||||
.with_timeout(self.nym_api_client_timeout)
|
||||
.build::<&str>()?;
|
||||
|
||||
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")?
|
||||
|
||||
@@ -16,9 +16,16 @@ pub struct Gateway {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct Wireguard {
|
||||
#[deprecated(note = "use specific port instead (tunnel or metadata service)")]
|
||||
#[cfg_attr(feature = "openapi", schema(example = 51822, default = 51822))]
|
||||
pub port: u16,
|
||||
|
||||
#[cfg_attr(feature = "openapi", schema(example = 51822, default = 51822))]
|
||||
pub tunnel_port: u16,
|
||||
|
||||
#[cfg_attr(feature = "openapi", schema(example = 51830, default = 51830))]
|
||||
pub metadata_port: u16,
|
||||
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user