Compare commits

..

8 Commits

Author SHA1 Message Date
benedetta davico 6731b89714 update rust version 2025-09-16 11:41:49 +02:00
benedetta davico 55aea37b89 typo 2025-09-16 11:29:32 +02:00
benedettadavico 737c4d79e0 update changelog 2025-09-16 09:16:40 +02:00
benedetta davico b105e5a15d TEMP remove nym-node from publishing 2025-09-16 09:15:19 +02:00
benedetta davico 87a188ca06 Bump cred-proxy version 2025-09-11 14:28:52 +02:00
benedettadavico 4d0898c633 bump versions 2025-09-08 09:30:59 +02:00
Bogdan-Ștefan Neacşu d984d085a7 Backport metadata endpoint (#6010)
* Wireguard private metadata (#5915)

* Wireguard metadata client library (#5943)
2025-09-05 11:14:37 +03:00
Jędrzej Stuczyński 2e22cad074 bugfix: make sure tables are removed in correct order to not trigger FK constraint issue (#5987) 2025-08-29 09:02:58 +01:00
172 changed files with 1968 additions and 3828 deletions
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v4
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.47.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v4
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.47.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -6,7 +6,7 @@ jobs:
greeting:
runs-on: ubuntu-latest
steps:
- uses: actions/first-interaction@v3
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: 'Thank you for raising this issue'
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
- name: Check out repository code
uses: actions/checkout@v4
- name: Download report from previous job
uses: actions/download-artifact@v5
uses: actions/download-artifact@v4
with:
name: report
path: .github/workflows/support-files/notifications
+1 -5
View File
@@ -30,13 +30,11 @@ jobs:
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }}
client_hash: ${{ steps.binary-hashes.outputs.client_hash }}
nymvisor_hash: ${{ steps.binary-hashes.outputs.nymvisor_hash }}
nymnode_hash: ${{ steps.binary-hashes.outputs.nymnode_hash }}
socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }}
netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }}
cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }}
client_version: ${{ steps.binary-versions.outputs.client_version }}
nymvisor_version: ${{ steps.binary-versions.outputs.nymvisor_version }}
nymnode_version: ${{ steps.binary-versions.outputs.nymnode_version }}
socks5_version: ${{ steps.binary-versions.outputs.socks5_version }}
netreq_version: ${{ steps.binary-versions.outputs.netreq_version }}
cli_version: ${{ steps.binary-versions.outputs.cli_version }}
@@ -56,7 +54,7 @@ jobs:
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: 1.86.0
toolchain: 1.88.0
override: true
- name: Build all binaries
@@ -76,7 +74,6 @@ jobs:
target/release/nym-network-requester
target/release/nym-cli
target/release/nymvisor
target/release/nym-node
retention-days: 30
- id: create-release
@@ -91,7 +88,6 @@ jobs:
target/release/nym-network-requester
target/release/nym-cli
target/release/nymvisor
target/release/nym-node
push-release-data-client:
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
@@ -25,7 +25,7 @@ jobs:
uses: actions/checkout@v4
- name: Install Java
uses: actions/setup-java@v5
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: "17"
@@ -91,7 +91,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Download binary artifact
uses: actions/download-artifact@v5
uses: actions/download-artifact@v4
with:
name: nyms5-apk-arch64
path: apk
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.47.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.47.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.47.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
+5 -9
View File
@@ -34,22 +34,18 @@ jobs:
- name: Get version from cargo.toml
id: get_version
run: |
VERSION=$(yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml)
echo "result=$VERSION" >> $GITHUB_OUTPUT
yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
- name: Set GIT_TAG variable
run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
- name: Initialise RELEASE_TAG
run: echo "RELEASE_TAG=" >> $GITHUB_ENV
- name: Set RELEASE_TAG for release
- name: Set RELEASE_TAG variable
if: github.event.inputs.release_image == 'true'
run: echo "RELEASE_TAG=golden-" >> $GITHUB_ENV
- name: Set IMAGE_NAME_AND_TAGS variable
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
- name: New env vars
run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
@@ -69,6 +65,6 @@ jobs:
- name: BuildAndPushImageOnHarbor
run: |
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile-pg . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.47.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-api/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.47.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.47.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.47.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.47.1
uses: mikefarah/yq@v4.45.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+42
View File
@@ -4,6 +4,48 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2025.16-halloumi] (2025-09-16)
- Backport metadata endpoint ([#6010])
- bugfix: make sure tables are removed in correct order to not trigger FK constraint issue ([#5987])
- chore: move authenticator into gateway crate ([#5982])
- Fix the ns api ci workflow ([#5981])
- Remove freshness check on testrun submit ([#5977])
- Update sysinfo to the latest ([#5976])
- bugfix: manually calculate per node work on rewarded set changes ([#5972])
- fixing the ci for ns agent ([#5965])
- Feature/testing utils ([#5963])
- bugfix: fix ci-build for linux (and use updated runner) ([#5958])
- chore: updated refs to cheddar rev of nym repo ([#5955])
- http api client adjustment ([#5953])
- chore: fix rust 1.89 clippy issues ([#5944])
- Wireguard metadata client library ([#5943])
- chore: remove unused import ([#5942])
- feat: introduce additional checks when attempting to send to bounded channels ([#5941])
- Move credential verifier in peer controller ([#5938])
- change PK/FK on expiration date signatures tables ([#5934])
- Wireguard private metadata ([#5915])
[#6010]: https://github.com/nymtech/nym/pull/6010
[#5987]: https://github.com/nymtech/nym/pull/5987
[#5982]: https://github.com/nymtech/nym/pull/5982
[#5981]: https://github.com/nymtech/nym/pull/5981
[#5977]: https://github.com/nymtech/nym/pull/5977
[#5976]: https://github.com/nymtech/nym/pull/5976
[#5972]: https://github.com/nymtech/nym/pull/5972
[#5965]: https://github.com/nymtech/nym/pull/5965
[#5963]: https://github.com/nymtech/nym/pull/5963
[#5958]: https://github.com/nymtech/nym/pull/5958
[#5955]: https://github.com/nymtech/nym/pull/5955
[#5953]: https://github.com/nymtech/nym/pull/5953
[#5944]: https://github.com/nymtech/nym/pull/5944
[#5943]: https://github.com/nymtech/nym/pull/5943
[#5942]: https://github.com/nymtech/nym/pull/5942
[#5941]: https://github.com/nymtech/nym/pull/5941
[#5938]: https://github.com/nymtech/nym/pull/5938
[#5934]: https://github.com/nymtech/nym/pull/5934
[#5915]: https://github.com/nymtech/nym/pull/5915
## [2025.15-gruyere] (2025-08-20)
- Migrate strum to 0.27.2 ([#5960])
Generated
+142 -170
View File
File diff suppressed because it is too large Load Diff
+7 -8
View File
@@ -53,11 +53,12 @@ members = [
"common/ecash-signer-check",
"common/ecash-signer-check-types",
"common/ecash-time",
"common/execute",
"common/exit-policy",
"common/gateway-requests",
"common/gateway-stats-storage",
"common/gateway-storage",
"common/http-api-client", "common/http-api-client-macro",
"common/http-api-client",
"common/http-api-common",
"common/inclusion-probability",
"common/ip-packet-requests",
@@ -181,7 +182,7 @@ homepage = "https://nymtech.net"
documentation = "https://nymtech.net"
edition = "2021"
license = "Apache-2.0"
rust-version = "1.81"
rust-version = "1.80"
readme = "README.md"
[workspace.dependencies]
@@ -222,7 +223,7 @@ clap_complete = "4.5"
clap_complete_fig = "4.5"
colored = "2.2"
comfy-table = "7.1.4"
console = "0.16.0"
console = "0.15.11"
console-subscriber = "0.4.1"
console_error_panic_hook = "0.1"
const-str = "0.5.6"
@@ -269,9 +270,8 @@ humantime = "2.2.0"
humantime-serde = "1.1.1"
hyper = "1.6.0"
hyper-util = "0.1"
indicatif = "0.18.0"
indicatif = "0.17.11"
inquire = "0.6.2"
inventory = "0.3.21"
ip_network = "0.4.1"
ipnetwork = "0.20"
itertools = "0.14.0"
@@ -318,7 +318,6 @@ serde_json_path = "0.7.2"
serde_repr = "0.1"
serde_with = "3.9.0"
serde_yaml = "0.9.25"
serde_plain = "1.0.2"
sha2 = "0.10.9"
si-scale = "0.2.3"
snow = "0.9.6"
@@ -327,14 +326,14 @@ sqlx = "0.8.6"
strum = "0.27.2"
strum_macros = "0.27.2"
subtle-encoding = "0.5"
syn = "2"
syn = "1"
sysinfo = "0.37.0"
tap = "1.0.1"
tar = "0.4.44"
tempfile = "3.20"
thiserror = "2.0"
time = "0.3.41"
tokio = "1.47"
tokio = "1.45"
tokio-postgres = "0.7"
tokio-stream = "0.1.17"
tokio-test = "0.4.4"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.61"
version = "1.1.62"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.61"
version = "1.1.62"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -5,7 +5,7 @@ use crate::error::BandwidthControllerError;
use crate::utils::{
get_aggregate_verification_key, get_coin_index_signatures, get_expiration_date_signatures,
};
use log::{error, info};
use log::info;
use nym_credential_storage::storage::Storage;
use nym_credentials::ecash::bandwidth::IssuanceTicketBook;
use nym_credentials::ecash::utils::obtain_aggregate_wallet;
@@ -42,8 +42,7 @@ where
deposit_amount.into(),
None,
)
.await
.inspect_err(|err| error!("ticketbook deposit fail with: {err}"))?;
.await?;
let deposit_id = result.parse_singleton_u32_contract_data()?;
+1 -1
View File
@@ -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, NymApiClientExt};
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use nym_validator_client::EcashApiClient;
use rand::prelude::SliceRandom;
-1
View File
@@ -53,7 +53,6 @@ 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 }
@@ -3,7 +3,6 @@ name = "nym-client-core-gateways-storage"
version = "0.1.0"
edition = "2021"
license.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -27,7 +26,6 @@ features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"]
optional = true
[build-dependencies]
anyhow = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
+4 -13
View File
@@ -2,30 +2,23 @@
// SPDX-License-Identifier: Apache-2.0
#[tokio::main]
async fn main() -> anyhow::Result<()> {
async fn main() {
#[cfg(feature = "fs-gateways-storage")]
{
use anyhow::Context;
use sqlx::{Connection, SqliteConnection};
use std::env;
let out_dir = env::var("OUT_DIR")?;
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{out_dir}/gateways-storage-example.sqlite");
// remove the db file if it already existed from previous build
// in case it was from a different branch
if std::fs::exists(&database_path)? {
std::fs::remove_file(&database_path)?;
}
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.context("Failed to create SQLx database connection")?;
.expect("Failed to create SQLx database connection");
sqlx::migrate!("./fs_gateways_migrations")
.run(&mut conn)
.await
.context("Failed to perform SQLx migrations")?;
.expect("Failed to perform SQLx migrations");
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -35,6 +28,4 @@ async fn main() -> anyhow::Result<()> {
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
}
Ok(())
}
@@ -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, UserAgent};
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, NymApiClient, 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: nym_http_api_client::Client,
nym_api_client: NymApiClient,
) -> Box<dyn TopologyProvider + Send + Sync> {
// if no custom provider was ... provided ..., create one using nym-api
custom_provider.unwrap_or_else(|| {
@@ -749,31 +749,21 @@ where
setup_gateway(setup_method, key_store, details_store).await
}
fn construct_nym_api_client(
config: &Config,
user_agent: Option<UserAgent>,
) -> Result<nym_http_api_client::Client, ClientCoreError> {
fn construct_nym_api_client(config: &Config, user_agent: Option<UserAgent>) -> NymApiClient {
let mut nym_api_urls = config.get_nym_api_endpoints();
nym_api_urls.shuffle(&mut thread_rng());
let mut builder = nym_http_api_client::Client::builder(nym_api_urls[0].clone())
.map_err(|source| ClientCoreError::NymApiQueryFailure { source })?;
if let Some(user_agent) = user_agent {
builder = builder.with_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_bincode();
builder
.build()
.map_err(|source| ClientCoreError::NymApiQueryFailure { source })
}
async fn determine_key_rotation_state(
client: &nym_http_api_client::Client,
client: &NymApiClient,
) -> Result<KeyRotationConfig, ClientCoreError> {
Ok(client.get_key_rotation_info().await?.into())
Ok(client.nym_api.get_key_rotation_info().await?.into())
}
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
@@ -840,7 +830,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,10 +2,8 @@
// 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;
@@ -41,43 +39,30 @@ impl Config {
pub struct NymApiTopologyProvider {
config: Config,
validator_client: nym_http_api_client::Client,
validator_client: nym_validator_client::client::NymApiClient,
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>,
validator_client: nym_http_api_client::Client,
mut validator_client: nym_validator_client::client::NymApiClient,
) -> Self {
nym_api_urls.shuffle(&mut thread_rng());
let mut provider = NymApiTopologyProvider {
validator_client.change_nym_api(nym_api_urls[0].clone());
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.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");
self.validator_client.use_bincode = false;
}
fn use_next_nym_api(&mut self) {
@@ -87,19 +72,8 @@ impl NymApiTopologyProvider {
}
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
// 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)
self.validator_client
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
}
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
@@ -125,13 +99,8 @@ impl NymApiTopologyProvider {
.filter(|n| n.performance.round_to_integer() >= self.config.min_node_performance())
.collect::<Vec<_>>();
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
NymTopology::new(
metadata.to_topology_metadata(),
epoch_rewarded_set,
Vec::new(),
)
.with_skimmed_nodes(&nodes_filtered)
NymTopology::new(metadata.to_topology_metadata(), 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
@@ -179,13 +148,8 @@ impl NymApiTopologyProvider {
}
}
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
NymTopology::new(
metadata.to_topology_metadata(),
epoch_rewarded_set,
Vec::new(),
)
.with_skimmed_nodes(&nodes)
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
.with_skimmed_nodes(&nodes)
};
if !topology.is_minimally_routable() {
+8 -65
View File
@@ -7,8 +7,7 @@ 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, NymApiClientExt};
use nym_validator_client::nym_nodes::SkimmedNodesWithMetadata;
use nym_validator_client::client::IdentityKeyRef;
use nym_validator_client::UserAgent;
use rand::{seq::SliceRandom, Rng};
#[cfg(unix)]
@@ -84,48 +83,6 @@ 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 }
@@ -142,30 +99,16 @@ pub async fn gateways_for_init<R: Rng>(
let nym_api = nym_apis
.choose(rng)
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
// Use the unified HTTP client directly with optional user agent
let mut builder = nym_http_api_client::Client::builder(nym_api.clone())
.map_err(|e| {
ClientCoreError::ValidatorClientError(
nym_validator_client::ValidatorClientError::NymAPIError { source: e },
)
})?
.with_bincode(); // Use bincode for better performance
if let Some(user_agent) = user_agent {
builder = builder.with_user_agent(user_agent);
}
let client = builder.build().map_err(|e| {
ClientCoreError::ValidatorClientError(
nym_validator_client::ValidatorClientError::NymAPIError { source: e },
)
})?;
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())
};
tracing::debug!("Fetching list of gateways from: {nym_api}");
// Use our helper to handle pagination
let gateways = get_all_basic_entry_nodes_with_metadata(&client, true)
let gateways = client
.get_all_basic_entry_assigned_nodes_with_metadata()
.await?
.nodes;
info!("nym api reports {} gateways", gateways.len());
@@ -30,7 +30,6 @@ optional = true
path = "../../../sqlx-pool-guard"
[build-dependencies]
anyhow = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
+4 -7
View File
@@ -2,24 +2,23 @@
// SPDX-License-Identifier: Apache-2.0
#[tokio::main]
async fn main() -> anyhow::Result<()> {
async fn main() {
#[cfg(feature = "fs-surb-storage")]
{
use anyhow::Context;
use sqlx::{Connection, SqliteConnection};
use std::env;
let out_dir = env::var("OUT_DIR")?;
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{out_dir}/fs-surbs-example.sqlite");
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.context("Failed to create SQLx database connection")?;
.expect("Failed to create SQLx database connection");
sqlx::migrate!("./fs_surbs_migrations")
.run(&mut conn)
.await
.context("Failed to perform SQLx migrations")?;
.expect("Failed to perform SQLx migrations");
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -29,6 +28,4 @@ async fn main() -> anyhow::Result<()> {
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
}
Ok(())
}
@@ -5,8 +5,8 @@ use crate::nyxd::{self, NyxdClient};
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
use crate::signing::signer::{NoSigner, OfflineSigner};
use crate::{
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient,
ValidatorClientError,
nym_api, 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_http_api_client::Client,
pub nym_api: nym_api::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_http_api_client::Client::new(config.api_url.clone(), None);
let nym_api_client = nym_api::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_http_api_client::Client::new(config.api_url.clone(), None);
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
Client {
nym_api: nym_api_client,
@@ -385,25 +385,38 @@ 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_http_api_client::Client,
pub nym_api: nym_api::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_http_api_client::Client::new(api_url, Some(timeout));
let nym_api = nym_api::Client::new(api_url, Some(timeout));
NymApiClient {
use_bincode: true,
@@ -418,10 +431,10 @@ impl NymApiClient {
}
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
let nym_api = nym_http_api_client::Client::builder(api_url)
let nym_api = nym_api::Client::builder::<_, ValidatorClientError>(api_url)
.expect("invalid api url")
.with_user_agent(user_agent.into())
.build()
.build::<ValidatorClientError>()
.expect("failed to build nym api client");
NymApiClient {
@@ -3,6 +3,7 @@
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;
@@ -14,7 +15,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: nym_http_api_client::Client,
pub api_client: NymApiClient,
pub verification_key: VerificationKeyAuth,
pub node_id: NodeIndex,
pub cosmos_address: cosmrs::AccountId,
@@ -24,15 +25,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
.base_urls()
.iter()
.map(|url| url.to_string())
.collect::<Vec<String>>()
.join(", ")
self.api_client.api_url()
)
}
}
@@ -64,9 +60,6 @@ pub enum EcashApiError {
source: CompactEcashError,
},
#[error("failed to create API client: {0}")]
ClientError(String),
#[error("the provided account address is malformed: {source}")]
MalformedAccountAddress {
#[from]
@@ -96,13 +89,8 @@ impl TryFrom<ContractVKShare> for EcashApiClient {
// In non-client applications this resolver can cause warning logs about H2 connection
// failure. This indicates that the long lived https connection was closed by the remote
// peer and the resolver will have to reconnect. It should not impact actual functionality
let api_client = nym_http_api_client::Client::builder(url_address)
.map_err(|e| EcashApiError::ClientError(e.to_string()))?
.build()
.map_err(|e| EcashApiError::ClientError(e.to_string()))?;
Ok(EcashApiClient {
api_client,
api_client: NymApiClient::new(url_address),
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
node_id: share.node_index,
cosmos_address: share.owner.as_str().parse()?,
@@ -1,8 +1,7 @@
use crate::nym_api::NymApiClientExt;
use crate::nyxd::contract_traits::MixnetQueryClient;
use crate::nyxd::error::NyxdError;
use crate::nyxd::Config as ClientConfig;
use crate::{QueryHttpRpcNyxdClient, ValidatorClientError};
use crate::{NymApiClient, QueryHttpRpcNyxdClient, ValidatorClientError};
use colored::Colorize;
use core::fmt;
use itertools::Itertools;
@@ -88,17 +87,8 @@ fn setup_connection_tests<H: BuildHasher + 'static>(
}
});
let api_connection_test_clients = api_urls.filter_map(|(network, url)| {
match nym_http_api_client::Client::builder(url.clone()).and_then(|b| b.build()) {
Ok(client) => Some(ClientForConnectionTest::Api(network, url, client)),
Err(err) => {
eprintln!(
"Failed to create API client for {}: {err}",
network.network_name
);
None
}
}
let api_connection_test_clients = api_urls.map(|(network, url)| {
ClientForConnectionTest::Api(network, url.clone(), NymApiClient::new(url))
});
nyxd_connection_test_clients.chain(api_connection_test_clients)
@@ -170,7 +160,7 @@ async fn test_nyxd_connection(
async fn test_nym_api_connection(
network: NymNetworkDetails,
url: &Url,
client: &nym_http_api_client::Client,
client: &NymApiClient,
) -> ConnectionResult {
let result = match timeout(
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
@@ -196,7 +186,7 @@ async fn test_nym_api_connection(
enum ClientForConnectionTest {
Nyxd(NymNetworkDetails, Url, Box<QueryHttpRpcNyxdClient>),
Api(NymNetworkDetails, Url, nym_http_api_client::Client),
Api(NymNetworkDetails, Url, NymApiClient),
}
impl ClientForConnectionTest {
@@ -14,6 +14,7 @@ pub mod signing;
pub use crate::error::ValidatorClientError;
pub use crate::rpc::reqwest::ReqwestRpcClient;
pub use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
pub use client::NymApiClient;
pub use client::{Client, Config, EcashApiClient};
pub use nym_api_requests::*;
pub use nym_http_api_client::UserAgent;
@@ -1,6 +1,7 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_api_requests::models::RequestError;
use nym_http_api_client::HttpClientError;
pub type NymAPIError = HttpClientError;
pub type NymAPIError = HttpClientError<RequestError>;
@@ -3,7 +3,6 @@
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,
@@ -38,7 +37,7 @@ pub use nym_api_requests::{
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
},
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata, SkimmedNode},
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SkimmedNode},
NymNetworkDetailsResponse,
};
use nym_contracts_common::IdentityKey;
@@ -50,8 +49,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;
@@ -63,9 +62,6 @@ 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(
&[
@@ -245,156 +241,6 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn get_current_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
self.get_rewarded_set().await
}
async fn get_all_basic_nodes_with_metadata(
&self,
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
// unroll first loop iteration in order to obtain the metadata
let mut page = 0;
let res = self
.get_basic_nodes_v2(false, Some(page), None, true)
.await?;
let mut nodes = res.nodes.data;
let metadata = res.metadata;
if res.nodes.pagination.total == nodes.len() {
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
}
page += 1;
loop {
let mut res = self
.get_basic_nodes_v2(false, Some(page), None, true)
.await?;
if !metadata.consistency_check(&res.metadata) {
// Create a custom error for inconsistent metadata
return Err(NymAPIError::InternalResponseInconsistency {
url: self.api_url().clone(),
details: "Inconsistent paged metadata".to_string(),
});
}
nodes.append(&mut res.nodes.data);
if nodes.len() >= res.nodes.pagination.total {
break;
} else {
page += 1
}
}
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
}
async fn get_all_basic_active_mixing_assigned_nodes_with_metadata(
&self,
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
// Get all mixing nodes that are in the active/rewarded set
let mut page = 0;
let res = self
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
.await?;
let metadata = res.metadata;
let mut nodes = res.nodes.data;
if res.nodes.pagination.total == nodes.len() {
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
}
page += 1;
loop {
let res = self
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
.await?;
if !metadata.consistency_check(&res.metadata) {
return Err(NymAPIError::InternalResponseInconsistency {
url: self.api_url().clone(),
details: "Inconsistent paged metadata".to_string(),
});
}
nodes.append(&mut res.nodes.data.clone());
// Check if we've got all nodes
if nodes.len() >= res.nodes.pagination.total {
break;
} else {
page += 1;
}
}
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
}
async fn get_all_basic_entry_assigned_nodes_with_metadata(
&self,
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
// Get all nodes that can act as entry gateways
let mut page = 0;
let res = self
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
.await?;
let metadata = res.metadata;
let mut nodes = res.nodes.data;
if res.nodes.pagination.total == nodes.len() {
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
}
page += 1;
loop {
let res = self
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
.await?;
if !metadata.consistency_check(&res.metadata) {
return Err(NymAPIError::InternalResponseInconsistency {
url: self.api_url().clone(),
details: "Inconsistent paged metadata".to_string(),
});
}
nodes.append(&mut res.nodes.data.clone());
// Check if we've got all nodes
if nodes.len() >= res.nodes.pagination.total {
break;
} else {
page += 1;
}
}
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
}
async fn get_all_described_nodes(&self) -> Result<Vec<NymNodeDescription>, NymAPIError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut descriptions = Vec::new();
loop {
let mut res = self.get_nodes_described(Some(page), None).await?;
descriptions.append(&mut res.data);
if descriptions.len() < res.pagination.total {
page += 1
} else {
break;
}
}
Ok(descriptions)
}
#[tracing::instrument(level = "debug", skip_all)]
async fn get_nym_nodes(
&self,
@@ -422,25 +268,6 @@ 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> {
@@ -1544,49 +1371,8 @@ 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 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());
}
}
impl NymApiClientExt for Client {}
-1
View File
@@ -38,7 +38,6 @@ 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" }
+1 -1
View File
@@ -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,
-3
View File
@@ -4,7 +4,6 @@
use clap::{Args, Subcommand};
pub mod ecash;
pub mod nyx;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -17,6 +16,4 @@ pub struct Internal {
pub enum InternalCommands {
/// Ecash related internal commands
Ecash(ecash::InternalEcash),
Nyx(nyx::InternalNyx),
}
@@ -1,116 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use anyhow::bail;
use clap::Parser;
use nym_mixnet_contract_common::nym_node::Role;
use nym_mixnet_contract_common::reward_params::NodeRewardingParameters;
use nym_mixnet_contract_common::{
EpochRewardedSet, EpochState, NodeId, RewardingParams, RoleAssignment,
};
use nym_validator_client::nyxd::contract_traits::mixnet_query_client::MixnetQueryClientExt;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
use rand::prelude::*;
use rand::thread_rng;
#[derive(Debug, Parser)]
pub struct Args {}
fn choose_new_nodes(
params: &RewardingParams,
rewarded_set: &EpochRewardedSet,
role: Role,
) -> Vec<NodeId> {
let mut rng = thread_rng();
match role {
Role::EntryGateway => rewarded_set
.assignment
.entry_gateways
.choose_multiple(&mut rng, params.rewarded_set.entry_gateways as usize)
.copied()
.collect(),
Role::Layer1 => rewarded_set
.assignment
.layer1
.choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3)
.copied()
.collect(),
Role::Layer2 => rewarded_set
.assignment
.layer2
.choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3)
.copied()
.collect(),
Role::Layer3 => rewarded_set
.assignment
.layer3
.choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3)
.copied()
.collect(),
Role::ExitGateway => rewarded_set
.assignment
.exit_gateways
.choose_multiple(&mut rng, params.rewarded_set.exit_gateways as usize)
.copied()
.collect(),
Role::Standby => rewarded_set
.assignment
.standby
.choose_multiple(&mut rng, params.rewarded_set.standby as usize)
.copied()
.collect(),
}
}
pub async fn force_advance_epoch(_: Args, client: SigningClient) -> anyhow::Result<()> {
let current_epoch = client.get_current_interval_details().await?;
let epoch_status = client.get_current_epoch_status().await?;
if epoch_status.being_advanced_by.as_str() != client.address().to_string() {
bail!(
"this client is not authorised to perform any epoch operations. we need {}",
client.address()
)
}
let rewarding_params = client.get_rewarding_parameters().await?;
let current_rewarded_set = client.get_rewarded_set().await?;
if !current_epoch.is_current_epoch_over {
println!("the current epoch is not over yet - there's nothing to do")
}
// is this most efficient? no. but it's simple
loop {
let epoch_status = client.get_current_epoch_status().await?;
match epoch_status.state {
EpochState::InProgress => break,
EpochState::Rewarding { final_node_id, .. } => {
println!("rewarding {final_node_id} with big fat 0...");
client
.reward_node(
final_node_id,
NodeRewardingParameters::new(Default::default(), Default::default()),
None,
)
.await?;
}
EpochState::ReconcilingEvents => {
println!("trying to reconcile events...");
client.reconcile_epoch_events(None, None).await?;
}
EpochState::RoleAssignment { next } => {
let nodes = choose_new_nodes(&rewarding_params, &current_rewarded_set, next);
println!("assigning {nodes:?} as {next}");
client
.assign_roles(RoleAssignment { role: next, nodes }, None)
.await?;
}
}
}
Ok(())
}
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{Args, Subcommand};
pub mod force_advance_epoch;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct InternalNyx {
#[clap(subcommand)]
pub command: InternalNyxCommands,
}
#[derive(Debug, Subcommand)]
pub enum InternalNyxCommands {
/// Attempt to force advance the current epoch
ForceAdvanceEpoch(force_advance_epoch::Args),
}
-2
View File
@@ -3,7 +3,6 @@ name = "nym-credential-storage"
version = "0.1.0"
edition = "2021"
license.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -34,7 +33,6 @@ features = ["rt-multi-thread", "net", "signal", "fs"]
[build-dependencies]
anyhow = { workspace = true }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
"sqlite",
+4 -13
View File
@@ -3,29 +3,22 @@
* SPDX-License-Identifier: Apache-2.0
*/
use anyhow::Context;
use sqlx::{Connection, SqliteConnection};
use std::env;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let out_dir = env::var("OUT_DIR")?;
async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{out_dir}/coconut-credential-example.sqlite");
// remove the db file if it already existed from previous build
// in case it was from a different branch
if std::fs::exists(&database_path)? {
std::fs::remove_file(&database_path)?;
}
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.context("Failed to create SQLx database connection")?;
.expect("Failed to create SQLx database connection");
sqlx::migrate!("./migrations")
.run(&mut conn)
.await
.context("Failed to perform SQLx migrations")?;
.expect("Failed to perform SQLx migrations");
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -34,6 +27,4 @@ async fn main() -> anyhow::Result<()> {
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
Ok(())
}
@@ -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, NymApiClientExt};
use nym_validator_client::nym_api::EpochId;
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: nym_validator_client::ValidatorClientError::NymAPIError { source: err },
source: err,
}))
}
}
-1
View File
@@ -22,7 +22,6 @@ 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, NymApiClientExt};
use nym_validator_client::nym_api::EpochId;
use serde::{Deserialize, Serialize};
use time::Date;
@@ -108,7 +108,7 @@ impl IssuanceTicketBook {
signing_request.withdrawal_request.clone(),
self.deposit_id,
request_signature,
signing_request.ecash_pub_key,
signing_request.ecash_pub_key.clone(),
signing_request.expiration_date,
signing_request.ticketbook_type,
)
@@ -116,7 +116,7 @@ impl IssuanceTicketBook {
pub async fn obtain_blinded_credential(
&self,
client: &nym_http_api_client::Client,
client: &nym_validator_client::client::NymApiClient,
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_http_api_client::Client,
client: &nym_validator_client::client::NymApiClient,
signer_index: u64,
validator_vk: &VerificationKeyAuth,
signing_data: CredentialSigningData,
-1
View File
@@ -10,7 +10,6 @@ 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};
+1 -4
View File
@@ -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::{nym_api::error::NymAPIError, ValidatorClientError};
use nym_validator_client::ValidatorClientError;
use thiserror::Error;
#[derive(Debug, Error)]
@@ -37,9 +37,6 @@ 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),
-1
View File
@@ -22,7 +22,6 @@ 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
+25 -31
View File
@@ -1,14 +1,15 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{LocalChainStatus, SignerCheckError, SigningStatus, TypedSignerResult};
use crate::{LocalChainStatus, 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;
@@ -31,38 +32,37 @@ pub(crate) mod signing_status {
}
struct ClientUnderTest {
api_client: nym_http_api_client::Client,
api_client: NymApiClient,
build_info: Option<BinaryBuildInformationOwned>,
}
impl ClientUnderTest {
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,
pub(crate) fn new(api_url: &Url) -> Self {
ClientUnderTest {
api_client: NymApiClient::new(api_url.clone()),
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.build_information())
.await
match tokio::time::timeout(
Duration::from_secs(5),
self.api_client.nym_api.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.current_url());
warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.api_url());
false
}
Err(_timeout) => {
warn!(
"{}: timed out while attempting to retrieve build information",
self.api_client.current_url()
self.api_client.api_url()
);
false
}
@@ -77,7 +77,7 @@ impl ClientUnderTest {
.inspect_err(|err| {
error!(
"ecash signer '{}' reports invalid version {}: {err}",
self.api_client.current_url(),
self.api_client.api_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.get_chain_blocks_status().await {
return match self.api_client.nym_api.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.current_url()
self.api_client.api_url()
);
LocalChainStatus::Unreachable
}
@@ -136,14 +136,14 @@ impl ClientUnderTest {
}
// fallback to the legacy query
match self.api_client.get_chain_status().await {
match self.api_client.nym_api.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.current_url()
self.api_client.api_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.get_signer_status().await {
return match self.api_client.nym_api.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.current_url()
self.api_client.api_url()
);
SigningStatus::Unreachable
}
@@ -173,14 +173,14 @@ impl ClientUnderTest {
}
// fallback to the legacy query
match self.api_client.get_signer_information().await {
match self.api_client.nym_api.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.current_url()
self.api_client.api_url()
);
// NOTE: this might equally mean the signing is disabled
SigningStatus::Unreachable
@@ -201,13 +201,7 @@ pub(crate) async fn check_client(
return SignerStatus::ProvidedInvalidDetails.with_details(dealer_information, dkg_epoch);
};
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);
}
};
let mut client = ClientUnderTest::new(&parsed_information.announce_address);
// 8. check basic connection status - can you retrieve build information?
if !client.try_retrieve_build_information().await {
-7
View File
@@ -1,7 +1,6 @@
// 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;
@@ -12,12 +11,6 @@ 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 {
+4 -37
View File
@@ -15,9 +15,6 @@ use nym_validator_client::ecash::models::EcashSignerStatusResponse;
use nym_validator_client::models::{
ChainBlocksStatusResponse, ChainStatusResponse, SignerInformationResponse,
};
use nym_validator_client::nyxd::contract_traits::dkg_query_client::{
ContractVKShare, DealerDetails, Epoch,
};
mod client_check;
pub mod error;
@@ -51,22 +48,7 @@ pub async fn check_signers(
check_signers_with_client(&client).await
}
pub struct DkgDetails {
pub dkg_epoch: Epoch,
pub threshold: Option<u64>,
pub network_dealers: Vec<DealerDetails>,
pub submitted_shared: HashMap<u64, ContractVKShare>,
}
pub async fn check_signers_with_client<C>(client: &C) -> Result<SignersTestResult, SignerCheckError>
where
C: DkgQueryClient + Sync,
{
let dkg_details = dkg_details_with_client(client).await?;
check_known_dealers(dkg_details).await
}
pub async fn dkg_details_with_client<C>(client: &C) -> Result<DkgDetails, SignerCheckError>
where
C: DkgQueryClient + Sync,
{
@@ -97,31 +79,16 @@ where
.map(|share| (share.node_index, share))
.collect();
Ok(DkgDetails {
dkg_epoch,
threshold,
network_dealers: dealers,
submitted_shared: shares,
})
}
pub async fn check_known_dealers(
dkg_details: DkgDetails,
) -> Result<SignersTestResult, SignerCheckError> {
// 6. for each dealer attempt to perform the checks
let results = dkg_details
.network_dealers
let results = dealers
.into_iter()
.map(|d| {
let share = dkg_details.submitted_shared.get(&d.assigned_index);
check_client(d, dkg_details.dkg_epoch.epoch_id, share)
let share = shares.get(&d.assigned_index);
check_client(d, dkg_epoch.epoch_id, share)
})
.collect::<FuturesUnordered<_>>()
.collect::<Vec<_>>()
.await;
Ok(SignersTestResult {
threshold: dkg_details.threshold,
results,
})
Ok(SignersTestResult { threshold, results })
}
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "nym-execute"
version = "0.1.0"
edition = "2021"
license.workspace = true
[lib]
proc-macro = true
[dependencies]
syn = { workspace = true, features = ["full"] }
quote = { workspace = true }
+110
View File
@@ -0,0 +1,110 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, Block, ExprMethodCall, FnArg, Ident, ItemFn, LitStr, ReturnType, Token,
VisPublic, Visibility,
};
#[proc_macro_attribute]
pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream {
let f = parse_macro_input!(item as ItemFn);
let target = parse_macro_input!(attr as LitStr).value();
let cl = if target == "mixnet" {
quote! {self.mixnet_contract_address()}
} else if target == "vesting" {
quote! {self.vesting_contract_address()}
} else {
panic!("Only `mixnet` and `vesting` targets are supported!")
};
let cl = proc_macro::TokenStream::from(cl);
let cl = parse_macro_input!(cl as ExprMethodCall);
let orig_f = f.clone();
let mut execute_f = f.clone();
let mut simulate_f = f.clone();
let name = f.sig.ident;
let name_str = name.to_string();
let call_args = f.sig.inputs.into_iter().filter_map(|arg| match arg {
FnArg::Receiver(_) => None,
FnArg::Typed(arg) => Some(arg.pat),
});
let execute_args = call_args.clone();
let simulate_args = call_args;
execute_f.sig.asyncness = Some(Token![async](execute_f.sig.ident.span()));
simulate_f.sig.asyncness = Some(Token![async](simulate_f.sig.ident.span()));
execute_f.vis = Visibility::Public(VisPublic {
pub_token: Token![pub](execute_f.sig.ident.span()),
});
simulate_f.vis = Visibility::Public(VisPublic {
pub_token: Token![pub](simulate_f.sig.ident.span()),
});
execute_f.sig.ident = Ident::new(
&format!("execute{}", execute_f.sig.ident),
execute_f.sig.ident.span(),
);
simulate_f.sig.ident = Ident::new(
&format!("simulate{}", simulate_f.sig.ident),
simulate_f.sig.ident.span(),
);
let execute_output = quote! {
-> Result<ExecuteResult, NyxdError>
};
let o_ts = proc_macro::TokenStream::from(execute_output);
execute_f.sig.output = parse_macro_input!(o_ts as ReturnType);
let simulate_output = quote! {
-> Result<SimulateResponse, NyxdError>
};
let o_ts = proc_macro::TokenStream::from(simulate_output);
simulate_f.sig.output = parse_macro_input!(o_ts as ReturnType);
let simulate_block = quote! {
{
let (msg, _fee) = self.#name(#(#simulate_args),*);
let msg = self.wrap_contract_execute_message(
#cl,
&msg,
vec![],
)?;
self.simulate(vec![msg]).await
}
};
let ts = proc_macro::TokenStream::from(simulate_block);
simulate_f.block = Box::new(parse_macro_input!(ts as Block));
let execute_block = quote! {
{
let (req, fee) = self.#name(#(#execute_args),*);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.execute(
self.address(),
#cl,
&req,
fee,
#name_str,
vec![],
)
.await
}
};
let ts = proc_macro::TokenStream::from(execute_block);
execute_f.block = Box::new(parse_macro_input!(ts as Block));
let out = quote! {
#orig_f
#execute_f
#simulate_f
};
out.into()
}
+1 -1
View File
@@ -89,7 +89,7 @@ mod tests {
.unwrap();
let blind_sig = issue(
keypair.secret_key(),
sig_req.ecash_pub_key,
sig_req.ecash_pub_key.clone(),
&sig_req.withdrawal_request,
expiration_date.ecash_unix_timestamp(),
issuance.ticketbook_type().encode(),
-2
View File
@@ -7,7 +7,6 @@ homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
sqlx = { workspace = true, features = [
@@ -28,7 +27,6 @@ nym-statistics-common = { path = "../statistics" }
[build-dependencies]
anyhow = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
+4 -13
View File
@@ -1,29 +1,22 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use anyhow::Context;
use sqlx::{Connection, SqliteConnection};
use std::env;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let out_dir = env::var("OUT_DIR")?;
async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{out_dir}/gateway-stats-example.sqlite");
// remove the db file if it already existed from previous build
// in case it was from a different branch
if std::fs::exists(&database_path)? {
std::fs::remove_file(&database_path)?;
}
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.context("Failed to create SQLx database connection")?;
.expect("Failed to create SQLx database connection");
sqlx::migrate!("./migrations")
.run(&mut conn)
.await
.context("Failed to perform SQLx migrations")?;
.expect("Failed to perform SQLx migrations");
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -32,6 +25,4 @@ async fn main() -> anyhow::Result<()> {
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
Ok(())
}
-2
View File
@@ -7,7 +7,6 @@ homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
async-trait = { workspace = true }
@@ -32,7 +31,6 @@ nym-gateway-requests = { path = "../gateway-requests" }
nym-sphinx = { path = "../nymsphinx" }
[build-dependencies]
anyhow = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { workspace = true, features = [
"runtime-tokio-rustls",
+4 -13
View File
@@ -1,29 +1,22 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use anyhow::Context;
use sqlx::{Connection, SqliteConnection};
use std::env;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let out_dir = env::var("OUT_DIR")?;
async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{out_dir}/gateway-example.sqlite");
// remove the db file if it already existed from previous build
// in case it was from a different branch
if std::fs::exists(&database_path)? {
std::fs::remove_file(&database_path)?;
}
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.context("Failed to create SQLx database connection")?;
.expect("Failed to create SQLx database connection");
sqlx::migrate!("./migrations")
.run(&mut conn)
.await
.context("Failed to perform SQLx migrations")?;
.expect("Failed to perform SQLx migrations");
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -32,6 +25,4 @@ async fn main() -> anyhow::Result<()> {
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
Ok(())
}
-27
View File
@@ -1,27 +0,0 @@
[package]
name = "nym-http-api-client-macro"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1"
syn = { workspace = true, features = ["full"] }
quote = "1.0.40"
proc-macro-crate = "3"
[dev-dependencies]
nym-http-api-client = { path = "../http-api-client" }
reqwest = { workspace = true }
[lints]
workspace = true
-311
View File
@@ -1,311 +0,0 @@
//! Proc-macros for configuring HTTP clients globally via the `inventory` crate.
//!
//! This crate provides macros that allow any crate in the workspace to contribute
//! configuration modifications to `reqwest::ClientBuilder` instances through a
//! compile-time registry pattern.
//!
//! # Overview
//!
//! The macros work by:
//! 1. Collecting configuration functions from across all crates at compile time
//! 2. Sorting them by priority (lower numbers run first)
//! 3. Applying them sequentially to build HTTP clients with consistent settings
//!
//! # Examples
//!
//! ## Basic Usage with `client_defaults!`
//!
//! ```ignore
//! use nym_http_api_client_macro::client_defaults;
//!
//! // Register default configurations with priority
//! client_defaults!(
//! priority = 10; // Optional, defaults to 0
//! timeout = std::time::Duration::from_secs(30),
//! gzip = true,
//! user_agent = "MyApp/1.0"
//! );
//! ```
//!
//! ## Using `client_cfg!` for one-off configurations
//!
//! ```ignore
//! use nym_http_api_client_macro::client_cfg;
//!
//! let configure = client_cfg!(
//! timeout = std::time::Duration::from_secs(60),
//! default_headers {
//! "X-Custom-Header" => "value",
//! "Authorization" => auth_token
//! }
//! );
//!
//! let builder = reqwest::ClientBuilder::new();
//! let configured = configure(builder);
//! ```
//!
//! # DSL Reference
//!
//! The macro DSL supports several patterns:
//! - `key = value` - Calls `builder.key(value)`
//! - `key(arg1, arg2)` - Calls `builder.key(arg1, arg2)`
//! - `flag` - Calls `builder.flag()` with no arguments
//! - `default_headers { "name" => "value", ... }` - Sets default headers
//!
//! # Priority System
//!
//! Configurations are applied in priority order (lower numbers first):
//! - Negative priorities: Early configuration (e.g., -100 for base settings)
//! - Zero (default): Standard configuration
//! - Positive priorities: Late configuration (e.g., 100 for overrides)
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::{format_ident, quote};
use syn::{
braced,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
token, Expr, Ident, LitInt, Result, Token,
};
// ------------------ core crate path resolution ------------------
fn core_path() -> TokenStream2 {
match crate_name("nym-http-api-client") {
Ok(FoundCrate::Itself) => quote!(crate),
Ok(FoundCrate::Name(name)) => {
let ident = Ident::new(&name, Span::call_site());
quote!( ::#ident )
}
Err(_) => {
// Fallback if the crate is not found by name (unlikely if deps set up correctly)
quote!(::nym_http_api_client)
}
}
}
// ------------------ DSL parsing ------------------
struct Items(Punctuated<Item, Token![,]>);
impl Parse for Items {
fn parse(input: ParseStream<'_>) -> Result<Self> {
Ok(Self(Punctuated::parse_terminated(input)?))
}
}
enum Item {
Assign {
key: Ident,
_eq: Token![=],
value: Expr,
}, // foo = EXPR
Call {
key: Ident,
args: Punctuated<Expr, Token![,]>,
_p: token::Paren,
}, // foo(a,b)
DefaultHeaders {
_key: Ident,
map: HeaderMapInit,
}, // default_headers { ... }
Flag {
key: Ident,
}, // foo
}
impl Parse for Item {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let key: Ident = input.parse()?;
if input.peek(Token![=]) {
let _eq: Token![=] = input.parse()?;
let value: Expr = input.parse()?;
return Ok(Self::Assign { key, _eq, value });
}
if input.peek(token::Paren) {
let content;
let _p = syn::parenthesized!(content in input);
let args = Punctuated::<Expr, Token![,]>::parse_terminated(&content)?;
return Ok(Self::Call { key, args, _p });
}
if input.peek(token::Brace) && key == format_ident!("default_headers") {
let map = input.parse::<HeaderMapInit>()?;
return Ok(Self::DefaultHeaders { _key: key, map });
}
Ok(Self::Flag { key })
}
}
struct HeaderPair {
k: Expr,
_arrow: Token![=>],
v: Expr,
}
impl Parse for HeaderPair {
fn parse(input: ParseStream<'_>) -> Result<Self> {
Ok(Self {
k: input.parse()?,
_arrow: input.parse()?,
v: input.parse()?,
})
}
}
struct HeaderMapInit {
_brace: token::Brace,
pairs: Punctuated<HeaderPair, Token![,]>,
}
impl Parse for HeaderMapInit {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let content;
let _brace = braced!(content in input);
let pairs = Punctuated::<HeaderPair, Token![,]>::parse_terminated(&content)?;
Ok(Self { _brace, pairs })
}
}
// Generate statements that mutate a builder named `b` using the resolved core path.
fn to_stmts(items: Items, core: &TokenStream2) -> TokenStream2 {
let mut stmts = Vec::new();
for it in items.0 {
match it {
Item::Assign { key, value, .. } => {
let m = key;
stmts.push(quote! { b = b.#m(#value); });
}
Item::Call { key, args, .. } => {
let m = key;
let args = args.iter();
stmts.push(quote! { b = b.#m( #( #args ),* ); });
}
Item::DefaultHeaders { map, .. } => {
let (ks, vs): (Vec<_>, Vec<_>) = map.pairs.into_iter().map(|p| (p.k, p.v)).unzip();
stmts.push(quote! {
let mut __cm = #core::reqwest::header::HeaderMap::new();
#(
{
use #core::reqwest::header::{HeaderName, HeaderValue};
let __k = HeaderName::try_from(#ks)
.unwrap_or_else(|e| panic!("Invalid header name: {}", e));
let __v = HeaderValue::try_from(#vs)
.unwrap_or_else(|e| panic!("Invalid header value: {}", e));
__cm.insert(__k, __v);
}
)*
b = b.default_headers(__cm);
});
}
Item::Flag { key } => {
let m = key;
stmts.push(quote! { b = b.#m(); });
}
}
}
quote! { #(#stmts)* }
}
// ------------------ client_cfg! ------------------
/// Creates a closure that configures a `ReqwestClientBuilder`.
///
/// This macro generates a closure that can be used to configure a single
/// `reqwest::ClientBuilder` instance without affecting global defaults.
///
/// # Example
///
/// ```ignore
/// let config = client_cfg!(
/// timeout = Duration::from_secs(30),
/// gzip = true
/// );
/// let client = config(reqwest::ClientBuilder::new()).build()?;
/// ```
#[proc_macro]
pub fn client_cfg(input: TokenStream) -> TokenStream {
let items = parse_macro_input!(input as Items);
let core = core_path();
let body = to_stmts(items, &core);
let out = quote! {
|mut b: #core::ReqwestClientBuilder| { #body b }
};
out.into()
}
// ------------------ client_defaults! with optional priority header ------------------
struct MaybePrioritized {
priority: i32,
items: Items,
}
impl Parse for MaybePrioritized {
fn parse(input: ParseStream<'_>) -> Result<Self> {
// Optional header: `priority = <int> ;`
let fork = input.fork();
let mut priority = 0i32;
if fork.peek(Ident) && fork.parse::<Ident>()? == "priority" && fork.peek(Token![=]) {
// commit
let _ = input.parse::<Ident>()?; // priority
let _ = input.parse::<Token![=]>()?;
let lit: LitInt = input.parse()?;
priority = lit.base10_parse()?;
let _ = input.parse::<Token![;]>()?;
}
let items = input.parse::<Items>()?;
Ok(Self { priority, items })
}
}
/// Registers global default configurations for HTTP clients.
///
/// This macro submits a configuration record to the global registry that will
/// be applied to all HTTP clients created with `default_builder()`.
///
/// # Parameters
///
/// - `priority` (optional): Integer priority for ordering (lower runs first, default: 0)
/// - Configuration items: Any valid `reqwest::ClientBuilder` method calls
///
/// # Example
///
/// ```ignore
/// client_defaults!(
/// priority = -50; // Run early in the configuration chain
/// connect_timeout = Duration::from_secs(10),
/// pool_max_idle_per_host = 32,
/// default_headers {
/// "User-Agent" => "MyApp/1.0",
/// "Accept" => "application/json"
/// }
/// );
/// ```
#[proc_macro]
pub fn client_defaults(input: TokenStream) -> TokenStream {
let MaybePrioritized { priority, items } = parse_macro_input!(input as MaybePrioritized);
let core = core_path();
let body = to_stmts(items, &core);
let out = quote! {
#[allow(non_snake_case)]
mod __client_defaults {
use super::*;
#[allow(unused)]
pub fn __cfg(
mut b: #core::ReqwestClientBuilder
) -> #core::ReqwestClientBuilder {
#body
b
}
#core::inventory::submit! {
#core::registry::ConfigRecord {
priority: #priority,
apply: __cfg,
}
}
}
};
out.into()
}
@@ -1,64 +0,0 @@
use nym_http_api_client_macro::{client_cfg, client_defaults};
use std::time::Duration;
#[test]
fn test_client_cfg_basic() {
// Test that the macro compiles with basic configuration
let _config = client_cfg!(timeout = Duration::from_secs(30), gzip = true);
}
#[test]
fn test_client_cfg_with_headers() {
// Test that the macro compiles with default headers
let _config = client_cfg!(
timeout = Duration::from_secs(30),
default_headers {
"User-Agent" => "TestApp/1.0",
"Accept" => "application/json"
}
);
}
#[test]
fn test_client_cfg_with_method_calls() {
// Test that the macro compiles with method calls
let _config = client_cfg!(
pool_max_idle_per_host = 32,
tcp_nodelay = true,
danger_accept_invalid_certs = true
);
}
#[test]
fn test_client_defaults_with_priority() {
// Test that client_defaults macro compiles with priority
client_defaults!(
priority = -100;
gzip = true,
deflate = true
);
}
#[test]
fn test_client_defaults_without_priority() {
// Test that client_defaults macro compiles without priority (defaults to 0)
client_defaults!(brotli = true, zstd = true);
}
#[test]
fn test_empty_client_cfg() {
// Test that empty configuration compiles
let _config = client_cfg!();
}
// Integration test to verify the closure actually works
#[test]
fn test_client_cfg_closure_application() {
let config = client_cfg!(gzip = true);
// Apply the configuration to a new builder
let builder = reqwest::ClientBuilder::new();
let _configured_builder = config(builder);
// Note: We can't easily test the internal state of the builder,
// but we verify it compiles and runs without panic
}
-6
View File
@@ -13,7 +13,6 @@ license.workspace = true
[features]
default=["tunneling"]
tunneling=[]
network-defaults = ["dep:nym-network-defaults"]
[dependencies]
async-trait = { workspace = true }
@@ -24,12 +23,9 @@ url = { workspace = true }
once_cell = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true}
serde_plain = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
itertools = { workspace = true }
inventory = { workspace = true }
# used for decoding text responses (they were already implicitly included)
bytes = { workspace = true }
@@ -38,8 +34,6 @@ mime = { workspace = true }
nym-http-api-common = { path = "../http-api-common", default-features = false }
nym-bin-common = { path = "../bin-common" }
nym-network-defaults = { path = "../network-defaults", optional = true }
nym-http-api-client-macro = { path = "../http-api-client-macro" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
hickory-resolver = { workspace = true, features = ["https-ring", "tls-ring", "webpki-roots"] }
+3 -7
View File
@@ -54,14 +54,10 @@ 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,
}
@@ -100,14 +96,14 @@ mod tests {
// Some(vec!["https://cdn77.com"]),
// ).unwrap(); // cdn77
let client = ClientBuilder::new(url1)
let client = ClientBuilder::new::<_, &str>(url1)
.expect("bad url")
.with_fronting(FrontPolicy::Always)
.build()
.build::<&str>()
.expect("failed to build client");
let response = client
.send_request::<_, (), &str, &str>(
.send_request::<_, (), &str, &str, &str>(
reqwest::Method::GET,
&["api", "v1", "network", "details"],
NO_PARAMS,
File diff suppressed because it is too large Load Diff
-53
View File
@@ -1,53 +0,0 @@
//! Global registry for HTTP client configurations.
//!
//! This module provides a compile-time registry system that allows any crate
//! in the workspace to contribute configuration modifications to HTTP clients.
use crate::ReqwestClientBuilder;
/// A configuration record that modifies a `ReqwestClientBuilder`.
///
/// Records are collected at compile-time via the `inventory` crate and
/// applied in priority order when building HTTP clients.
pub struct ConfigRecord {
/// Lower numbers run earlier.
pub priority: i32,
/// A function that takes a builder and returns a mutated builder.
pub apply: fn(ReqwestClientBuilder) -> ReqwestClientBuilder,
}
inventory::collect!(ConfigRecord);
/// Returns the default builder with all registered configurations applied.
pub fn default_builder() -> ReqwestClientBuilder {
let mut b = ReqwestClientBuilder::new();
let mut records: Vec<&'static ConfigRecord> =
inventory::iter::<ConfigRecord>.into_iter().collect();
records.sort_by_key(|r| r.priority); // lower runs first
for r in records {
b = (r.apply)(b);
}
b
}
/// Builds a client using the default builder with all registered configurations.
pub fn build_client() -> reqwest::Result<reqwest::Client> {
default_builder().build()
}
/// Debug function to inspect registered configurations.
/// Returns a vector of (priority, function_pointer) tuples for debugging.
#[cfg(debug_assertions)]
pub fn inspect_registered_configs() -> Vec<(i32, usize)> {
let mut configs: Vec<(i32, usize)> = inventory::iter::<ConfigRecord>
.into_iter()
.map(|record| (record.priority, record.apply as usize))
.collect();
configs.sort_by_key(|(priority, _)| *priority);
configs
}
/// Returns the count of registered configuration records.
pub fn registered_config_count() -> usize {
inventory::iter::<ConfigRecord>.into_iter().count()
}
+9 -6
View File
@@ -95,10 +95,10 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
"http://example.com/".parse()?,
])
.with_retries(3)
.build()?;
.build::<HttpClientError>()?;
let req = client.create_get_request(&["/"], NO_PARAMS).unwrap();
let resp = client.send(req).await?;
let req = client.create_get_request(&["/"], NO_PARAMS);
let resp = client.send::<HttpClientError>(req).await?;
assert_eq!(resp.status(), 200);
@@ -111,7 +111,10 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
#[test]
fn host_updating() {
let url = Url::new("http://example.com", None).unwrap();
let mut client = ClientBuilder::new(url).unwrap().build().unwrap();
let mut client = ClientBuilder::new::<_, &str>(url)
.unwrap()
.build::<&str>()
.unwrap();
// check that the url is set correctly
let current_url = client.current_url();
@@ -168,10 +171,10 @@ fn host_updating() {
#[cfg(feature = "tunneling")]
fn fronted_host_updating() {
let url = Url::new("http://example.com", Some(vec!["http://front1.com"])).unwrap();
let mut client = ClientBuilder::new(url)
let mut client = ClientBuilder::new::<_, &str>(url)
.unwrap()
.with_fronting(crate::fronted::FrontPolicy::Always)
.build()
.build::<&str>()
.unwrap();
// check that the url is set correctly
@@ -1,70 +0,0 @@
use nym_http_api_client::registry;
// Create separate modules to avoid name conflicts
mod config_early {
use nym_http_api_client_macro::client_defaults;
client_defaults!(
priority = -200;
tcp_nodelay = true
);
}
mod config_late {
use nym_http_api_client_macro::client_defaults;
client_defaults!(
priority = 100;
pool_idle_timeout = std::time::Duration::from_secs(90)
);
}
#[test]
fn test_registry_collects_configs() {
// Verify that configurations are being registered
let count = registry::registered_config_count();
// Should have at least the ones we registered above plus the default from lib.rs
assert!(
count >= 3,
"Expected at least 3 registered configs, got {}",
count
);
}
#[test]
fn test_default_builder_applies_configs() {
// Test that default_builder returns a configured builder
let _builder = registry::default_builder();
// The builder should have all configurations applied
// We can't easily inspect the internals, but we verify it doesn't panic
}
#[test]
fn test_build_client_works() {
// Test that we can successfully build a client with all configurations
let result = registry::build_client();
assert!(result.is_ok(), "Failed to build client: {:?}", result.err());
}
#[cfg(debug_assertions)]
#[test]
fn test_inspect_configs() {
// In debug mode, test that we can inspect registered configurations
let configs = registry::inspect_registered_configs();
// Verify configs are sorted by priority
for window in configs.windows(2) {
assert!(window[0].0 <= window[1].0, "Configs not sorted by priority");
}
// Verify we have configs at different priority levels
let priorities: Vec<i32> = configs.iter().map(|(p, _)| *p).collect();
assert!(
priorities.iter().any(|&p| p < 0),
"Expected negative priority configs"
);
assert!(
priorities.iter().any(|&p| p >= 0),
"Expected non-negative priority configs"
);
}
+2 -9
View File
@@ -55,7 +55,6 @@ 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]>,
@@ -189,14 +188,8 @@ impl NymNetworkDetails {
),
},
nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API),
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(),
),
nym_api_urls: None,
nym_vpn_api_urls: None,
}
}
@@ -319,9 +319,9 @@ mod tests {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key();
public_keys.push(pk_user);
public_keys.push(pk_user.clone());
}
public_keys.push(user_keypair.public_key());
public_keys.push(user_keypair.public_key().clone());
let (req, req_info) =
withdrawal_request(user_keypair.secret_key(), expiration_date, t_type).unwrap();
@@ -462,9 +462,9 @@ mod tests {
let sk = grp.random_scalar();
let sk_user = SecretKeyUser { sk };
let pk_user = sk_user.public_key();
public_keys.push(pk_user);
public_keys.push(pk_user.clone());
}
public_keys.push(user_keypair.public_key());
public_keys.push(user_keypair.public_key().clone());
let (req, req_info) =
withdrawal_request(user_keypair.secret_key(), expiration_date, t_type).unwrap();
@@ -401,7 +401,7 @@ impl Bytable for SecretKeyUser {
impl Base58 for SecretKeyUser {}
#[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub struct PublicKeyUser {
pub(crate) pk: G1Projective,
}
@@ -554,7 +554,7 @@ impl KeyPairUser {
}
pub fn public_key(&self) -> PublicKeyUser {
self.public_key
self.public_key.clone()
}
pub fn to_bytes(&self) -> Vec<u8> {
-3
View File
@@ -16,6 +16,3 @@ thiserror = { workspace = true }
[dev-dependencies]
rand = { workspace = true }
nym-crypto = { path = "../../crypto", features = ["rand"] }
bincode = { workspace = true }
serde_json = { workspace = true }
serde = { workspace = true, features = ["derive"] }
+2 -86
View File
@@ -7,7 +7,7 @@
use crate::nodes::{NodeIdentity, NODE_IDENTITY_SIZE};
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_sphinx_types::Destination;
use serde::de::{Error as SerdeError, SeqAccess, Unexpected, Visitor};
use serde::de::{Error as SerdeError, Unexpected, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{self, Formatter};
use std::str::FromStr;
@@ -64,7 +64,7 @@ impl<'de> Deserialize<'de> for Recipient {
{
struct RecipientVisitor;
impl<'de> Visitor<'de> for RecipientVisitor {
impl Visitor<'_> for RecipientVisitor {
type Value = Recipient;
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
@@ -90,42 +90,6 @@ impl<'de> Deserialize<'de> for Recipient {
)
})
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
// if we know the size hint, check if it matches expectation,
// otherwise return an error
if let Some(size_hint) = seq.size_hint() {
if size_hint != Recipient::LEN {
return Err(SerdeError::invalid_length(size_hint, &self));
}
}
let mut recipient_bytes = [0u8; Recipient::LEN];
// clippy's suggestion is completely wrong and it iterates wrong sequence
#[allow(clippy::needless_range_loop)]
for i in 0..Recipient::LEN {
let Some(elem) = seq.next_element::<u8>()? else {
return Err(SerdeError::invalid_length(i + 1, &self));
};
recipient_bytes[i] = elem;
}
// make sure there are no trailing bytes
if seq.next_element::<u8>()?.is_some() {
return Err(SerdeError::invalid_length(Recipient::LEN + 1, &self));
}
Recipient::try_from_bytes(recipient_bytes).map_err(|_| {
SerdeError::invalid_value(
Unexpected::Other("At least one of the curve points was malformed"),
&self,
)
})
}
}
deserializer.deserialize_bytes(RecipientVisitor)
@@ -281,18 +245,6 @@ impl FromStr for Recipient {
mod tests {
use super::*;
fn mock_recipient() -> Recipient {
Recipient::try_from_bytes([
67, 5, 132, 146, 3, 236, 116, 89, 254, 57, 131, 159, 69, 181, 55, 208, 12, 108, 136,
83, 58, 76, 171, 195, 31, 98, 92, 64, 68, 53, 156, 184, 100, 189, 73, 3, 238, 103, 156,
108, 124, 199, 42, 79, 172, 98, 81, 177, 182, 100, 167, 164, 74, 183, 199, 213, 162,
173, 102, 112, 30, 159, 148, 66, 44, 75, 230, 182, 138, 114, 170, 163, 209, 82, 204,
100, 118, 91, 57, 150, 212, 147, 151, 135, 148, 16, 213, 223, 182, 164, 242, 37, 40,
73, 137, 228,
])
.unwrap()
}
#[test]
fn string_conversion_works() {
let mut rng = rand::thread_rng();
@@ -356,40 +308,4 @@ mod tests {
recovered_recipient.gateway.to_bytes()
);
}
// calls `visit_bytes`
#[test]
fn bincode_serialisation_works() {
let recipient = mock_recipient();
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
struct MyStruct {
recipient: Recipient,
}
let a = MyStruct { recipient };
let s = bincode::serialize(&a).unwrap();
let b = bincode::deserialize(&s).unwrap();
assert_eq!(a, b);
}
// calls `visit_seq`
#[test]
fn json_serialisation_works() {
use serde::{Deserialize, Serialize};
let recipient = mock_recipient();
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
struct MyStruct {
recipient: Recipient,
}
let a = MyStruct { recipient };
let s = serde_json::to_string(&a).unwrap();
let b = serde_json::from_str(&s).unwrap();
assert_eq!(a, b);
}
}
-2
View File
@@ -180,7 +180,6 @@ impl NymPacket {
}
#[cfg(feature = "sphinx")]
#[allow(unreachable_patterns)]
pub fn sphinx_packet_ref(&self) -> Option<&SphinxPacket> {
match self {
NymPacket::Sphinx(packet) => Some(packet),
@@ -189,7 +188,6 @@ impl NymPacket {
}
#[cfg(feature = "sphinx")]
#[allow(unreachable_patterns)]
pub fn to_sphinx_packet(self) -> Option<SphinxPacket> {
match self {
NymPacket::Sphinx(packet) => Some(packet),
-2
View File
@@ -7,7 +7,6 @@ homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -37,6 +36,5 @@ url.workspace = true
[build-dependencies]
anyhow = { workspace = true }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
+4 -14
View File
@@ -1,30 +1,22 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use anyhow::Context;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
async fn main() {
use sqlx::{Connection, SqliteConnection};
use std::env;
let out_dir = env::var("OUT_DIR")?;
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{out_dir}/scraper-example.sqlite");
// remove the db file if it already existed from previous build
// in case it was from a different branch
if std::fs::exists(&database_path)? {
std::fs::remove_file(&database_path)?;
}
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.context("Failed to create SQLx database connection")?;
.expect("Failed to create SQLx database connection");
sqlx::migrate!("./sql_migrations")
.run(&mut conn)
.await
.context("Failed to perform SQLx migrations")?;
.expect("Failed to perform SQLx migrations");
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
@@ -33,6 +25,4 @@ async fn main() -> anyhow::Result<()> {
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
Ok(())
}
-2
View File
@@ -25,5 +25,3 @@ 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" }
+5 -10
View File
@@ -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::nym_api::NymApiClientExt;
use nym_validator_client::NymApiClient;
use rand::prelude::SliceRandom;
use rand::thread_rng;
use std::net::SocketAddr;
@@ -135,15 +135,10 @@ 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 = match nym_http_api_client::Client::builder(api_endpoint.clone())
.and_then(|b| b.with_user_agent(self.config.user_agent.clone()).build())
{
Ok(c) => c,
Err(err) => {
warn!("failed to create client for {api_endpoint}: {err}");
continue;
}
};
let client = NymApiClient::new_with_user_agent(
api_endpoint.clone(),
self.config.user_agent.clone(),
);
match client.get_all_described_nodes().await {
Ok(res) => return Some(res),
Err(err) => {
+6 -1
View File
@@ -130,7 +130,12 @@ impl VerlocMeasurement {
let variance_micros = data
.iter()
.map(|&value| {
let diff = mean.abs_diff(value);
// make sure we don't underflow
let diff = if mean > value {
mean - value
} else {
value - mean
};
// we don't need nanos precision
let diff_micros = diff.as_micros();
diff_micros * diff_micros
-1
View File
@@ -34,7 +34,6 @@ 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" }
-6
View File
@@ -37,12 +37,6 @@ 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]
+6 -20
View File
@@ -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::{EpochRewardedSet, NymTopology, RoutingNode};
use nym_topology::{NymTopology, RoutingNode};
use nym_validator_client::client::IdentityKey;
use nym_validator_client::{nym_api::NymApiClientExt, UserAgent};
use nym_validator_client::{NymApiClient, UserAgent};
use rand::thread_rng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
@@ -72,16 +72,7 @@ pub async fn current_network_topology_async(
}
};
let api_client = nym_http_api_client::Client::builder(url.clone())
.map_err(|_err| WasmCoreError::MalformedUrl {
raw: nym_api_url.to_string(),
source: url::ParseError::EmptyHost,
})?
.build()
.map_err(|_err| WasmCoreError::MalformedUrl {
raw: nym_api_url.to_string(),
source: url::ParseError::EmptyHost,
})?;
let api_client = NymApiClient::new(url);
let rewarded_set = api_client.get_current_rewarded_set().await?;
let mixnodes_res = api_client
.get_all_basic_active_mixing_assigned_nodes_with_metadata()
@@ -99,14 +90,9 @@ pub async fn current_network_topology_async(
let gateways = gateways_res.nodes;
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);
let topology = NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
.with_skimmed_nodes(&mixnodes)
.with_skimmed_nodes(&gateways);
Ok(topology.into())
}
+1 -1
View File
@@ -21,7 +21,6 @@ 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,
@@ -30,6 +29,7 @@ pub use nym_sphinx::{
pub use nym_statistics_common::clients::ClientStatsSender;
pub use nym_task;
pub use nym_topology::{HardcodedTopologyProvider, MixLayer, NymTopology, TopologyProvider};
pub use nym_validator_client::nym_api::Client as ApiClient;
pub use nym_validator_client::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient};
// TODO: that's a very nasty import path. it should come from contracts instead!
pub use nym_validator_client::client::IdentityKey;
@@ -7,14 +7,16 @@ use tracing::instrument;
use nym_http_api_client::{ApiClient, Client, HttpClientError, NO_PARAMS};
use nym_wireguard_private_metadata_shared::{
routes, Version, {Request, Response},
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, HttpClientError> {
async fn version(&self) -> Result<Version, WireguardMetadataApiClientError> {
let version: u64 = self
.get_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::VERSION],
@@ -28,7 +30,7 @@ pub trait WireguardMetadataApiClient: ApiClient {
async fn available_bandwidth(
&self,
request_body: &Request,
) -> Result<Response, HttpClientError> {
) -> Result<Response, WireguardMetadataApiClientError> {
self.post_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::AVAILABLE],
NO_PARAMS,
@@ -38,7 +40,10 @@ pub trait WireguardMetadataApiClient: ApiClient {
}
#[instrument(level = "debug", skip(self, request_body))]
async fn topup_bandwidth(&self, request_body: &Request) -> Result<Response, HttpClientError> {
async fn topup_bandwidth(
&self,
request_body: &Request,
) -> Result<Response, WireguardMetadataApiClientError> {
self.post_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::TOPUP],
NO_PARAMS,
@@ -13,7 +13,7 @@ mod tests {
use nym_wireguard_private_metadata_server::{
AppState, PeerControllerTransceiver, RouterBuilder,
};
use nym_wireguard_private_metadata_shared::{latest, v0, v1};
use nym_wireguard_private_metadata_shared::{latest, v0, v1, ErrorResponse};
use tokio::{net::TcpListener, sync::mpsc};
pub(crate) const VERIFIER_AVAILABLE_BANDWIDTH: i64 = 42;
@@ -140,7 +140,7 @@ mod tests {
.await
.unwrap();
});
Client::new_url(addr.to_string(), None).unwrap()
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
}
#[tokio::test]
@@ -15,6 +15,7 @@ pub(crate) mod test {
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_wireguard::{peer_controller::PeerControlRequest, CONTROL_CHANNEL_SIZE};
use nym_wireguard_private_metadata_server::PeerControllerTransceiver;
use nym_wireguard_private_metadata_shared::ErrorResponse;
use nym_wireguard_private_metadata_shared::{
v0 as latest, AxumErrorResponse, AxumResult, Construct, Extract, Request, Response,
};
@@ -140,6 +141,6 @@ pub(crate) mod test {
.await
.unwrap();
});
Client::new_url(addr.to_string(), None).unwrap()
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
}
}
+1 -1
View File
@@ -744,7 +744,7 @@ version = "0.1.0"
dependencies = [
"cosmwasm-std",
"quote",
"syn 2.0.98",
"syn 1.0.109",
]
[[package]]
+1 -1
View File
@@ -52,7 +52,7 @@ impl TestSetup {
}
}
pub fn query_ctx(&self) -> QueryCtx<'_> {
pub fn query_ctx(&self) -> QueryCtx {
QueryCtx::from((self.deps.as_ref(), self.env.clone()))
}
}
+2 -2
View File
@@ -73,13 +73,13 @@ impl TestSetupSimple {
message_info(&admin, &[])
}
pub fn execute_ctx(&mut self, sender: MessageInfo) -> ExecCtx<'_> {
pub fn execute_ctx(&mut self, sender: MessageInfo) -> ExecCtx {
let env = self.env.clone();
ExecCtx::from((self.deps.as_mut(), env, sender))
}
#[allow(dead_code)]
pub fn query_ctx(&self) -> QueryCtx<'_> {
pub fn query_ctx(&self) -> QueryCtx {
QueryCtx::from((self.deps.as_ref(), self.env.clone()))
}
+2 -2
View File
@@ -139,9 +139,9 @@ mod tests {
fn add_dummy_mixes_with_delegations(test: &mut TestSetup, delegators: usize, mixes: usize) {
for i in 0..mixes {
let mix_id = test.add_legacy_mixnode(&test.make_addr(format!("mix-owner{i}")), None);
let mix_id = test.add_legacy_mixnode(&test.make_addr(format!("mix-owner{}", i)), None);
for delegator in 0..delegators {
let name = &test.make_addr(format!("delegator{delegator}"));
let name = &test.make_addr(format!("delegator{}", delegator));
test.add_immediate_delegation(name, 100_000_000u32, mix_id)
}
}
@@ -300,7 +300,7 @@ mod tests {
let amount1 = coin(100_000_000, TEST_COIN_DENOM);
let sender1 = message_info(owner, std::slice::from_ref(&amount1));
let sender1 = message_info(owner, &[amount1.clone()]);
try_delegate_to_node(test.deps_mut(), env.clone(), sender1, mix_id).unwrap();
+4 -4
View File
@@ -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,
},
+7 -7
View File
@@ -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,
+3 -3
View File
@@ -131,7 +131,7 @@ impl PreInitContract {
}
}
pub(crate) fn deps(&self) -> Deps<'_> {
pub(crate) fn deps(&self) -> Deps {
Deps {
storage: &self.storage,
api: &self.api,
@@ -139,7 +139,7 @@ impl PreInitContract {
}
}
pub(crate) fn deps_mut(&mut self) -> DepsMut<'_> {
pub(crate) fn deps_mut(&mut self) -> DepsMut {
DepsMut {
storage: &mut self.storage,
api: &self.api,
@@ -147,7 +147,7 @@ impl PreInitContract {
}
}
pub(crate) fn querier(&self) -> QuerierWrapper<'_> {
pub(crate) fn querier(&self) -> QuerierWrapper {
self.tester_builder.querier()
}
+1 -1
View File
@@ -137,7 +137,7 @@ mod tests {
let response = execute(
deps.as_mut(),
env.clone(),
message_info(&admin, std::slice::from_ref(&amount)),
message_info(&admin, &[amount.clone()]),
msg,
);
assert_eq!(
@@ -20,22 +20,16 @@ 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_http_api_client::Client,
validator_client: nym_validator_client::client::NymApiClient,
}
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,
validator_client: nym_validator_client::client::NymApiClient::new(nym_api_url),
}
}
+1 -1
View File
@@ -86,5 +86,5 @@ defguard_wireguard_rs = { workspace = true }
[dev-dependencies]
nym-gateway-storage = { path = "../common/gateway-storage", features = ["mock"] }
nym-wireguard = { path = "../common/wireguard", features = ["mock"] }
mock_instant = "0.6.0"
mock_instant = "0.5.3"
time = { workspace = true }
+1 -2
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.64"
version = "1.1.65"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
@@ -93,7 +93,6 @@ 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" }
+10 -15
View File
@@ -1,20 +1,16 @@
use anyhow::Context;
use sqlx::{Connection, FromRow, SqliteConnection};
use std::env;
const SQLITE_DB_FILENAME: &str = "nym-api-example.sqlite";
// it's fine if compilation fails
#[allow(clippy::unwrap_used)]
#[allow(clippy::expect_used)]
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let out_dir = env::var("OUT_DIR")?;
async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{out_dir}/{SQLITE_DB_FILENAME}");
// remove the db file if it already existed from previous build
// in case it was from a different branch
if std::fs::exists(&database_path)? {
std::fs::remove_file(&database_path)?;
}
#[cfg(target_family = "unix")]
write_db_path_to_file(&out_dir, SQLITE_DB_FILENAME)
.await
@@ -22,12 +18,12 @@ async fn main() -> anyhow::Result<()> {
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
.await
.context("Failed to create SQLx database connection")?;
.expect("Failed to create SQLx database connection");
sqlx::migrate!("./migrations")
.run(&mut conn)
.await
.context("Failed to perform SQLx migrations")?;
.expect("Failed to perform SQLx migrations");
#[derive(FromRow)]
struct Exists {
@@ -37,7 +33,8 @@ async fn main() -> anyhow::Result<()> {
// check if it was already run
let res: Exists = sqlx::query_as("SELECT EXISTS (SELECT 1 FROM v3_migration_info) AS 'exists'")
.fetch_one(&mut conn)
.await?;
.await
.unwrap();
let already_run = res.exists;
@@ -61,7 +58,7 @@ async fn main() -> anyhow::Result<()> {
)
.execute(&mut conn)
.await
.context("failed to update post v3 migration tables")?;
.expect("failed to update post v3 migration tables");
}
#[cfg(target_family = "unix")]
@@ -71,8 +68,6 @@ async fn main() -> anyhow::Result<()> {
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
Ok(())
}
/// use `./enter_db.sh` to inspect DB
@@ -8,9 +8,7 @@ use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::NodeId;
use nym_network_defaults::{
DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT, WG_TUNNEL_PORT,
};
use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT};
use nym_node_requests::api::v1::authenticator::models::Authenticator;
use nym_node_requests::api::v1::gateway::models::Wireguard;
use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
@@ -315,20 +313,11 @@ impl From<Authenticator> for AuthenticatorDetails {
pub struct WireguardDetails {
// NOTE: the port field is deprecated in favour of tunnel_port
pub port: u16,
#[serde(default = "default_tunnel_port")]
pub tunnel_port: u16,
#[serde(default = "default_metadata_port")]
pub metadata_port: u16,
pub public_key: String,
}
fn default_tunnel_port() -> u16 {
WG_TUNNEL_PORT
}
fn default_metadata_port() -> u16 {
WG_METADATA_PORT
}
// works for current simple case.
impl From<Wireguard> for WireguardDetails {
fn from(value: Wireguard) -> Self {

Some files were not shown because too many files have changed in this diff Show More