Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b03626ffb2 | |||
| c0f8d98b63 | |||
| 91995da4f1 | |||
| 01fa1df66c | |||
| baddaaac22 | |||
| 2c4b5f168b |
@@ -14,7 +14,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-15]
|
||||
platform: [macos-14]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
outputs:
|
||||
|
||||
Generated
+179
-26
@@ -847,6 +847,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "binstring"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0669d5a35b64fdb5ab7fb19cae13148b6b5cbdf4b8247faf54ece47f699c8cef"
|
||||
|
||||
[[package]]
|
||||
name = "bip32"
|
||||
version = "0.5.3"
|
||||
@@ -942,6 +948,17 @@ dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2b_simd"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"constant_time_eq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.8.2"
|
||||
@@ -1332,6 +1349,17 @@ version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
|
||||
[[package]]
|
||||
name = "coarsetime"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasix",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
@@ -1855,6 +1883,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ct-codecs"
|
||||
version = "1.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8"
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
@@ -2411,6 +2445,16 @@ dependencies = [
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-compact"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190"
|
||||
dependencies = [
|
||||
"ct-codecs",
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-consensus"
|
||||
version = "2.1.0"
|
||||
@@ -2475,6 +2519,8 @@ dependencies = [
|
||||
"ff",
|
||||
"generic-array 0.14.7",
|
||||
"group",
|
||||
"hkdf",
|
||||
"pem-rfc7468",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sec1",
|
||||
@@ -3336,6 +3382,30 @@ dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac-sha1-compact"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3"
|
||||
|
||||
[[package]]
|
||||
name = "hmac-sha256"
|
||||
version = "1.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac-sha512"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89e8d20b3799fa526152a5301a771eaaad80857f83e01b23216ceaafb2d9280"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.11"
|
||||
@@ -4062,6 +4132,32 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jwt-simple"
|
||||
version = "0.12.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "731011e9647a71ff4f8474176ff6ce6e0d2de87a0173f15613af3a84c3e3401a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"binstring",
|
||||
"blake2b_simd",
|
||||
"coarsetime",
|
||||
"ct-codecs",
|
||||
"ed25519-compact",
|
||||
"hmac-sha1-compact",
|
||||
"hmac-sha256",
|
||||
"hmac-sha512",
|
||||
"k256",
|
||||
"p256",
|
||||
"p384",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"superboring",
|
||||
"thiserror 2.0.12",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.13.4"
|
||||
@@ -4831,7 +4927,6 @@ dependencies = [
|
||||
"nym-ecash-signer-check",
|
||||
"nym-ecash-time",
|
||||
"nym-gateway-client",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-node-requests",
|
||||
@@ -5043,7 +5138,6 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-ecash-time",
|
||||
"nym-http-api-client",
|
||||
"nym-id",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
@@ -5132,7 +5226,6 @@ dependencies = [
|
||||
"nym-http-api-client",
|
||||
"nym-id",
|
||||
"nym-mixnet-client",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-nonexhaustive-delayqueue",
|
||||
"nym-pemstore",
|
||||
@@ -5485,7 +5578,6 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-ecash-time",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-serde-helpers",
|
||||
"nym-validator-client",
|
||||
@@ -5529,6 +5621,7 @@ dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"jwt-simple",
|
||||
"nym-pemstore",
|
||||
"nym-sphinx-types",
|
||||
"rand 0.8.5",
|
||||
@@ -5585,7 +5678,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"nym-ecash-signer-check-types",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-validator-client",
|
||||
"semver 1.0.26",
|
||||
@@ -5850,13 +5942,10 @@ dependencies = [
|
||||
"mime",
|
||||
"nym-bin-common",
|
||||
"nym-http-api-common",
|
||||
"nym-network-defaults",
|
||||
"once_cell",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_plain",
|
||||
"serde_yaml",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -6113,8 +6202,6 @@ dependencies = [
|
||||
"nym-client-core",
|
||||
"nym-crypto",
|
||||
"nym-gateway-requests",
|
||||
"nym-http-api-client",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-sdk",
|
||||
"nym-sphinx",
|
||||
@@ -6582,7 +6669,6 @@ dependencies = [
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-gateway-requests",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-ordered-buffer",
|
||||
"nym-service-providers-common",
|
||||
@@ -6650,6 +6736,26 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-signers-monitor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"humantime",
|
||||
"itertools 0.14.0",
|
||||
"nym-bin-common",
|
||||
"nym-ecash-signer-check",
|
||||
"nym-network-defaults",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"zulip-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.61"
|
||||
@@ -7088,6 +7194,22 @@ dependencies = [
|
||||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-upgrade-mode-check"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"jwt-simple",
|
||||
"nym-crypto",
|
||||
"nym-http-api-client",
|
||||
"reqwest 0.12.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator-client"
|
||||
version = "0.1.0"
|
||||
@@ -7158,7 +7280,6 @@ dependencies = [
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ecash-time",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-pemstore",
|
||||
"nym-serde-helpers",
|
||||
@@ -7188,9 +7309,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"humantime",
|
||||
"nym-api-requests",
|
||||
"nym-crypto",
|
||||
"nym-http-api-client",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"rand 0.8.5",
|
||||
@@ -7659,6 +7778,18 @@ dependencies = [
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "p384"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
|
||||
dependencies = [
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"primeorder",
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pairing"
|
||||
version = "0.23.0"
|
||||
@@ -8639,6 +8770,7 @@ dependencies = [
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sha2 0.10.9",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle 2.6.1",
|
||||
@@ -9259,15 +9391,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_plain"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.20"
|
||||
@@ -9937,6 +10060,19 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142"
|
||||
|
||||
[[package]]
|
||||
name = "superboring"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "515cce34a781d7250b8a65706e0f2a5b99236ea605cb235d4baed6685820478f"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"hmac-sha256",
|
||||
"hmac-sha512",
|
||||
"rand 0.8.5",
|
||||
"rsa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@@ -10190,7 +10326,6 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-group-contract-common",
|
||||
"nym-http-api-client",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"nym-pemstore",
|
||||
@@ -11313,7 +11448,6 @@ dependencies = [
|
||||
"clap",
|
||||
"comfy-table",
|
||||
"nym-bin-common",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-validator-client",
|
||||
"serde",
|
||||
@@ -11436,6 +11570,15 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
|
||||
|
||||
[[package]]
|
||||
name = "wasix"
|
||||
version = "0.12.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d"
|
||||
dependencies = [
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
@@ -11544,7 +11687,6 @@ dependencies = [
|
||||
"nym-credential-storage",
|
||||
"nym-crypto",
|
||||
"nym-gateway-client",
|
||||
"nym-http-api-client",
|
||||
"nym-sphinx",
|
||||
"nym-sphinx-acknowledgements",
|
||||
"nym-statistics-common",
|
||||
@@ -11830,6 +11972,17 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
|
||||
+3
-3
@@ -95,7 +95,7 @@ members = [
|
||||
"common/ticketbooks-merkle",
|
||||
"common/topology",
|
||||
"common/tun",
|
||||
"common/types",
|
||||
"common/types", "common/upgrade-mode-check",
|
||||
"common/verloc",
|
||||
"common/wasm/client-core",
|
||||
"common/wasm/storage",
|
||||
@@ -122,7 +122,7 @@ members = [
|
||||
"nym-node-status-api/nym-node-status-client",
|
||||
"nym-node/nym-node-metrics",
|
||||
"nym-node/nym-node-requests",
|
||||
"nym-outfox",
|
||||
"nym-outfox", "nym-signers-monitor",
|
||||
"nym-statistics-api",
|
||||
"nym-validator-rewarder",
|
||||
"nyx-chain-watcher",
|
||||
@@ -274,6 +274,7 @@ inquire = "0.6.2"
|
||||
ip_network = "0.4.1"
|
||||
ipnetwork = "0.20"
|
||||
itertools = "0.14.0"
|
||||
jwt-simple = { version = "0.12.12", default-features = false, features = ["pure-rust"] }
|
||||
k256 = "0.13"
|
||||
lazy_static = "1.5.0"
|
||||
ledger-transport = "0.10.0"
|
||||
@@ -317,7 +318,6 @@ serde_json_path = "0.7.2"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
serde_plain = "1.0.2"
|
||||
sha2 = "0.10.9"
|
||||
si-scale = "0.2.3"
|
||||
snow = "0.9.6"
|
||||
|
||||
@@ -13,7 +13,7 @@ use nym_credentials_interface::{
|
||||
};
|
||||
use nym_ecash_time::Date;
|
||||
use nym_validator_client::coconut::all_ecash_api_clients;
|
||||
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::prelude::SliceRandom;
|
||||
|
||||
@@ -53,7 +53,6 @@ nym-client-core-config-types = { path = "./config-types", features = [
|
||||
nym-client-core-surb-storage = { path = "./surb-storage" }
|
||||
nym-client-core-gateways-storage = { path = "./gateways-storage" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
|
||||
|
||||
@@ -57,7 +57,7 @@ use nym_task::{TaskClient, TaskHandle};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::HardcodedTopologyProvider;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, UserAgent};
|
||||
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, NymApiClient, UserAgent};
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::thread_rng;
|
||||
@@ -566,7 +566,7 @@ where
|
||||
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
config_topology: config::Topology,
|
||||
nym_api_urls: Vec<Url>,
|
||||
nym_api_client: nym_http_api_client::Client,
|
||||
nym_api_client: NymApiClient,
|
||||
) -> Box<dyn TopologyProvider + Send + Sync> {
|
||||
// if no custom provider was ... provided ..., create one using nym-api
|
||||
custom_provider.unwrap_or_else(|| {
|
||||
@@ -749,42 +749,21 @@ where
|
||||
setup_gateway(setup_method, key_store, details_store).await
|
||||
}
|
||||
|
||||
fn construct_nym_api_client(
|
||||
config: &Config,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Result<nym_http_api_client::Client, ClientCoreError> {
|
||||
fn construct_nym_api_client(config: &Config, user_agent: Option<UserAgent>) -> NymApiClient {
|
||||
let mut nym_api_urls = config.get_nym_api_endpoints();
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
let mut builder =
|
||||
nym_http_api_client::Client::builder(nym_api_urls[0].clone()).map_err(|e| {
|
||||
ClientCoreError::NymApiQueryFailure {
|
||||
source:
|
||||
nym_validator_client::nym_api::error::NymAPIError::GenericRequestFailure(
|
||||
e.to_string(),
|
||||
),
|
||||
}
|
||||
})?;
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
NymApiClient::new_with_user_agent(nym_api_urls[0].clone(), user_agent)
|
||||
} else {
|
||||
NymApiClient::new(nym_api_urls[0].clone())
|
||||
}
|
||||
|
||||
builder = builder.with_bincode();
|
||||
|
||||
builder
|
||||
.build()
|
||||
.map_err(|e| ClientCoreError::NymApiQueryFailure {
|
||||
source: nym_validator_client::nym_api::error::NymAPIError::GenericRequestFailure(
|
||||
e.to_string(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
async fn determine_key_rotation_state(
|
||||
client: &nym_http_api_client::Client,
|
||||
client: &NymApiClient,
|
||||
) -> Result<KeyRotationConfig, ClientCoreError> {
|
||||
Ok(client.get_key_rotation_info().await?.into())
|
||||
Ok(client.nym_api.get_key_rotation_info().await?.into())
|
||||
}
|
||||
|
||||
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
|
||||
@@ -851,7 +830,7 @@ where
|
||||
.dkg_query_client
|
||||
.map(|client| BandwidthController::new(credential_store, client));
|
||||
|
||||
let nym_api_client = Self::construct_nym_api_client(&self.config, self.user_agent.clone())?;
|
||||
let nym_api_client = Self::construct_nym_api_client(&self.config, self.user_agent.clone());
|
||||
let key_rotation_config = Self::determine_key_rotation_state(&nym_api_client).await?;
|
||||
|
||||
let topology_provider = Self::setup_topology_provider(
|
||||
|
||||
@@ -175,6 +175,7 @@ impl MixTrafficController {
|
||||
},
|
||||
None => {
|
||||
tracing::trace!("MixTrafficController, client request channel closed");
|
||||
break
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::helpers::get_time_now;
|
||||
use crate::client::replies::{
|
||||
reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys,
|
||||
};
|
||||
@@ -22,7 +23,7 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C
|
||||
use nym_task::TaskClient;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::Duration;
|
||||
use tracing::*;
|
||||
|
||||
// The interval at which we check for stale buffers
|
||||
@@ -54,7 +55,7 @@ struct ReceivedMessagesBufferInner<R: MessageReceiver> {
|
||||
stats_tx: ClientStatsSender,
|
||||
|
||||
// Periodically check for stale buffers to clean up
|
||||
last_stale_check: Instant,
|
||||
last_stale_check: crate::client::helpers::Instant,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
@@ -154,7 +155,7 @@ impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
}
|
||||
|
||||
fn cleanup_stale_buffers(&mut self) {
|
||||
let now = Instant::now();
|
||||
let now = get_time_now();
|
||||
if now - self.last_stale_check > STALE_BUFFER_CHECK_INTERVAL {
|
||||
self.last_stale_check = now;
|
||||
self.message_receiver
|
||||
@@ -190,7 +191,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
message_sender: None,
|
||||
recently_reconstructed: HashSet::new(),
|
||||
stats_tx,
|
||||
last_stale_check: Instant::now(),
|
||||
last_stale_check: get_time_now(),
|
||||
})),
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
use nym_topology::provider_trait::{ToTopologyMetadata, TopologyProvider};
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::cmp::min;
|
||||
@@ -41,43 +39,30 @@ impl Config {
|
||||
pub struct NymApiTopologyProvider {
|
||||
config: Config,
|
||||
|
||||
validator_client: nym_http_api_client::Client,
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
nym_api_urls: Vec<Url>,
|
||||
currently_used_api: usize,
|
||||
use_bincode: bool,
|
||||
}
|
||||
|
||||
impl NymApiTopologyProvider {
|
||||
pub fn new(
|
||||
config: impl Into<Config>,
|
||||
mut nym_api_urls: Vec<Url>,
|
||||
validator_client: nym_http_api_client::Client,
|
||||
mut validator_client: nym_validator_client::client::NymApiClient,
|
||||
) -> Self {
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
let mut provider = NymApiTopologyProvider {
|
||||
validator_client.change_nym_api(nym_api_urls[0].clone());
|
||||
|
||||
NymApiTopologyProvider {
|
||||
config: config.into(),
|
||||
validator_client,
|
||||
nym_api_urls,
|
||||
currently_used_api: 0,
|
||||
use_bincode: true,
|
||||
};
|
||||
// Set all API URLs - the client will try them in order with automatic failover
|
||||
provider.validator_client.change_base_urls(
|
||||
provider
|
||||
.nym_api_urls
|
||||
.iter()
|
||||
.map(|u| u.clone().into())
|
||||
.collect(),
|
||||
);
|
||||
provider
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disable_bincode(&mut self) {
|
||||
self.use_bincode = false;
|
||||
// Note: The unified client doesn't support toggling bincode after creation.
|
||||
// This would require recreating the client without bincode.
|
||||
// For now, we'll track the preference but it won't take effect.
|
||||
warn!("Disabling bincode on existing client is not currently supported");
|
||||
self.validator_client.use_bincode = false;
|
||||
}
|
||||
|
||||
fn use_next_nym_api(&mut self) {
|
||||
@@ -87,19 +72,8 @@ impl NymApiTopologyProvider {
|
||||
}
|
||||
|
||||
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
|
||||
|
||||
// Provide all URLs starting from the next one in rotation order
|
||||
// This enables automatic failover to other endpoints
|
||||
let rotated_urls: Vec<_> = self
|
||||
.nym_api_urls
|
||||
.iter()
|
||||
.cycle()
|
||||
.skip(self.currently_used_api)
|
||||
.take(self.nym_api_urls.len())
|
||||
.map(|u| u.clone().into())
|
||||
.collect();
|
||||
|
||||
self.validator_client.change_base_urls(rotated_urls)
|
||||
self.validator_client
|
||||
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
|
||||
}
|
||||
|
||||
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
|
||||
@@ -125,13 +99,8 @@ impl NymApiTopologyProvider {
|
||||
.filter(|n| n.performance.round_to_integer() >= self.config.min_node_performance())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
|
||||
NymTopology::new(
|
||||
metadata.to_topology_metadata(),
|
||||
epoch_rewarded_set,
|
||||
Vec::new(),
|
||||
)
|
||||
.with_skimmed_nodes(&nodes_filtered)
|
||||
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
|
||||
.with_skimmed_nodes(&nodes_filtered)
|
||||
} else {
|
||||
// if we're not using extended topology, we're only getting active set mixnodes and gateways
|
||||
|
||||
@@ -179,13 +148,8 @@ impl NymApiTopologyProvider {
|
||||
}
|
||||
}
|
||||
|
||||
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
|
||||
NymTopology::new(
|
||||
metadata.to_topology_metadata(),
|
||||
epoch_rewarded_set,
|
||||
Vec::new(),
|
||||
)
|
||||
.with_skimmed_nodes(&nodes)
|
||||
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
|
||||
.with_skimmed_nodes(&nodes)
|
||||
};
|
||||
|
||||
if !topology.is_minimally_routable() {
|
||||
|
||||
@@ -7,8 +7,7 @@ use futures::{SinkExt, StreamExt};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::{IdentityKeyRef, NymApiClientExt};
|
||||
use nym_validator_client::nym_nodes::SkimmedNodesWithMetadata;
|
||||
use nym_validator_client::client::IdentityKeyRef;
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
#[cfg(unix)]
|
||||
@@ -84,48 +83,6 @@ struct GatewayWithLatency<'a, G: ConnectableGateway> {
|
||||
latency: Duration,
|
||||
}
|
||||
|
||||
// Helper to collect all pages of entry nodes - replicates NymApiClient's convenience method
|
||||
async fn get_all_basic_entry_nodes_with_metadata(
|
||||
client: &nym_http_api_client::Client,
|
||||
use_bincode: bool,
|
||||
) -> Result<SkimmedNodesWithMetadata, ClientCoreError> {
|
||||
// Get first page to obtain metadata
|
||||
let mut page = 0;
|
||||
let res = client
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
// Collect remaining pages
|
||||
loop {
|
||||
let mut res = client
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
return Err(ClientCoreError::ValidatorClientError(
|
||||
nym_validator_client::ValidatorClientError::InconsistentPagedMetadata,
|
||||
));
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
fn new(gateway: &'a G, latency: Duration) -> Self {
|
||||
GatewayWithLatency { gateway, latency }
|
||||
@@ -142,30 +99,16 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
|
||||
// Use the unified HTTP client directly with optional user agent
|
||||
let mut builder = nym_http_api_client::Client::builder(nym_api.clone())
|
||||
.map_err(|e| {
|
||||
ClientCoreError::ValidatorClientError(
|
||||
nym_validator_client::ValidatorClientError::NymAPIError { source: e },
|
||||
)
|
||||
})?
|
||||
.with_bincode(); // Use bincode for better performance
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
let client = builder.build().map_err(|e| {
|
||||
ClientCoreError::ValidatorClientError(
|
||||
nym_validator_client::ValidatorClientError::NymAPIError { source: e },
|
||||
)
|
||||
})?;
|
||||
let client = if let Some(user_agent) = user_agent {
|
||||
nym_validator_client::client::NymApiClient::new_with_user_agent(nym_api.clone(), user_agent)
|
||||
} else {
|
||||
nym_validator_client::client::NymApiClient::new(nym_api.clone())
|
||||
};
|
||||
|
||||
tracing::debug!("Fetching list of gateways from: {nym_api}");
|
||||
|
||||
// Use our helper to handle pagination
|
||||
let gateways = get_all_basic_entry_nodes_with_metadata(&client, true)
|
||||
let gateways = client
|
||||
.get_all_basic_entry_assigned_nodes_with_metadata()
|
||||
.await?
|
||||
.nodes;
|
||||
info!("nym api reports {} gateways", gateways.len());
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::nyxd::{self, NyxdClient};
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use crate::signing::signer::{NoSigner, OfflineSigner};
|
||||
use crate::{
|
||||
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient,
|
||||
ValidatorClientError,
|
||||
nym_api, DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient,
|
||||
ReqwestRpcClient, ValidatorClientError,
|
||||
};
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -153,7 +153,7 @@ impl Config {
|
||||
pub struct Client<C, S = NoSigner> {
|
||||
// ideally they would have been read-only, but unfortunately rust doesn't have such features
|
||||
// #[deprecated(note = "please use `nym_api_client` instead")]
|
||||
pub nym_api: nym_http_api_client::Client,
|
||||
pub nym_api: nym_api::Client,
|
||||
// pub nym_api_client: NymApiClient,
|
||||
pub nyxd: NyxdClient<C, S>,
|
||||
}
|
||||
@@ -214,7 +214,7 @@ impl Client<ReqwestRpcClient> {
|
||||
|
||||
impl<C> Client<C> {
|
||||
pub fn new_with_rpc_client(config: Config, rpc_client: C) -> Self {
|
||||
let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
|
||||
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
|
||||
|
||||
Client {
|
||||
nym_api: nym_api_client,
|
||||
@@ -228,7 +228,7 @@ impl<C, S> Client<C, S> {
|
||||
where
|
||||
S: OfflineSigner,
|
||||
{
|
||||
let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
|
||||
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
|
||||
|
||||
Client {
|
||||
nym_api: nym_api_client,
|
||||
@@ -385,25 +385,38 @@ impl<C, S> Client<C, S> {
|
||||
}
|
||||
}
|
||||
|
||||
/// DEPRECATED: Use nym_http_api_client::Client with from_network() or with_bincode() instead
|
||||
#[deprecated(
|
||||
since = "1.2.0",
|
||||
note = "Use nym_http_api_client::Client::from_network() or ClientBuilder::with_bincode() instead"
|
||||
)]
|
||||
#[derive(Clone)]
|
||||
pub struct NymApiClient {
|
||||
pub use_bincode: bool,
|
||||
pub nym_api: nym_http_api_client::Client,
|
||||
pub nym_api: nym_api::Client,
|
||||
// TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
|
||||
// we could re-implement the communication with the REST API on port 1317
|
||||
}
|
||||
|
||||
impl From<nym_api::Client> for NymApiClient {
|
||||
fn from(nym_api: nym_api::Client) -> Self {
|
||||
NymApiClient {
|
||||
use_bincode: false,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
|
||||
#[allow(deprecated)]
|
||||
impl NymApiClient {
|
||||
pub fn new(api_url: Url) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, None);
|
||||
|
||||
NymApiClient {
|
||||
use_bincode: true,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
|
||||
let nym_api = nym_http_api_client::Client::new(api_url, Some(timeout));
|
||||
let nym_api = nym_api::Client::new(api_url, Some(timeout));
|
||||
|
||||
NymApiClient {
|
||||
use_bincode: true,
|
||||
@@ -418,10 +431,10 @@ impl NymApiClient {
|
||||
}
|
||||
|
||||
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
|
||||
let nym_api = nym_http_api_client::Client::builder(api_url)
|
||||
let nym_api = nym_api::Client::builder::<_, ValidatorClientError>(api_url)
|
||||
.expect("invalid api url")
|
||||
.with_user_agent(user_agent.into())
|
||||
.build()
|
||||
.build::<ValidatorClientError>()
|
||||
.expect("failed to build nym api client");
|
||||
|
||||
NymApiClient {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::NymApiClient;
|
||||
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
|
||||
use nym_coconut_dkg_common::verification_key::ContractVKShare;
|
||||
use nym_compact_ecash::error::CompactEcashError;
|
||||
@@ -14,7 +15,7 @@ use url::Url;
|
||||
// TODO: it really doesn't feel like this should live in this crate.
|
||||
#[derive(Clone)]
|
||||
pub struct EcashApiClient {
|
||||
pub api_client: nym_http_api_client::Client,
|
||||
pub api_client: NymApiClient,
|
||||
pub verification_key: VerificationKeyAuth,
|
||||
pub node_id: NodeIndex,
|
||||
pub cosmos_address: cosmrs::AccountId,
|
||||
@@ -24,10 +25,10 @@ impl Display for EcashApiClient {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[id: {}] {} @ {:?}",
|
||||
"[id: {}] {} @ {}",
|
||||
self.node_id,
|
||||
self.cosmos_address,
|
||||
self.api_client.base_urls()
|
||||
self.api_client.api_url()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -59,9 +60,6 @@ pub enum EcashApiError {
|
||||
source: CompactEcashError,
|
||||
},
|
||||
|
||||
#[error("failed to create API client: {0}")]
|
||||
ClientError(String),
|
||||
|
||||
#[error("the provided account address is malformed: {source}")]
|
||||
MalformedAccountAddress {
|
||||
#[from]
|
||||
@@ -91,13 +89,8 @@ impl TryFrom<ContractVKShare> for EcashApiClient {
|
||||
// In non-client applications this resolver can cause warning logs about H2 connection
|
||||
// failure. This indicates that the long lived https connection was closed by the remote
|
||||
// peer and the resolver will have to reconnect. It should not impact actual functionality
|
||||
let api_client = nym_http_api_client::Client::builder(url_address)
|
||||
.map_err(|e| EcashApiError::ClientError(e.to_string()))?
|
||||
.build()
|
||||
.map_err(|e| EcashApiError::ClientError(e.to_string()))?;
|
||||
|
||||
Ok(EcashApiClient {
|
||||
api_client,
|
||||
api_client: NymApiClient::new(url_address),
|
||||
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
|
||||
node_id: share.node_index,
|
||||
cosmos_address: share.owner.as_str().parse()?,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::nym_api::NymApiClientExt;
|
||||
use crate::nyxd::contract_traits::MixnetQueryClient;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::Config as ClientConfig;
|
||||
use crate::{QueryHttpRpcNyxdClient, ValidatorClientError};
|
||||
use crate::{NymApiClient, QueryHttpRpcNyxdClient, ValidatorClientError};
|
||||
use colored::Colorize;
|
||||
use core::fmt;
|
||||
use itertools::Itertools;
|
||||
@@ -88,17 +87,8 @@ fn setup_connection_tests<H: BuildHasher + 'static>(
|
||||
}
|
||||
});
|
||||
|
||||
let api_connection_test_clients = api_urls.filter_map(|(network, url)| {
|
||||
match nym_http_api_client::Client::builder(url.clone()).and_then(|b| b.build()) {
|
||||
Ok(client) => Some(ClientForConnectionTest::Api(network, url, client)),
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"Failed to create API client for {}: {err}",
|
||||
network.network_name
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
let api_connection_test_clients = api_urls.map(|(network, url)| {
|
||||
ClientForConnectionTest::Api(network, url.clone(), NymApiClient::new(url))
|
||||
});
|
||||
|
||||
nyxd_connection_test_clients.chain(api_connection_test_clients)
|
||||
@@ -170,7 +160,7 @@ async fn test_nyxd_connection(
|
||||
async fn test_nym_api_connection(
|
||||
network: NymNetworkDetails,
|
||||
url: &Url,
|
||||
client: &nym_http_api_client::Client,
|
||||
client: &NymApiClient,
|
||||
) -> ConnectionResult {
|
||||
let result = match timeout(
|
||||
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
|
||||
@@ -196,7 +186,7 @@ async fn test_nym_api_connection(
|
||||
|
||||
enum ClientForConnectionTest {
|
||||
Nyxd(NymNetworkDetails, Url, Box<QueryHttpRpcNyxdClient>),
|
||||
Api(NymNetworkDetails, Url, nym_http_api_client::Client),
|
||||
Api(NymNetworkDetails, Url, NymApiClient),
|
||||
}
|
||||
|
||||
impl ClientForConnectionTest {
|
||||
|
||||
@@ -14,6 +14,7 @@ pub mod signing;
|
||||
pub use crate::error::ValidatorClientError;
|
||||
pub use crate::rpc::reqwest::ReqwestRpcClient;
|
||||
pub use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
pub use client::NymApiClient;
|
||||
pub use client::{Client, Config, EcashApiClient};
|
||||
pub use nym_api_requests::*;
|
||||
pub use nym_http_api_client::UserAgent;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_api_requests::models::RequestError;
|
||||
use nym_http_api_client::HttpClientError;
|
||||
|
||||
pub type NymAPIError = HttpClientError;
|
||||
pub type NymAPIError = HttpClientError<RequestError>;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::nym_api::error::NymAPIError;
|
||||
use crate::nym_api::routes::{ecash, CORE_STATUS_COUNT, SINCE_ARG};
|
||||
use crate::nym_nodes::SkimmedNodesWithMetadata;
|
||||
use async_trait::async_trait;
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -38,7 +37,7 @@ pub use nym_api_requests::{
|
||||
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
},
|
||||
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata, SkimmedNode},
|
||||
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SkimmedNode},
|
||||
NymNetworkDetailsResponse,
|
||||
};
|
||||
use nym_contracts_common::IdentityKey;
|
||||
@@ -50,8 +49,8 @@ use time::format_description::BorrowedFormatItem;
|
||||
use time::Date;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::ValidatorClientError;
|
||||
pub use nym_coconut_dkg_common::types::EpochId;
|
||||
pub use nym_http_api_client::Client;
|
||||
|
||||
pub mod error;
|
||||
pub mod routes;
|
||||
@@ -63,9 +62,6 @@ pub fn rfc_3339_date() -> Vec<BorrowedFormatItem<'static>> {
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait NymApiClientExt: ApiClient {
|
||||
/// Get the current API URL being used by the client
|
||||
fn api_url(&self) -> &url::Url;
|
||||
|
||||
async fn health(&self) -> Result<ApiHealthResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
@@ -245,156 +241,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_current_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
|
||||
self.get_rewarded_set().await
|
||||
}
|
||||
|
||||
async fn get_all_basic_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
|
||||
// unroll first loop iteration in order to obtain the metadata
|
||||
let mut page = 0;
|
||||
let res = self
|
||||
.get_basic_nodes_v2(false, Some(page), None, true)
|
||||
.await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.get_basic_nodes_v2(false, Some(page), None, true)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
// Create a custom error for inconsistent metadata
|
||||
return Err(NymAPIError::EndpointFailure {
|
||||
status: reqwest::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
error: "Inconsistent paged metadata".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() >= res.nodes.pagination.total {
|
||||
break;
|
||||
} else {
|
||||
page += 1
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
async fn get_all_basic_active_mixing_assigned_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
|
||||
// Get all mixing nodes that are in the active/rewarded set
|
||||
let mut page = 0;
|
||||
let res = self
|
||||
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
let metadata = res.metadata;
|
||||
let mut nodes = res.nodes.data;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let res = self
|
||||
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
return Err(NymAPIError::EndpointFailure {
|
||||
status: reqwest::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
error: "Inconsistent paged metadata".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data.clone());
|
||||
|
||||
// Check if we've got all nodes
|
||||
if nodes.len() >= res.nodes.pagination.total {
|
||||
break;
|
||||
} else {
|
||||
page += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
async fn get_all_basic_entry_assigned_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
|
||||
// Get all nodes that can act as entry gateways
|
||||
let mut page = 0;
|
||||
let res = self
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
let metadata = res.metadata;
|
||||
let mut nodes = res.nodes.data;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let res = self
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
return Err(NymAPIError::EndpointFailure {
|
||||
status: reqwest::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
error: "Inconsistent paged metadata".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data.clone());
|
||||
|
||||
// Check if we've got all nodes
|
||||
if nodes.len() >= res.nodes.pagination.total {
|
||||
break;
|
||||
} else {
|
||||
page += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
async fn get_all_described_nodes(&self) -> Result<Vec<NymNodeDescription>, NymAPIError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut descriptions = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self.get_nodes_described(Some(page), None).await?;
|
||||
|
||||
descriptions.append(&mut res.data);
|
||||
if descriptions.len() < res.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(descriptions)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_nym_nodes(
|
||||
&self,
|
||||
@@ -422,25 +268,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_all_bonded_nym_nodes(&self) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut bonds = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self.get_nym_nodes(Some(page), None).await?;
|
||||
|
||||
bonds.append(&mut res.data);
|
||||
if bonds.len() < res.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bonds)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
@@ -1544,49 +1371,8 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Method to change the base API URLs being used by the client
|
||||
fn change_base_urls(&mut self, urls: Vec<url::Url>);
|
||||
|
||||
/// Retrieve expanded information for all bonded nodes on the network
|
||||
async fn get_all_expanded_nodes(&self) -> Result<SemiSkimmedNodesWithMetadata, NymAPIError> {
|
||||
// Unroll the first iteration to get the metadata
|
||||
let mut page = 0;
|
||||
|
||||
let res = self.get_expanded_nodes(false, Some(page), None).await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let mut res = self.get_expanded_nodes(false, Some(page), None).await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
}
|
||||
|
||||
// Client is already nym_http_api_client::Client (re-exported above), so just one impl needed
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl NymApiClientExt for nym_http_api_client::Client {
|
||||
fn api_url(&self) -> &url::Url {
|
||||
self.current_url().as_ref()
|
||||
}
|
||||
|
||||
fn change_base_urls(&mut self, urls: Vec<url::Url>) {
|
||||
self.change_base_urls(urls.into_iter().map(|u| u.into()).collect());
|
||||
}
|
||||
}
|
||||
impl NymApiClientExt for Client {}
|
||||
|
||||
+3
-14
@@ -28,7 +28,7 @@ use cosmrs::proto::cosmwasm::wasm::v1::{
|
||||
QueryRawContractStateResponse, QuerySmartContractStateRequest, QuerySmartContractStateResponse,
|
||||
};
|
||||
use cosmrs::tendermint::{block, chain, Hash};
|
||||
use cosmrs::{AccountId, Coin as CosmosCoin, Tx};
|
||||
use cosmrs::{AccountId, Coin as CosmosCoin};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -556,23 +556,12 @@ pub trait CosmWasmClient: TendermintRpcClient {
|
||||
Ok(serde_json::from_slice(&res.data)?)
|
||||
}
|
||||
|
||||
// deprecation warning is due to the fact the protobuf files built were based on cosmos-sdk 0.44,
|
||||
// where they prefer using tx_bytes directly. However, in 0.42, which we are using at the time
|
||||
// of writing this, the option does not work
|
||||
// TODO: we should really stop using the `tx` argument here and use `tx_bytes` exlusively,
|
||||
// however, at the time of writing this update, while our QA and mainnet networks do support it,
|
||||
// sandbox is still running old version of wasmd that lacks support for `tx_bytes`
|
||||
#[allow(deprecated)]
|
||||
async fn query_simulate(
|
||||
&self,
|
||||
tx: Option<Tx>,
|
||||
tx_bytes: Vec<u8>,
|
||||
) -> Result<SimulateResponse, NyxdError> {
|
||||
async fn query_simulate(&self, tx_bytes: Vec<u8>) -> Result<SimulateResponse, NyxdError> {
|
||||
let path = Some("/cosmos.tx.v1beta1.Service/Simulate".to_owned());
|
||||
|
||||
let req = SimulateRequest {
|
||||
tx: tx.map(Into::into),
|
||||
tx_bytes,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let res = self
|
||||
|
||||
+7
-10
@@ -81,17 +81,14 @@ where
|
||||
auth_info: single_unspecified_signer_auth(public_key, sequence_response.sequence),
|
||||
signatures: vec![Vec::new()],
|
||||
};
|
||||
self.query_simulate(Some(partial_tx), Vec::new()).await
|
||||
|
||||
// for completion sake, once we're able to transition into using `tx_bytes`,
|
||||
// we might want to use something like this instead:
|
||||
// let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
|
||||
// body_bytes: partial_tx.body.into_bytes().unwrap(),
|
||||
// auth_info_bytes: partial_tx.auth_info.into_bytes().unwrap(),
|
||||
// signatures: partial_tx.signatures,
|
||||
// }
|
||||
// .into();
|
||||
// self.query_simulate(None, tx_raw.to_bytes().unwrap()).await
|
||||
let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
|
||||
body_bytes: partial_tx.body.into_bytes()?,
|
||||
auth_info_bytes: partial_tx.auth_info.into_bytes()?,
|
||||
signatures: partial_tx.signatures,
|
||||
}
|
||||
.into();
|
||||
self.query_simulate(tx_raw.to_bytes()?).await
|
||||
}
|
||||
|
||||
async fn upload(
|
||||
|
||||
@@ -38,7 +38,6 @@ cosmrs = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::errors::ContextError;
|
||||
pub use nym_http_api_client::Client as NymApiClient;
|
||||
use nym_network_defaults::{
|
||||
setup_env,
|
||||
var_names::{MIXNET_CONTRACT_ADDRESS, NYM_API, NYXD, VESTING_CONTRACT_ADDRESS},
|
||||
NymNetworkDetails,
|
||||
};
|
||||
pub use nym_validator_client::nym_api::Client as NymApiClient;
|
||||
use nym_validator_client::nyxd::{self, AccountId, NyxdClient};
|
||||
use nym_validator_client::{
|
||||
DirectSigningHttpRpcNyxdClient, DirectSigningHttpRpcValidatorClient, QueryHttpRpcNyxdClient,
|
||||
|
||||
@@ -14,7 +14,7 @@ use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketB
|
||||
use nym_credentials_interface::Bandwidth;
|
||||
use nym_credentials_interface::{ClientTicket, TicketType};
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
|
||||
};
|
||||
@@ -354,7 +354,7 @@ impl CredentialHandler {
|
||||
Err(err) => {
|
||||
error!("failed to send ticket {ticket_id} for verification to ecash signer '{client}': {err}. if we don't reach quorum, we'll retry later");
|
||||
Err(EcashTicketError::ApiFailure(EcashApiError::NymApi {
|
||||
source: nym_validator_client::ValidatorClientError::NymAPIError { source: err },
|
||||
source: err,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ nym-ecash-time = { path = "../ecash-time", features = ["expiration"] }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
|
||||
@@ -15,7 +15,7 @@ use nym_credentials_interface::{
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_ecash_time::{ecash_default_expiration_date, ecash_today, EcashTime};
|
||||
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::Date;
|
||||
|
||||
@@ -116,7 +116,7 @@ impl IssuanceTicketBook {
|
||||
|
||||
pub async fn obtain_blinded_credential(
|
||||
&self,
|
||||
client: &nym_http_api_client::Client,
|
||||
client: &nym_validator_client::client::NymApiClient,
|
||||
request_body: &BlindSignRequestBody,
|
||||
) -> Result<BlindedSignature, Error> {
|
||||
let server_response = client.blind_sign(request_body).await?;
|
||||
@@ -179,7 +179,7 @@ impl IssuanceTicketBook {
|
||||
// ideally this would have been generic over credential type, but we really don't need secp256k1 keys for bandwidth vouchers
|
||||
pub async fn obtain_partial_ticketbook_credential(
|
||||
&self,
|
||||
client: &nym_http_api_client::Client,
|
||||
client: &nym_validator_client::client::NymApiClient,
|
||||
signer_index: u64,
|
||||
validator_vk: &VerificationKeyAuth,
|
||||
signing_data: CredentialSigningData,
|
||||
|
||||
@@ -10,7 +10,6 @@ use nym_credentials_interface::{
|
||||
VerificationKeyAuth, WalletSignatures,
|
||||
};
|
||||
use nym_validator_client::client::EcashApiClient;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
|
||||
// so we wouldn't break all the existing imports
|
||||
pub use nym_ecash_time::{cred_exp_date, ecash_date_offset, ecash_today, EcashTime};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::ecash::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
|
||||
use nym_credentials_interface::CompactEcashError;
|
||||
use nym_crypto::asymmetric::x25519::KeyRecoveryError;
|
||||
use nym_validator_client::{nym_api::error::NymAPIError, ValidatorClientError};
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -37,9 +37,6 @@ pub enum Error {
|
||||
#[error("Ran into a validator client error - {0}")]
|
||||
ValidatorClientError(#[from] ValidatorClientError),
|
||||
|
||||
#[error("Nym API request failed - {0}")]
|
||||
NymAPIError(#[from] NymAPIError),
|
||||
|
||||
#[error("Bandwidth operation overflowed. {0}")]
|
||||
BandwidthOverflow(String),
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ digest = { workspace = true, optional = true }
|
||||
generic-array = { workspace = true, optional = true }
|
||||
hkdf = { workspace = true, optional = true }
|
||||
hmac = { workspace = true, optional = true }
|
||||
jwt-simple = { workspace = true, optional = true }
|
||||
cipher = { workspace = true, optional = true }
|
||||
x25519-dalek = { workspace = true, features = ["static_secrets"], optional = true }
|
||||
ed25519-dalek = { workspace = true, features = ["rand_core"], optional = true }
|
||||
@@ -39,6 +40,7 @@ rand_chacha = { workspace = true }
|
||||
[features]
|
||||
default = []
|
||||
aead = ["dep:aead", "aead/std", "aes-gcm-siv", "generic-array"]
|
||||
naive_jwt = ["asymmetric", "jwt-simple"]
|
||||
serde = ["dep:serde", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
|
||||
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array", "sha2"]
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use ed25519_dalek::SignatureError;
|
||||
use ed25519_dalek::{SecretKey, Signer, SigningKey};
|
||||
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
|
||||
|
||||
use ed25519_dalek::Signer;
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
@@ -13,6 +14,9 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod serde_helpers;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
pub use serde_helpers::*;
|
||||
|
||||
#[cfg(feature = "sphinx")]
|
||||
use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
|
||||
|
||||
@@ -81,8 +85,8 @@ impl KeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_secret(secret: SecretKey, index: u32) -> Self {
|
||||
let ed25519_signing_key = SigningKey::from(secret);
|
||||
pub fn from_secret(secret: ed25519_dalek::SecretKey, index: u32) -> Self {
|
||||
let ed25519_signing_key = ed25519_dalek::SigningKey::from(secret);
|
||||
|
||||
KeyPair {
|
||||
private_key: PrivateKey(ed25519_signing_key.to_bytes()),
|
||||
@@ -276,7 +280,7 @@ impl Display for PrivateKey {
|
||||
|
||||
impl<'a> From<&'a PrivateKey> for PublicKey {
|
||||
fn from(pk: &'a PrivateKey) -> Self {
|
||||
PublicKey(SigningKey::from_bytes(&pk.0).verifying_key())
|
||||
PublicKey(ed25519_dalek::SigningKey::from_bytes(&pk.0).verifying_key())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +324,7 @@ impl PrivateKey {
|
||||
}
|
||||
|
||||
pub fn sign<M: AsRef<[u8]>>(&self, message: M) -> Signature {
|
||||
let signing_key: SigningKey = self.0.into();
|
||||
let signing_key: ed25519_dalek::SigningKey = self.0.into();
|
||||
let sig = signing_key.sign(message.as_ref());
|
||||
Signature(sig)
|
||||
}
|
||||
@@ -425,9 +429,57 @@ impl<'d> Deserialize<'d> for Signature {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl PublicKey {
|
||||
pub fn to_jwt_compatible_key(&self) -> jwt_simple::algorithms::Ed25519PublicKey {
|
||||
(*self).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl From<PublicKey> for jwt_simple::algorithms::Ed25519PublicKey {
|
||||
fn from(value: PublicKey) -> Self {
|
||||
// SAFETY: we have a valid ed25519 pubkey, we're just changing to a different library wrapper
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_simple::algorithms::Ed25519PublicKey::from_bytes(&value.to_bytes()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl PrivateKey {
|
||||
pub fn to_jwt_compatible_keys(&self) -> jwt_simple::algorithms::Ed25519KeyPair {
|
||||
let pub_key = self.public_key();
|
||||
let mut bytes = zeroize::Zeroizing::new([0u8; 64]);
|
||||
|
||||
bytes[..SECRET_KEY_LENGTH]
|
||||
.copy_from_slice(zeroize::Zeroizing::new(self.to_bytes()).as_ref());
|
||||
bytes[SECRET_KEY_LENGTH..].copy_from_slice(&pub_key.to_bytes());
|
||||
|
||||
// SAFETY: we have a valid ed25519 keys, we're just changing to a different library wrapper
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_simple::algorithms::Ed25519KeyPair::from_bytes(bytes.as_ref()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "naive_jwt")]
|
||||
impl KeyPair {
|
||||
pub fn to_jwt_compatible_keys(&self) -> jwt_simple::algorithms::Ed25519KeyPair {
|
||||
let mut bytes = zeroize::Zeroizing::new([0u8; 64]);
|
||||
|
||||
bytes[..SECRET_KEY_LENGTH]
|
||||
.copy_from_slice(zeroize::Zeroizing::new(self.private_key.to_bytes()).as_ref());
|
||||
bytes[SECRET_KEY_LENGTH..].copy_from_slice(&self.public_key.to_bytes());
|
||||
|
||||
// SAFETY: we have a valid ed25519 keys, we're just changing to a different library wrapper
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_simple::algorithms::Ed25519KeyPair::from_bytes(bytes.as_ref()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rand::thread_rng;
|
||||
|
||||
fn assert_zeroize_on_drop<T: ZeroizeOnDrop>() {}
|
||||
|
||||
@@ -438,4 +490,29 @@ mod tests {
|
||||
assert_zeroize::<PrivateKey>();
|
||||
assert_zeroize_on_drop::<PrivateKey>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "naive_jwt", feature = "rand"))]
|
||||
fn check_jwt_key_compat_conversion() {
|
||||
let mut rng = thread_rng();
|
||||
let keys = KeyPair::new(&mut rng);
|
||||
let jwt_keys = keys.to_jwt_compatible_keys();
|
||||
|
||||
// internally they're represented by hidden `Edwards25519KeyPair` (plus key_id)
|
||||
// which has way nicer API for assertions
|
||||
let jwt_keys_inner =
|
||||
jwt_simple::algorithms::Edwards25519KeyPair::from_bytes(&jwt_keys.to_bytes()).unwrap();
|
||||
|
||||
let compact_ed25519 = jwt_keys_inner.as_ref();
|
||||
assert!(compact_ed25519
|
||||
.sk
|
||||
.validate_public_key(&compact_ed25519.pk)
|
||||
.is_ok());
|
||||
|
||||
let dummy_message = "hello world";
|
||||
let sig1 = keys.private_key.sign(dummy_message).to_bytes();
|
||||
let sig2 = compact_ed25519.sk.sign(dummy_message, None).to_vec();
|
||||
|
||||
assert_eq!(sig1.to_vec(), sig2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,14 @@ impl<LS, TS, LC, TC> SignerResult<LS, TS, LC, TC> {
|
||||
pub fn malformed_details(&self) -> bool {
|
||||
self.information.parse().is_err()
|
||||
}
|
||||
|
||||
pub fn try_get_test_result(&self) -> Option<&SignerTestResult<LS, TS, LC, TC>> {
|
||||
if let SignerStatus::Tested { result } = &self.status {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<LS, TS, LC, TC> SignerResult<LS, TS, LC, TC>
|
||||
|
||||
@@ -22,7 +22,6 @@ url = { workspace = true }
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-ecash-signer-check-types = { path = "../ecash-signer-check-types" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{LocalChainStatus, SignerCheckError, SigningStatus, TypedSignerResult};
|
||||
use crate::{LocalChainStatus, SigningStatus, TypedSignerResult};
|
||||
use nym_ecash_signer_check_types::dealer_information::RawDealerInformation;
|
||||
use nym_ecash_signer_check_types::status::{SignerStatus, SignerTestResult};
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::models::BinaryBuildInformationOwned;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::{
|
||||
ContractVKShare, DealerDetails,
|
||||
};
|
||||
use nym_validator_client::NymApiClient;
|
||||
use std::time::Duration;
|
||||
use tracing::{error, warn};
|
||||
use url::Url;
|
||||
@@ -31,38 +32,37 @@ pub(crate) mod signing_status {
|
||||
}
|
||||
|
||||
struct ClientUnderTest {
|
||||
api_client: nym_http_api_client::Client,
|
||||
api_client: NymApiClient,
|
||||
build_info: Option<BinaryBuildInformationOwned>,
|
||||
}
|
||||
|
||||
impl ClientUnderTest {
|
||||
pub(crate) fn new(api_url: &Url) -> Result<Self, SignerCheckError> {
|
||||
// The builder should not fail with a valid URL that's already parsed
|
||||
// If it does fail, it's an internal error that we can't recover from
|
||||
let api_client = nym_http_api_client::Client::builder(api_url.clone())?.build()?;
|
||||
|
||||
Ok(ClientUnderTest {
|
||||
api_client,
|
||||
pub(crate) fn new(api_url: &Url) -> Self {
|
||||
ClientUnderTest {
|
||||
api_client: NymApiClient::new(api_url.clone()),
|
||||
build_info: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn try_retrieve_build_information(&mut self) -> bool {
|
||||
match tokio::time::timeout(Duration::from_secs(5), self.api_client.build_information())
|
||||
.await
|
||||
match tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
self.api_client.nym_api.build_information(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(build_information)) => {
|
||||
self.build_info = Some(build_information);
|
||||
true
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.current_url());
|
||||
warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.api_url());
|
||||
false
|
||||
}
|
||||
Err(_timeout) => {
|
||||
warn!(
|
||||
"{}: timed out while attempting to retrieve build information",
|
||||
self.api_client.current_url()
|
||||
self.api_client.api_url()
|
||||
);
|
||||
false
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl ClientUnderTest {
|
||||
.inspect_err(|err| {
|
||||
error!(
|
||||
"ecash signer '{}' reports invalid version {}: {err}",
|
||||
self.api_client.current_url(),
|
||||
self.api_client.api_url(),
|
||||
build_info.build_version
|
||||
)
|
||||
})
|
||||
@@ -121,14 +121,14 @@ impl ClientUnderTest {
|
||||
|
||||
// check if it supports the current query
|
||||
if self.supports_chain_status_query() {
|
||||
return match self.api_client.get_chain_blocks_status().await {
|
||||
return match self.api_client.nym_api.get_chain_blocks_status().await {
|
||||
Ok(status) => LocalChainStatus::Reachable {
|
||||
response: Box::new(status),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve local chain status: {err}",
|
||||
self.api_client.current_url()
|
||||
self.api_client.api_url()
|
||||
);
|
||||
LocalChainStatus::Unreachable
|
||||
}
|
||||
@@ -136,14 +136,14 @@ impl ClientUnderTest {
|
||||
}
|
||||
|
||||
// fallback to the legacy query
|
||||
match self.api_client.get_chain_status().await {
|
||||
match self.api_client.nym_api.get_chain_status().await {
|
||||
Ok(status) => LocalChainStatus::ReachableLegacy {
|
||||
response: Box::new(status),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve [legacy] local chain status: {err}",
|
||||
self.api_client.current_url()
|
||||
self.api_client.api_url()
|
||||
);
|
||||
LocalChainStatus::Unreachable
|
||||
}
|
||||
@@ -158,14 +158,14 @@ impl ClientUnderTest {
|
||||
|
||||
// check if it supports the current query
|
||||
if self.supports_signing_status_query() {
|
||||
return match self.api_client.get_signer_status().await {
|
||||
return match self.api_client.nym_api.get_signer_status().await {
|
||||
Ok(response) => SigningStatus::Reachable {
|
||||
response: Box::new(response),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve signer chain status: {err}",
|
||||
self.api_client.current_url()
|
||||
self.api_client.api_url()
|
||||
);
|
||||
SigningStatus::Unreachable
|
||||
}
|
||||
@@ -173,14 +173,14 @@ impl ClientUnderTest {
|
||||
}
|
||||
|
||||
// fallback to the legacy query
|
||||
match self.api_client.get_signer_information().await {
|
||||
match self.api_client.nym_api.get_signer_information().await {
|
||||
Ok(status) => SigningStatus::ReachableLegacy {
|
||||
response: Box::new(status),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve [legacy] signer chain status: {err}",
|
||||
self.api_client.current_url()
|
||||
self.api_client.api_url()
|
||||
);
|
||||
// NOTE: this might equally mean the signing is disabled
|
||||
SigningStatus::Unreachable
|
||||
@@ -201,13 +201,7 @@ pub(crate) async fn check_client(
|
||||
return SignerStatus::ProvidedInvalidDetails.with_details(dealer_information, dkg_epoch);
|
||||
};
|
||||
|
||||
let mut client = match ClientUnderTest::new(&parsed_information.announce_address) {
|
||||
Ok(client) => client,
|
||||
Err(err) => {
|
||||
error!("failed to create client instance: {err}");
|
||||
return SignerStatus::Unreachable.with_details(dealer_information, dkg_epoch);
|
||||
}
|
||||
};
|
||||
let mut client = ClientUnderTest::new(&parsed_information.announce_address);
|
||||
|
||||
// 8. check basic connection status - can you retrieve build information?
|
||||
if !client.try_retrieve_build_information().await {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_http_api_client::HttpClientError;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -12,12 +11,6 @@ pub enum SignerCheckError {
|
||||
|
||||
#[error("failed to query the DKG contract: {source}")]
|
||||
DKGContractQueryFailure { source: NyxdError },
|
||||
|
||||
#[error("failed to build client: {source}")]
|
||||
HttpClient {
|
||||
#[from]
|
||||
source: HttpClientError,
|
||||
},
|
||||
}
|
||||
|
||||
impl SignerCheckError {
|
||||
|
||||
@@ -13,8 +13,6 @@ license.workspace = true
|
||||
[features]
|
||||
default=["tunneling"]
|
||||
tunneling=[]
|
||||
network-defaults = ["dep:nym-network-defaults"]
|
||||
force-ipv4 = []
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
@@ -25,8 +23,6 @@ url = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true}
|
||||
serde_plain = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
@@ -38,7 +34,6 @@ mime = { workspace = true }
|
||||
|
||||
nym-http-api-common = { path = "../http-api-common", default-features = false }
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
nym-network-defaults = { path = "../network-defaults", optional = true }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
hickory-resolver = { workspace = true, features = ["https-ring", "tls-ring", "webpki-roots"] }
|
||||
@@ -51,4 +46,3 @@ features = ["tokio"]
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
|
||||
|
||||
|
||||
@@ -54,14 +54,10 @@ impl Front {
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
#[cfg(feature = "tunneling")]
|
||||
/// Policy for when to use domain fronting for HTTP requests.
|
||||
pub enum FrontPolicy {
|
||||
/// Always use domain fronting for all requests.
|
||||
Always,
|
||||
/// Only use domain fronting when retrying failed requests.
|
||||
OnRetry,
|
||||
#[default]
|
||||
/// Never use domain fronting.
|
||||
Off,
|
||||
}
|
||||
|
||||
@@ -100,14 +96,14 @@ mod tests {
|
||||
// Some(vec!["https://cdn77.com"]),
|
||||
// ).unwrap(); // cdn77
|
||||
|
||||
let client = ClientBuilder::new(url1)
|
||||
let client = ClientBuilder::new::<_, &str>(url1)
|
||||
.expect("bad url")
|
||||
.with_fronting(FrontPolicy::Always)
|
||||
.build()
|
||||
.build::<&str>()
|
||||
.expect("failed to build client");
|
||||
|
||||
let response = client
|
||||
.send_request::<_, (), &str, &str>(
|
||||
.send_request::<_, (), &str, &str, &str>(
|
||||
reqwest::Method::GET,
|
||||
&["api", "v1", "network", "details"],
|
||||
NO_PARAMS,
|
||||
|
||||
+102
-243
@@ -15,7 +15,7 @@
|
||||
//! # use url::Url;
|
||||
//! # use nym_http_api_client::{ApiClient, NO_PARAMS, HttpClientError};
|
||||
//!
|
||||
//! # type Err = HttpClientError;
|
||||
//! # type Err = HttpClientError<String>;
|
||||
//! # async fn run() -> Result<(), Err> {
|
||||
//! let url: Url = "https://nymvpn.com".parse()?;
|
||||
//! let client = nym_http_api_client::Client::new(url, None);
|
||||
@@ -114,7 +114,7 @@
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! pub type SpecificAPIError = HttpClientError;
|
||||
//! pub type SpecificAPIError = HttpClientError<error::RequestError>;
|
||||
//!
|
||||
//! #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
//! #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
@@ -142,7 +142,7 @@ pub use reqwest::StatusCode;
|
||||
use crate::path::RequestPath;
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use http::header::{ACCEPT, CONTENT_TYPE};
|
||||
use http::header::CONTENT_TYPE;
|
||||
use http::HeaderMap;
|
||||
use itertools::Itertools;
|
||||
use mime::Mime;
|
||||
@@ -151,7 +151,6 @@ use reqwest::{RequestBuilder, Response};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Display;
|
||||
use std::net::IpAddr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
@@ -159,13 +158,10 @@ use tracing::{debug, instrument, warn};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::net::SocketAddr;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "tunneling")]
|
||||
mod fronted;
|
||||
#[cfg(feature = "tunneling")]
|
||||
pub use fronted::FrontPolicy;
|
||||
mod url;
|
||||
pub use url::{IntoUrl, Url};
|
||||
mod user_agent;
|
||||
@@ -196,46 +192,10 @@ pub type Params<'a, K, V> = &'a [(K, V)];
|
||||
/// Empty collection of HTTP Request Parameters.
|
||||
pub const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
|
||||
|
||||
/// Serialization format for API requests and responses
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SerializationFormat {
|
||||
/// Use JSON serialization (default, always works)
|
||||
Json,
|
||||
/// Use bincode serialization (must be explicitly opted into)
|
||||
Bincode,
|
||||
/// Use YAML serialization
|
||||
Yaml,
|
||||
/// Use Text serialization
|
||||
Text,
|
||||
}
|
||||
|
||||
impl Display for SerializationFormat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SerializationFormat::Json => write!(f, "json"),
|
||||
SerializationFormat::Bincode => write!(f, "bincode"),
|
||||
SerializationFormat::Yaml => write!(f, "yaml"),
|
||||
SerializationFormat::Text => write!(f, "text"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializationFormat {
|
||||
#[allow(missing_docs)]
|
||||
pub fn content_type(&self) -> String {
|
||||
match self {
|
||||
SerializationFormat::Json => "application/json".to_string(),
|
||||
SerializationFormat::Bincode => "application/bincode".to_string(),
|
||||
SerializationFormat::Yaml => "application/yaml".to_string(),
|
||||
SerializationFormat::Text => "text/plain".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Errors that may occur when creating or using an HTTP client.
|
||||
#[derive(Debug, Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum HttpClientError {
|
||||
pub enum HttpClientError<E: Display = String> {
|
||||
#[error("there was an issue with the REST request: {source}")]
|
||||
ReqwestClientError {
|
||||
#[from]
|
||||
@@ -264,23 +224,11 @@ pub enum HttpClientError {
|
||||
EmptyResponse { status: StatusCode },
|
||||
|
||||
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
|
||||
EndpointFailure { status: StatusCode, error: String },
|
||||
EndpointFailure { status: StatusCode, error: E },
|
||||
|
||||
#[error("failed to decode response body: {message} from {content}")]
|
||||
ResponseDecodeFailure { message: String, content: String },
|
||||
|
||||
#[error("Failed to encode bincode: {0}")]
|
||||
Bincode(#[from] bincode::Error),
|
||||
|
||||
#[error("Failed to json: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[error("Failed to yaml: {0}")]
|
||||
Yaml(#[from] serde_yaml::Error),
|
||||
|
||||
#[error("Failed to plain: {0}")]
|
||||
Plain(#[from] serde_plain::Error),
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[error("the request has timed out")]
|
||||
RequestTimeout,
|
||||
@@ -322,8 +270,8 @@ pub trait ApiClientCore {
|
||||
method: reqwest::Method,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
body: Option<&B>,
|
||||
) -> Result<RequestBuilder, HttpClientError>
|
||||
json_body: Option<&B>,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
B: Serialize + ?Sized,
|
||||
@@ -349,8 +297,8 @@ pub trait ApiClientCore {
|
||||
&self,
|
||||
method: reqwest::Method,
|
||||
endpoint: S,
|
||||
body: Option<&B>,
|
||||
) -> Result<RequestBuilder, HttpClientError>
|
||||
json_body: Option<&B>,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
S: AsRef<str>,
|
||||
@@ -376,7 +324,7 @@ pub trait ApiClientCore {
|
||||
};
|
||||
let params: Vec<(String, String)> = standin_url.query_pairs().into_owned().collect();
|
||||
|
||||
self.create_request(method, path.as_slice(), ¶ms, body)
|
||||
self.create_request(method, path.as_slice(), ¶ms, json_body)
|
||||
}
|
||||
|
||||
/// Send a created HTTP request.
|
||||
@@ -384,23 +332,26 @@ pub trait ApiClientCore {
|
||||
/// A [`RequestBuilder`] can be created with [`ApiClientCore::create_request`] or
|
||||
/// [`ApiClientCore::create_request_endpoint`] or if absolutely necessary, using reqwest
|
||||
/// tooling directly.
|
||||
async fn send(&self, request: RequestBuilder) -> Result<Response, HttpClientError>;
|
||||
async fn send<E>(&self, request: RequestBuilder) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
E: Display;
|
||||
|
||||
/// Create and send a created HTTP request.
|
||||
async fn send_request<P, B, K, V>(
|
||||
async fn send_request<P, B, K, V, E>(
|
||||
&self,
|
||||
method: reqwest::Method,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: Option<&B>,
|
||||
) -> Result<Response, HttpClientError>
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
B: Serialize + ?Sized + Sync,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display,
|
||||
{
|
||||
let req = self.create_request(method, path, params, json_body)?;
|
||||
let req = self.create_request(method, path, params, json_body);
|
||||
self.send(req).await
|
||||
}
|
||||
}
|
||||
@@ -420,16 +371,16 @@ pub struct ClientBuilder {
|
||||
front: Option<fronted::Front>,
|
||||
|
||||
retry_limit: usize,
|
||||
serialization: SerializationFormat,
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
/// Constructs a new `ClientBuilder`.
|
||||
///
|
||||
/// This is the same as `Client::builder()`.
|
||||
pub fn new<U>(url: U) -> Result<Self, HttpClientError>
|
||||
pub fn new<U, E>(url: U) -> Result<Self, HttpClientError<E>>
|
||||
where
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
let str_url = url.as_str();
|
||||
|
||||
@@ -445,50 +396,6 @@ impl ClientBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a client builder from network details with sensible defaults
|
||||
#[cfg(feature = "network-defaults")]
|
||||
pub fn from_network(
|
||||
network: &nym_network_defaults::NymNetworkDetails,
|
||||
) -> Result<Self, HttpClientError> {
|
||||
let urls = network
|
||||
.nym_api_urls
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
HttpClientError::GenericRequestFailure(
|
||||
"No API URLs configured in network details".to_string(),
|
||||
)
|
||||
})?
|
||||
.iter()
|
||||
.map(|api_url| {
|
||||
// Convert ApiUrl to our Url type with fronting support
|
||||
let mut url = Url::parse(&api_url.url)?;
|
||||
|
||||
// Add fronting domains if available
|
||||
#[cfg(feature = "tunneling")]
|
||||
if let Some(ref front_hosts) = api_url.front_hosts {
|
||||
let fronts: Vec<String> = front_hosts
|
||||
.iter()
|
||||
.map(|host| format!("https://{}", host))
|
||||
.collect();
|
||||
url = Url::new(api_url.url.clone(), Some(fronts))
|
||||
.map_err(|e| HttpClientError::GenericRequestFailure(e.to_string()))?;
|
||||
}
|
||||
|
||||
Ok(url)
|
||||
})
|
||||
.collect::<Result<Vec<_>, HttpClientError>>()?;
|
||||
|
||||
let mut builder = Self::new_with_urls(urls);
|
||||
|
||||
// Enable domain fronting by default (on retry)
|
||||
#[cfg(feature = "tunneling")]
|
||||
{
|
||||
builder = builder.with_fronting(FrontPolicy::OnRetry);
|
||||
}
|
||||
|
||||
Ok(builder)
|
||||
}
|
||||
|
||||
/// Constructs a new http `ClientBuilder` from a valid url.
|
||||
pub fn new_with_urls(urls: Vec<Url>) -> Self {
|
||||
let urls = Self::check_urls(urls);
|
||||
@@ -496,8 +403,6 @@ impl ClientBuilder {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client_builder = reqwest::ClientBuilder::new();
|
||||
|
||||
warn!("FORCING IPv4 CONNECTIONS");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client_builder = {
|
||||
// Note: I believe the manual enable calls for the compression methods are extra
|
||||
@@ -514,9 +419,6 @@ impl ClientBuilder {
|
||||
.zstd(true)
|
||||
};
|
||||
|
||||
#[cfg(feature = "force-ipv4")]
|
||||
let reqwest_client_builder = reqwest_client_builder.local_address(IpAddr::from_str("0.0.0.0").unwrap());
|
||||
|
||||
ClientBuilder {
|
||||
urls,
|
||||
timeout: None,
|
||||
@@ -527,7 +429,6 @@ impl ClientBuilder {
|
||||
front: None,
|
||||
|
||||
retry_limit: 0,
|
||||
serialization: SerializationFormat::Json,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,19 +501,11 @@ impl ClientBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the serialization format for API requests and responses
|
||||
pub fn with_serialization(mut self, format: SerializationFormat) -> Self {
|
||||
self.serialization = format;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure the client to use bincode serialization
|
||||
pub fn with_bincode(self) -> Self {
|
||||
self.with_serialization(SerializationFormat::Bincode)
|
||||
}
|
||||
|
||||
/// Returns a Client that uses this ClientBuilder configuration.
|
||||
pub fn build(self) -> Result<Client, HttpClientError> {
|
||||
pub fn build<E>(self) -> Result<Client, HttpClientError<E>>
|
||||
where
|
||||
E: Display,
|
||||
{
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client = self.reqwest_client_builder.build()?;
|
||||
|
||||
@@ -649,7 +542,6 @@ impl ClientBuilder {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
request_timeout: self.timeout.unwrap_or(DEFAULT_TIMEOUT),
|
||||
retry_limit: self.retry_limit,
|
||||
serialization: self.serialization,
|
||||
};
|
||||
|
||||
Ok(client)
|
||||
@@ -670,7 +562,6 @@ pub struct Client {
|
||||
request_timeout: Duration,
|
||||
|
||||
retry_limit: usize,
|
||||
serialization: SerializationFormat,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@@ -680,15 +571,16 @@ impl Client {
|
||||
// In order to prevent interference in API requests at the DNS phase we default to a resolver
|
||||
// that uses DoT and DoH.
|
||||
pub fn new(base_url: ::url::Url, timeout: Option<Duration>) -> Self {
|
||||
Self::new_url(base_url, timeout).expect(
|
||||
Self::new_url::<_, String>(base_url, timeout).expect(
|
||||
"we provided valid url and we were unwrapping previous construction errors anyway",
|
||||
)
|
||||
}
|
||||
|
||||
/// Attempt to create a new http client from a something that can be converted to a URL
|
||||
pub fn new_url<U>(url: U, timeout: Option<Duration>) -> Result<Self, HttpClientError>
|
||||
pub fn new_url<U, E>(url: U, timeout: Option<Duration>) -> Result<Self, HttpClientError<E>>
|
||||
where
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
let builder = Self::builder(url)?;
|
||||
match timeout {
|
||||
@@ -700,9 +592,10 @@ impl Client {
|
||||
/// Creates a [`ClientBuilder`] to configure a [`Client`].
|
||||
///
|
||||
/// This is the same as [`ClientBuilder::new()`].
|
||||
pub fn builder<U>(url: U) -> Result<ClientBuilder, HttpClientError>
|
||||
pub fn builder<U, E>(url: U) -> Result<ClientBuilder, HttpClientError<E>>
|
||||
where
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
ClientBuilder::new(url)
|
||||
}
|
||||
@@ -726,7 +619,6 @@ impl Client {
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
request_timeout: self.request_timeout,
|
||||
serialization: self.serialization,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -802,42 +694,26 @@ impl Client {
|
||||
/// this method. For example, if the client is configured to rotate hosts after each error, this
|
||||
/// method should be called after the host has been updated -- i.e. as part of the subsequent
|
||||
/// send.
|
||||
fn apply_hosts_to_req(&self, r: &mut reqwest::Request) -> (&str, Option<&str>) {
|
||||
fn apply_hosts_to_req(&self, r: &mut reqwest::Request) {
|
||||
let url = self.current_url();
|
||||
r.url_mut().set_host(url.host_str()).unwrap();
|
||||
|
||||
#[cfg(feature = "tunneling")]
|
||||
if let Some(ref front) = self.front {
|
||||
if front.is_enabled() {
|
||||
if let Some(front_host) = url.front_str() {
|
||||
if let Some(actual_host) = url.host_str() {
|
||||
tracing::debug!(
|
||||
"Domain fronting enabled: routing via CDN {} to actual host {}",
|
||||
front_host,
|
||||
actual_host
|
||||
);
|
||||
// this should never fail as we are transplanting the host from one url to another
|
||||
r.url_mut().set_host(url.front_str()).unwrap();
|
||||
|
||||
// this should never fail as we are transplanting the host from one url to another
|
||||
r.url_mut().set_host(Some(front_host)).unwrap();
|
||||
|
||||
let actual_host_header: HeaderValue =
|
||||
actual_host.parse().unwrap_or(HeaderValue::from_static(""));
|
||||
// If the map did have this key present, the new value is associated with the key
|
||||
// and all previous values are removed. (reqwest HeaderMap docs)
|
||||
_ = r
|
||||
.headers_mut()
|
||||
.insert(reqwest::header::HOST, actual_host_header);
|
||||
|
||||
return (url.as_str(), url.front_str());
|
||||
} else {
|
||||
warn!("Domain fronting is enabled, but no host_url is defined! Domain fronting WILL NOT WORK")
|
||||
}
|
||||
} else {
|
||||
warn!("Domain fronting is enabled, but no front_url is defined! Domain fronting WILL NOT WORK")
|
||||
}
|
||||
let actual_host: HeaderValue = url
|
||||
.host_str()
|
||||
.unwrap_or("")
|
||||
.parse()
|
||||
.unwrap_or(HeaderValue::from_static(""));
|
||||
// If the map did have this key present, the new value is associated with the key
|
||||
// and all previous values are removed. (reqwest HeaderMap docs)
|
||||
_ = r.headers_mut().insert(reqwest::header::HOST, actual_host);
|
||||
}
|
||||
}
|
||||
(url.as_str(), None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -850,8 +726,8 @@ impl ApiClientCore for Client {
|
||||
method: reqwest::Method,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
body: Option<&B>,
|
||||
) -> Result<RequestBuilder, HttpClientError>
|
||||
json_body: Option<&B>,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
B: Serialize + ?Sized,
|
||||
@@ -867,35 +743,17 @@ impl ApiClientCore for Client {
|
||||
|
||||
let mut rb = RequestBuilder::from_parts(self.reqwest_client.clone(), req);
|
||||
|
||||
rb = rb
|
||||
.header(ACCEPT, self.serialization.content_type())
|
||||
.header(CONTENT_TYPE, self.serialization.content_type());
|
||||
|
||||
if let Some(body) = body {
|
||||
match self.serialization {
|
||||
SerializationFormat::Json => {
|
||||
rb = rb.json(body);
|
||||
}
|
||||
SerializationFormat::Bincode => {
|
||||
let body = bincode::serialize(body)?;
|
||||
rb = rb.body(body);
|
||||
}
|
||||
SerializationFormat::Yaml => {
|
||||
let mut body_bytes = Vec::new();
|
||||
serde_yaml::to_writer(&mut body_bytes, &body)?;
|
||||
rb = rb.body(body_bytes);
|
||||
}
|
||||
SerializationFormat::Text => {
|
||||
let body = serde_plain::to_string(&body)?.as_bytes().to_vec();
|
||||
rb = rb.body(body);
|
||||
}
|
||||
}
|
||||
if let Some(body) = json_body {
|
||||
rb = rb.json(body);
|
||||
}
|
||||
|
||||
Ok(rb)
|
||||
rb
|
||||
}
|
||||
|
||||
async fn send(&self, request: RequestBuilder) -> Result<Response, HttpClientError> {
|
||||
async fn send<E>(&self, request: RequestBuilder) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
E: Display,
|
||||
{
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
// try_clone may fail if the body is a stream in which case using retries is not advised.
|
||||
@@ -911,7 +769,7 @@ impl ApiClientCore for Client {
|
||||
self.apply_hosts_to_req(&mut req);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let response: Result<Response, HttpClientError> = {
|
||||
let response: Result<Response, HttpClientError<E>> = {
|
||||
Ok(wasmtimer::tokio::timeout(
|
||||
self.request_timeout,
|
||||
self.reqwest_client.execute(req),
|
||||
@@ -933,14 +791,7 @@ impl ApiClientCore for Client {
|
||||
if let Some(ref front) = self.front {
|
||||
// If fronting is set to be enabled on error, enable domain fronting as we
|
||||
// have encountered an error.
|
||||
let was_enabled = front.is_enabled();
|
||||
front.retry_enable();
|
||||
if !was_enabled && front.is_enabled() {
|
||||
tracing::info!(
|
||||
"Domain fronting activated after connection failure: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if attempts < self.retry_limit {
|
||||
@@ -965,11 +816,7 @@ impl ApiClientCore for Client {
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait ApiClient: ApiClientCore {
|
||||
/// Create an HTTP GET Request with the provided path and parameters
|
||||
fn create_get_request<P, K, V>(
|
||||
&self,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<RequestBuilder, HttpClientError>
|
||||
fn create_get_request<P, K, V>(&self, path: P, params: Params<'_, K, V>) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
K: AsRef<str>,
|
||||
@@ -984,7 +831,7 @@ pub trait ApiClient: ApiClientCore {
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<RequestBuilder, HttpClientError>
|
||||
) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
B: Serialize + ?Sized,
|
||||
@@ -995,11 +842,7 @@ pub trait ApiClient: ApiClientCore {
|
||||
}
|
||||
|
||||
/// Create an HTTP DELETE Request with the provided path and parameters
|
||||
fn create_delete_request<P, K, V>(
|
||||
&self,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<RequestBuilder, HttpClientError>
|
||||
fn create_delete_request<P, K, V>(&self, path: P, params: Params<'_, K, V>) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
K: AsRef<str>,
|
||||
@@ -1014,7 +857,7 @@ pub trait ApiClient: ApiClientCore {
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<RequestBuilder, HttpClientError>
|
||||
) -> RequestBuilder
|
||||
where
|
||||
P: RequestPath,
|
||||
B: Serialize + ?Sized,
|
||||
@@ -1026,64 +869,68 @@ pub trait ApiClient: ApiClientCore {
|
||||
|
||||
/// Create and send an HTTP GET Request with the provided path and parameters
|
||||
#[instrument(level = "debug", skip_all, fields(path=?path))]
|
||||
async fn send_get_request<P, K, V>(
|
||||
async fn send_get_request<P, K, V, E>(
|
||||
&self,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<Response, HttpClientError>
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display,
|
||||
{
|
||||
self.send_request(reqwest::Method::GET, path, params, None::<&()>)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Create and send an HTTP POST Request with the provided path, parameters, and json data
|
||||
async fn send_post_request<P, B, K, V>(
|
||||
async fn send_post_request<P, B, K, V, E>(
|
||||
&self,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<Response, HttpClientError>
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
B: Serialize + ?Sized + Sync,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display,
|
||||
{
|
||||
self.send_request(reqwest::Method::POST, path, params, Some(json_body))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Create and send an HTTP DELETE Request with the provided path and parameters
|
||||
async fn send_delete_request<P, K, V>(
|
||||
async fn send_delete_request<P, K, V, E>(
|
||||
&self,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<Response, HttpClientError>
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display,
|
||||
{
|
||||
self.send_request(reqwest::Method::DELETE, path, params, None::<&()>)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Create and send an HTTP PATCH Request with the provided path, parameters, and json data
|
||||
async fn send_patch_request<P, B, K, V>(
|
||||
async fn send_patch_request<P, B, K, V, E>(
|
||||
&self,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<Response, HttpClientError>
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
B: Serialize + ?Sized + Sync,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display,
|
||||
{
|
||||
self.send_request(reqwest::Method::PATCH, path, params, Some(json_body))
|
||||
.await
|
||||
@@ -1094,16 +941,17 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// into the provided type `T`.
|
||||
#[instrument(level = "debug", skip_all, fields(path=?path))]
|
||||
// TODO: deprecate in favour of get_response that works based on mime type in the response
|
||||
async fn get_json<P, T, K, V>(
|
||||
async fn get_json<P, T, K, V, E>(
|
||||
&self,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HttpClientError>
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
self.get_response(path, params).await
|
||||
}
|
||||
@@ -1111,16 +959,17 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// 'get' data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
|
||||
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
|
||||
/// into the provided type `T` based on the content type header
|
||||
async fn get_response<P, T, K, V>(
|
||||
async fn get_response<P, T, K, V, E>(
|
||||
&self,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HttpClientError>
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
let res = self
|
||||
.send_request(reqwest::Method::GET, path, params, None::<&()>)
|
||||
@@ -1131,18 +980,19 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// 'post' json data to the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
|
||||
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
|
||||
/// into the provided type `T`.
|
||||
async fn post_json<P, B, T, K, V>(
|
||||
async fn post_json<P, B, T, K, V, E>(
|
||||
&self,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError>
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
let res = self
|
||||
.send_request(reqwest::Method::POST, path, params, Some(json_body))
|
||||
@@ -1153,16 +1003,17 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// 'delete' json data from the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with
|
||||
/// tuple defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the
|
||||
/// response into the provided type `T`.
|
||||
async fn delete_json<P, T, K, V>(
|
||||
async fn delete_json<P, T, K, V, E>(
|
||||
&self,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
) -> Result<T, HttpClientError>
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
let res = self
|
||||
.send_request(reqwest::Method::DELETE, path, params, None::<&()>)
|
||||
@@ -1173,18 +1024,19 @@ pub trait ApiClient: ApiClientCore {
|
||||
/// 'patch' json data at the segment-defined path, e.g. `["api", "v1", "mixnodes"]`, with tuple
|
||||
/// defined key-value parameters, e.g. `[("since", "12345")]`. Attempt to parse the response
|
||||
/// into the provided type `T`.
|
||||
async fn patch_json<P, B, T, K, V>(
|
||||
async fn patch_json<P, B, T, K, V, E>(
|
||||
&self,
|
||||
path: P,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError>
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
P: RequestPath + Send + Sync,
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str> + Sync,
|
||||
V: AsRef<str> + Sync,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
let res = self
|
||||
.send_request(reqwest::Method::PATCH, path, params, Some(json_body))
|
||||
@@ -1194,59 +1046,62 @@ pub trait ApiClient: ApiClientCore {
|
||||
|
||||
/// `get` json data from the provided absolute endpoint, e.g. `"/api/v1/mixnodes?since=12345"`.
|
||||
/// Attempt to parse the response into the provided type `T`.
|
||||
async fn get_json_from<T, S>(&self, endpoint: S) -> Result<T, HttpClientError>
|
||||
async fn get_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str> + Sync + Send,
|
||||
{
|
||||
let req = self.create_request_endpoint(reqwest::Method::GET, endpoint, None::<&()>)?;
|
||||
let req = self.create_request_endpoint(reqwest::Method::GET, endpoint, None::<&()>);
|
||||
let res = self.send(req).await?;
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
/// `post` json data to the provided absolute endpoint, e.g. `"/api/v1/mixnodes?since=12345"`.
|
||||
/// Attempt to parse the response into the provided type `T`.
|
||||
async fn post_json_data_to<B, T, S>(
|
||||
async fn post_json_data_to<B, T, S, E>(
|
||||
&self,
|
||||
endpoint: S,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError>
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str> + Sync + Send,
|
||||
{
|
||||
let req = self.create_request_endpoint(reqwest::Method::POST, endpoint, Some(json_body))?;
|
||||
let req = self.create_request_endpoint(reqwest::Method::POST, endpoint, Some(json_body));
|
||||
let res = self.send(req).await?;
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
/// `delete` json data from the provided absolute endpoint, e.g.
|
||||
/// `"/api/v1/mixnodes?since=12345"`. Attempt to parse the response into the provided type `T`.
|
||||
async fn delete_json_from<T, S>(&self, endpoint: S) -> Result<T, HttpClientError>
|
||||
async fn delete_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str> + Sync + Send,
|
||||
{
|
||||
let req = self.create_request_endpoint(reqwest::Method::DELETE, endpoint, None::<&()>)?;
|
||||
let req = self.create_request_endpoint(reqwest::Method::DELETE, endpoint, None::<&()>);
|
||||
let res = self.send(req).await?;
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
/// `patch` json data at the provided absolute endpoint, e.g. `"/api/v1/mixnodes?since=12345"`.
|
||||
/// Attempt to parse the response into the provided type `T`.
|
||||
async fn patch_json_data_at<B, T, S>(
|
||||
async fn patch_json_data_at<B, T, S, E>(
|
||||
&self,
|
||||
endpoint: S,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError>
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized + Sync,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str> + Sync + Send,
|
||||
{
|
||||
let req =
|
||||
self.create_request_endpoint(reqwest::Method::PATCH, endpoint, Some(json_body))?;
|
||||
let req = self.create_request_endpoint(reqwest::Method::PATCH, endpoint, Some(json_body));
|
||||
let res = self.send(req).await?;
|
||||
parse_response(res, false).await
|
||||
}
|
||||
@@ -1302,9 +1157,10 @@ fn decode_as_text(bytes: &bytes::Bytes, headers: &HeaderMap) -> String {
|
||||
|
||||
/// Attempt to parse a response object from an HTTP response
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn parse_response<T>(res: Response, allow_empty: bool) -> Result<T, HttpClientError>
|
||||
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
{
|
||||
let status = res.status();
|
||||
tracing::trace!("Status: {} (success: {})", &status, status.is_success());
|
||||
@@ -1340,9 +1196,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_as_json<T>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError>
|
||||
fn decode_as_json<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
{
|
||||
match serde_json::from_slice(&content) {
|
||||
Ok(data) => Ok(data),
|
||||
@@ -1356,9 +1213,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_as_bincode<T>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError>
|
||||
fn decode_as_bincode<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
{
|
||||
use bincode::Options;
|
||||
|
||||
@@ -1375,9 +1233,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_raw_response<T>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError>
|
||||
fn decode_raw_response<T, E>(headers: &HeaderMap, content: Bytes) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
E: DeserializeOwned + Display,
|
||||
{
|
||||
// if content type header is missing, fallback to our old default, json
|
||||
let mime = try_get_mime_type(headers).unwrap_or(mime::APPLICATION_JSON);
|
||||
|
||||
@@ -95,10 +95,10 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
"http://example.com/".parse()?,
|
||||
])
|
||||
.with_retries(3)
|
||||
.build()?;
|
||||
.build::<HttpClientError>()?;
|
||||
|
||||
let req = client.create_get_request(&["/"], NO_PARAMS).unwrap();
|
||||
let resp = client.send(req).await?;
|
||||
let req = client.create_get_request(&["/"], NO_PARAMS);
|
||||
let resp = client.send::<HttpClientError>(req).await?;
|
||||
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
@@ -111,7 +111,10 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[test]
|
||||
fn host_updating() {
|
||||
let url = Url::new("http://example.com", None).unwrap();
|
||||
let mut client = ClientBuilder::new(url).unwrap().build().unwrap();
|
||||
let mut client = ClientBuilder::new::<_, &str>(url)
|
||||
.unwrap()
|
||||
.build::<&str>()
|
||||
.unwrap();
|
||||
|
||||
// check that the url is set correctly
|
||||
let current_url = client.current_url();
|
||||
@@ -168,10 +171,10 @@ fn host_updating() {
|
||||
#[cfg(feature = "tunneling")]
|
||||
fn fronted_host_updating() {
|
||||
let url = Url::new("http://example.com", Some(vec!["http://front1.com"])).unwrap();
|
||||
let mut client = ClientBuilder::new(url)
|
||||
let mut client = ClientBuilder::new::<_, &str>(url)
|
||||
.unwrap()
|
||||
.with_fronting(crate::fronted::FrontPolicy::Always)
|
||||
.build()
|
||||
.build::<&str>()
|
||||
.unwrap();
|
||||
|
||||
// check that the url is set correctly
|
||||
|
||||
@@ -55,7 +55,6 @@ pub struct ApiUrl {
|
||||
pub front_hosts: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ApiUrlConst<'a> {
|
||||
pub url: &'a str,
|
||||
pub front_hosts: Option<&'a [&'a str]>,
|
||||
@@ -189,14 +188,8 @@ impl NymNetworkDetails {
|
||||
),
|
||||
},
|
||||
nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API),
|
||||
nym_api_urls: Some(mainnet::NYM_APIS.iter().copied().map(Into::into).collect()),
|
||||
nym_vpn_api_urls: Some(
|
||||
mainnet::NYM_VPN_APIS
|
||||
.iter()
|
||||
.copied()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
),
|
||||
nym_api_urls: None,
|
||||
nym_vpn_api_urls: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "nym-upgrade-mode-check"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
jwt-simple = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["rustls-tls"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
time = { workspace = true, features = ["serde"] }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
nym-http-api-client = { path = "../http-api-client", default-features = false }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric", "serde", "naive_jwt"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
time = { workspace = true, features = ["macros"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::UpgradeModeCheckError;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_http_api_client::generate_user_agent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
pub struct UpgradeModeAttestation {
|
||||
#[serde(flatten)]
|
||||
pub content: UpgradeModeAttestationContent,
|
||||
|
||||
#[serde(with = "ed25519::bs58_ed25519_signature")]
|
||||
pub signature: ed25519::Signature,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(rename = "upgrade_mode")]
|
||||
pub struct UpgradeModeAttestationContent {
|
||||
#[serde(with = "time::serde::timestamp")]
|
||||
pub starting_time: OffsetDateTime,
|
||||
|
||||
#[serde(with = "ed25519::bs58_ed25519_pubkey")]
|
||||
pub attester_public_key: ed25519::PublicKey,
|
||||
}
|
||||
|
||||
impl UpgradeModeAttestation {
|
||||
pub fn verify(&self) -> bool {
|
||||
self.content
|
||||
.attester_public_key
|
||||
.verify(self.content.as_json(), &self.signature)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeModeAttestationContent {
|
||||
pub fn as_json(&self) -> String {
|
||||
// SAFETY: Serialize impl is valid and we have no non-string map keys
|
||||
#[allow(clippy::unwrap_used)]
|
||||
serde_json::to_string(&self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_new_attestation(key: &ed25519::PrivateKey) -> UpgradeModeAttestation {
|
||||
generate_new_attestation_with_starting_time(key, OffsetDateTime::now_utc())
|
||||
}
|
||||
|
||||
pub fn generate_new_attestation_with_starting_time(
|
||||
key: &ed25519::PrivateKey,
|
||||
starting_time: OffsetDateTime,
|
||||
) -> UpgradeModeAttestation {
|
||||
let content = UpgradeModeAttestationContent {
|
||||
starting_time,
|
||||
attester_public_key: key.into(),
|
||||
};
|
||||
UpgradeModeAttestation {
|
||||
signature: key.sign(content.as_json()),
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn attempt_retrieve(
|
||||
url: &str,
|
||||
) -> Result<Option<UpgradeModeAttestation>, UpgradeModeCheckError> {
|
||||
let retrieval_failure = |source| UpgradeModeCheckError::AttestationRetrievalFailure {
|
||||
url: url.to_string(),
|
||||
source,
|
||||
};
|
||||
|
||||
let attestation = reqwest::ClientBuilder::new()
|
||||
.user_agent(generate_user_agent!())
|
||||
.timeout(Duration::from_secs(5))
|
||||
.build()
|
||||
.map_err(retrieval_failure)?
|
||||
.get(url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(retrieval_failure)?
|
||||
.json::<Option<UpgradeModeAttestation>>()
|
||||
.await
|
||||
.map_err(retrieval_failure)?;
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn upgrade_mode_attestation_serde_json() -> anyhow::Result<()> {
|
||||
// unix timestamp: 1629720000
|
||||
let starting_time = time::macros::datetime!(2021-08-23 12:00 UTC);
|
||||
|
||||
let key = ed25519::PrivateKey::from_bytes(&[
|
||||
108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248,
|
||||
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
|
||||
])?;
|
||||
|
||||
let attestation = generate_new_attestation_with_starting_time(&key, starting_time);
|
||||
|
||||
let attestation_json = serde_json::to_string(&attestation)?;
|
||||
let attestation_content_json = attestation.content.as_json();
|
||||
|
||||
let expected_attestation = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt","signature":"5rWUr2ypaDTtrMKegMP3tQkkZGFAuhNTnEVCVe5Azv6QqvLzoGdQiMkFmeyhDd1XSfoXpL9fFM58rsdA1kf4GYMM"}"#;
|
||||
let expected_content = r#"{"type":"upgrade_mode","starting_time":1629720000,"attester_public_key":"3pkFcBXCEmbmXBT2G8CkFMuKisJcH54mbBGvncHaDibt"}"#;
|
||||
|
||||
assert_eq!(attestation_content_json, expected_content);
|
||||
assert_eq!(attestation_json, expected_attestation);
|
||||
|
||||
let recovered_attestation = serde_json::from_str(&attestation_json)?;
|
||||
assert_eq!(attestation, recovered_attestation);
|
||||
|
||||
let recovered_content = serde_json::from_str(&attestation_content_json)?;
|
||||
assert_eq!(attestation.content, recovered_content);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UpgradeModeCheckError {
|
||||
#[error("failed to decode jwt metadata")]
|
||||
TokenMetadataDecodeFailure { source: jwt_simple::Error },
|
||||
|
||||
#[error("the jwt metadata didn't contain explicit public key")]
|
||||
MissingTokenPublicKey,
|
||||
|
||||
#[error("the attached public key was not valid ed25519 public key")]
|
||||
MalformedEd25519PublicKey { source: Ed25519RecoveryError },
|
||||
|
||||
#[error("failed to verify the jwt: {source}")]
|
||||
JwtVerificationFailure { source: jwt_simple::Error },
|
||||
|
||||
#[error("failed to retrieve attestation from {url}:{source}")]
|
||||
AttestationRetrievalFailure { url: String, source: reqwest::Error },
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{UpgradeModeAttestation, UpgradeModeCheckError};
|
||||
use jwt_simple::claims::Claims;
|
||||
use jwt_simple::common::{KeyMetadata, VerificationOptions};
|
||||
use jwt_simple::prelude::{EdDSAKeyPairLike, EdDSAPublicKeyLike};
|
||||
use jwt_simple::token::Token;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
|
||||
// for now use static issuer such as "nym-credential-proxy"
|
||||
pub fn generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation: UpgradeModeAttestation,
|
||||
validity: Duration,
|
||||
keys: &ed25519::KeyPair,
|
||||
issuer: Option<&'static str>,
|
||||
) -> String {
|
||||
let claim = Claims::with_custom_claims(attestation, validity.into());
|
||||
let mut claim = if let Some(issuer) = issuer {
|
||||
claim.with_issuer(issuer)
|
||||
} else {
|
||||
claim
|
||||
};
|
||||
claim.create_nonce();
|
||||
|
||||
let md = KeyMetadata::default().with_public_key(keys.public_key().to_base58_string());
|
||||
|
||||
let mut jwt_keys = keys.to_jwt_compatible_keys();
|
||||
// SAFETY: trait impl for EdDSA is infallible
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_keys.attach_metadata(md).unwrap();
|
||||
|
||||
// SAFETY: our construction of the jwt is valid
|
||||
#[allow(clippy::unwrap_used)]
|
||||
jwt_keys.sign(claim).unwrap()
|
||||
}
|
||||
|
||||
pub fn validate_upgrade_mode_jwt(
|
||||
token: &str,
|
||||
expected_issuer: Option<&'static str>,
|
||||
) -> Result<UpgradeModeAttestation, UpgradeModeCheckError> {
|
||||
// for now, we completely ignore the validity of the pubkey (I know, I know).
|
||||
// that will be changed later on
|
||||
// so as a bypass we have to extract the claimed issuer from the jwt to verify against it
|
||||
let metadata = Token::decode_metadata(token)
|
||||
.map_err(|source| UpgradeModeCheckError::TokenMetadataDecodeFailure { source })?;
|
||||
|
||||
let pub_key = metadata
|
||||
.public_key()
|
||||
.ok_or(UpgradeModeCheckError::MissingTokenPublicKey)?;
|
||||
|
||||
let ed25519_pub_key = ed25519::PublicKey::from_base58_string(pub_key)
|
||||
.map_err(|source| UpgradeModeCheckError::MalformedEd25519PublicKey { source })?;
|
||||
|
||||
let mut opts = VerificationOptions::default();
|
||||
if let Some(issuer) = expected_issuer {
|
||||
opts.allowed_issuers = Some(HashSet::from_iter(vec![issuer.to_string()]));
|
||||
}
|
||||
|
||||
let attestation = ed25519_pub_key
|
||||
.to_jwt_compatible_key()
|
||||
.verify_token::<UpgradeModeAttestation>(token, Some(opts))
|
||||
.map_err(|source| UpgradeModeCheckError::JwtVerificationFailure { source })?
|
||||
.custom;
|
||||
|
||||
Ok(attestation)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::generate_new_attestation;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
|
||||
#[test]
|
||||
fn generate_and_validate_jwt() {
|
||||
let attestation_key = ed25519::PrivateKey::from_bytes(&[
|
||||
108, 49, 193, 21, 126, 161, 249, 85, 242, 207, 74, 195, 238, 6, 64, 149, 201, 140, 248,
|
||||
163, 122, 170, 79, 198, 87, 85, 36, 29, 243, 92, 64, 161,
|
||||
])
|
||||
.unwrap();
|
||||
let jwt_key = ed25519::PrivateKey::from_bytes(&[
|
||||
152, 17, 144, 255, 213, 219, 246, 208, 109, 33, 100, 73, 1, 141, 32, 63, 141, 89, 167,
|
||||
2, 52, 215, 241, 219, 200, 18, 159, 241, 76, 111, 42, 32,
|
||||
])
|
||||
.unwrap();
|
||||
let keys = ed25519::KeyPair::from(jwt_key);
|
||||
|
||||
let attestation = generate_new_attestation(&attestation_key);
|
||||
let jwt_issuer = generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation,
|
||||
Duration::from_secs(60 * 60),
|
||||
&keys,
|
||||
Some("nym-credential-proxy"),
|
||||
);
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("nym-credential-proxy")).is_ok());
|
||||
|
||||
// we don't care about issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, None).is_ok());
|
||||
|
||||
// we expect another-issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_issuer, Some("another-issuer")).is_err());
|
||||
|
||||
let jwt_no_issuer = generate_jwt_for_upgrade_mode_attestation(
|
||||
attestation,
|
||||
Duration::from_secs(60 * 60),
|
||||
&keys,
|
||||
None,
|
||||
);
|
||||
// we expect 'nym-credential-proxy' issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, Some("nym-credential-proxy")).is_err());
|
||||
|
||||
// we don't care about issuer
|
||||
assert!(validate_upgrade_mode_jwt(&jwt_no_issuer, None).is_ok());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) mod attestation;
|
||||
pub(crate) mod error;
|
||||
pub(crate) mod jwt;
|
||||
|
||||
pub use attestation::{
|
||||
attempt_retrieve, generate_new_attestation, generate_new_attestation_with_starting_time,
|
||||
UpgradeModeAttestation,
|
||||
};
|
||||
pub use error::UpgradeModeCheckError;
|
||||
pub use jwt::{generate_jwt_for_upgrade_mode_attestation, validate_upgrade_mode_jwt};
|
||||
@@ -25,5 +25,3 @@ url = { workspace = true }
|
||||
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
|
||||
nym-task = { path = "../task" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
|
||||
@@ -10,7 +10,7 @@ use futures::StreamExt;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_validator_client::models::NymNodeDescription;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::net::SocketAddr;
|
||||
@@ -135,15 +135,10 @@ impl VerlocMeasurer {
|
||||
let mut api_endpoints = self.config.nym_api_urls.clone();
|
||||
api_endpoints.shuffle(&mut thread_rng());
|
||||
for api_endpoint in api_endpoints {
|
||||
let client = match nym_http_api_client::Client::builder(api_endpoint.clone())
|
||||
.and_then(|b| b.with_user_agent(self.config.user_agent.clone()).build())
|
||||
{
|
||||
Ok(c) => c,
|
||||
Err(err) => {
|
||||
warn!("failed to create client for {api_endpoint}: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let client = NymApiClient::new_with_user_agent(
|
||||
api_endpoint.clone(),
|
||||
self.config.user_agent.clone(),
|
||||
);
|
||||
match client.get_all_described_nodes().await {
|
||||
Ok(res) => return Some(res),
|
||||
Err(err) => {
|
||||
|
||||
@@ -34,7 +34,6 @@ nym-statistics-common = { path = "../../statistics" }
|
||||
nym-task = { path = "../../task" }
|
||||
nym-topology = { path = "../../topology", features = ["wasm-serde-types"] }
|
||||
nym-validator-client = { path = "../../client-libs/validator-client", default-features = false }
|
||||
nym-http-api-client = { path = "../../http-api-client" }
|
||||
wasm-utils = { path = "../utils" }
|
||||
wasm-storage = { path = "../storage" }
|
||||
|
||||
|
||||
@@ -37,12 +37,6 @@ pub enum WasmCoreError {
|
||||
source: ValidatorClientError,
|
||||
},
|
||||
|
||||
#[error("failed to query nym api: {source}")]
|
||||
NymApiQueryError {
|
||||
#[from]
|
||||
source: nym_validator_client::nym_api::error::NymAPIError,
|
||||
},
|
||||
|
||||
#[error("The provided wasm topology was invalid: {source}")]
|
||||
WasmTopologyError {
|
||||
#[from]
|
||||
|
||||
@@ -19,9 +19,9 @@ use nym_client_core::init::{
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_topology::wasm_helpers::WasmFriendlyNymTopology;
|
||||
use nym_topology::{EpochRewardedSet, NymTopology, RoutingNode};
|
||||
use nym_topology::{NymTopology, RoutingNode};
|
||||
use nym_validator_client::client::IdentityKey;
|
||||
use nym_validator_client::{nym_api::NymApiClientExt, UserAgent};
|
||||
use nym_validator_client::{NymApiClient, UserAgent};
|
||||
use rand::thread_rng;
|
||||
use url::Url;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
@@ -72,16 +72,7 @@ pub async fn current_network_topology_async(
|
||||
}
|
||||
};
|
||||
|
||||
let api_client = nym_http_api_client::Client::builder(url.clone())
|
||||
.map_err(|_err| WasmCoreError::MalformedUrl {
|
||||
raw: nym_api_url.to_string(),
|
||||
source: url::ParseError::EmptyHost,
|
||||
})?
|
||||
.build()
|
||||
.map_err(|_err| WasmCoreError::MalformedUrl {
|
||||
raw: nym_api_url.to_string(),
|
||||
source: url::ParseError::EmptyHost,
|
||||
})?;
|
||||
let api_client = NymApiClient::new(url);
|
||||
let rewarded_set = api_client.get_current_rewarded_set().await?;
|
||||
let mixnodes_res = api_client
|
||||
.get_all_basic_active_mixing_assigned_nodes_with_metadata()
|
||||
@@ -99,14 +90,9 @@ pub async fn current_network_topology_async(
|
||||
|
||||
let gateways = gateways_res.nodes;
|
||||
|
||||
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
|
||||
let topology = NymTopology::new(
|
||||
metadata.to_topology_metadata(),
|
||||
epoch_rewarded_set,
|
||||
Vec::new(),
|
||||
)
|
||||
.with_skimmed_nodes(&mixnodes)
|
||||
.with_skimmed_nodes(&gateways);
|
||||
let topology = NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
|
||||
.with_skimmed_nodes(&mixnodes)
|
||||
.with_skimmed_nodes(&gateways);
|
||||
|
||||
Ok(topology.into())
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ pub use nym_client_core::{
|
||||
pub use nym_gateway_client::{
|
||||
error::GatewayClientError, GatewayClient, GatewayClientConfig, GatewayConfig,
|
||||
};
|
||||
pub use nym_http_api_client::Client as ApiClient;
|
||||
pub use nym_sphinx::{
|
||||
addressing::{clients::Recipient, nodes::NodeIdentity},
|
||||
params::PacketType,
|
||||
@@ -30,6 +29,7 @@ pub use nym_sphinx::{
|
||||
pub use nym_statistics_common::clients::ClientStatsSender;
|
||||
pub use nym_task;
|
||||
pub use nym_topology::{HardcodedTopologyProvider, MixLayer, NymTopology, TopologyProvider};
|
||||
pub use nym_validator_client::nym_api::Client as ApiClient;
|
||||
pub use nym_validator_client::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient};
|
||||
// TODO: that's a very nasty import path. it should come from contracts instead!
|
||||
pub use nym_validator_client::client::IdentityKey;
|
||||
|
||||
@@ -7,14 +7,16 @@ use tracing::instrument;
|
||||
use nym_http_api_client::{ApiClient, Client, HttpClientError, NO_PARAMS};
|
||||
|
||||
use nym_wireguard_private_metadata_shared::{
|
||||
routes, Version, {Request, Response},
|
||||
routes, Version, {ErrorResponse, Request, Response},
|
||||
};
|
||||
|
||||
pub type WireguardMetadataApiClientError = HttpClientError<ErrorResponse>;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait WireguardMetadataApiClient: ApiClient {
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn version(&self) -> Result<Version, HttpClientError> {
|
||||
async fn version(&self) -> Result<Version, WireguardMetadataApiClientError> {
|
||||
let version: u64 = self
|
||||
.get_json(
|
||||
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::VERSION],
|
||||
@@ -28,7 +30,7 @@ pub trait WireguardMetadataApiClient: ApiClient {
|
||||
async fn available_bandwidth(
|
||||
&self,
|
||||
request_body: &Request,
|
||||
) -> Result<Response, HttpClientError> {
|
||||
) -> Result<Response, WireguardMetadataApiClientError> {
|
||||
self.post_json(
|
||||
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::AVAILABLE],
|
||||
NO_PARAMS,
|
||||
@@ -38,7 +40,10 @@ pub trait WireguardMetadataApiClient: ApiClient {
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, request_body))]
|
||||
async fn topup_bandwidth(&self, request_body: &Request) -> Result<Response, HttpClientError> {
|
||||
async fn topup_bandwidth(
|
||||
&self,
|
||||
request_body: &Request,
|
||||
) -> Result<Response, WireguardMetadataApiClientError> {
|
||||
self.post_json(
|
||||
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::TOPUP],
|
||||
NO_PARAMS,
|
||||
|
||||
@@ -13,7 +13,7 @@ mod tests {
|
||||
use nym_wireguard_private_metadata_server::{
|
||||
AppState, PeerControllerTransceiver, RouterBuilder,
|
||||
};
|
||||
use nym_wireguard_private_metadata_shared::{latest, v0, v1};
|
||||
use nym_wireguard_private_metadata_shared::{latest, v0, v1, ErrorResponse};
|
||||
use tokio::{net::TcpListener, sync::mpsc};
|
||||
|
||||
pub(crate) const VERIFIER_AVAILABLE_BANDWIDTH: i64 = 42;
|
||||
@@ -140,7 +140,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
Client::new_url(addr.to_string(), None).unwrap()
|
||||
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -15,6 +15,7 @@ pub(crate) mod test {
|
||||
use nym_http_api_common::{FormattedResponse, OutputParams};
|
||||
use nym_wireguard::{peer_controller::PeerControlRequest, CONTROL_CHANNEL_SIZE};
|
||||
use nym_wireguard_private_metadata_server::PeerControllerTransceiver;
|
||||
use nym_wireguard_private_metadata_shared::ErrorResponse;
|
||||
use nym_wireguard_private_metadata_shared::{
|
||||
v0 as latest, AxumErrorResponse, AxumResult, Construct, Extract, Request, Response,
|
||||
};
|
||||
@@ -140,6 +141,6 @@ pub(crate) mod test {
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
Client::new_url(addr.to_string(), None).unwrap()
|
||||
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
//! ```
|
||||
|
||||
use crate::error::ZulipClientError;
|
||||
use crate::message::{SendMessageResponse, SendableMessage};
|
||||
use crate::message::{DirectMessage, SendMessageResponse, SendableMessage, StreamMessage};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use reqwest::{header, Method, RequestBuilder};
|
||||
@@ -92,6 +92,20 @@ impl Client {
|
||||
.map_err(|source| ZulipClientError::RequestDecodeFailure { source })
|
||||
}
|
||||
|
||||
pub async fn send_direct_message(
|
||||
&self,
|
||||
msg: impl Into<DirectMessage>,
|
||||
) -> Result<SendMessageResponse, ZulipClientError> {
|
||||
self.send_message(msg.into()).await
|
||||
}
|
||||
|
||||
pub async fn send_channel_message(
|
||||
&self,
|
||||
msg: impl Into<StreamMessage>,
|
||||
) -> Result<SendMessageResponse, ZulipClientError> {
|
||||
self.send_message(msg.into()).await
|
||||
}
|
||||
|
||||
fn build_request(&self, method: Method, endpoint: &'static str) -> RequestBuilder {
|
||||
let url = format!("{}{endpoint}", self.server_url);
|
||||
trace!("posting to {url}");
|
||||
|
||||
@@ -22,7 +22,7 @@ pub enum SendMessageResponse {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum SendableMessageContent {
|
||||
@@ -40,7 +40,7 @@ pub enum SendableMessageContent {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct SendableMessage {
|
||||
#[serde(flatten)]
|
||||
@@ -117,17 +117,17 @@ impl StreamMessage {
|
||||
pub fn new(
|
||||
to: impl Into<ToChannel>,
|
||||
content: impl Into<String>,
|
||||
topic: Option<String>,
|
||||
topic: impl IntoMaybeTopic,
|
||||
) -> Self {
|
||||
StreamMessage {
|
||||
to: to.into().to_string(),
|
||||
topic,
|
||||
topic: topic.into_maybe_topic(),
|
||||
content: content.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_topic(to: impl Into<ToChannel>, content: impl Into<String>) -> Self {
|
||||
Self::new(to, content, None)
|
||||
Self::new(to, content, None::<String>)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -194,22 +194,74 @@ impl From<StreamMessage> for SendableMessageContent {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> From<(T, S, Option<S>)> for StreamMessage
|
||||
impl<T, S, U> From<(T, S, U)> for StreamMessage
|
||||
where
|
||||
T: Into<ToChannel>,
|
||||
S: Into<String>,
|
||||
U: IntoMaybeTopic,
|
||||
{
|
||||
fn from((to, content, topic): (T, S, Option<S>)) -> Self {
|
||||
StreamMessage::new(to, content, topic.map(Into::into))
|
||||
fn from((to, content, topic): (T, S, U)) -> Self {
|
||||
StreamMessage::new(to, content, topic)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> From<(T, S, Option<S>)> for SendableMessage
|
||||
impl<T, S> From<(T, S)> for StreamMessage
|
||||
where
|
||||
T: Into<ToChannel>,
|
||||
S: Into<String>,
|
||||
{
|
||||
fn from(inner: (T, S, Option<S>)) -> Self {
|
||||
fn from((to, content): (T, S)) -> Self {
|
||||
StreamMessage::no_topic(to, content)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, U> From<(T, S, U)> for SendableMessage
|
||||
where
|
||||
T: Into<ToChannel>,
|
||||
S: Into<String>,
|
||||
U: IntoMaybeTopic,
|
||||
{
|
||||
fn from(inner: (T, S, U)) -> Self {
|
||||
StreamMessage::from(inner).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoMaybeTopic {
|
||||
fn into_maybe_topic(self) -> Option<String>;
|
||||
}
|
||||
|
||||
impl<S> IntoMaybeTopic for &Option<S>
|
||||
where
|
||||
S: Into<String> + Clone,
|
||||
{
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
self.clone().map(|s| s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> IntoMaybeTopic for Option<S>
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
self.map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoMaybeTopic for String {
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoMaybeTopic for &String {
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoMaybeTopic for &str {
|
||||
fn into_maybe_topic(self) -> Option<String> {
|
||||
Some(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ impl TestSetup {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_ctx(&self) -> QueryCtx<'_> {
|
||||
pub fn query_ctx(&self) -> QueryCtx {
|
||||
QueryCtx::from((self.deps.as_ref(), self.env.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,13 +73,13 @@ impl TestSetupSimple {
|
||||
message_info(&admin, &[])
|
||||
}
|
||||
|
||||
pub fn execute_ctx(&mut self, sender: MessageInfo) -> ExecCtx<'_> {
|
||||
pub fn execute_ctx(&mut self, sender: MessageInfo) -> ExecCtx {
|
||||
let env = self.env.clone();
|
||||
ExecCtx::from((self.deps.as_mut(), env, sender))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn query_ctx(&self) -> QueryCtx<'_> {
|
||||
pub fn query_ctx(&self) -> QueryCtx {
|
||||
QueryCtx::from((self.deps.as_ref(), self.env.clone()))
|
||||
}
|
||||
|
||||
|
||||
@@ -139,9 +139,9 @@ mod tests {
|
||||
|
||||
fn add_dummy_mixes_with_delegations(test: &mut TestSetup, delegators: usize, mixes: usize) {
|
||||
for i in 0..mixes {
|
||||
let mix_id = test.add_legacy_mixnode(&test.make_addr(format!("mix-owner{i}")), None);
|
||||
let mix_id = test.add_legacy_mixnode(&test.make_addr(format!("mix-owner{}", i)), None);
|
||||
for delegator in 0..delegators {
|
||||
let name = &test.make_addr(format!("delegator{delegator}"));
|
||||
let name = &test.make_addr(format!("delegator{}", delegator));
|
||||
test.add_immediate_delegation(name, 100_000_000u32, mix_id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ mod tests {
|
||||
|
||||
let amount1 = coin(100_000_000, TEST_COIN_DENOM);
|
||||
|
||||
let sender1 = message_info(owner, std::slice::from_ref(&amount1));
|
||||
let sender1 = message_info(owner, &[amount1.clone()]);
|
||||
|
||||
try_delegate_to_node(test.deps_mut(), env.clone(), sender1, mix_id).unwrap();
|
||||
|
||||
|
||||
@@ -503,8 +503,8 @@ pub(crate) mod tests {
|
||||
storage,
|
||||
id,
|
||||
&UnbondedMixnode {
|
||||
identity_key: format!("dummy{id}"),
|
||||
owner: Addr::unchecked(format!("dummy{id}")),
|
||||
identity_key: format!("dummy{}", id),
|
||||
owner: Addr::unchecked(format!("dummy{}", id)),
|
||||
proxy: None,
|
||||
unbonding_height: 123,
|
||||
},
|
||||
@@ -570,7 +570,7 @@ pub(crate) mod tests {
|
||||
storage,
|
||||
id,
|
||||
&UnbondedMixnode {
|
||||
identity_key: format!("dummy{id}"),
|
||||
identity_key: format!("dummy{}", id),
|
||||
owner: owner.clone(),
|
||||
proxy: None,
|
||||
unbonding_height: 123,
|
||||
@@ -817,7 +817,7 @@ pub(crate) mod tests {
|
||||
id,
|
||||
&UnbondedMixnode {
|
||||
identity_key: identity.to_string(),
|
||||
owner: Addr::unchecked(format!("dummy{id}")),
|
||||
owner: Addr::unchecked(format!("dummy{}", id)),
|
||||
proxy: None,
|
||||
unbonding_height: 123,
|
||||
},
|
||||
|
||||
@@ -165,9 +165,9 @@ pub mod test_helpers {
|
||||
#[track_caller]
|
||||
pub fn assert_eq_with_leeway(a: Uint128, b: Uint128, leeway: Uint128) {
|
||||
if a > b {
|
||||
assert!(a - b <= leeway, "{a} != {b}")
|
||||
assert!(a - b <= leeway, "{} != {}", a, b)
|
||||
} else {
|
||||
assert!(b - a <= leeway, "{a} != {b}")
|
||||
assert!(b - a <= leeway, "{} != {}", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,9 +175,9 @@ pub mod test_helpers {
|
||||
pub fn assert_decimals(a: Decimal, b: Decimal) {
|
||||
let epsilon = Decimal::from_ratio(1u128, 100_000_000u128);
|
||||
if a > b {
|
||||
assert!(a - b < epsilon, "{a} != {b}")
|
||||
assert!(a - b < epsilon, "{} != {}", a, b)
|
||||
} else {
|
||||
assert!(b - a < epsilon, "{a} != {b}")
|
||||
assert!(b - a < epsilon, "{} != {}", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1699,7 +1699,7 @@ pub mod test_helpers {
|
||||
deps.branch(),
|
||||
&env,
|
||||
env.block.height,
|
||||
Addr::unchecked(format!("owner{i}")),
|
||||
Addr::unchecked(format!("owner{}", i)),
|
||||
mix_id,
|
||||
tests::fixtures::good_mixnode_pledge().pop().unwrap(),
|
||||
)
|
||||
@@ -1713,7 +1713,7 @@ pub mod test_helpers {
|
||||
n: usize,
|
||||
) {
|
||||
for i in 0..n {
|
||||
add_unbonded_mixnode(&mut rng, deps.branch(), None, &addr(format!("owner{i}")));
|
||||
add_unbonded_mixnode(&mut rng, deps.branch(), None, &addr(format!("owner{}", i)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1765,7 +1765,7 @@ pub mod test_helpers {
|
||||
id,
|
||||
&UnbondedMixnode {
|
||||
identity_key: identity_key
|
||||
.unwrap_or(&*format!("identity{id}"))
|
||||
.unwrap_or(&*format!("identity{}", id))
|
||||
.to_string(),
|
||||
owner: Addr::unchecked(owner),
|
||||
proxy: None,
|
||||
|
||||
@@ -131,7 +131,7 @@ impl PreInitContract {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deps(&self) -> Deps<'_> {
|
||||
pub(crate) fn deps(&self) -> Deps {
|
||||
Deps {
|
||||
storage: &self.storage,
|
||||
api: &self.api,
|
||||
@@ -139,7 +139,7 @@ impl PreInitContract {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deps_mut(&mut self) -> DepsMut<'_> {
|
||||
pub(crate) fn deps_mut(&mut self) -> DepsMut {
|
||||
DepsMut {
|
||||
storage: &mut self.storage,
|
||||
api: &self.api,
|
||||
@@ -147,7 +147,7 @@ impl PreInitContract {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn querier(&self) -> QuerierWrapper<'_> {
|
||||
pub(crate) fn querier(&self) -> QuerierWrapper {
|
||||
self.tester_builder.querier()
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ mod tests {
|
||||
let response = execute(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
message_info(&admin, std::slice::from_ref(&amount)),
|
||||
message_info(&admin, &[amount.clone()]),
|
||||
msg,
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
+2
-8
@@ -20,22 +20,16 @@ use nym_sdk::mixnet;
|
||||
use nym_sdk::mixnet::MixnetMessageSender;
|
||||
use nym_topology::provider_trait::{async_trait, TopologyProvider};
|
||||
use nym_topology::{nym_topology_from_detailed, NymTopology};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use url::Url;
|
||||
|
||||
struct MyTopologyProvider {
|
||||
validator_client: nym_http_api_client::Client,
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
}
|
||||
|
||||
impl MyTopologyProvider {
|
||||
fn new(nym_api_url: Url) -> MyTopologyProvider {
|
||||
let validator_client = nym_http_api_client::Client::builder::<_, nym_validator_client::models::RequestError>(nym_api_url)
|
||||
.expect("Failed to create API client builder")
|
||||
.build::<nym_validator_client::models::RequestError>()
|
||||
.expect("Failed to build API client");
|
||||
|
||||
MyTopologyProvider {
|
||||
validator_client,
|
||||
validator_client: nym_validator_client::client::NymApiClient::new(nym_api_url),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,6 @@ nym-task = { path = "../common/task" }
|
||||
nym-topology = { path = "../common/topology" }
|
||||
nym-api-requests = { path = "nym-api-requests" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
nym-http-api-client = { path = "../common/http-api-client" }
|
||||
nym-bin-common = { path = "../common/bin-common", features = ["output_format", "openapi", "basic_tracing"] }
|
||||
nym-node-tester-utils = { path = "../common/node-tester-utils" }
|
||||
nym-node-requests = { path = "../nym-node/nym-node-requests" }
|
||||
|
||||
@@ -8,9 +8,7 @@ use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use nym_mixnet_contract_common::reward_params::Performance;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_network_defaults::{
|
||||
DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT, WG_TUNNEL_PORT,
|
||||
};
|
||||
use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT};
|
||||
use nym_node_requests::api::v1::authenticator::models::Authenticator;
|
||||
use nym_node_requests::api::v1::gateway::models::Wireguard;
|
||||
use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
|
||||
@@ -315,20 +313,11 @@ impl From<Authenticator> for AuthenticatorDetails {
|
||||
pub struct WireguardDetails {
|
||||
// NOTE: the port field is deprecated in favour of tunnel_port
|
||||
pub port: u16,
|
||||
#[serde(default = "default_tunnel_port")]
|
||||
pub tunnel_port: u16,
|
||||
#[serde(default = "default_metadata_port")]
|
||||
pub metadata_port: u16,
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
fn default_tunnel_port() -> u16 {
|
||||
WG_TUNNEL_PORT
|
||||
}
|
||||
fn default_metadata_port() -> u16 {
|
||||
WG_METADATA_PORT
|
||||
}
|
||||
|
||||
// works for current simple case.
|
||||
impl From<Wireguard> for WireguardDetails {
|
||||
fn from(value: Wireguard) -> Self {
|
||||
|
||||
@@ -13,7 +13,6 @@ use nym_dkg::Threshold;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITLE;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use thiserror::Error;
|
||||
@@ -47,9 +46,6 @@ pub enum EcashError {
|
||||
#[error("coconut api query failure: {0}")]
|
||||
CoconutApiError(#[from] EcashApiError),
|
||||
|
||||
#[error("nym api query failure: {0}")]
|
||||
NymApiError(#[from] NymAPIError),
|
||||
|
||||
#[error(transparent)]
|
||||
SerdeJsonError(#[from] serde_json::Error),
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITL
|
||||
use nym_ecash_time::{ecash_default_expiration_date, ecash_today_date};
|
||||
use nym_task::TaskClient;
|
||||
use nym_ticketbooks_merkle::{IssuedTicketbook, IssuedTicketbooksFullMerkleProof, MerkleLeaf};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::{thread_rng, RngCore};
|
||||
|
||||
@@ -67,7 +67,7 @@ use nym_validator_client::nym_api::routes::{
|
||||
use nym_validator_client::nyxd::cosmwasm_client::logs::Log;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
use nym_validator_client::nyxd::{AccountId, ExecTxResult, Fee, Hash, TxResponse};
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use nym_validator_client::{EcashApiClient, NymApiClient};
|
||||
use rand::rngs::OsRng;
|
||||
use rand::RngCore;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
@@ -1148,10 +1148,7 @@ impl DummyCommunicationChannel {
|
||||
cosmos_address: AccountId,
|
||||
) -> Self {
|
||||
let client = EcashApiClient {
|
||||
api_client: nym_http_api_client::Client::new(
|
||||
"http://localhost:1234".parse().unwrap(),
|
||||
None,
|
||||
),
|
||||
api_client: NymApiClient::new("http://localhost:1234".parse().unwrap()),
|
||||
verification_key: aggregated_verification_key,
|
||||
node_id: 1,
|
||||
cosmos_address,
|
||||
|
||||
@@ -97,7 +97,7 @@ impl NymVpnApiClient for VpnApiClient {
|
||||
{
|
||||
let req = self
|
||||
.inner
|
||||
.create_get_request(path, NO_PARAMS)?
|
||||
.create_get_request(path, NO_PARAMS)
|
||||
.bearer_auth(&self.bearer_token)
|
||||
.send();
|
||||
|
||||
@@ -129,7 +129,7 @@ impl NymVpnApiClient for VpnApiClient {
|
||||
{
|
||||
let req = self
|
||||
.inner
|
||||
.create_post_request(path, params, json_body)?
|
||||
.create_post_request(path, params, json_body)
|
||||
.bearer_auth(&self.bearer_token)
|
||||
.send();
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
};
|
||||
use nym_credentials_interface::Base58;
|
||||
use nym_validator_client::ecash::BlindSignRequestBody;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use nym_ecash_signer_check::SignerCheckError;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::{error::NymAPIError, EpochId};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
@@ -71,12 +71,6 @@ pub enum CredentialProxyError {
|
||||
source: EcashApiError,
|
||||
},
|
||||
|
||||
#[error("Nym API request failed: {source}")]
|
||||
NymApiFailure {
|
||||
#[from]
|
||||
source: NymAPIError,
|
||||
},
|
||||
|
||||
#[error("Compact ecash internal error: {0}")]
|
||||
CompactEcashInternalError(#[from] nym_compact_ecash::error::CompactEcashError),
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_ecash_contract_common::msg::ExecuteMsg;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
DkgQueryClient, NymContractsProvider, PagedDkgQueryClient,
|
||||
|
||||
@@ -40,5 +40,3 @@ nym-sphinx = { path = "../common/nymsphinx" }
|
||||
nym-topology = { path = "../common/topology" }
|
||||
nym-types = { path = "../common/types" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
nym-http-api-client = { path = "../common/http-api-client" }
|
||||
nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
|
||||
@@ -6,15 +6,12 @@ use log::{info, warn};
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_client_core::config::ForgetMe;
|
||||
use nym_crypto::asymmetric::ed25519::PrivateKey;
|
||||
use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
use nym_network_defaults::setup_env;
|
||||
use nym_network_defaults::var_names::NYM_API;
|
||||
use nym_sdk::mixnet::{self, MixnetClient};
|
||||
use nym_sphinx::chunking::monitoring;
|
||||
use nym_topology::provider_trait::ToTopologyMetadata;
|
||||
use nym_topology::{HardcodedTopologyProvider, NymTopology};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::UserAgent;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::sync::LazyLock;
|
||||
@@ -163,9 +160,10 @@ async fn nym_topology_from_env() -> anyhow::Result<NymTopology> {
|
||||
let api_url = std::env::var(NYM_API)?;
|
||||
|
||||
info!("Generating topology from {api_url}");
|
||||
let client = nym_http_api_client::Client::builder(api_url)?
|
||||
.with_user_agent(UserAgent::from(bin_info!()))
|
||||
.build()?;
|
||||
let client = nym_validator_client::client::NymApiClient::new_with_user_agent(
|
||||
api_url.parse()?,
|
||||
bin_info!(),
|
||||
);
|
||||
|
||||
let rewarded_set = client.get_current_rewarded_set().await?;
|
||||
|
||||
@@ -174,15 +172,10 @@ async fn nym_topology_from_env() -> anyhow::Result<NymTopology> {
|
||||
let nodes = nodes_response.nodes;
|
||||
let metadata = nodes_response.metadata;
|
||||
|
||||
// Convert RewardedSetResponse to EpochRewardedSet which can then be converted to CachedEpochRewardedSet
|
||||
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
|
||||
|
||||
Ok(NymTopology::new(
|
||||
metadata.to_topology_metadata(),
|
||||
epoch_rewarded_set,
|
||||
Vec::new(),
|
||||
Ok(
|
||||
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
|
||||
.with_skimmed_nodes(&nodes),
|
||||
)
|
||||
.with_skimmed_nodes(&nodes))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
||||
@@ -5,11 +5,11 @@ use nym_node_requests::api::{client::NymNodeApiClientExt, v1::metrics::models::S
|
||||
use nym_validator_client::{
|
||||
client::{NodeId, NymNodeDetails},
|
||||
models::{DescribedNodeType, NymNodeDescription},
|
||||
NymApiClient,
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use nym_statistics_common::types::SessionType;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use std::collections::HashMap;
|
||||
use tokio::time::Duration;
|
||||
use tracing::instrument;
|
||||
@@ -60,11 +60,13 @@ async fn run(
|
||||
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
||||
.no_hickory_dns()
|
||||
.with_timeout(nym_api_client_timeout)
|
||||
.build()?;
|
||||
.build::<&str>()?;
|
||||
|
||||
let api_client = NymApiClient::from(nym_api);
|
||||
|
||||
//SW TBC what nodes exactly need to be scraped, the skimmed node endpoint seems to return more nodes
|
||||
let bonded_nodes = nym_api.get_all_bonded_nym_nodes().await?;
|
||||
let all_nodes = nym_api.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet
|
||||
let bonded_nodes = api_client.get_all_bonded_nym_nodes().await?;
|
||||
let all_nodes = api_client.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet
|
||||
tracing::debug!("Fetched {} total nodes", all_nodes.len());
|
||||
|
||||
let mut nodes_to_scrape: HashMap<NodeId, MetricsScrapingData> = bonded_nodes
|
||||
|
||||
@@ -19,7 +19,7 @@ use nym_validator_client::{
|
||||
use nym_validator_client::{
|
||||
nym_nodes::{NodeRole, SkimmedNode},
|
||||
nyxd::{contract_traits::PagedMixnetQueryClient, AccountId},
|
||||
QueryHttpRpcNyxdClient,
|
||||
NymApiClient, QueryHttpRpcNyxdClient,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
@@ -109,9 +109,11 @@ impl Monitor {
|
||||
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
||||
.no_hickory_dns()
|
||||
.with_timeout(self.nym_api_client_timeout)
|
||||
.build()?;
|
||||
.build::<&str>()?;
|
||||
|
||||
let described_nodes = nym_api
|
||||
let api_client = NymApiClient::from(nym_api);
|
||||
|
||||
let described_nodes = api_client
|
||||
.get_all_described_nodes()
|
||||
.await
|
||||
.log_error("get_all_described_nodes")?
|
||||
@@ -133,7 +135,7 @@ impl Monitor {
|
||||
|
||||
tracing::info!("🟣 🚪 gateway nodes: {}", gateways.len());
|
||||
|
||||
let bonded_nym_nodes = nym_api
|
||||
let bonded_nym_nodes = api_client
|
||||
.get_all_bonded_nym_nodes()
|
||||
.await?
|
||||
.into_iter()
|
||||
@@ -144,11 +146,10 @@ impl Monitor {
|
||||
tracing::info!("🟣 bonded_nodes: {}", bonded_nym_nodes.len());
|
||||
|
||||
// returns only bonded nodes
|
||||
let nym_nodes = nym_api
|
||||
.get_all_basic_nodes_with_metadata()
|
||||
let nym_nodes = api_client
|
||||
.get_all_basic_nodes()
|
||||
.await
|
||||
.log_error("get_all_basic_nodes")?
|
||||
.nodes;
|
||||
.log_error("get_all_basic_nodes")?;
|
||||
|
||||
let nym_node_count = nym_nodes.len();
|
||||
tracing::info!("🟣 get_all_basic_nodes: {}", nym_node_count);
|
||||
@@ -166,7 +167,8 @@ impl Monitor {
|
||||
self.location_cached(node_description).await;
|
||||
}
|
||||
|
||||
let mixnodes_detailed = nym_api
|
||||
let mixnodes_detailed = api_client
|
||||
.nym_api
|
||||
.get_mixnodes_detailed_unfiltered()
|
||||
.await
|
||||
.log_error("get_mixnodes_detailed_unfiltered")?;
|
||||
@@ -188,13 +190,15 @@ impl Monitor {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mixnodes_described = nym_api
|
||||
let mixnodes_described = api_client
|
||||
.nym_api
|
||||
.get_mixnodes_described()
|
||||
.await
|
||||
.log_error("get_mixnodes_described")?;
|
||||
|
||||
tracing::info!("🟣 mixnodes_described: {}", mixnodes_described.len());
|
||||
let mixing_assigned_nodes = nym_api
|
||||
let mixing_assigned_nodes = api_client
|
||||
.nym_api
|
||||
.get_basic_active_mixing_assigned_nodes(false, None, None, false)
|
||||
.await
|
||||
.log_error("get_basic_active_mixing_assigned_nodes")?
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::api::v1::gateway::models::WebSockets;
|
||||
use crate::api::v1::node::models::{
|
||||
AuxiliaryDetails, NodeDescription, NodeRoles, SignedHostInformation,
|
||||
};
|
||||
use crate::api::ErrorResponse;
|
||||
use crate::routes;
|
||||
use async_trait::async_trait;
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
@@ -20,7 +21,7 @@ use crate::api::v1::network_requester::models::NetworkRequester;
|
||||
use crate::api::v1::node_load::models::NodeLoad;
|
||||
pub use nym_http_api_client::Client;
|
||||
|
||||
pub type NymNodeApiClientError = HttpClientError;
|
||||
pub type NymNodeApiClientError = HttpClientError<ErrorResponse>;
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
|
||||
@@ -10,6 +10,7 @@ use nym_task::ShutdownToken;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::models::{KeyRotationInfoResponse, NodeRefreshBody};
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::sync::Arc;
|
||||
@@ -26,7 +27,7 @@ pub struct NymApisClient {
|
||||
|
||||
struct InnerClient {
|
||||
// NOTE: this was implemented before the internal http client supported multiple URLs
|
||||
active_client: Client,
|
||||
active_client: NymApiClient,
|
||||
available_urls: Vec<Url>,
|
||||
shutdown_token: ShutdownToken,
|
||||
currently_used_api: usize,
|
||||
@@ -44,7 +45,7 @@ impl NymApisClient {
|
||||
let mut urls = nym_apis.to_vec();
|
||||
urls.shuffle(&mut thread_rng());
|
||||
|
||||
let active_client = Client::builder(urls[0].clone())?
|
||||
let active_client = nym_http_api_client::Client::builder(urls[0].clone())?
|
||||
.no_hickory_dns()
|
||||
.with_user_agent(NymNode::user_agent())
|
||||
.with_timeout(Duration::from_secs(5))
|
||||
@@ -52,7 +53,7 @@ impl NymApisClient {
|
||||
|
||||
Ok(NymApisClient {
|
||||
inner: Arc::new(RwLock::new(InnerClient {
|
||||
active_client: active_client.clone(),
|
||||
active_client: NymApiClient::from(active_client),
|
||||
available_urls: urls,
|
||||
shutdown_token,
|
||||
currently_used_api: 0,
|
||||
@@ -87,19 +88,9 @@ impl NymApisClient {
|
||||
if guard.currently_used_api != last_working_endpoint {
|
||||
drop(guard);
|
||||
let mut guard = self.inner.write().await;
|
||||
let next_url = guard.available_urls[last_working_endpoint].clone();
|
||||
guard.currently_used_api = last_working_endpoint;
|
||||
|
||||
// Provide all URLs starting from the working endpoint for automatic failover
|
||||
let rotated_urls: Vec<_> = guard
|
||||
.available_urls
|
||||
.iter()
|
||||
.cycle()
|
||||
.skip(last_working_endpoint)
|
||||
.take(guard.available_urls.len())
|
||||
.map(|u| u.clone().into())
|
||||
.collect();
|
||||
|
||||
guard.active_client.change_base_urls(rotated_urls);
|
||||
guard.active_client.change_nym_api(next_url);
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
@@ -132,10 +123,10 @@ impl InnerClient {
|
||||
{
|
||||
let broadcast_fut =
|
||||
stream::iter(self.available_urls.clone()).for_each_concurrent(None, |url| {
|
||||
let mut nym_api = self.active_client.clone();
|
||||
// For broadcast, we intentionally set a single URL per client
|
||||
// to ensure each endpoint receives the request
|
||||
nym_api.change_base_urls(vec![url.clone().into()]);
|
||||
let nym_api = self
|
||||
.active_client
|
||||
.nym_api
|
||||
.clone_with_new_url(url.clone().into());
|
||||
let req_fut = req(nym_api, request_body);
|
||||
async move {
|
||||
if let Err(err) = req_fut.await {
|
||||
@@ -181,10 +172,10 @@ impl InnerClient {
|
||||
.skip(last_working)
|
||||
.chain(self.available_urls.iter().enumerate().take(last_working))
|
||||
{
|
||||
let mut nym_api = self.active_client.clone();
|
||||
// For exhaustive query, we test each endpoint individually in sequence
|
||||
// to find a working one - so single URL is correct here
|
||||
nym_api.change_base_urls(vec![url.clone().into()]);
|
||||
let nym_api = self
|
||||
.active_client
|
||||
.nym_api
|
||||
.clone_with_new_url(url.clone().into());
|
||||
|
||||
let timeout_fut = sleep(timeout_duration);
|
||||
let query_fut = req(nym_api);
|
||||
@@ -225,8 +216,8 @@ impl InnerClient {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Client> for InnerClient {
|
||||
fn as_ref(&self) -> &Client {
|
||||
impl AsRef<NymApiClient> for InnerClient {
|
||||
fn as_ref(&self) -> &NymApiClient {
|
||||
&self.active_client
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::node::routing_filter::network_filter::NetworkRoutingFilter;
|
||||
use async_trait::async_trait;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway::node::UserAgent;
|
||||
use nym_http_api_client::Client;
|
||||
use nym_node_metrics::prometheus_wrapper::{PrometheusMetric, PROMETHEUS_METRICS};
|
||||
use nym_noise::config::NoiseNetworkView;
|
||||
use nym_task::ShutdownToken;
|
||||
@@ -21,7 +20,7 @@ use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nym_nodes::{
|
||||
NodesByAddressesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata,
|
||||
};
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use nym_validator_client::{NymApiClient, ValidatorClientError};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::ops::Deref;
|
||||
@@ -36,7 +35,7 @@ use url::Url;
|
||||
const LOCAL_NODE_ID: NodeId = 1234567890;
|
||||
|
||||
struct NodesQuerier {
|
||||
client: Client,
|
||||
client: NymApiClient,
|
||||
nym_api_urls: Vec<Url>,
|
||||
currently_used_api: usize,
|
||||
}
|
||||
@@ -50,7 +49,7 @@ impl NodesQuerier {
|
||||
|
||||
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
|
||||
self.client
|
||||
.change_base_urls(self.nym_api_urls.iter().map(|u| u.clone().into()).collect())
|
||||
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
|
||||
}
|
||||
|
||||
async fn rewarded_set(&mut self) -> Result<EpochRewardedSet, ValidatorClientError> {
|
||||
@@ -63,7 +62,7 @@ impl NodesQuerier {
|
||||
if res.is_err() {
|
||||
self.use_next_nym_api()
|
||||
}
|
||||
Ok(res?.into())
|
||||
res
|
||||
}
|
||||
|
||||
async fn current_nymnodes(
|
||||
@@ -78,7 +77,7 @@ impl NodesQuerier {
|
||||
if res.is_err() {
|
||||
self.use_next_nym_api()
|
||||
}
|
||||
Ok(res?)
|
||||
res
|
||||
}
|
||||
|
||||
async fn query_for_info(
|
||||
@@ -87,6 +86,7 @@ impl NodesQuerier {
|
||||
) -> Result<NodesByAddressesResponse, ValidatorClientError> {
|
||||
let res = self
|
||||
.client
|
||||
.nym_api
|
||||
.nodes_by_addresses(ips)
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to obtain node information: {err}"));
|
||||
@@ -228,7 +228,7 @@ impl NetworkRefresher {
|
||||
|
||||
let mut this = NetworkRefresher {
|
||||
querier: NodesQuerier {
|
||||
client: nym_api,
|
||||
client: NymApiClient::from(nym_api),
|
||||
nym_api_urls,
|
||||
currently_used_api: 0,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "nym-signers-monitor"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "derive", "env", "string"] }
|
||||
humantime = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
||||
nym-bin-common = { path = "../common/bin-common", features = ["output_format", "basic_tracing"] }
|
||||
nym-ecash-signer-check = { path = "../common/ecash-signer-check" }
|
||||
nym-network-defaults = { path = "../common/network-defaults" }
|
||||
nym-task = { path = "../common/task" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
zulip-client = { path = "../common/zulip-client" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_bin_common::bin_info_owned;
|
||||
use nym_bin_common::output_format::OutputFormat;
|
||||
|
||||
#[derive(clap::Args, Debug)]
|
||||
pub(crate) struct Args {
|
||||
#[arg(short, long, default_value_t = OutputFormat::default())]
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: Args) {
|
||||
println!("{}", args.output.format(&bin_info_owned!()))
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub(crate) mod vars {
|
||||
pub(crate) const ZULIP_BOT_EMAIL_ARG: &str = "ZULIP_BOT_EMAIL";
|
||||
pub(crate) const ZULIP_BOT_API_KEY_ARG: &str = "ZULIP_BOT_API_KEY";
|
||||
pub(crate) const ZULIP_SERVER_URL_ARG: &str = "ZULIP_SERVER_URL";
|
||||
pub(crate) const ZULIP_NOTIFICATION_CHANNEL_ID_ARG: &str = "ZULIP_NOTIFICATION_CHANNEL_ID";
|
||||
pub(crate) const ZULIP_NOTIFICATION_CHANNEL_TOPIC_ARG: &str =
|
||||
"ZULIP_NOTIFICATION_CHANNEL_TOPIC";
|
||||
|
||||
pub(crate) const SIGNERS_MONITOR_CHECK_INTERVAL_ARG: &str = "SIGNERS_MONITOR_CHECK_INTERVAL";
|
||||
pub(crate) const SIGNERS_MONITOR_MIN_NOTIFICATION_DELAY_ARG: &str =
|
||||
"SIGNERS_MONITOR_MIN_NOTIFICATION_DELAY";
|
||||
|
||||
pub(crate) const KNOWN_NETWORK_NAME_ARG: &str = "KNOWN_NETWORK_NAME";
|
||||
pub(crate) const NYXD_CLIENT_CONFIG_ENV_FILE_ARG: &str = "NYXD_CLIENT_CONFIG_ENV_FILE";
|
||||
pub(crate) const NYXD_RPC_ENDPOINT_ARG: &str = "NYXD_RPC_ENDPOINT";
|
||||
pub(crate) const NYXD_DKG_CONTRACT_ADDRESS_ARG: &str = "NYXD_DKG_CONTRACT_ADDRESS";
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use nym_bin_common::bin_info;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub(crate) mod build_info;
|
||||
pub(crate) mod env;
|
||||
pub(crate) mod run;
|
||||
|
||||
// Helper for passing LONG_VERSION to clap
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub async fn execute(self) -> anyhow::Result<()> {
|
||||
match self.command {
|
||||
Commands::BuildInfo(args) => build_info::execute(args),
|
||||
Commands::Run(args) => run::execute(*args).await?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub(crate) enum Commands {
|
||||
/// Show build information of this binary
|
||||
BuildInfo(build_info::Args),
|
||||
|
||||
/// Start signers monitor and send notifications on any failures
|
||||
Run(Box<run::Args>),
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::env::vars::*;
|
||||
use crate::monitor::SignersMonitor;
|
||||
use anyhow::{bail, Context};
|
||||
use clap::ArgGroup;
|
||||
use nym_network_defaults::{setup_env, NymNetworkDetails};
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::QueryHttpRpcNyxdClient;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, clap::Args)]
|
||||
pub(crate) struct NyxdConnectionArgs {
|
||||
// for well-known networks, such mainnet, we can use hardcoded values
|
||||
/// Name of a well known network (such as 'mainnet') that has well-known
|
||||
/// pre-configured setup values
|
||||
#[clap(long, env = KNOWN_NETWORK_NAME_ARG)]
|
||||
pub(crate) known_network_name: Option<String>,
|
||||
|
||||
/// Path pointing to an env file that configures the nyxd client.
|
||||
#[clap(
|
||||
short,
|
||||
long,
|
||||
env = NYXD_CLIENT_CONFIG_ENV_FILE_ARG
|
||||
)]
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
/// For unknown networks (or if one wishes to override defaults),
|
||||
/// specify the RPC endpoint of a node from which signer information should be retrieved
|
||||
#[clap(long, env = NYXD_RPC_ENDPOINT_ARG)]
|
||||
pub(crate) nyxd_rpc_endpoint: Option<Url>,
|
||||
|
||||
/// For unknown networks, specify address of the DKG contract to pull signer information from.
|
||||
#[clap(
|
||||
long,
|
||||
requires("nyxd_rpc_endpoint"),
|
||||
env = NYXD_DKG_CONTRACT_ADDRESS_ARG
|
||||
)]
|
||||
pub(crate) dkg_contract_address: Option<AccountId>,
|
||||
// if needed down the line (not sure why), we could define additional args
|
||||
// for specifying denoms, etc.
|
||||
// #[clap(long, requires("dkg_contract_address"))]
|
||||
// pub(crate) mix_denom: Option<String>,
|
||||
}
|
||||
|
||||
impl NyxdConnectionArgs {
|
||||
fn get_minimal_nym_network_details(&self) -> anyhow::Result<NymNetworkDetails> {
|
||||
if let Some(known_network_name) = &self.known_network_name {
|
||||
match known_network_name.as_str() {
|
||||
"mainnet" => return Ok(NymNetworkDetails::new_mainnet()),
|
||||
other => bail!("{other} is not a known network name - please use another method of setting up chain connection"),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(config_env_file) = &self.config_env_file {
|
||||
setup_env(Some(config_env_file));
|
||||
return Ok(NymNetworkDetails::new_from_env());
|
||||
}
|
||||
|
||||
// SAFETY: clap ensures at least one of the fields is set
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let dkg_contract = self.dkg_contract_address.as_ref().unwrap();
|
||||
|
||||
// use mainnet's chain details (i.e. prefixes, denoms, etc)
|
||||
let mainnet_chain_details = NymNetworkDetails::new_mainnet().chain_details;
|
||||
Ok(NymNetworkDetails::new_empty()
|
||||
.with_chain_details(mainnet_chain_details)
|
||||
.with_coconut_dkg_contract(Some(dkg_contract.to_string())))
|
||||
}
|
||||
|
||||
pub(crate) fn try_create_nyxd_client(&self) -> anyhow::Result<QueryHttpRpcNyxdClient> {
|
||||
let network_details = self.get_minimal_nym_network_details()?;
|
||||
|
||||
let nyxd_endpoint = match &self.nyxd_rpc_endpoint {
|
||||
Some(nyxd_rpc_endpoint) => nyxd_rpc_endpoint.clone(),
|
||||
None => network_details
|
||||
.endpoints
|
||||
.first()
|
||||
.context("no nyxd endpoints provided")?
|
||||
.nyxd_url
|
||||
.parse()?,
|
||||
};
|
||||
|
||||
Ok(QueryHttpRpcNyxdClient::connect_with_network_details(
|
||||
nyxd_endpoint.as_str(),
|
||||
network_details,
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(clap::Args, Debug)]
|
||||
#[command(group(
|
||||
ArgGroup::new("nyxd_connection")
|
||||
.multiple(true)
|
||||
.required(true)
|
||||
.args([
|
||||
"known_network_name",
|
||||
"config_env_file",
|
||||
"nyxd_rpc_endpoint"
|
||||
])
|
||||
))]
|
||||
pub(crate) struct Args {
|
||||
/// Specify email address for the bot responsible for sending notifications to the zulip server
|
||||
/// in case 'upgrade' mode is detected
|
||||
#[clap(
|
||||
long,
|
||||
env = ZULIP_BOT_EMAIL_ARG
|
||||
)]
|
||||
pub(crate) zulip_bot_email: String,
|
||||
|
||||
/// Specify the API key for the bot responsible for sending notifications to the zulip server
|
||||
/// in case 'upgrade' mode is detected
|
||||
#[clap(
|
||||
long,
|
||||
env = ZULIP_BOT_API_KEY_ARG
|
||||
)]
|
||||
pub(crate) zulip_bot_api_key: String,
|
||||
|
||||
/// Specify the sever endpoint for the bot responsible for sending notifications
|
||||
/// in case 'upgrade' mode is detected
|
||||
#[clap(
|
||||
long,
|
||||
env = ZULIP_SERVER_URL_ARG
|
||||
)]
|
||||
pub(crate) zulip_server_url: Url,
|
||||
|
||||
/// Specify the channel id for where the notification is going to be sent
|
||||
#[clap(
|
||||
long,
|
||||
env = ZULIP_NOTIFICATION_CHANNEL_ID_ARG
|
||||
)]
|
||||
pub(crate) zulip_notification_channel_id: u32,
|
||||
|
||||
/// Optionally specify the channel topic for where the notification is going to be sent
|
||||
#[clap(
|
||||
long,
|
||||
env = ZULIP_NOTIFICATION_CHANNEL_TOPIC_ARG
|
||||
)]
|
||||
pub(crate) zulip_notification_topic: Option<String>,
|
||||
|
||||
/// Specify the delay between subsequent signers checks
|
||||
#[clap(
|
||||
long,
|
||||
env = SIGNERS_MONITOR_CHECK_INTERVAL_ARG,
|
||||
value_parser = humantime::parse_duration,
|
||||
default_value = "15m"
|
||||
)]
|
||||
pub(crate) signers_check_interval: Duration,
|
||||
|
||||
/// Specify the minimum delay between two subsequent notifications
|
||||
#[clap(
|
||||
long,
|
||||
env = SIGNERS_MONITOR_MIN_NOTIFICATION_DELAY_ARG,
|
||||
value_parser = humantime::parse_duration,
|
||||
default_value = "1h"
|
||||
)]
|
||||
pub(crate) minimum_notification_delay: Duration,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub(crate) nyxd_connection: NyxdConnectionArgs,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
SignersMonitor::new(args)?.run().await
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::Cli;
|
||||
use clap::Parser;
|
||||
use nym_bin_common::bin_info_owned;
|
||||
use nym_bin_common::logging::setup_tracing_logger;
|
||||
use tracing::{info, trace};
|
||||
|
||||
mod cli;
|
||||
mod monitor;
|
||||
pub(crate) mod test_result;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
setup_tracing_logger();
|
||||
let cli = Cli::parse();
|
||||
trace!("args: {cli:#?}");
|
||||
|
||||
let bin_info = bin_info_owned!();
|
||||
info!("using the following version: {bin_info}");
|
||||
|
||||
cli.execute().await
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::cli::run;
|
||||
use crate::test_result::{DisplayableSignerResult, Summary, TestResult};
|
||||
use nym_ecash_signer_check::check_signers_with_client;
|
||||
use nym_task::ShutdownManager;
|
||||
use nym_validator_client::QueryHttpRpcNyxdClient;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::time::interval;
|
||||
use tracing::{error, info};
|
||||
|
||||
const LOST_QUORUM_MSG: &str = r#"
|
||||
# 🔥🔥🔥 LOST SIGNING QUORUM 🔥🔥🔥
|
||||
We seem to have lost the signing quorum - check if we should enable the 'upgrade' mode!
|
||||
"#;
|
||||
|
||||
const UNKNOWN_QUORUM_MSG: &str = r#"
|
||||
# ❓❓❓ UNKNOWN SIGNING QUORUM ❓❓❓
|
||||
We can't determine the signing quroum - if we're not undergoing DKG exchange check if we should enable the 'upgrade' mode!
|
||||
"#;
|
||||
|
||||
pub(crate) struct SignersMonitor {
|
||||
zulip_client: zulip_client::Client,
|
||||
nyxd_client: QueryHttpRpcNyxdClient,
|
||||
|
||||
notification_channel_id: u32,
|
||||
notification_topic: Option<String>,
|
||||
check_interval: Duration,
|
||||
min_notification_delay: Duration,
|
||||
last_notification_sent: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl SignersMonitor {
|
||||
pub(crate) fn new(args: run::Args) -> anyhow::Result<Self> {
|
||||
let zulip_client = zulip_client::Client::builder(
|
||||
args.zulip_bot_email,
|
||||
args.zulip_bot_api_key,
|
||||
args.zulip_server_url,
|
||||
)?
|
||||
.build()?;
|
||||
let nyxd_client = args.nyxd_connection.try_create_nyxd_client()?;
|
||||
|
||||
Ok(SignersMonitor {
|
||||
zulip_client,
|
||||
nyxd_client,
|
||||
notification_channel_id: args.zulip_notification_channel_id,
|
||||
notification_topic: args.zulip_notification_topic,
|
||||
check_interval: args.signers_check_interval,
|
||||
min_notification_delay: args.minimum_notification_delay,
|
||||
last_notification_sent: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn check_signers(&mut self) -> anyhow::Result<TestResult> {
|
||||
info!("starting signer check...");
|
||||
let check_result = check_signers_with_client(&self.nyxd_client).await?;
|
||||
|
||||
let mut unreachable_signers = 0;
|
||||
let mut unknown_local_chain_status = 0;
|
||||
let mut stalled_local_chain = 0;
|
||||
let mut working_local_chain = 0;
|
||||
let mut unknown_credential_issuance_status = 0;
|
||||
let mut working_credential_issuance = 0;
|
||||
let mut unavailable_credential_issuance = 0;
|
||||
|
||||
let mut fully_working = 0;
|
||||
|
||||
let mut signers = Vec::new();
|
||||
for result in &check_result.results {
|
||||
if result.signer_unreachable() {
|
||||
unreachable_signers += 1;
|
||||
}
|
||||
|
||||
if result.unknown_chain_status() {
|
||||
unknown_local_chain_status += 1;
|
||||
}
|
||||
if result.chain_available() {
|
||||
working_local_chain += 1;
|
||||
}
|
||||
if result.chain_provably_stalled() || result.chain_unprovably_stalled() {
|
||||
stalled_local_chain += 1;
|
||||
}
|
||||
|
||||
if result.unknown_signing_status() {
|
||||
unknown_credential_issuance_status += 1;
|
||||
}
|
||||
if result.signing_available() {
|
||||
working_credential_issuance += 1;
|
||||
}
|
||||
if result.signing_provably_unavailable() || result.signing_unprovably_unavailable() {
|
||||
unavailable_credential_issuance += 1;
|
||||
}
|
||||
|
||||
let signing_available = if result.unknown_signing_status() {
|
||||
None
|
||||
} else {
|
||||
Some(result.signing_available())
|
||||
};
|
||||
|
||||
let chain_not_stalled = if result.unknown_chain_status() {
|
||||
None
|
||||
} else {
|
||||
Some(result.chain_available())
|
||||
};
|
||||
|
||||
if (result.chain_available()) && (result.signing_available()) {
|
||||
fully_working += 1;
|
||||
}
|
||||
|
||||
signers.push(DisplayableSignerResult {
|
||||
version: result
|
||||
.try_get_test_result()
|
||||
.map(|r| r.reported_version.clone()),
|
||||
url: result.information.announce_address.clone(),
|
||||
signing_available,
|
||||
chain_not_stalled,
|
||||
})
|
||||
}
|
||||
|
||||
let signing_quorum_available = check_result.threshold.map(|threshold| {
|
||||
(working_local_chain as u64) >= threshold
|
||||
&& (working_credential_issuance as u64) >= threshold
|
||||
});
|
||||
signers.sort_by_key(|s| s.version.clone());
|
||||
|
||||
let summary = Summary {
|
||||
signing_quorum_available,
|
||||
fully_working,
|
||||
unreachable_signers,
|
||||
registered_signers: check_result.results.len(),
|
||||
unknown_local_chain_status,
|
||||
stalled_local_chain,
|
||||
working_local_chain,
|
||||
unknown_credential_issuance_status,
|
||||
working_credential_issuance,
|
||||
unavailable_credential_issuance,
|
||||
threshold: check_result.threshold,
|
||||
};
|
||||
|
||||
Ok(TestResult { summary, signers })
|
||||
}
|
||||
|
||||
async fn perform_signer_check(&mut self) -> anyhow::Result<()> {
|
||||
let result = self.check_signers().await?;
|
||||
let result_md = result.results_to_markdown_message();
|
||||
|
||||
let msg = if result.quorum_unavailable() {
|
||||
Some(format!("{LOST_QUORUM_MSG}\n\n{result_md}",))
|
||||
} else if result.quorum_unknown() {
|
||||
Some(format!("{UNKNOWN_QUORUM_MSG}\n\n{result_md}",))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(msg) = msg {
|
||||
self.maybe_notify_about_failure(&msg).await?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn maybe_notify_about_failure(&mut self, message: &String) -> anyhow::Result<()> {
|
||||
if let Some(last_notification_sent) = self.last_notification_sent {
|
||||
if last_notification_sent + self.min_notification_delay > OffsetDateTime::now_utc() {
|
||||
info!("too soon to send another notification");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
self.send_zulip_notification(message).await?;
|
||||
self.last_notification_sent = Some(OffsetDateTime::now_utc());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_zulip_notification(&self, message: &String) -> anyhow::Result<()> {
|
||||
self.zulip_client
|
||||
.send_channel_message((
|
||||
self.notification_channel_id,
|
||||
message,
|
||||
&self.notification_topic,
|
||||
))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_shutdown_notification(&self) -> anyhow::Result<()> {
|
||||
println!("here be sending shutdown notification");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) -> anyhow::Result<()> {
|
||||
let shutdown_manager =
|
||||
ShutdownManager::new("nym-signers-monitor").with_default_shutdown_signals()?;
|
||||
|
||||
let mut check_interval = interval(self.check_interval);
|
||||
|
||||
while !shutdown_manager.root_token.is_cancelled() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown_manager.root_token.cancelled() => {
|
||||
info!("received shutdown");
|
||||
break;
|
||||
}
|
||||
_ = check_interval.tick() => {
|
||||
if let Err(err) = self.perform_signer_check().await {
|
||||
error!("failed to check signers: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
shutdown_manager.close();
|
||||
shutdown_manager.run_until_shutdown().await;
|
||||
|
||||
if let Err(err) = self.send_shutdown_notification().await {
|
||||
error!("failed to send shutdown notification: {err}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
fn maybe_bool_to_emoji_string(maybe_bool: Option<bool>) -> String {
|
||||
match maybe_bool {
|
||||
None => "⚠️ unknown".into(),
|
||||
Some(true) => "✅ yes".into(),
|
||||
Some(false) => "❌ no".into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DisplayableSignerResult {
|
||||
pub(crate) url: String,
|
||||
pub(crate) version: Option<String>,
|
||||
pub(crate) signing_available: Option<bool>,
|
||||
pub(crate) chain_not_stalled: Option<bool>,
|
||||
}
|
||||
|
||||
impl DisplayableSignerResult {
|
||||
fn to_markdown_table_row(&self) -> String {
|
||||
format!(
|
||||
"| {} | {} | {} | {} |",
|
||||
self.url,
|
||||
self.version.as_deref().unwrap_or("unknown"),
|
||||
maybe_bool_to_emoji_string(self.signing_available),
|
||||
maybe_bool_to_emoji_string(self.chain_not_stalled)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TestResult {
|
||||
pub(crate) summary: Summary,
|
||||
pub(crate) signers: Vec<DisplayableSignerResult>,
|
||||
}
|
||||
|
||||
impl TestResult {
|
||||
pub(crate) fn quorum_unavailable(&self) -> bool {
|
||||
self.summary.signing_quorum_available.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub(crate) fn quorum_unknown(&self) -> bool {
|
||||
self.summary.signing_quorum_available.is_none()
|
||||
}
|
||||
|
||||
pub(crate) fn results_to_markdown_message(&self) -> String {
|
||||
let p_available = format!(
|
||||
"{:.2}",
|
||||
(self.summary.fully_working as f32 / self.summary.registered_signers as f32) * 100.
|
||||
);
|
||||
|
||||
format!(
|
||||
r#"
|
||||
## Summary
|
||||
- quorum available: {} ({p_available}% of signers fully available)
|
||||
- signers fully working: {}
|
||||
- signing threshold: {}
|
||||
- registered signers: {}
|
||||
- unreachable signers: {}
|
||||
|
||||
### Chain Status
|
||||
- unknown status: {}
|
||||
- working chain: {}
|
||||
- stalled chain: {}
|
||||
|
||||
### Credential Issuance Status
|
||||
(note: signers below 1.1.64 do not return fully reliable results)
|
||||
- unknown status: {}
|
||||
- working issuance: {}
|
||||
- unavailable issuance: {}
|
||||
|
||||
## Detailed Results
|
||||
| address | version | chain working | issuance (maybe) available |
|
||||
| - | - | - | - |
|
||||
{}
|
||||
"#,
|
||||
maybe_bool_to_emoji_string(self.summary.signing_quorum_available),
|
||||
self.summary.fully_working,
|
||||
self.summary
|
||||
.threshold
|
||||
.map(|threshold| threshold.to_string())
|
||||
.unwrap_or("???".to_string()),
|
||||
self.summary.registered_signers,
|
||||
self.summary.unreachable_signers,
|
||||
self.summary.unknown_local_chain_status,
|
||||
self.summary.working_local_chain,
|
||||
self.summary.stalled_local_chain,
|
||||
self.summary.unknown_credential_issuance_status,
|
||||
self.summary.working_credential_issuance,
|
||||
self.summary.unavailable_credential_issuance,
|
||||
self.signers
|
||||
.iter()
|
||||
.map(|r| r.to_markdown_table_row())
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Summary {
|
||||
pub(crate) signing_quorum_available: Option<bool>,
|
||||
pub(crate) fully_working: usize,
|
||||
pub(crate) threshold: Option<u64>,
|
||||
|
||||
pub(crate) registered_signers: usize,
|
||||
pub(crate) unreachable_signers: usize,
|
||||
|
||||
pub(crate) unknown_local_chain_status: usize,
|
||||
pub(crate) stalled_local_chain: usize,
|
||||
pub(crate) working_local_chain: usize,
|
||||
|
||||
pub(crate) unknown_credential_issuance_status: usize,
|
||||
pub(crate) working_credential_issuance: usize,
|
||||
pub(crate) unavailable_credential_issuance: usize,
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use nym_task::ShutdownToken;
|
||||
|
||||
use celes::Country;
|
||||
use nym_validator_client::models::NymNodeDescription;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use std::{net::IpAddr, sync::Arc};
|
||||
@@ -13,8 +14,6 @@ use tokio::sync::RwLock;
|
||||
use tokio::time::interval;
|
||||
use url::Url;
|
||||
|
||||
use nym_http_api_client::Client;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use tracing::{error, info, trace, warn};
|
||||
|
||||
const NETWORK_CACHE_TTL: Duration = Duration::from_secs(600);
|
||||
@@ -23,7 +22,7 @@ type IpToCountryMap = HashMap<IpAddr, Option<Country>>;
|
||||
|
||||
// SW this should use a proper NS API client once it exists
|
||||
struct NodesQuerier {
|
||||
client: Client,
|
||||
client: NymApiClient,
|
||||
}
|
||||
|
||||
impl NodesQuerier {
|
||||
@@ -100,11 +99,14 @@ impl NetworkRefresher {
|
||||
this
|
||||
}
|
||||
|
||||
fn build_http_api_client(url: Url) -> Result<Client> {
|
||||
Ok(Client::builder(url)?
|
||||
.no_hickory_dns()
|
||||
.with_user_agent("node-statistics-api")
|
||||
.build()?)
|
||||
fn build_http_api_client(url: Url) -> Result<NymApiClient> {
|
||||
Ok(
|
||||
nym_http_api_client::Client::builder::<_, anyhow::Error>(url)?
|
||||
.no_hickory_dns()
|
||||
.with_user_agent("node-statistics-api")
|
||||
.build::<anyhow::Error>()?
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
async fn refresh_network_nodes(&mut self) -> Result<()> {
|
||||
|
||||
@@ -42,7 +42,6 @@ nym-credentials = { path = "../common/credentials" }
|
||||
nym-network-defaults = { path = "../common/network-defaults" }
|
||||
nym-task = { path = "../common/task" }
|
||||
nym-validator-client = { path = "../common/client-libs/validator-client" }
|
||||
nym-http-api-client = { path = "../common/http-api-client" }
|
||||
nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" }
|
||||
nyxd-scraper = { path = "../common/nyxd-scraper" }
|
||||
nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" }
|
||||
|
||||
@@ -9,7 +9,6 @@ use crate::rewarder::ticketbook_issuance::verifier::TicketbookIssuanceVerifier;
|
||||
use crate::rewarder::Rewarder;
|
||||
use anyhow::bail;
|
||||
use nym_ecash_time::ecash_default_expiration_date;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use time::macros::format_description;
|
||||
|
||||
@@ -15,7 +15,7 @@ use nym_validator_client::nyxd::module_traits::staking::{
|
||||
use nym_validator_client::nyxd::{
|
||||
AccountId, Coin, CosmWasmClient, Hash, PageRequest, StakingQueryClient,
|
||||
};
|
||||
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
|
||||
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient, NymApiClient};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
@@ -139,23 +139,10 @@ impl NyxdClient {
|
||||
continue;
|
||||
};
|
||||
|
||||
let api_client = match nym_http_api_client::Client::builder(api_address)
|
||||
.and_then(|b| b.build())
|
||||
{
|
||||
Ok(client) => client,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to create API client for issuer {}: {}",
|
||||
info.assigned_index, err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
issuers.push(CredentialIssuer {
|
||||
public_key,
|
||||
operator_account: addr_to_account_id(share.owner),
|
||||
api_client,
|
||||
api_client: NymApiClient::new(api_address),
|
||||
verification_key,
|
||||
node_id: info.assigned_index,
|
||||
})
|
||||
|
||||
@@ -6,8 +6,8 @@ use cosmwasm_std::{Addr, Decimal, Uint128};
|
||||
use nym_coconut_dkg_common::types::NodeIndex;
|
||||
use nym_compact_ecash::VerificationKeyAuth;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::{AccountId, Coin};
|
||||
use nym_validator_client::NymApiClient;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use tracing::info;
|
||||
|
||||
@@ -68,7 +68,7 @@ impl TicketbookIssuanceResults {
|
||||
pub struct CredentialIssuer {
|
||||
pub public_key: ed25519::PublicKey,
|
||||
pub operator_account: AccountId,
|
||||
pub api_client: nym_http_api_client::Client,
|
||||
pub api_client: NymApiClient,
|
||||
pub verification_key: VerificationKeyAuth,
|
||||
pub node_id: NodeIndex,
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ use nym_validator_client::ecash::models::{
|
||||
IssuedTicketbooksChallengeCommitmentResponse, IssuedTicketbooksDataRequestBody,
|
||||
IssuedTicketbooksDataResponse, IssuedTicketbooksDataResponseBody, IssuedTicketbooksForResponse,
|
||||
};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::signable::{SignableMessageBody, SignedMessage};
|
||||
use rand::distributions::{Distribution, WeightedIndex};
|
||||
|
||||
Generated
-30
@@ -4224,8 +4224,6 @@ dependencies = [
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_plain",
|
||||
"serde_yaml",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"url",
|
||||
@@ -6310,15 +6308,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_plain"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.20"
|
||||
@@ -6381,19 +6370,6 @@ dependencies = [
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 2.8.0",
|
||||
"itoa 1.0.15",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serdect"
|
||||
version = "0.2.0"
|
||||
@@ -7883,12 +7859,6 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
|
||||
@@ -2,6 +2,7 @@ use nym_contracts_common::signing::SigningAlgorithm;
|
||||
use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError;
|
||||
use nym_node_requests::api::client::NymNodeApiClientError;
|
||||
use nym_types::error::TypesError;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWalletError;
|
||||
use nym_validator_client::{nyxd::error::NyxdError, ValidatorClientError};
|
||||
use nym_wallet_types::network::Network;
|
||||
@@ -44,6 +45,11 @@ pub enum BackendError {
|
||||
source: eyre::Report,
|
||||
},
|
||||
#[error(transparent)]
|
||||
NymApiError {
|
||||
#[from]
|
||||
source: NymAPIError,
|
||||
},
|
||||
#[error(transparent)]
|
||||
NymNodeApiError {
|
||||
#[from]
|
||||
source: NymNodeApiClientError,
|
||||
|
||||
@@ -15,6 +15,7 @@ use nym_mixnet_contract_common::nym_node::{NodeConfigUpdate, StakeSaturationResp
|
||||
use nym_mixnet_contract_common::{MixNodeConfigUpdate, NodeId, NymNode};
|
||||
use nym_node_requests::api::client::NymNodeApiClientExt;
|
||||
use nym_node_requests::api::v1::node::models::NodeDescription;
|
||||
use nym_node_requests::api::ErrorResponse;
|
||||
use nym_types::currency::DecCoin;
|
||||
use nym_types::gateway::GatewayBond;
|
||||
use nym_types::mixnode::{MixNodeDetails, NodeCostParams};
|
||||
@@ -585,12 +586,14 @@ pub async fn get_nym_node_description(
|
||||
port: u16,
|
||||
) -> Result<NodeDescription, BackendError> {
|
||||
Ok(
|
||||
nym_node_requests::api::Client::builder(format!("http://{host}:{port}"))?
|
||||
.with_timeout(Duration::from_millis(1000))
|
||||
.with_user_agent(format!("nym-wallet/{}", env!("CARGO_PKG_VERSION")))
|
||||
.build()?
|
||||
.get_description()
|
||||
.await?,
|
||||
nym_node_requests::api::Client::builder::<_, ErrorResponse>(format!(
|
||||
"http://{host}:{port}"
|
||||
))?
|
||||
.with_timeout(Duration::from_millis(1000))
|
||||
.with_user_agent(format!("nym-wallet/{}", env!("CARGO_PKG_VERSION")))
|
||||
.build::<ErrorResponse>()?
|
||||
.get_description()
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"minimumSystemVersion": "15.3.2",
|
||||
"minimumSystemVersion": "14.4.1",
|
||||
"exceptionDomain": "",
|
||||
"signingIdentity": "Developer ID Application: Nym Technologies SA (VW5DZLFHM5)",
|
||||
"entitlements": null
|
||||
|
||||
@@ -38,7 +38,6 @@ nym-socks5-client-core = { path = "../../../common/socks5-client-core" }
|
||||
nym-validator-client = { path = "../../../common/client-libs/validator-client", features = [
|
||||
"http-client",
|
||||
] }
|
||||
nym-http-api-client = { path = "../../../common/http-api-client" }
|
||||
nym-socks5-requests = { path = "../../../common/socks5/requests" }
|
||||
nym-ordered-buffer = { path = "../../../common/socks5/ordered-buffer" }
|
||||
nym-service-providers-common = { path = "../../../service-providers/common" }
|
||||
|
||||
@@ -4,22 +4,18 @@
|
||||
use nym_sdk::mixnet;
|
||||
use nym_sdk::mixnet::MixnetMessageSender;
|
||||
use nym_topology::provider_trait::{async_trait, ToTopologyMetadata, TopologyProvider};
|
||||
use nym_topology::{EpochRewardedSet, NymTopology};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_topology::NymTopology;
|
||||
use url::Url;
|
||||
|
||||
struct MyTopologyProvider {
|
||||
validator_client: nym_http_api_client::Client,
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
}
|
||||
|
||||
impl MyTopologyProvider {
|
||||
fn new(nym_api_url: Url) -> MyTopologyProvider {
|
||||
let validator_client = nym_http_api_client::Client::builder(nym_api_url)
|
||||
.expect("Failed to create API client builder")
|
||||
.build()
|
||||
.expect("Failed to build API client");
|
||||
|
||||
MyTopologyProvider { validator_client }
|
||||
MyTopologyProvider {
|
||||
validator_client: nym_validator_client::client::NymApiClient::new(nym_api_url),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_topology(&self) -> NymTopology {
|
||||
@@ -37,8 +33,7 @@ impl MyTopologyProvider {
|
||||
|
||||
let metadata = mixnodes_response.metadata.to_topology_metadata();
|
||||
|
||||
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
|
||||
let mut base_topology = NymTopology::new(metadata, epoch_rewarded_set, Vec::new());
|
||||
let mut base_topology = NymTopology::new(metadata, rewarded_set, Vec::new());
|
||||
|
||||
// in our topology provider only use mixnodes that have node_id divisible by 3
|
||||
// and has exactly 100 performance score
|
||||
|
||||
@@ -37,7 +37,6 @@ nym-crypto = { path = "../../../common/crypto", features = ["asymmetric", "rand"
|
||||
nym-config = { path = "../../../common/config" }
|
||||
nym-validator-client = { path = "../../../common/client-libs/validator-client" }
|
||||
nym-compact-ecash = { path = "../../../common/nym_offline_compact_ecash" }
|
||||
nym-http-api-client = { path = "../../../common/http-api-client" }
|
||||
dkg-bypass-contract = { path = "dkg-bypass-contract", default-features = false }
|
||||
|
||||
# contracts:
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::manager::network::LoadedNetwork;
|
||||
use crate::manager::NetworkManager;
|
||||
use console::style;
|
||||
use nym_config::{must_get_home, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR};
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::NymApiClient;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
@@ -91,10 +91,7 @@ impl NetworkManager {
|
||||
"⌛waiting for any gateway to appear in the directory ({api_url})..."
|
||||
));
|
||||
|
||||
let api_client = nym_http_api_client::Client::builder(api_url.clone())
|
||||
.expect("Failed to create API client builder")
|
||||
.build()
|
||||
.expect("Failed to build API client");
|
||||
let api_client = NymApiClient::new(api_url);
|
||||
|
||||
let wait_fut = async {
|
||||
let inner_fut = async {
|
||||
|
||||
@@ -25,7 +25,6 @@ time = { workspace = true }
|
||||
nym-validator-client = { path = "../../../common/client-libs/validator-client" }
|
||||
nym-bin-common = { path = "../../../common/bin-common", features = ["output_format", "basic_tracing"] }
|
||||
nym-network-defaults = { path = "../../../common/network-defaults" }
|
||||
nym-http-api-client = { path = "../../../common/http-api-client" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user