Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 54f6c98c22 | |||
| 846484bbb4 | |||
| fb3f5501ba | |||
| e8a607f520 | |||
| f5f6df9eaf | |||
| c647ab5605 | |||
| 416c21a42e | |||
| fd5a95fa4d | |||
| c61df79182 | |||
| 08559a7660 | |||
| 6dce55a99b | |||
| bc0b89b31c | |||
| 67c32faa11 | |||
| aa0d15ee67 | |||
| 4f0974fcf1 | |||
| bd2174641e | |||
| 59b62fabc9 | |||
| e6f4bae895 | |||
| d71742af32 | |||
| 3b7c07e249 | |||
| 3b429dde69 | |||
| 3a29c296da | |||
| 8544c54f8f | |||
| 9f9639950b | |||
| 111a0b20b6 | |||
| 67b300d0b8 | |||
| 88c4e0ce6c | |||
| f6800aff0a | |||
| 0c7c927ca2 | |||
| a69c8b1660 | |||
| f3ea310a46 | |||
| 92f9ff035d | |||
| 5a817e1df1 | |||
| a07a24db00 | |||
| a0cb812eff | |||
| 923c1fa184 | |||
| 35ea7e4926 | |||
| d1cb9afaf0 | |||
| 79d4b4b2e3 | |||
| 8460b33946 | |||
| ae6539e07c | |||
| 18cebdfedc | |||
| c448ec823a | |||
| a266137278 | |||
| 6f4dfd1dab | |||
| 57719787db | |||
| 29a57bf172 | |||
| db813b6e3e | |||
| 1be5ba310a | |||
| 41ff3f7824 | |||
| c9d4d62446 | |||
| e839a0d80e | |||
| cd61f930bf | |||
| 0674f31227 | |||
| 3e4f563dce | |||
| edcf2b1204 | |||
| b07fb18113 | |||
| 017dea4afd | |||
| 5a9ce13beb | |||
| 514cf25c68 | |||
| 49ee0636e4 | |||
| bb971ce99c | |||
| 54de369c1e | |||
| 6d6ce284df | |||
| 56ad1c6c8e | |||
| 10b4a288c8 | |||
| bbbb9486ce | |||
| 16e86e1a07 | |||
| ca0c9898f0 | |||
| 8b73d4e615 | |||
| 6a9a767ab4 | |||
| e03a9fa16f | |||
| a0fbd57d5b | |||
| 9856198356 | |||
| 5c33846e57 | |||
| cfa7635ae1 | |||
| 5d45544c27 | |||
| aa6a79cb3e | |||
| b3a940770a | |||
| e980f76a81 | |||
| 9b38fef28f | |||
| 43910ca635 | |||
| d3ccd7575a | |||
| 422f889df7 | |||
| c9e96edc35 | |||
| 7768317046 | |||
| 0ebbb1a540 | |||
| 827c13b69e | |||
| 18ff09608c | |||
| 8cc996bc0d | |||
| 83a598907f | |||
| bb06a1b7a8 | |||
| e783a5fced | |||
| 8a24b45b5d | |||
| 10e4eba727 | |||
| 8ebf482f36 | |||
| 6940ca427e | |||
| 24f877fda5 |
@@ -54,7 +54,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --lib --manifest-path contracts/Cargo.toml
|
||||
args: --lib --manifest-path contracts/Cargo.toml --all-features
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
|
||||
@@ -3,6 +3,11 @@ name: Build and upload Node Status agent container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gateway_probe_git_ref:
|
||||
type: string
|
||||
default: nym-vpn-core-v1.4.0
|
||||
required: true
|
||||
description: Which gateway probe git ref to build the image with
|
||||
release_image:
|
||||
description: 'Tag image as a release'
|
||||
required: true
|
||||
@@ -38,6 +43,16 @@ jobs:
|
||||
VERSION=$(yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml)
|
||||
echo "result=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: cleanup-gateway-probe-ref
|
||||
id: cleanup_gateway_probe_ref
|
||||
run: |
|
||||
GATEWAY_PROBE_GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }}
|
||||
GIT_REF_SLUG="${GATEWAY_PROBE_GIT_REF//\//-}"
|
||||
echo "git_ref=${GIT_REF_SLUG}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set GIT_TAG variable
|
||||
run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Initialize RELEASE_TAG
|
||||
run: echo "RELEASE_TAG=" >> $GITHUB_ENV
|
||||
|
||||
@@ -46,12 +61,24 @@ jobs:
|
||||
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
|
||||
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: New env vars
|
||||
run: echo "RELEASE_TAG='$RELEASE_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
|
||||
run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
|
||||
|
||||
# - name: Remove existing tag if exists
|
||||
# run: |
|
||||
# if git rev-parse $${{ env.GIT_TAG }} >/dev/null 2>&1; then
|
||||
# git push --delete origin $${{ env.GIT_TAG }}
|
||||
# git tag -d $${{ env.GIT_TAG }}
|
||||
# fi
|
||||
|
||||
# - name: Create tag
|
||||
# run: |
|
||||
# git tag -a $${{ env.GIT_TAG }} -m "Version ${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}"
|
||||
# git push origin $${{ env.GIT_TAG }}
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
|
||||
docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
|
||||
@@ -4,46 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.19-kase] (2025-10-30)
|
||||
|
||||
- update ns agent workflow ([#6154])
|
||||
- Cherry pick - request #6143 from nymtech/bugfix/mix-tx-closed-v2 ([#6153])
|
||||
- bugfix: nym-credential-proxy query params parsing regression ([#6121])
|
||||
- bugfix: revert some dep updates introduced in #6043 ([#6120])
|
||||
- Skip ipv6 metadata endpoint request ([#6118])
|
||||
- update to no longer use 1mb files ([#6117])
|
||||
- chore: restore pending dkg contract state migration ([#6116])
|
||||
- Revert "Propagate cancel token to mixnet client" ([#6115])
|
||||
- Update dirs to 6.0 ([#6109])
|
||||
- Propagate cancel token to mixnet client ([#6105])
|
||||
- bugfix: retrieve and update ticketbook in the same query ([#6101])
|
||||
- bugfix: include network name in the default gateway probe config path ([#6100])
|
||||
- Bugfix/incompatibility fixes ([#6099])
|
||||
- [DOCs/operators] QUIC deployment script & docs ([#6098])
|
||||
- bugfix: testnet manager 02sql migration ([#6096])
|
||||
- feat: move gateway probe to monorepo (and update to rust edition 2024) ([#6094])
|
||||
- bugfix: use custom topology provider for list of init gateways ([#6092])
|
||||
- Max/fix wasm client + build commands ([#6043])
|
||||
|
||||
[#6154]: https://github.com/nymtech/nym/pull/6154
|
||||
[#6153]: https://github.com/nymtech/nym/pull/6153
|
||||
[#6121]: https://github.com/nymtech/nym/pull/6121
|
||||
[#6120]: https://github.com/nymtech/nym/pull/6120
|
||||
[#6118]: https://github.com/nymtech/nym/pull/6118
|
||||
[#6117]: https://github.com/nymtech/nym/pull/6117
|
||||
[#6116]: https://github.com/nymtech/nym/pull/6116
|
||||
[#6115]: https://github.com/nymtech/nym/pull/6115
|
||||
[#6109]: https://github.com/nymtech/nym/pull/6109
|
||||
[#6105]: https://github.com/nymtech/nym/pull/6105
|
||||
[#6101]: https://github.com/nymtech/nym/pull/6101
|
||||
[#6100]: https://github.com/nymtech/nym/pull/6100
|
||||
[#6099]: https://github.com/nymtech/nym/pull/6099
|
||||
[#6098]: https://github.com/nymtech/nym/pull/6098
|
||||
[#6096]: https://github.com/nymtech/nym/pull/6096
|
||||
[#6094]: https://github.com/nymtech/nym/pull/6094
|
||||
[#6092]: https://github.com/nymtech/nym/pull/6092
|
||||
[#6043]: https://github.com/nymtech/nym/pull/6043
|
||||
|
||||
## [2025.18-jarlsberg] (2025-10-14)
|
||||
|
||||
- ns-api: add descriptions to dVPN gateway responses ([#6102])
|
||||
|
||||
Generated
+29
-8
@@ -2262,7 +2262,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4824,7 +4824,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.68"
|
||||
version = "1.1.67"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -5050,7 +5050,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.65"
|
||||
version = "1.1.64"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -5133,7 +5133,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.65"
|
||||
version = "1.1.64"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -6354,7 +6354,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.66"
|
||||
version = "1.1.65"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -6404,7 +6404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.20.0"
|
||||
version = "1.19.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -6798,6 +6798,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"typed-builder",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -6930,7 +6931,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.65"
|
||||
version = "1.1.64"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -7669,7 +7670,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.30"
|
||||
version = "0.1.29"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -11391,6 +11392,26 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-builder"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d0dd654273fc253fde1df4172c31fb6615cf8b041d3a4008a028ef8b1119e66"
|
||||
dependencies = [
|
||||
"typed-builder-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-builder-macro"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "016c26257f448222014296978b2c8456e2cad4de308c35bdb1e383acd569ef5b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.106",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
|
||||
+1
-12
@@ -215,7 +215,6 @@ base64 = "0.22.1"
|
||||
base85rs = "0.1.3"
|
||||
bincode = "1.3.3"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
bit-vec = "0.7.0" # can we unify those?
|
||||
bitvec = "1.0.0"
|
||||
blake3 = "1.7.0"
|
||||
bloomfilter = "3.0.1"
|
||||
@@ -243,13 +242,11 @@ criterion = "0.5"
|
||||
csv = "1.3.1"
|
||||
ctr = "0.9.1"
|
||||
cupid = "0.6.1"
|
||||
curve25519-dalek = "4.1"
|
||||
dashmap = "5.5.3"
|
||||
# We want https://github.com/DefGuard/wireguard-rs/pull/64 , but there's no crates.io release being pushed out anymore
|
||||
defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.7" }
|
||||
digest = "0.10.7"
|
||||
dirs = "6.0"
|
||||
doc-comment = "0.3"
|
||||
dotenvy = "0.15.6"
|
||||
dyn-clone = "1.0.19"
|
||||
ecdsa = "0.16"
|
||||
@@ -265,11 +262,8 @@ futures = "0.3.31"
|
||||
futures-util = "0.3"
|
||||
generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
getset = "0.1.5"
|
||||
handlebars = "3.5.5"
|
||||
headers = "0.4.0"
|
||||
hex = "0.4.3"
|
||||
hex-literal = "0.3.3"
|
||||
hickory-resolver = "0.25"
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12.1"
|
||||
@@ -293,12 +287,10 @@ lazy_static = "1.5.0"
|
||||
ledger-transport = "0.10.0"
|
||||
ledger-transport-hid = "0.10.0"
|
||||
log = "0.4"
|
||||
maxminddb = "0.23.0"
|
||||
mime = "0.3.17"
|
||||
moka = { version = "0.12", features = ["future"] }
|
||||
nix = "0.27.1"
|
||||
notify = "5.1.0"
|
||||
okapi = "0.7.0"
|
||||
once_cell = "1.21.3"
|
||||
opentelemetry = "0.19.0"
|
||||
opentelemetry-jaeger = "0.18.0"
|
||||
@@ -307,7 +299,6 @@ pem = "0.8"
|
||||
petgraph = "0.6.5"
|
||||
pin-project = "1.1"
|
||||
pnet_packet = "0.35.0"
|
||||
pin-project-lite = "0.2.16"
|
||||
publicsuffix = "2.3.0"
|
||||
proc_pidinfo = "0.1.3"
|
||||
quote = "1"
|
||||
@@ -315,13 +306,10 @@ rand = "0.8.5"
|
||||
rand_chacha = "0.3"
|
||||
rand_core = "0.6.3"
|
||||
rand_distr = "0.4"
|
||||
rand_pcg = "0.3.1"
|
||||
rand_seeder = "0.2.3"
|
||||
rayon = "1.5.1"
|
||||
regex = "1.10.6"
|
||||
reqwest = { version = "0.12.15", default-features = false }
|
||||
rs_merkle = "1.5.0"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.22"
|
||||
semver = "1.0.26"
|
||||
serde = "1.0.219"
|
||||
@@ -368,6 +356,7 @@ tracing-indicatif = "0.3.9"
|
||||
tracing-test = "0.2.5"
|
||||
ts-rs = "10.1.0"
|
||||
tungstenite = { version = "0.20.1", default-features = false }
|
||||
typed-builder = "0.23.0"
|
||||
uniffi = "0.29.2"
|
||||
uniffi_build = "0.29.0"
|
||||
url = "2.5"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.65"
|
||||
version = "1.1.64"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.65"
|
||||
version = "1.1.64"
|
||||
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"
|
||||
|
||||
@@ -36,7 +36,7 @@ nym-bandwidth-controller = { path = "../bandwidth-controller" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
||||
nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-http-api-client = { path = "../http-api-client", features = ["network-defaults"] }
|
||||
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-statistics-common = { path = "../statistics" }
|
||||
|
||||
@@ -114,13 +114,12 @@ where
|
||||
})?;
|
||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::gateways_for_init(
|
||||
&mut rng,
|
||||
&core.client.nym_api_urls,
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -173,13 +173,12 @@ where
|
||||
})?;
|
||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::gateways_for_init(
|
||||
&mut rng,
|
||||
&core.client.nym_api_urls,
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -73,6 +73,10 @@ use url::Url;
|
||||
#[cfg(debug_assertions)]
|
||||
use wasm_utils::console_log;
|
||||
|
||||
/// Default number of retries for Nym API requests when using network details with domain fronting.
|
||||
/// This allows the client to try alternative URLs if the primary endpoint is unavailable.
|
||||
const DEFAULT_NYM_API_RETRIES: usize = 3;
|
||||
|
||||
#[cfg(all(
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "fs-surb-storage",
|
||||
@@ -212,13 +216,15 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
client_store: S,
|
||||
dkg_query_client: Option<C>,
|
||||
|
||||
// Optional API URLs for domain fronting support
|
||||
nym_api_urls: Option<Vec<nym_network_defaults::ApiUrl>>,
|
||||
|
||||
wait_for_gateway: bool,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||
shutdown: Option<ShutdownTracker>,
|
||||
event_tx: Option<EventSender>,
|
||||
user_agent: Option<UserAgent>,
|
||||
custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
|
||||
setup_method: GatewaySetup,
|
||||
|
||||
@@ -242,13 +248,13 @@ where
|
||||
config: base_config,
|
||||
client_store,
|
||||
dkg_query_client,
|
||||
nym_api_urls: None,
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
shutdown: None,
|
||||
event_tx: None,
|
||||
user_agent: None,
|
||||
custom_nym_api_client: None,
|
||||
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
@@ -256,12 +262,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_nym_api_client(mut self, client: nym_http_api_client::Client) -> Self {
|
||||
self.custom_nym_api_client = Some(client);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_derivation_material(
|
||||
mut self,
|
||||
@@ -271,6 +271,16 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Set Nym API URLs for domain fronting support.
|
||||
///
|
||||
/// When provided, the client will use these API URLs (which include front_hosts)
|
||||
/// to construct HTTP clients with domain fronting enabled.
|
||||
#[must_use]
|
||||
pub fn with_nym_api_urls(mut self, nym_api_urls: Vec<nym_network_defaults::ApiUrl>) -> Self {
|
||||
self.nym_api_urls = Some(nym_api_urls);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
||||
self.config.debug.forget_me = *forget_me;
|
||||
@@ -871,31 +881,67 @@ where
|
||||
}
|
||||
|
||||
fn construct_nym_api_client(
|
||||
nym_api_urls: Option<&Vec<nym_network_defaults::ApiUrl>>,
|
||||
config: &Config,
|
||||
user_agent: Option<UserAgent>,
|
||||
custom_client: Option<nym_http_api_client::Client>,
|
||||
) -> Result<nym_http_api_client::Client, ClientCoreError> {
|
||||
// If a custom client was provided (e.g., with domain fronting support), use it
|
||||
if let Some(client) = custom_client {
|
||||
tracing::info!("Using CUSTOM nym-api HTTP client (with domain fronting support)");
|
||||
return Ok(client);
|
||||
tracing::debug!(
|
||||
"construct_nym_api_client called with nym_api_urls: {}",
|
||||
nym_api_urls.is_some()
|
||||
);
|
||||
|
||||
// If API URLs are provided, use new_with_fronted_urls() which handles domain fronting
|
||||
if let Some(nym_api_urls) = nym_api_urls {
|
||||
if nym_api_urls.is_empty() {
|
||||
tracing::warn!("Provided nym_api_urls is empty, falling back to config endpoints");
|
||||
} else {
|
||||
tracing::info!(
|
||||
"Building nym-api client from provided URLs (with domain fronting support): {} URLs",
|
||||
nym_api_urls.len()
|
||||
);
|
||||
|
||||
let mut builder =
|
||||
nym_http_api_client::ClientBuilder::new_with_fronted_urls(nym_api_urls.clone())
|
||||
.map_err(ClientCoreError::from)?
|
||||
.with_retries(DEFAULT_NYM_API_RETRIES);
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
return builder.build().map_err(ClientCoreError::from);
|
||||
}
|
||||
}
|
||||
|
||||
tracing::warn!("No custom HTTP client provided - creating DEFAULT client from config");
|
||||
// Fallback to basic client for backwards compatibility
|
||||
tracing::debug!("Building basic nym-api HTTP client from config endpoints");
|
||||
|
||||
// Otherwise, create a basic client
|
||||
let mut nym_api_urls = config.get_nym_api_endpoints();
|
||||
if nym_api_urls.is_empty() {
|
||||
tracing::warn!("No API endpoints configured in config, this may cause issues");
|
||||
}
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
let mut builder = nym_http_api_client::Client::builder(nym_api_urls[0].clone())
|
||||
.map_err(ClientCoreError::from)?;
|
||||
// Convert config URLs to ApiUrl format for consistency
|
||||
let api_urls: Vec<nym_network_defaults::ApiUrl> = nym_api_urls
|
||||
.into_iter()
|
||||
.map(|url| nym_network_defaults::ApiUrl {
|
||||
url: url.to_string(),
|
||||
front_hosts: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
tracing::debug!("Using {} config API endpoints", api_urls.len());
|
||||
|
||||
let mut builder = nym_http_api_client::ClientBuilder::new_with_fronted_urls(api_urls)
|
||||
.map_err(ClientCoreError::from)?
|
||||
.with_retries(DEFAULT_NYM_API_RETRIES)
|
||||
.with_bincode();
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
builder = builder.with_bincode();
|
||||
|
||||
builder.build().map_err(ClientCoreError::from)
|
||||
}
|
||||
|
||||
@@ -980,9 +1026,9 @@ where
|
||||
.map(|client| BandwidthController::new(credential_store, client));
|
||||
|
||||
let nym_api_client = Self::construct_nym_api_client(
|
||||
self.nym_api_urls.as_ref(),
|
||||
&self.config,
|
||||
self.user_agent.clone(),
|
||||
self.custom_nym_api_client,
|
||||
)?;
|
||||
let key_rotation_config = Self::determine_key_rotation_state(&nym_api_client).await?;
|
||||
|
||||
@@ -1158,3 +1204,53 @@ pub struct BaseClient {
|
||||
pub forget_me: ForgetMe,
|
||||
pub remember_me: RememberMe,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_network_defaults::{ApiUrl, NymNetworkDetails};
|
||||
|
||||
#[test]
|
||||
fn test_network_details_with_multiple_urls() {
|
||||
// Verify that network details can be configured with multiple API URLs
|
||||
let mut network_details = NymNetworkDetails::new_empty();
|
||||
network_details.nym_api_urls = Some(vec![
|
||||
ApiUrl {
|
||||
url: "https://validator.nymtech.net/api/".to_string(),
|
||||
front_hosts: None,
|
||||
},
|
||||
ApiUrl {
|
||||
url: "https://nym-frontdoor.vercel.app/api/".to_string(),
|
||||
front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]),
|
||||
},
|
||||
]);
|
||||
|
||||
assert_eq!(network_details.nym_api_urls.as_ref().unwrap().len(), 2);
|
||||
assert!(network_details.nym_api_urls.as_ref().unwrap()[1]
|
||||
.front_hosts
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_network_details_with_front_hosts() {
|
||||
// Verify that ApiUrl can store domain fronting configuration
|
||||
let api_url = ApiUrl {
|
||||
url: "https://nym-frontdoor.vercel.app/api/".to_string(),
|
||||
front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]),
|
||||
};
|
||||
|
||||
assert_eq!(api_url.url, "https://nym-frontdoor.vercel.app/api/");
|
||||
assert_eq!(api_url.front_hosts.as_ref().unwrap().len(), 2);
|
||||
assert!(api_url
|
||||
.front_hosts
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&"vercel.app".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_nym_api_retries_constant() {
|
||||
// Verify the retry constant is set correctly
|
||||
assert_eq!(DEFAULT_NYM_API_RETRIES, 3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ 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 nym_validator_client::nym_nodes::NodeRole;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::cmp::min;
|
||||
@@ -136,31 +135,31 @@ impl NymApiTopologyProvider {
|
||||
} else {
|
||||
// if we're not using extended topology, we're only getting active set mixnodes and gateways
|
||||
|
||||
// Get ALL bonded nodes and filter for both mixnodes and gateways
|
||||
// This ensures consistent metadata between both calls
|
||||
let all_nodes_fut = self.validator_client.get_all_basic_nodes_with_metadata();
|
||||
let mixnodes_fut = self
|
||||
.validator_client
|
||||
.get_all_basic_active_mixing_assigned_nodes_with_metadata();
|
||||
|
||||
let (rewarded_set, all_nodes_res) = futures::try_join!(rewarded_set_fut, all_nodes_fut)
|
||||
.inspect_err(|err| {
|
||||
error!("failed to get network nodes: {err}");
|
||||
})
|
||||
.ok()?;
|
||||
// TODO: we really should be getting ACTIVE gateways only
|
||||
let gateways_fut = self
|
||||
.validator_client
|
||||
.get_all_basic_entry_assigned_nodes_with_metadata();
|
||||
|
||||
let metadata = all_nodes_res.metadata;
|
||||
let all_nodes = all_nodes_res.nodes;
|
||||
let (rewarded_set, mixnodes_res, gateways_res) =
|
||||
futures::try_join!(rewarded_set_fut, mixnodes_fut, gateways_fut)
|
||||
.inspect_err(|err| {
|
||||
error!("failed to get network nodes: {err}");
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
// Filter for active mixing nodes (role=Mixnode)
|
||||
let mixnodes: Vec<_> = all_nodes
|
||||
.iter()
|
||||
.filter(|n| n.supported_roles.mixnode && matches!(n.role, NodeRole::Mixnode { .. }))
|
||||
.cloned()
|
||||
.collect();
|
||||
let metadata = mixnodes_res.metadata;
|
||||
let mixnodes = mixnodes_res.nodes;
|
||||
|
||||
// Filter for entry-capable nodes (supported_roles.entry == true)
|
||||
let gateways: Vec<_> = all_nodes
|
||||
.into_iter()
|
||||
.filter(|n| n.supported_roles.entry)
|
||||
.collect();
|
||||
if !gateways_res.metadata.consistency_check(&metadata) {
|
||||
warn!("inconsistent nodes metadata between mixnodes and gateways calls! {metadata:?} and {:?}", gateways_res.metadata);
|
||||
return None;
|
||||
}
|
||||
|
||||
let gateways = gateways_res.nodes;
|
||||
|
||||
debug!(
|
||||
"there are {} mixnodes and {} gateways in total (before performance filtering)",
|
||||
@@ -222,27 +221,3 @@ impl TopologyProvider for NymApiTopologyProvider {
|
||||
Some(topology)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_filtering_logic() {
|
||||
// This test verifies the filtering logic used in get_current_compatible_topology
|
||||
// We test that the role matching pattern works correctly
|
||||
use nym_validator_client::nym_nodes::NodeRole;
|
||||
|
||||
// Test that the pattern matching for mixnodes works
|
||||
let mixnode_role = NodeRole::Mixnode { layer: 1 };
|
||||
assert!(
|
||||
matches!(mixnode_role, NodeRole::Mixnode { .. }),
|
||||
"Should match mixnode role pattern"
|
||||
);
|
||||
|
||||
// Test that other roles don't match
|
||||
let entry_role = NodeRole::EntryGateway;
|
||||
assert!(
|
||||
!matches!(entry_role, NodeRole::Mixnode { .. }),
|
||||
"Entry gateway should not match mixnode pattern"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ type WsConn = JSWebsocket;
|
||||
|
||||
const CONCURRENT_GATEWAYS_MEASURED: usize = 20;
|
||||
const MEASUREMENTS: usize = 3;
|
||||
const DEFAULT_NYM_API_RETRIES: usize = 3;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
@@ -89,22 +90,16 @@ async fn get_all_basic_entry_nodes_with_metadata(
|
||||
client: &nym_http_api_client::Client,
|
||||
use_bincode: bool,
|
||||
) -> Result<SkimmedNodesWithMetadata, ClientCoreError> {
|
||||
// Get ALL nodes (not just entry-assigned) because in some environments (like sandbox),
|
||||
// nodes may be capable of entry gateway role but not currently assigned to it
|
||||
// Get first page to obtain metadata
|
||||
let mut page = 0;
|
||||
let res = client
|
||||
.get_basic_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.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() {
|
||||
// Filter for entry-capable nodes (nodes with supported_roles.entry == true)
|
||||
let entry_nodes: Vec<_> = nodes
|
||||
.into_iter()
|
||||
.filter(|n| n.supported_roles.entry)
|
||||
.collect();
|
||||
return Ok(SkimmedNodesWithMetadata::new(entry_nodes, metadata));
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
@@ -112,7 +107,7 @@ async fn get_all_basic_entry_nodes_with_metadata(
|
||||
// Collect remaining pages
|
||||
loop {
|
||||
let mut res = client
|
||||
.get_basic_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
@@ -129,13 +124,7 @@ async fn get_all_basic_entry_nodes_with_metadata(
|
||||
}
|
||||
}
|
||||
|
||||
// Filter for entry-capable nodes (nodes with supported_roles.entry == true)
|
||||
let entry_nodes: Vec<_> = nodes
|
||||
.into_iter()
|
||||
.filter(|n| n.supported_roles.entry)
|
||||
.collect();
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(entry_nodes, metadata))
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
@@ -144,35 +133,27 @@ impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn gateways_for_init<R: Rng>(
|
||||
_rng: &mut R,
|
||||
pub async fn gateways_for_init(
|
||||
nym_apis: &[Url],
|
||||
user_agent: Option<UserAgent>,
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
retry_count: Option<usize>,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
// Build client with ALL URLs for fallback support
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = nym_apis
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
if nym_api_urls.is_empty() {
|
||||
return Err(ClientCoreError::ListOfNymApisIsEmpty);
|
||||
}
|
||||
|
||||
let mut builder = if nym_api_urls.len() == 1 {
|
||||
nym_http_api_client::Client::builder(nym_api_urls[0].clone()).map_err(|e| {
|
||||
ClientCoreError::ValidatorClientError(nym_validator_client::ValidatorClientError::from(
|
||||
e,
|
||||
))
|
||||
})?
|
||||
} else {
|
||||
nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone()).with_retries(3)
|
||||
// Enable URL rotation on failure
|
||||
};
|
||||
|
||||
builder = builder.with_bincode();
|
||||
let retry_count = retry_count.unwrap_or(DEFAULT_NYM_API_RETRIES);
|
||||
let mut builder = nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone())?
|
||||
.with_retries(retry_count)
|
||||
.with_bincode();
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
@@ -194,56 +175,18 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
|
||||
// filter out gateways below minimum performance and ones that could operate as a mixnode
|
||||
// (we don't want instability)
|
||||
let total_fetched = gateways.len();
|
||||
|
||||
let after_role_filter_count = gateways
|
||||
.iter()
|
||||
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
|
||||
.count();
|
||||
tracing::debug!(
|
||||
"After role filter: {} (removed {} mixnode-capable)",
|
||||
after_role_filter_count,
|
||||
total_fetched - after_role_filter_count
|
||||
);
|
||||
|
||||
let after_performance_filter_count = gateways
|
||||
.iter()
|
||||
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
|
||||
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
||||
.count();
|
||||
tracing::debug!(
|
||||
"After performance filter (>= {}%): {} (removed {} low-performance)",
|
||||
minimum_performance,
|
||||
after_performance_filter_count,
|
||||
after_role_filter_count - after_performance_filter_count
|
||||
);
|
||||
|
||||
let valid_gateways: Vec<RoutingNode> = gateways
|
||||
.iter()
|
||||
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
|
||||
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
||||
.filter_map(|gateway| gateway.try_into().ok())
|
||||
.collect();
|
||||
tracing::debug!(
|
||||
"After conversion to RoutingNode: {} (removed {} invalid)",
|
||||
valid_gateways.len(),
|
||||
after_performance_filter_count - valid_gateways.len()
|
||||
);
|
||||
tracing::trace!("Valid gateways: {valid_gateways:#?}");
|
||||
|
||||
tracing::info!(
|
||||
"and {} after validity and performance filtering",
|
||||
"Found {} valid gateways after filtering",
|
||||
valid_gateways.len()
|
||||
);
|
||||
|
||||
for gw in &valid_gateways {
|
||||
tracing::debug!(
|
||||
"Available gateway: {} (node_id: {})",
|
||||
gw.identity_key.to_base58_string(),
|
||||
gw.node_id
|
||||
);
|
||||
}
|
||||
|
||||
Ok(valid_gateways)
|
||||
}
|
||||
|
||||
@@ -403,14 +346,6 @@ pub(super) fn get_specified_gateway(
|
||||
must_use_tls: bool,
|
||||
) -> Result<RoutingNode, ClientCoreError> {
|
||||
tracing::debug!("Requesting specified gateway: {gateway_identity}");
|
||||
tracing::debug!("Searching in {} available gateways", gateways.len());
|
||||
for gw in gateways {
|
||||
tracing::debug!(
|
||||
"Gateway in list: {} (node_id: {})",
|
||||
gw.identity_key.to_base58_string(),
|
||||
gw.node_id
|
||||
);
|
||||
}
|
||||
|
||||
let user_gateway = ed25519::PublicKey::from_base58_string(gateway_identity)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
@@ -419,7 +354,10 @@ pub(super) fn get_specified_gateway(
|
||||
.iter()
|
||||
.find(|gateway| gateway.identity_key == user_gateway)
|
||||
.ok_or_else(|| {
|
||||
tracing::error!("Gateway {gateway_identity} NOT FOUND in available gateways list!");
|
||||
tracing::debug!(
|
||||
"Gateway {gateway_identity} not found in {} available gateways",
|
||||
gateways.len()
|
||||
);
|
||||
ClientCoreError::NoGatewayWithId(gateway_identity.to_string())
|
||||
})?;
|
||||
|
||||
@@ -491,11 +429,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_single_url_builds_without_retries() {
|
||||
let urls = vec![Url::parse("https://api.nym.com").unwrap()];
|
||||
let urls = [Url::parse("https://api.nym.com").unwrap()];
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(nym_api_urls.len(), 1, "Should have exactly one URL");
|
||||
@@ -511,7 +449,7 @@ mod tests {
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(nym_api_urls.len(), 3, "Should have all three URLs");
|
||||
@@ -527,118 +465,9 @@ mod tests {
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.filter_map(|url| nym_http_api_client::Url::parse(url.as_str()).ok())
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
assert!(nym_api_urls.is_empty(), "Empty list should remain empty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gateway_filtering_logic() {
|
||||
// NOTE: This test validates the filtering logic in isolation.
|
||||
// It does NOT test the actual implementation in get_all_basic_entry_nodes_with_metadata
|
||||
// or gateways_for_init (which would require mocking the HTTP client).
|
||||
// The real proof is building and running the daemon.
|
||||
//
|
||||
// Test the core filtering logic used in gateways_for_init:
|
||||
// 1. Filter by supported_roles.entry (not by epoch role assignment)
|
||||
// 2. Filter by performance
|
||||
//
|
||||
// This test verifies the fix where nodes have role=Inactive
|
||||
// but supported_roles.entry=true
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestNode {
|
||||
id: u32,
|
||||
entry_capable: bool, // supported_roles.entry
|
||||
mixnode_capable: bool, // supported_roles.mixnode
|
||||
performance: u8, // 0-100
|
||||
}
|
||||
|
||||
let nodes = vec![
|
||||
// Node 53: entry-capable, good performance
|
||||
TestNode {
|
||||
id: 53,
|
||||
entry_capable: true,
|
||||
mixnode_capable: false,
|
||||
performance: 100,
|
||||
},
|
||||
// Node 97: entry-capable (but role=Inactive in sandbox), good performance
|
||||
TestNode {
|
||||
id: 97,
|
||||
entry_capable: true,
|
||||
mixnode_capable: false,
|
||||
performance: 100,
|
||||
},
|
||||
// Node 75: NOT entry-capable (mixnode only)
|
||||
TestNode {
|
||||
id: 75,
|
||||
entry_capable: false,
|
||||
mixnode_capable: true,
|
||||
performance: 100,
|
||||
},
|
||||
// Node 99: entry-capable but low performance
|
||||
TestNode {
|
||||
id: 99,
|
||||
entry_capable: true,
|
||||
mixnode_capable: false,
|
||||
performance: 0,
|
||||
},
|
||||
];
|
||||
|
||||
let minimum_performance = 50;
|
||||
let ignore_epoch_roles = true;
|
||||
|
||||
// Step 1: Filter by supported_roles.entry (this is what the fix enables)
|
||||
let entry_capable: Vec<_> = nodes.iter().filter(|n| n.entry_capable).collect();
|
||||
|
||||
assert_eq!(
|
||||
entry_capable.len(),
|
||||
3,
|
||||
"Should have 3 entry-capable nodes (53, 97, 99) - this includes Inactive nodes!"
|
||||
);
|
||||
|
||||
// Step 2: Filter by role (exclude mixnode-capable if not ignoring epoch roles)
|
||||
let after_role_filter: Vec<_> = entry_capable
|
||||
.iter()
|
||||
.filter(|g| ignore_epoch_roles || !g.mixnode_capable)
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
after_role_filter.len(),
|
||||
3,
|
||||
"All entry-capable nodes pass role filter with ignore_epoch_roles=true"
|
||||
);
|
||||
|
||||
// Step 3: Filter by performance
|
||||
let after_performance_filter: Vec<_> = after_role_filter
|
||||
.iter()
|
||||
.filter(|g| g.performance >= minimum_performance)
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
after_performance_filter.len(),
|
||||
2,
|
||||
"Should have 2 nodes after performance filter (53 and 97, excluding 99 with 0%)"
|
||||
);
|
||||
|
||||
// Verify the correct nodes made it through
|
||||
let node_ids: Vec<u32> = after_performance_filter.iter().map(|n| n.id).collect();
|
||||
assert!(
|
||||
node_ids.contains(&53),
|
||||
"Node 53 (actively assigned entry gateway) should be included"
|
||||
);
|
||||
assert!(
|
||||
node_ids.contains(&97),
|
||||
"Node 97 (Inactive but entry-capable) should be included - THIS IS THE FIX!"
|
||||
);
|
||||
assert!(
|
||||
!node_ids.contains(&75),
|
||||
"Node 75 (mixnode-only, not entry-capable) should be excluded"
|
||||
);
|
||||
assert!(
|
||||
!node_ids.contains(&99),
|
||||
"Node 99 (low performance) should be excluded"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +296,9 @@ impl std::error::Error for ReqwestErrorWrapper {}
|
||||
#[derive(Debug, Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum HttpClientError {
|
||||
#[error("did not provide any valid client URLs")]
|
||||
NoUrlsProvided,
|
||||
|
||||
#[error("failed to construct inner reqwest client: {source}")]
|
||||
ReqwestBuildError {
|
||||
#[source]
|
||||
@@ -582,24 +585,29 @@ impl ClientBuilder {
|
||||
Self::new(alt)
|
||||
} else {
|
||||
let url = url.to_url()?;
|
||||
Ok(Self::new_with_urls(vec![url]))
|
||||
Self::new_with_urls(vec![url])
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a client builder from network details with sensible defaults
|
||||
#[cfg(feature = "network-defaults")]
|
||||
// deprecating function since it's not clear from its signature whether the client
|
||||
// would be constructed using `nym_api_urls` or `nym_vpn_api_urls`
|
||||
#[deprecated(note = "use explicit Self::new_with_fronted_urls instead")]
|
||||
pub fn from_network(
|
||||
network: &nym_network_defaults::NymNetworkDetails,
|
||||
) -> Result<Self, HttpClientError> {
|
||||
let urls = network
|
||||
.nym_api_urls
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
HttpClientError::GenericRequestFailure(
|
||||
"No API URLs configured in network details".to_string(),
|
||||
)
|
||||
})?
|
||||
.iter()
|
||||
let urls = network.nym_api_urls.as_ref().cloned().unwrap_or_default();
|
||||
Self::new_with_fronted_urls(urls.clone())
|
||||
}
|
||||
|
||||
/// Create a client builder using the provided set of domain-fronted URLs
|
||||
#[cfg(feature = "network-defaults")]
|
||||
pub fn new_with_fronted_urls(
|
||||
urls: Vec<nym_network_defaults::ApiUrl>,
|
||||
) -> Result<Self, HttpClientError> {
|
||||
let urls = urls
|
||||
.into_iter()
|
||||
.map(|api_url| {
|
||||
// Convert ApiUrl to our Url type with fronting support
|
||||
let mut url = Url::parse(&api_url.url)?;
|
||||
@@ -611,15 +619,19 @@ impl ClientBuilder {
|
||||
.iter()
|
||||
.map(|host| format!("https://{}", host))
|
||||
.collect();
|
||||
url = Url::new(api_url.url.clone(), Some(fronts))
|
||||
.map_err(|e| HttpClientError::GenericRequestFailure(e.to_string()))?;
|
||||
url = Url::new(api_url.url.clone(), Some(fronts)).map_err(|source| {
|
||||
HttpClientError::MalformedUrl {
|
||||
raw: api_url.url.clone(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(url)
|
||||
})
|
||||
.collect::<Result<Vec<_>, HttpClientError>>()?;
|
||||
|
||||
let mut builder = Self::new_with_urls(urls);
|
||||
let mut builder = Self::new_with_urls(urls)?;
|
||||
|
||||
// Enable domain fronting by default (on retry)
|
||||
#[cfg(feature = "tunneling")]
|
||||
@@ -631,7 +643,11 @@ impl ClientBuilder {
|
||||
}
|
||||
|
||||
/// Constructs a new http `ClientBuilder` from a valid url.
|
||||
pub fn new_with_urls(urls: Vec<Url>) -> Self {
|
||||
pub fn new_with_urls(urls: Vec<Url>) -> Result<Self, HttpClientError> {
|
||||
if urls.is_empty() {
|
||||
return Err(HttpClientError::NoUrlsProvided);
|
||||
}
|
||||
|
||||
let urls = Self::check_urls(urls);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -640,7 +656,7 @@ impl ClientBuilder {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client_builder = default_builder();
|
||||
|
||||
ClientBuilder {
|
||||
Ok(ClientBuilder {
|
||||
urls,
|
||||
timeout: None,
|
||||
custom_user_agent: false,
|
||||
@@ -651,7 +667,7 @@ impl ClientBuilder {
|
||||
|
||||
retry_limit: 0,
|
||||
serialization: SerializationFormat::Json,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Add an additional URL to the set usable by this constructed `Client`
|
||||
@@ -948,13 +964,13 @@ impl Client {
|
||||
|
||||
return (url.as_str(), url.front_str());
|
||||
} else {
|
||||
warn!(
|
||||
"Domain fronting is enabled, but no host_url is defined! Domain fronting WILL NOT WORK"
|
||||
tracing::debug!(
|
||||
"Domain fronting is enabled, but no host_url is defined for current URL"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
"Domain fronting is enabled, but no front_url is defined! Domain fronting WILL NOT WORK"
|
||||
tracing::debug!(
|
||||
"Domain fronting is enabled, but current URL has no front_hosts configured"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ inventory::collect!(ConfigRecord);
|
||||
/// Returns the default builder with all registered configurations applied.
|
||||
pub fn default_builder() -> ReqwestClientBuilder {
|
||||
let mut b = ReqwestClientBuilder::new();
|
||||
|
||||
#[cfg(feature = "debug-inventory")]
|
||||
let mut test_client = ReqwestClientBuilder::new();
|
||||
|
||||
let mut records: Vec<&'static ConfigRecord> =
|
||||
inventory::iter::<ConfigRecord>.into_iter().collect();
|
||||
records.sort_by_key(|r| r.priority); // lower runs first
|
||||
@@ -35,6 +39,10 @@ pub fn default_builder() -> ReqwestClientBuilder {
|
||||
|
||||
for r in records {
|
||||
b = (r.apply)(b);
|
||||
#[cfg(feature = "debug-inventory")]
|
||||
{
|
||||
test_client = (r.apply)(test_client);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug-inventory")]
|
||||
@@ -47,7 +55,7 @@ pub fn default_builder() -> ReqwestClientBuilder {
|
||||
eprintln!("[HTTP-INVENTORY] Building test client to verify configuration...");
|
||||
|
||||
// Try to build a client to see if it works
|
||||
match b.try_clone().unwrap().build() {
|
||||
match test_client.build() {
|
||||
Ok(client) => {
|
||||
eprintln!("[HTTP-INVENTORY] ✓ Client built successfully");
|
||||
eprintln!("[HTTP-INVENTORY] Client debug info: {:#?}", client);
|
||||
|
||||
@@ -2,77 +2,77 @@ use super::*;
|
||||
|
||||
#[test]
|
||||
fn sanitizing_urls() {
|
||||
let base_url: Url = "http://foomp.com".parse().unwrap();
|
||||
let base_url: Url = "http://api.test".parse().unwrap();
|
||||
|
||||
// works with a full string
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
"http://api.test/foo/bar",
|
||||
sanitize_url(&base_url, "/foo//bar/", NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// (and leading slash doesn't matter)
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
"http://api.test/foo/bar",
|
||||
sanitize_url(&base_url, "foo//bar/", NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with 1 segment
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
"http://api.test/foo",
|
||||
sanitize_url(&base_url, &["foo"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with 2 segments
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
"http://api.test/foo/bar",
|
||||
sanitize_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with leading slash
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
"http://api.test/foo",
|
||||
sanitize_url(&base_url, &["/foo"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
"http://api.test/foo/bar",
|
||||
sanitize_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
"http://api.test/foo/bar",
|
||||
sanitize_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with trailing slash
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
"http://api.test/foo",
|
||||
sanitize_url(&base_url, &["foo/"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
"http://api.test/foo/bar",
|
||||
sanitize_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
"http://api.test/foo/bar",
|
||||
sanitize_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with both leading and trailing slash
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo",
|
||||
"http://api.test/foo",
|
||||
sanitize_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar",
|
||||
"http://api.test/foo/bar",
|
||||
sanitize_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// adds params
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar?foomp=baz",
|
||||
"http://api.test/foo/bar?foomp=baz",
|
||||
sanitize_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://foomp.com/foo/bar?arg1=val1&arg2=val2",
|
||||
"http://api.test/foo/bar?arg1=val1&arg2=val2",
|
||||
sanitize_url(
|
||||
&base_url,
|
||||
&["/foo/", "/bar/"],
|
||||
@@ -91,83 +91,87 @@ fn sanitizing_urls() {
|
||||
#[tokio::test]
|
||||
async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client = ClientBuilder::new_with_urls(vec![
|
||||
"http://broken.nym.badurl".parse()?,
|
||||
"http://example.com/".parse()?,
|
||||
])
|
||||
"http://broken.nym.test".parse()?, // This will fail
|
||||
"https://httpbin.org/status/200".parse()?, // This will succeed
|
||||
])?
|
||||
.with_retries(3)
|
||||
.build()?;
|
||||
|
||||
let req = client.create_get_request(&["/"], NO_PARAMS).unwrap();
|
||||
let req = client.create_get_request(&[], NO_PARAMS).unwrap();
|
||||
let resp = client.send(req).await?;
|
||||
|
||||
assert_eq!(resp.status(), 200);
|
||||
// The main test is that we successfully retried and switched to the working URL
|
||||
// We accept any response from the working endpoint since external services can be unreliable
|
||||
assert_eq!(
|
||||
client.current_url().as_str(),
|
||||
"https://httpbin.org/status/200"
|
||||
);
|
||||
|
||||
// check that the url was updated
|
||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
||||
println!("Response status: {}", resp.status());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_updating() {
|
||||
let url = Url::new("http://example.com", None).unwrap();
|
||||
let url = Url::new("http://nym-api1.test", None).unwrap();
|
||||
let mut client = ClientBuilder::new(url).unwrap().build().unwrap();
|
||||
|
||||
// check that the url is set correctly
|
||||
let current_url = client.current_url();
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.as_str(), "http://nym-api1.test/");
|
||||
assert_eq!(current_url.front_str(), None);
|
||||
|
||||
// update the url
|
||||
client.update_host();
|
||||
|
||||
// check that the url is still the same since there is one URL
|
||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
|
||||
// =======================================
|
||||
// we rotate through urls when available
|
||||
|
||||
let new_urls = vec![
|
||||
Url::new("http://example.com", None).unwrap(),
|
||||
Url::new("http://example.org", None).unwrap(),
|
||||
Url::new("http://nym-api1.test", None).unwrap(),
|
||||
Url::new("http://nym-api2.test", None).unwrap(),
|
||||
];
|
||||
client.change_base_urls(new_urls);
|
||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
|
||||
client.update_host();
|
||||
|
||||
// check that the url got updated now that there are multiple URLs
|
||||
assert_eq!(client.current_url().as_str(), "http://example.org/");
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api2.test/");
|
||||
assert_eq!(client.current_url().front_str(), None);
|
||||
|
||||
client.update_host();
|
||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
|
||||
// =======================================
|
||||
// we rotate through urls when available if fronting is disabled
|
||||
|
||||
let new_urls = vec![
|
||||
Url::new(
|
||||
"http://example.com",
|
||||
Some(vec!["http://front1.com", "http://front2.com"]),
|
||||
"http://nym-api1.test",
|
||||
Some(vec!["http://cdn1.test", "http://cdn2.test"]),
|
||||
)
|
||||
.unwrap(),
|
||||
Url::new("http://example.org", None).unwrap(),
|
||||
Url::new("http://nym-api2.test", None).unwrap(),
|
||||
];
|
||||
client.change_base_urls(new_urls);
|
||||
|
||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
|
||||
client.update_host();
|
||||
|
||||
// check that the url got updated now that there are multiple URLs
|
||||
assert_eq!(client.current_url().as_str(), "http://example.org/");
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api2.test/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "tunneling")]
|
||||
fn fronted_host_updating() {
|
||||
let url = Url::new("http://example.com", Some(vec!["http://front1.com"])).unwrap();
|
||||
let url = Url::new("http://nym-api.test", Some(vec!["http://cdn1.test"])).unwrap();
|
||||
let mut client = ClientBuilder::new(url)
|
||||
.unwrap()
|
||||
.with_fronting(crate::fronted::FrontPolicy::Always)
|
||||
@@ -176,46 +180,103 @@ fn fronted_host_updating() {
|
||||
|
||||
// check that the url is set correctly
|
||||
let current_url = client.current_url();
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
||||
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||
|
||||
// update the url
|
||||
client.update_host();
|
||||
|
||||
// check that the url is still the same since there is one URL and one front
|
||||
let current_url = client.current_url();
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
||||
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||
|
||||
// =======================================
|
||||
// we rotate through front urls when available if fronting is enabled
|
||||
|
||||
let new_urls = vec![
|
||||
Url::new(
|
||||
"http://example.com",
|
||||
Some(vec!["http://front1.com", "http://front2.com"]),
|
||||
"http://nym-api.test",
|
||||
Some(vec!["http://cdn1.test", "http://cdn2.test"]),
|
||||
)
|
||||
.unwrap(),
|
||||
Url::new("http://example.org", None).unwrap(),
|
||||
Url::new("http://nym-api2.test", None).unwrap(),
|
||||
];
|
||||
client.change_base_urls(new_urls);
|
||||
|
||||
let current_url = client.current_url();
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
||||
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||
|
||||
// update the url - this should keep the same host but change the front
|
||||
client.update_host();
|
||||
|
||||
let current_url = client.current_url();
|
||||
// check that the url is still the same since there is one URL
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.front_str(), Some("front2.com"));
|
||||
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||
assert_eq!(current_url.front_str(), Some("cdn2.test"));
|
||||
|
||||
// update the url - this should wrap around to the first front as the second url is not fronted
|
||||
client.update_host();
|
||||
|
||||
let current_url = client.current_url();
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
||||
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "network-defaults")]
|
||||
fn from_network_configures_multiple_urls_and_retries() {
|
||||
use nym_network_defaults::{ApiUrl, NymNetworkDetails};
|
||||
|
||||
// Create network details with multiple URLs and fronting
|
||||
let mut network_details = NymNetworkDetails::new_empty();
|
||||
network_details.nym_api_urls = Some(vec![
|
||||
ApiUrl {
|
||||
url: "https://validator.nymtech.net/api/".to_string(),
|
||||
front_hosts: None,
|
||||
},
|
||||
ApiUrl {
|
||||
url: "https://nym-frontdoor.vercel.app/api/".to_string(),
|
||||
front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]),
|
||||
},
|
||||
ApiUrl {
|
||||
url: "https://nym-frontdoor.global.ssl.fastly.net/api/".to_string(),
|
||||
front_hosts: Some(vec!["yelp.global.ssl.fastly.net".to_string()]),
|
||||
},
|
||||
]);
|
||||
|
||||
// Build client from network details
|
||||
let client = ClientBuilder::new_with_fronted_urls(
|
||||
network_details.nym_api_urls.clone().unwrap_or_default(),
|
||||
)
|
||||
.expect("Failed to create client from network")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
// Verify all URLs were configured
|
||||
assert_eq!(
|
||||
client.base_urls().len(),
|
||||
3,
|
||||
"Expected 3 URLs to be configured from network details"
|
||||
);
|
||||
|
||||
// Verify the URLs have fronting configured where appropriate
|
||||
assert_eq!(
|
||||
client.base_urls()[0].as_str(),
|
||||
"https://validator.nymtech.net/api/"
|
||||
);
|
||||
assert!(client.base_urls()[0].front_str().is_none());
|
||||
|
||||
assert_eq!(
|
||||
client.base_urls()[1].as_str(),
|
||||
"https://nym-frontdoor.vercel.app/api/"
|
||||
);
|
||||
assert!(client.base_urls()[1].front_str().is_some());
|
||||
|
||||
assert_eq!(
|
||||
client.base_urls()[2].as_str(),
|
||||
"https://nym-frontdoor.global.ssl.fastly.net/api/"
|
||||
);
|
||||
assert!(client.base_urls()[2].front_str().is_some());
|
||||
}
|
||||
|
||||
@@ -183,6 +183,11 @@ impl Url {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the underlying URL
|
||||
pub fn inner_url(&self) -> &url::Url {
|
||||
&self.url
|
||||
}
|
||||
|
||||
/// Returns true if the URL has a front domain set
|
||||
pub fn has_front(&self) -> bool {
|
||||
if let Some(fronts) = &self.fronts {
|
||||
@@ -201,6 +206,11 @@ impl Url {
|
||||
.and_then(|url| url.host_str())
|
||||
}
|
||||
|
||||
/// Returns the fronts
|
||||
pub fn fronts(&self) -> Option<&[url::Url]> {
|
||||
self.fronts.as_deref()
|
||||
}
|
||||
|
||||
/// Return the string representation of the host (domain or IP address) for this URL, if any.
|
||||
pub fn host_str(&self) -> Option<&str> {
|
||||
self.url.host_str()
|
||||
|
||||
@@ -124,6 +124,8 @@ impl NymNetworkDetails {
|
||||
}
|
||||
}
|
||||
|
||||
let nym_api = var(var_names::NYM_API).expect("nym api not set");
|
||||
|
||||
NymNetworkDetails::new_empty()
|
||||
.with_network_name(var(var_names::NETWORK_NAME).expect("network name not set"))
|
||||
.with_bech32_account_prefix(
|
||||
@@ -149,7 +151,7 @@ impl NymNetworkDetails {
|
||||
})
|
||||
.with_additional_validator_endpoint(ValidatorDetails::new(
|
||||
var(var_names::NYXD).expect("nyxd validator not set"),
|
||||
Some(var(var_names::NYM_API).expect("nym api not set")),
|
||||
Some(nym_api.clone()),
|
||||
get_optional_env(var_names::NYXD_WEBSOCKET),
|
||||
))
|
||||
.with_mixnet_contract(get_optional_env(var_names::MIXNET_CONTRACT_ADDRESS))
|
||||
@@ -159,6 +161,10 @@ impl NymNetworkDetails {
|
||||
.with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
|
||||
.with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
|
||||
.with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
|
||||
.with_nym_api_urls(Some(vec![ApiUrl {
|
||||
url: nym_api,
|
||||
front_hosts: None,
|
||||
}]))
|
||||
}
|
||||
|
||||
pub fn new_mainnet() -> Self {
|
||||
@@ -348,6 +354,12 @@ impl NymNetworkDetails {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_nym_api_urls(mut self, urls: Option<Vec<ApiUrl>>) -> Self {
|
||||
self.nym_api_urls = urls;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn nym_vpn_api_url(&self) -> Option<Url> {
|
||||
self.nym_vpn_api_url.as_ref().map(|url| {
|
||||
url.parse()
|
||||
|
||||
@@ -160,13 +160,12 @@ pub async fn setup_gateway_from_api(
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<InitialisationResult, WasmCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
let gateways = gateways_for_init(
|
||||
&mut rng,
|
||||
nym_apis,
|
||||
None,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
|
||||
@@ -178,13 +177,12 @@ pub async fn current_gateways_wasm(
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
gateways_for_init(
|
||||
&mut rng,
|
||||
nym_apis,
|
||||
user_agent,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::epoch_state::storage::{load_current_epoch, save_epoch};
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::storage::STATE;
|
||||
use crate::verification_key_shares::storage::vk_shares;
|
||||
use crate::Dealer;
|
||||
use cosmwasm_std::{Deps, DepsMut, Env, Event, MessageInfo, Response};
|
||||
use nym_coconut_dkg_common::dealer::{DealerRegistrationDetails, OwnershipTransfer};
|
||||
@@ -109,7 +110,7 @@ pub fn try_transfer_ownership(
|
||||
DEALERS_INDICES.save(deps.storage, &transfer_to, ¤t_index)?;
|
||||
DEALERS_INDICES.remove(deps.storage, &info.sender);
|
||||
|
||||
// update registration detail for every epoch the current dealer has participated in the protocol
|
||||
// update registration detail and share information for every epoch the current dealer has participated in the protocol
|
||||
// ideally, we'd have only updated the current epoch, but the way the contract is constructed
|
||||
// forbids that otherwise we'd have introduced inconsistency
|
||||
for epoch_id in 0..=epoch.epoch_id {
|
||||
@@ -117,6 +118,11 @@ pub fn try_transfer_ownership(
|
||||
EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &info.sender));
|
||||
EPOCH_DEALERS_MAP.save(deps.storage, (epoch_id, &transfer_to), &details)?;
|
||||
}
|
||||
if let Some(mut vk_share) = vk_shares().may_load(deps.storage, (&info.sender, epoch_id))? {
|
||||
vk_shares().remove(deps.storage, (&info.sender, epoch_id))?;
|
||||
vk_share.owner = transfer_to.clone();
|
||||
vk_shares().save(deps.storage, (&transfer_to, epoch_id), &vk_share)?;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(transaction_info) = env.transaction else {
|
||||
@@ -161,6 +167,14 @@ pub fn try_update_announce_address(
|
||||
details.announce_address = new_address.clone();
|
||||
EPOCH_DEALERS_MAP.save(deps.storage, (epoch.epoch_id, &info.sender), &details)?;
|
||||
|
||||
let mut contract_share = vk_shares().load(deps.storage, (&info.sender, epoch.epoch_id))?;
|
||||
contract_share.announce_address = new_address.clone();
|
||||
vk_shares().save(
|
||||
deps.storage,
|
||||
(&info.sender, epoch.epoch_id),
|
||||
&contract_share,
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_event(
|
||||
Event::new("dkg-announce-address-update")
|
||||
.add_attribute("dealer", info.sender)
|
||||
@@ -228,9 +242,14 @@ pub(crate) mod tests {
|
||||
#[cfg(feature = "testable-dkg-contract")]
|
||||
mod tests_with_mock {
|
||||
use super::*;
|
||||
use crate::testable_dkg_contract::{init_contract_tester, DkgContractTesterExt};
|
||||
use crate::testable_dkg_contract::{
|
||||
init_contract_tester, init_contract_tester_with_group_members, DkgContractTesterExt,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use cosmwasm_std::testing::message_info;
|
||||
use nym_contracts_common_testing::ContractOpts;
|
||||
use nym_coconut_dkg_common::msg::QueryMsg;
|
||||
use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse;
|
||||
use nym_contracts_common_testing::{ChainOpts, ContractOpts};
|
||||
|
||||
#[test]
|
||||
fn transferring_ownership() -> anyhow::Result<()> {
|
||||
@@ -248,6 +267,7 @@ mod tests_with_mock {
|
||||
contract.run_initial_dummy_dkg();
|
||||
let old_index = DEALERS_INDICES.load(&contract, &group_member)?;
|
||||
let old_details = EPOCH_DEALERS_MAP.load(&contract, (0, &group_member))?;
|
||||
let old_share = vk_shares().load(&contract, (&group_member, 0))?;
|
||||
|
||||
let not_group_member = contract.addr_make("not_group_member");
|
||||
let (deps, env) = contract.deps_mut_env();
|
||||
@@ -277,13 +297,20 @@ mod tests_with_mock {
|
||||
assert!(EPOCH_DEALERS_MAP
|
||||
.may_load(&contract, (0, &group_member))?
|
||||
.is_none());
|
||||
assert!(vk_shares()
|
||||
.may_load(&contract, (&group_member, 0))?
|
||||
.is_none());
|
||||
|
||||
let new_index = DEALERS_INDICES.load(&contract, &new_group_member)?;
|
||||
let new_details = EPOCH_DEALERS_MAP.load(&contract, (0, &new_group_member))?;
|
||||
let new_share = vk_shares().load(&contract, (&new_group_member, 0))?;
|
||||
|
||||
// the underlying info hasn't changed
|
||||
assert_eq!(old_index, new_index);
|
||||
assert_eq!(old_details, new_details);
|
||||
assert_ne!(old_share, new_share);
|
||||
assert_eq!(old_share.owner, group_member);
|
||||
assert_eq!(new_share.owner, new_group_member);
|
||||
|
||||
assert_eq!(
|
||||
OWNERSHIP_TRANSFER_LOG.load(
|
||||
@@ -436,9 +463,91 @@ mod tests_with_mock {
|
||||
assert_eq!(old_details1, new_details1);
|
||||
assert_eq!(old_details2, new_details2);
|
||||
|
||||
// most recent entry is updated
|
||||
// most recent entry is updated
|
||||
assert_eq!(new_details3.announce_address, new_address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_announce_address_updates_vk_shares() -> anyhow::Result<()> {
|
||||
let mut contract = init_contract_tester_with_group_members(3);
|
||||
let group_member = contract.random_group_member();
|
||||
|
||||
contract.run_initial_dummy_dkg(); // => epoch 0
|
||||
contract.run_reset_dkg(); // => epoch 1
|
||||
|
||||
// LEAVE DKG MEMBERSHIP
|
||||
contract.remove_group_member(group_member.clone());
|
||||
contract.run_reset_dkg(); // => epoch 2
|
||||
|
||||
// COME BACK
|
||||
contract.add_group_member(group_member.clone());
|
||||
contract.run_reset_dkg(); // => epoch 3
|
||||
|
||||
let old_address = EPOCH_DEALERS_MAP
|
||||
.load(&contract, (3, &group_member))?
|
||||
.announce_address;
|
||||
|
||||
let old_share0 = vk_shares().load(&contract, (&group_member, 0))?;
|
||||
let old_share1 = vk_shares().load(&contract, (&group_member, 1))?;
|
||||
let old_share2 = vk_shares().may_load(&contract, (&group_member, 2))?;
|
||||
assert!(old_share2.is_none());
|
||||
let old_share3 = vk_shares().may_load(&contract, (&group_member, 3))?;
|
||||
assert!(old_share3.is_some());
|
||||
|
||||
let new_address = "https://new-address.com".to_string();
|
||||
try_update_announce_address(
|
||||
contract.deps_mut(),
|
||||
message_info(&group_member, &[]),
|
||||
new_address.clone(),
|
||||
)?;
|
||||
|
||||
let new_share0 = vk_shares().load(&contract, (&group_member, 0))?;
|
||||
let new_share1 = vk_shares().load(&contract, (&group_member, 1))?;
|
||||
let new_share2 = vk_shares().may_load(&contract, (&group_member, 2))?;
|
||||
assert!(new_share2.is_none());
|
||||
let new_share3 = vk_shares().load(&contract, (&group_member, 3))?;
|
||||
|
||||
// old epoch data is unchanged
|
||||
assert_eq!(old_share0, new_share0);
|
||||
assert_eq!(old_share1, new_share1);
|
||||
assert_eq!(old_share2, new_share2);
|
||||
|
||||
// most recent entry is updated
|
||||
assert_eq!(new_share3.announce_address, new_address);
|
||||
|
||||
// finally an integration check against query endpoint
|
||||
let epoch0_shares: PagedVKSharesResponse =
|
||||
contract.query(&QueryMsg::GetVerificationKeys {
|
||||
epoch_id: 0,
|
||||
limit: None,
|
||||
start_after: None,
|
||||
})?;
|
||||
assert_eq!(epoch0_shares.shares.len(), 3);
|
||||
|
||||
let member_share = epoch0_shares
|
||||
.shares
|
||||
.iter()
|
||||
.find(|s| s.owner == group_member)
|
||||
.context("failed to find member's share")?;
|
||||
assert_eq!(member_share.announce_address, old_address);
|
||||
|
||||
let epoch0_shares: PagedVKSharesResponse =
|
||||
contract.query(&QueryMsg::GetVerificationKeys {
|
||||
epoch_id: 3,
|
||||
limit: None,
|
||||
start_after: None,
|
||||
})?;
|
||||
assert_eq!(epoch0_shares.shares.len(), 3);
|
||||
|
||||
let member_share = epoch0_shares
|
||||
.shares
|
||||
.iter()
|
||||
.find(|s| s.owner == group_member)
|
||||
.context("failed to find member's share")?;
|
||||
assert_eq!(member_share.announce_address, new_address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,12 +62,18 @@ impl TestableNymContract for DkgContract {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
init_contract_tester_with_group_members(DEFAULT_GROUP_MEMBERS)
|
||||
init_contract_tester()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_contract_tester() -> ContractTester<DkgContract> {
|
||||
DkgContract::init().with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
|
||||
init_contract_tester_with_group_members(DEFAULT_GROUP_MEMBERS)
|
||||
}
|
||||
|
||||
pub fn init_contract_tester_with_group_members(members: usize) -> ContractTester<DkgContract> {
|
||||
prepare_contract_tester_builder_with_group_members(members)
|
||||
.build()
|
||||
.with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
|
||||
}
|
||||
|
||||
pub fn prepare_contract_tester_builder_with_group_members<C>(
|
||||
@@ -137,12 +143,6 @@ where
|
||||
builder
|
||||
}
|
||||
|
||||
pub fn init_contract_tester_with_group_members(members: usize) -> ContractTester<DkgContract> {
|
||||
prepare_contract_tester_builder_with_group_members(members)
|
||||
.build()
|
||||
.with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
|
||||
}
|
||||
|
||||
pub trait DkgContractTesterExt:
|
||||
ContractOpts<ExecuteMsg = ExecuteMsg, QueryMsg = QueryMsg, ContractError = ContractError>
|
||||
+ ChainOpts
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn legacy_mixnode_bonding() {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,3 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) mod transactions;
|
||||
|
||||
// the purpose of that module is to keep track of tests of legacy features that will eventually be phased out
|
||||
// such as standalone mixnode/gateway bonding
|
||||
pub(crate) mod legacy;
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.68"
|
||||
version = "1.1.67"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
# this will only work with VPN, otherwise remove the harbor part
|
||||
FROM harbor.nymte.ch/dockerhub/rust:latest AS builder
|
||||
|
||||
ARG GIT_REF=main
|
||||
|
||||
RUN apt update && apt install -yy libdbus-1-dev pkg-config libclang-dev
|
||||
|
||||
# Install go
|
||||
RUN wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz -O go.tar.gz
|
||||
RUN tar -xzvf go.tar.gz -C /usr/local
|
||||
ENV PATH=/go/bin:/usr/local/go/bin:$PATH
|
||||
|
||||
COPY ./ /usr/src/nym
|
||||
WORKDIR /usr/src/nym
|
||||
RUN git clone https://github.com/nymtech/nym-vpn-client /usr/src/nym-vpn-client
|
||||
RUN cd /usr/src/nym-vpn-client && git checkout $GIT_REF
|
||||
ENV PATH=/go/bin:/usr/local/go/bin:$PATH
|
||||
WORKDIR /usr/src/nym-vpn-client/nym-vpn-core
|
||||
RUN cargo build --release --package nym-gateway-probe
|
||||
|
||||
COPY ./ /usr/src/nym
|
||||
WORKDIR /usr/src/nym/nym-node-status-api/nym-node-status-agent
|
||||
RUN cargo build --release
|
||||
|
||||
@@ -31,7 +35,7 @@ RUN apt-get update && apt-get install -y ca-certificates
|
||||
WORKDIR /nym
|
||||
|
||||
COPY --from=builder /usr/src/nym/target/release/nym-node-status-agent ./
|
||||
COPY --from=builder /usr/src/nym/target/release/nym-gateway-probe ./
|
||||
COPY --from=builder /usr/src/nym-vpn-client/nym-vpn-core/target/release/nym-gateway-probe ./
|
||||
COPY --from=builder /usr/src/nym/nym-node-status-api/nym-node-status-agent/entrypoint.sh ./
|
||||
RUN chmod +x /nym/entrypoint.sh
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ async fn run(
|
||||
.clone()
|
||||
.expect("rust sdk mainnet default missing api_url");
|
||||
|
||||
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
||||
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])?
|
||||
.no_hickory_dns()
|
||||
.with_timeout(nym_api_client_timeout)
|
||||
.build()?;
|
||||
|
||||
@@ -98,7 +98,7 @@ impl Monitor {
|
||||
.expect("rust sdk mainnet default missing api_url");
|
||||
|
||||
let nym_api =
|
||||
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
||||
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])?
|
||||
.no_hickory_dns()
|
||||
.with_timeout(self.nym_api_client_timeout)
|
||||
.build()?;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node"
|
||||
version = "1.20.0"
|
||||
version = "1.19.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -17,13 +17,13 @@ thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tracing.workspace = true
|
||||
typed-builder.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
nym-authenticator-client = { path = "../nym-authenticator-client" }
|
||||
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
|
||||
nym-credential-storage = { path = "../common/credential-storage" }
|
||||
nym-credentials-interface = { path = "../common/credentials-interface" }
|
||||
nym-http-api-client = { path = "../common/http-api-client" }
|
||||
nym-ip-packet-client = { path = "../nym-ip-packet-client" }
|
||||
nym-registration-common = { path = "../common/registration" }
|
||||
nym-sdk = { path = "../sdk/rust/nym-sdk" }
|
||||
|
||||
@@ -15,10 +15,12 @@ use nym_sdk::{
|
||||
use std::os::fd::RawFd;
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
use crate::error::RegistrationClientError;
|
||||
|
||||
const VPN_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15);
|
||||
const MIXNET_CLIENT_STARTUP_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NymNodeWithKeys {
|
||||
@@ -26,15 +28,17 @@ pub struct NymNodeWithKeys {
|
||||
pub keys: Arc<KeyPair>,
|
||||
}
|
||||
|
||||
#[derive(TypedBuilder)]
|
||||
pub struct BuilderConfig {
|
||||
pub entry_node: NymNodeWithKeys,
|
||||
pub exit_node: NymNodeWithKeys,
|
||||
pub data_path: Option<PathBuf>,
|
||||
pub mixnet_client_config: MixnetClientConfig,
|
||||
#[builder(default = MIXNET_CLIENT_STARTUP_TIMEOUT)]
|
||||
pub mixnet_client_startup_timeout: Duration,
|
||||
pub two_hops: bool,
|
||||
pub user_agent: UserAgent,
|
||||
pub custom_topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
pub custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
pub network_env: NymNetworkDetails,
|
||||
pub cancel_token: CancellationToken,
|
||||
#[cfg(unix)]
|
||||
@@ -57,44 +61,6 @@ pub struct MixnetClientConfig {
|
||||
}
|
||||
|
||||
impl BuilderConfig {
|
||||
/// Create a new BuilderConfig without domain fronting support
|
||||
///
|
||||
/// For domain fronting support, set `custom_nym_api_client` to Some(client) after creation
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
entry_node: NymNodeWithKeys,
|
||||
exit_node: NymNodeWithKeys,
|
||||
data_path: Option<PathBuf>,
|
||||
mixnet_client_config: MixnetClientConfig,
|
||||
two_hops: bool,
|
||||
user_agent: UserAgent,
|
||||
custom_topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
network_env: NymNetworkDetails,
|
||||
cancel_token: CancellationToken,
|
||||
#[cfg(unix)] connection_fd_callback: Arc<dyn Fn(RawFd) + Send + Sync>,
|
||||
) -> Self {
|
||||
Self {
|
||||
entry_node,
|
||||
exit_node,
|
||||
data_path,
|
||||
mixnet_client_config,
|
||||
two_hops,
|
||||
user_agent,
|
||||
custom_topology_provider,
|
||||
custom_nym_api_client: None,
|
||||
network_env,
|
||||
cancel_token,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a custom nym-api HTTP client (for domain fronting support)
|
||||
pub fn with_nym_api_client(mut self, client: nym_http_api_client::Client) -> Self {
|
||||
self.custom_nym_api_client = Some(client);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mixnet_client_debug_config(&self) -> DebugConfig {
|
||||
if self.two_hops {
|
||||
two_hop_debug_config(&self.mixnet_client_config)
|
||||
@@ -147,7 +113,7 @@ impl BuilderConfig {
|
||||
RememberMe::new_mixnet()
|
||||
};
|
||||
|
||||
let mut builder = builder
|
||||
let builder = builder
|
||||
.with_user_agent(self.user_agent)
|
||||
.request_gateway(self.entry_node.node.identity.to_string())
|
||||
.network_details(self.network_env)
|
||||
@@ -156,13 +122,6 @@ impl BuilderConfig {
|
||||
.with_remember_me(remember_me)
|
||||
.custom_topology_provider(self.custom_topology_provider);
|
||||
|
||||
if let Some(nym_api_client) = self.custom_nym_api_client {
|
||||
tracing::info!("Registration client: Passing custom nym-api HTTP client to SDK");
|
||||
builder = builder.with_nym_api_client(nym_api_client);
|
||||
} else {
|
||||
tracing::warn!("Registration client: No custom nym-api HTTP client provided");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
let builder = builder.with_connection_fd_callback(self.connection_fd_callback);
|
||||
|
||||
@@ -265,23 +224,4 @@ mod tests {
|
||||
assert_eq!(config.min_mixnode_performance, None);
|
||||
assert_eq!(config.min_gateway_performance, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_config_has_custom_client_field() {
|
||||
// Verify that BuilderConfig has the custom_nym_api_client field
|
||||
// by creating a simple HTTP client
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
// Verify the client works
|
||||
let urls = http_client.base_urls();
|
||||
assert!(
|
||||
!urls.is_empty() && urls[0].as_str().contains("validator.nymtech.net"),
|
||||
"HTTP client should be configured with correct URL"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,12 @@ use nym_validator_client::{
|
||||
QueryHttpRpcNyxdClient,
|
||||
nyxd::{Config as NyxdClientConfig, NyxdClient},
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{RegistrationClient, config::RegistrationClientConfig, error::RegistrationClientError};
|
||||
use config::BuilderConfig;
|
||||
|
||||
pub(crate) mod config;
|
||||
|
||||
pub(crate) const MIXNET_CLIENT_STARTUP_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
pub struct RegistrationClientBuilder {
|
||||
pub config: BuilderConfig,
|
||||
}
|
||||
@@ -49,7 +46,7 @@ impl RegistrationClientBuilder {
|
||||
let builder = MixnetClientBuilder::new_with_storage(mixnet_client_storage)
|
||||
.event_tx(EventSender(event_tx));
|
||||
let mixnet_client = tokio::time::timeout(
|
||||
MIXNET_CLIENT_STARTUP_TIMEOUT,
|
||||
self.config.mixnet_client_startup_timeout,
|
||||
self.config.build_and_connect_mixnet_client(builder),
|
||||
)
|
||||
.await??;
|
||||
@@ -59,7 +56,7 @@ impl RegistrationClientBuilder {
|
||||
} else {
|
||||
let builder = MixnetClientBuilder::new_ephemeral().event_tx(EventSender(event_tx));
|
||||
let mixnet_client = tokio::time::timeout(
|
||||
MIXNET_CLIENT_STARTUP_TIMEOUT,
|
||||
self.config.mixnet_client_startup_timeout,
|
||||
self.config.build_and_connect_mixnet_client(builder),
|
||||
)
|
||||
.await??;
|
||||
|
||||
Generated
+3
-24
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "NymWallet"
|
||||
@@ -773,7 +773,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "bls12_381"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd"
|
||||
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp%2Fexperimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
@@ -1723,15 +1723,6 @@ dependencies = [
|
||||
"dirs-sys 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
@@ -1752,18 +1743,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.4.6",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
@@ -4100,7 +4079,7 @@ dependencies = [
|
||||
name = "nym-config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dirs 5.0.1",
|
||||
"dirs 6.0.0",
|
||||
"handlebars",
|
||||
"log",
|
||||
"nym-network-defaults",
|
||||
|
||||
@@ -155,7 +155,7 @@ check_ip_routing() {
|
||||
|
||||
perform_pings() {
|
||||
echo "Performing IPv4 ping to google.com..."
|
||||
ping -c 4 google.com
|
||||
ping -4 -c 4 google.com
|
||||
echo "---------------------------------------"
|
||||
echo "Performing IPv6 ping to google.com..."
|
||||
ping6 -c 4 google.com
|
||||
@@ -189,7 +189,7 @@ test_bridge_connectivity() {
|
||||
echo -e "Testing IPv4 connectivity..."
|
||||
echo
|
||||
|
||||
if ping -c 1 -I "$ipv4_address" google.com >/dev/null 2>&1; then
|
||||
if ping -4 -c 1 -I "$ipv4_address" google.com >/dev/null 2>&1; then
|
||||
echo -e "${green}IPv4 connectivity is working. Fetching test data...${reset}"
|
||||
joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke)
|
||||
[[ -n "$joke" && "$joke" != "null" ]] && echo -e "${green}IPv4 test joke: $joke${reset}" || echo -e "${red}Failed to fetch test data via IPv4.${reset}"
|
||||
@@ -277,7 +277,7 @@ check_bridge_installation() {
|
||||
# Check keys directory
|
||||
if [[ -d "$BRIDGE_KEYS_DIR" ]]; then
|
||||
echo -e "${green}✓ Keys directory exists: $BRIDGE_KEYS_DIR${reset}"
|
||||
key_count=$(ls -1 "$BRIDGE_KEYS_DIR"/*.pem 2>/dev/null | wc -l)
|
||||
key_count=$(sudo bash -c "ls -1 \"$BRIDGE_KEYS_DIR\"/*.pem" 2>/dev/null | wc -l)
|
||||
echo " Keys found: $key_count"
|
||||
else
|
||||
echo -e "${red}✗ Keys directory not found: $BRIDGE_KEYS_DIR${reset}"
|
||||
@@ -494,7 +494,7 @@ generate_bridge_keys() {
|
||||
# Generate ED25519 private key
|
||||
local key_file="$BRIDGE_KEYS_DIR/ed25519_bridge_identity.pem"
|
||||
|
||||
if [[ -f "$key_file" ]]; then
|
||||
if sudo test -f "$key_file"; then
|
||||
echo -e "${yellow}Warning: Key file already exists at $key_file${reset}"
|
||||
read -p "Overwrite existing key? (yes/no): " confirm
|
||||
if [[ "$confirm" != "yes" ]]; then
|
||||
@@ -532,14 +532,14 @@ create_client_params() {
|
||||
|
||||
# Check if key exists
|
||||
local key_file="$BRIDGE_KEYS_DIR/ed25519_bridge_identity.pem"
|
||||
if [[ ! -f "$key_file" ]]; then
|
||||
if ! sudo test -f "$key_file"; then
|
||||
echo -e "${red}Error: Bridge key not found at $key_file${reset}"
|
||||
echo "Run 'nym-bridge-helper generate_bridge_keys' first"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get forward address
|
||||
read -p "Enter forward address (e.g., <IPv4>:51822, can be found by running 'curl -6 https://ifconfig.co/ip'): " forward_addr
|
||||
read -p "Enter forward address (e.g., <IPv4>:51822, can be found by running 'curl -4 https://ifconfig.co/ip'): " forward_addr
|
||||
if [[ -z "$forward_addr" ]]; then
|
||||
echo -e "${red}Error: Forward address is required${reset}"
|
||||
return 1
|
||||
@@ -722,24 +722,28 @@ install_bridge_binary() {
|
||||
local red="\033[0;31m"
|
||||
local yellow="\033[0;33m"
|
||||
|
||||
local deb_arch="amd64"
|
||||
local sys_arch=$(dpkg --print-architecture)
|
||||
|
||||
if [[ "$deb_arch" != "$sys_arch" ]]; then
|
||||
echo -e "${red}Error: aborting .deb package installation as this system's arch is not amd64.${reset}"
|
||||
echo " Refer to https://github.com/nymtech/nym-bridges for build instructions."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${yellow}=== Installing nym-bridge Binary ===${reset}"
|
||||
echo ""
|
||||
|
||||
read -p "Enter bridge binary URL for your system from here https://builds.ci.nymte.ch/QUIC/: " binary_url
|
||||
if [[ -z "$binary_url" ]]; then
|
||||
echo -e "${red}Error: Binary URL is required${reset}"
|
||||
return 1
|
||||
fi
|
||||
echo "Retrieving URL to latest release..."
|
||||
local binary_url=$(
|
||||
curl -s "https://api.github.com/repos/nymtech/nym-bridges/releases/latest" |
|
||||
grep "browser_download_url.*amd64.deb" |
|
||||
grep -o 'https://[^"]*'
|
||||
)
|
||||
|
||||
echo "Downloading nym-bridge binary..."
|
||||
if sudo curl -L "$binary_url" -o "$BRIDGE_BINARY"; then
|
||||
sudo chmod 755 "$BRIDGE_BINARY"
|
||||
echo -e "${green}✓ Bridge binary installed at $BRIDGE_BINARY${reset}"
|
||||
|
||||
# Show version
|
||||
echo ""
|
||||
echo "Binary version:"
|
||||
$BRIDGE_BINARY --version || echo "Unable to determine version"
|
||||
echo "Downloading $binary_url ..."
|
||||
if curl -L -o "/tmp/$(basename "$binary_url")" "$binary_url"; then
|
||||
sudo dpkg -i "/tmp/$(basename "$binary_url")"
|
||||
else
|
||||
echo -e "${red}✗ Failed to download bridge binary${reset}"
|
||||
return 1
|
||||
|
||||
@@ -54,7 +54,6 @@ pub struct MixnetClientBuilder<S: MixnetClientStorage = Ephemeral> {
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send + Sync>>,
|
||||
custom_shutdown: Option<ShutdownTracker>,
|
||||
custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
event_tx: Option<EventSender>,
|
||||
force_tls: bool,
|
||||
user_agent: Option<UserAgent>,
|
||||
@@ -94,7 +93,6 @@ impl MixnetClientBuilder<OnDiskPersistent> {
|
||||
socks5_config: None,
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
custom_nym_api_client: None,
|
||||
storage: storage_paths
|
||||
.initialise_default_persistent_storage()
|
||||
.await?,
|
||||
@@ -133,7 +131,6 @@ where
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
custom_nym_api_client: None,
|
||||
custom_shutdown: None,
|
||||
event_tx: None,
|
||||
force_tls: false,
|
||||
@@ -158,7 +155,6 @@ where
|
||||
wait_for_gateway: self.wait_for_gateway,
|
||||
custom_topology_provider: self.custom_topology_provider,
|
||||
custom_gateway_transceiver: self.custom_gateway_transceiver,
|
||||
custom_nym_api_client: self.custom_nym_api_client,
|
||||
custom_shutdown: self.custom_shutdown,
|
||||
event_tx: self.event_tx,
|
||||
force_tls: self.force_tls,
|
||||
@@ -298,12 +294,6 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_nym_api_client(mut self, client: nym_http_api_client::Client) -> Self {
|
||||
self.custom_nym_api_client = Some(client);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_statistics_reporting(mut self, config: StatsReporting) -> Self {
|
||||
self.config.debug_config.stats_reporting = config;
|
||||
@@ -348,7 +338,6 @@ where
|
||||
|
||||
client.custom_gateway_transceiver = self.custom_gateway_transceiver;
|
||||
client.custom_topology_provider = self.custom_topology_provider;
|
||||
client.custom_nym_api_client = self.custom_nym_api_client;
|
||||
client.custom_shutdown = self.custom_shutdown;
|
||||
client.wait_for_gateway = self.wait_for_gateway;
|
||||
client.force_tls = self.force_tls;
|
||||
@@ -398,9 +387,6 @@ where
|
||||
/// advanced usage of custom gateways
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send + Sync>>,
|
||||
|
||||
/// Custom nym-api HTTP client (for domain fronting support)
|
||||
custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
|
||||
/// Attempt to wait for the selected gateway (if applicable) to come online if its currently not bonded.
|
||||
wait_for_gateway: bool,
|
||||
|
||||
@@ -473,7 +459,6 @@ where
|
||||
storage,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
custom_nym_api_client: None,
|
||||
wait_for_gateway: false,
|
||||
force_tls: false,
|
||||
custom_shutdown: None,
|
||||
@@ -579,14 +564,13 @@ where
|
||||
let nym_api_endpoints = self.get_api_endpoints();
|
||||
let topology_cfg = &self.config.debug_config.topology;
|
||||
let user_agent = self.user_agent.clone();
|
||||
let mut rng = OsRng;
|
||||
|
||||
gateways_for_init(
|
||||
&mut rng,
|
||||
&nym_api_endpoints,
|
||||
user_agent,
|
||||
topology_cfg.minimum_gateway_performance,
|
||||
topology_cfg.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -722,6 +706,16 @@ where
|
||||
.config
|
||||
.as_base_client_config(nyxd_endpoints, nym_api_endpoints.clone());
|
||||
|
||||
tracing::debug!(
|
||||
"SDK: Passing nym_api_urls to BaseClientBuilder (has {} nym_api_urls)",
|
||||
self.config
|
||||
.network_details
|
||||
.nym_api_urls
|
||||
.as_ref()
|
||||
.map(|urls| urls.len())
|
||||
.unwrap_or(0)
|
||||
);
|
||||
|
||||
let mut base_builder: BaseClientBuilder<_, _> =
|
||||
BaseClientBuilder::new(base_config, self.storage, self.dkg_query_client)
|
||||
.with_wait_for_gateway(self.wait_for_gateway)
|
||||
@@ -729,15 +723,13 @@ where
|
||||
.with_remember_me(&self.remember_me)
|
||||
.with_derivation_material(self.derivation_material);
|
||||
|
||||
if let Some(user_agent) = self.user_agent {
|
||||
base_builder = base_builder.with_user_agent(user_agent);
|
||||
// Add nym_api_urls if available in network_details
|
||||
if let Some(nym_api_urls) = self.config.network_details.nym_api_urls.clone() {
|
||||
base_builder = base_builder.with_nym_api_urls(nym_api_urls);
|
||||
}
|
||||
|
||||
if let Some(nym_api_client) = self.custom_nym_api_client {
|
||||
tracing::info!("SDK: Passing custom nym-api HTTP client to BaseClientBuilder");
|
||||
base_builder = base_builder.with_nym_api_client(nym_api_client);
|
||||
} else {
|
||||
tracing::warn!("SDK: No custom nym-api HTTP client configured");
|
||||
if let Some(user_agent) = self.user_agent {
|
||||
base_builder = base_builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
if let Some(topology_provider) = self.custom_topology_provider {
|
||||
@@ -913,63 +905,4 @@ mod tests {
|
||||
"Builder should succeed without custom client"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixnet_builder_with_custom_client() {
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
let builder = MixnetClientBuilder::new_ephemeral().with_nym_api_client(http_client);
|
||||
|
||||
assert!(
|
||||
builder.build().is_ok(),
|
||||
"Builder should succeed with custom client"
|
||||
);
|
||||
}
|
||||
|
||||
// Note: Tests for entry_capable_nodes() vs entry_gateways() are in nym-topology crate
|
||||
// These tests verify the builder functionality only
|
||||
|
||||
#[test]
|
||||
fn test_custom_client_transfer_through_build() {
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
let builder = MixnetClientBuilder::new_ephemeral().with_nym_api_client(http_client);
|
||||
let disconnected_client = builder.build();
|
||||
|
||||
assert!(
|
||||
disconnected_client.is_ok(),
|
||||
"Build should transfer custom_nym_api_client successfully"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_storage_transfer_includes_custom_client() {
|
||||
use nym_client_core::client::base_client::storage::Ephemeral;
|
||||
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
let builder = MixnetClientBuilder::new_ephemeral().with_nym_api_client(http_client);
|
||||
let new_storage = Ephemeral::default();
|
||||
let builder_with_new_storage = builder.set_storage(new_storage);
|
||||
|
||||
assert!(
|
||||
builder_with_new_storage.build().is_ok(),
|
||||
"set_storage should preserve custom_nym_api_client"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-network-requester"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.66"
|
||||
version = "1.1.65"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version = "1.85"
|
||||
|
||||
@@ -226,7 +226,8 @@ mod tests {
|
||||
error!("{err}");
|
||||
// this is not an ideal way of checking it, but if test fails due to networking failures
|
||||
// it should be fine to progress
|
||||
if err.to_string().contains("nym api request failed") {
|
||||
let err_str = err.to_string();
|
||||
if err_str.contains("nym api") || err_str.contains("failed to connect") {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(err);
|
||||
@@ -291,7 +292,8 @@ mod tests {
|
||||
error!("{err}");
|
||||
// this is not an ideal way of checking it, but if test fails due to networking failures
|
||||
// it should be fine to progress
|
||||
if err.to_string().contains("nym api request failed") {
|
||||
let err_str = err.to_string();
|
||||
if err_str.contains("nym api") || err_str.contains("failed to connect") {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(err);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-cli"
|
||||
version = "1.1.65"
|
||||
version = "1.1.64"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nymvisor"
|
||||
version = "0.1.30"
|
||||
version = "0.1.29"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
Reference in New Issue
Block a user