Compare commits

...

25 Commits

Author SHA1 Message Date
Jędrzej Stuczyński 3c48fa6b8c temp: dont setup hickory 2025-09-09 17:22:25 +01:00
Jędrzej Stuczyński 689cb3a9c2 TEMP TO REMOVE: extra log during deposit 2025-09-09 17:16:55 +01:00
Jędrzej Stuczyński 60870fc458 another restoration 2025-09-09 16:44:09 +01:00
Jędrzej Stuczyński b44997f412 temporarily restored GenericRequestFailure variant 2025-09-09 16:17:00 +01:00
Jędrzej Stuczyński 34aaa60a44 feat: attempt to add more details to reqwest errors 2025-09-09 14:47:44 +01:00
Jędrzej Stuczyński dfa4563d22 use display impl for urls 2025-09-09 13:35:40 +01:00
Bogdan-Ștefan Neacşu f30cc7a8a1 Use default value for the ports until api is deployed 2025-09-08 11:17:36 +03:00
durch 4f08161315 fix: Address critical issues in client configuration registry implementation
- Fixed HeaderMapInit parsing bug that would cause compilation errors
- Added comprehensive documentation with usage examples and DSL reference
- Improved error handling with better error messages for invalid headers
- Added test coverage for both macro and registry functionality
- Added debug inspection capabilities for registered configurations
- Fixed module name conflicts in tests by using separate modules

All tests now passing:
- 7 macro tests validating DSL parsing and code generation
- 4 registry tests verifying configuration collection and application
2025-09-05 20:58:27 +02:00
durch 23d4c75597 Ergonomics and call sites, incept the builder 2025-09-05 18:18:45 +02:00
durch c24ca29212 Where do we need to put inventory 2025-09-05 17:30:03 +02:00
durch 6cff49caf6 Syn workspace version 2025-09-05 17:16:56 +02:00
durch ea31aa6bcf Rename macro 2025-09-05 17:15:13 +02:00
durch 410683dae6 Registry proc-macro 2025-09-05 17:12:44 +02:00
Drazen Urch 043fb31c09 Merge branch 'develop' into domain-fronting-integration 2025-09-01 19:10:41 +02:00
durch e36573463c Merge branch 'domain-fronting-integration' of https://github.com/nymtech/nym into domain-fronting-integration 2025-09-01 14:50:05 +02:00
durch c79471e9ad Assorted CI fixes 2025-09-01 14:48:19 +02:00
Drazen Urch 1cd8713d26 Merge branch 'develop' into domain-fronting-integration 2025-09-01 14:04:10 +02:00
durch edb797da68 Explicit warning on missing fronting configuration 2025-09-01 12:25:25 +02:00
durch 72e1290d8f Remove error generics, address PR comments 2025-09-01 11:28:17 +02:00
Drazen Urch 905d8ed7de Update common/client-core/src/client/base_client/mod.rs
Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2025-09-01 11:21:24 +02:00
durch 4e5ccf7926 fix: provide all API URLs for automatic failover in endpoint rotation
Previously, when rotating API endpoints, only a single URL was provided to the
HTTP client, defeating the purpose of having multiple URLs for resilience.

Changes:
- NymApiTopologyProvider now provides all URLs in rotated order when switching endpoints
- NymApisClient similarly provides all URLs starting from the working endpoint
- Added clarifying comments for broadcast/exhaustive query methods where single URLs are intentionally used
- This enables the HTTP client's built-in failover mechanism while maintaining endpoint rotation behavior

The fix ensures that if the primary endpoint fails, the client can automatically
failover to alternative endpoints without manual intervention, improving overall
network resilience.
2025-08-29 13:37:45 +02:00
durch 0a8eb940bb feat: complete migration to unified HTTP client and fix all compilation errors
- Added missing NymApiClientExt trait methods (get_all_expanded_nodes, change_base_urls)
- Fixed all compilation errors across the workspace
- Updated nym-node to use unified client instead of deprecated NymApiClient
- Fixed type conversions for RewardedSetResponse → EpochRewardedSet
- Added nym-http-api-client dependency where needed
- Updated all examples and documentation to use new client API
2025-08-29 13:36:22 +02:00
durch 34fb67602c fix: resolve all compilation errors after NymApiClient migration
- Add missing nym-http-api-client dependencies to multiple crates
- Add NymApiClientExt trait imports where needed
- Fix type mismatches from NymApiClient to unified Client
- Add error conversions for NymAPIError in various error enums
- Implement missing trait methods (get_current_rewarded_set, get_all_basic_nodes_with_metadata, get_all_described_nodes)
- Fix type conversions for RewardedSetResponse in network monitor
- Update all API client instantiation to use new unified HTTP client
2025-08-29 13:35:41 +02:00
durch 766ae8dd8e feat: migrate NymApiClient usage to unified HTTP client
- Wire up domain fronting configuration in NymNetworkDetails
- Implement NymApiClientExt trait for base nym_http_api_client::Client
- Migrate direct NymApiClient usage in multiple components:
  - nym-network-monitor
  - verloc measurements
  - connection tester
  - coconut/ecash client
  - validator rewarder
- Add Copy derive to ApiUrlConst to enable iteration
- Update error handling and Display implementations

This enables automatic domain fronting for all Nym API calls via the configured CDN front hosts.
2025-08-29 13:33:37 +02:00
durch bd1fd73ba0 feat: unify HTTP client creation and enable domain fronting
Enhanced the base nym_http_api_client to reduce fragmentation and enable domain fronting:

- Added SerializationFormat enum for explicit JSON/bincode choice (no auto-detection)
- Added from_network() method to create clients from NymNetworkDetails with domain fronting
- Added with_bincode() builder method for explicit serialization configuration
- Set Accept header based on serialization preference
- Added deprecation paths for NymApiClient wrapper and nym_api::Client re-export
- Enabled domain fronting support via network defaults feature

This is part of a broader effort to consolidate HTTP client implementations across the codebase,
reducing ~500 lines of wrapper code and providing automatic domain fronting for censorship resistance.
2025-08-29 13:33:37 +02:00
87 changed files with 1876 additions and 541 deletions
Generated
+127 -89
View File
@@ -11,7 +11,7 @@ dependencies = [
"macroific",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -408,7 +408,7 @@ dependencies = [
"rustc-hash",
"serde",
"serde_derive",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -490,7 +490,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -501,7 +501,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -709,7 +709,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -1323,7 +1323,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -1345,7 +1345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -1563,7 +1563,7 @@ checksum = "a782b93fae93e57ca8ad3e9e994e784583f5933aeaaa5c80a545c4b437be2047"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -1587,7 +1587,7 @@ checksum = "e01c9214319017f6ebd8e299036e1f717fa9bb6724e758f7d6fb2477599d1a29"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -1831,7 +1831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -1929,7 +1929,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -2081,7 +2081,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -2092,7 +2092,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -2145,7 +2145,7 @@ dependencies = [
"macroific",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -2188,7 +2188,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -2217,7 +2217,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
"unicode-xid",
]
@@ -2229,7 +2229,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
"unicode-xid",
]
@@ -2298,7 +2298,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -2357,7 +2357,7 @@ version = "0.1.0"
dependencies = [
"cosmwasm-std",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -2507,7 +2507,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -2637,7 +2637,7 @@ dependencies = [
"macroific",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -2867,7 +2867,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -3720,7 +3720,7 @@ checksum = "0ab604ee7085efba6efc65e4ebca0e9533e3aff6cb501d7d77b211e3a781c6d5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -3789,7 +3789,7 @@ dependencies = [
"macroific",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -3891,9 +3891,9 @@ checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02"
[[package]]
name = "inventory"
version = "0.3.20"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83"
checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e"
dependencies = [
"rustversion",
]
@@ -3960,7 +3960,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -4039,7 +4039,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -4285,7 +4285,7 @@ dependencies = [
"proc-macro2",
"quote",
"sealed",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -4297,7 +4297,7 @@ dependencies = [
"proc-macro2",
"quote",
"sealed",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -4310,7 +4310,7 @@ dependencies = [
"macroific_core",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -4338,7 +4338,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -4831,6 +4831,7 @@ dependencies = [
"nym-ecash-signer-check",
"nym-ecash-time",
"nym-gateway-client",
"nym-http-api-client",
"nym-http-api-common",
"nym-mixnet-contract-common",
"nym-node-requests",
@@ -5042,6 +5043,7 @@ dependencies = [
"nym-crypto",
"nym-ecash-contract-common",
"nym-ecash-time",
"nym-http-api-client",
"nym-id",
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
@@ -5130,6 +5132,7 @@ dependencies = [
"nym-http-api-client",
"nym-id",
"nym-mixnet-client",
"nym-mixnet-contract-common",
"nym-network-defaults",
"nym-nonexhaustive-delayqueue",
"nym-pemstore",
@@ -5482,6 +5485,7 @@ dependencies = [
"nym-crypto",
"nym-ecash-contract-common",
"nym-ecash-time",
"nym-http-api-client",
"nym-network-defaults",
"nym-serde-helpers",
"nym-validator-client",
@@ -5581,6 +5585,7 @@ version = "0.1.0"
dependencies = [
"futures",
"nym-ecash-signer-check-types",
"nym-http-api-client",
"nym-network-defaults",
"nym-validator-client",
"semver 1.0.26",
@@ -5841,14 +5846,19 @@ dependencies = [
"encoding_rs",
"hickory-resolver",
"http 1.3.1",
"inventory",
"itertools 0.14.0",
"mime",
"nym-bin-common",
"nym-http-api-client-macro",
"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",
@@ -5856,6 +5866,18 @@ dependencies = [
"wasmtimer",
]
[[package]]
name = "nym-http-api-client-macro"
version = "0.1.0"
dependencies = [
"nym-http-api-client",
"proc-macro-crate",
"proc-macro2",
"quote",
"reqwest 0.12.22",
"syn 2.0.106",
]
[[package]]
name = "nym-http-api-common"
version = "0.1.0"
@@ -6105,6 +6127,8 @@ dependencies = [
"nym-client-core",
"nym-crypto",
"nym-gateway-requests",
"nym-http-api-client",
"nym-mixnet-contract-common",
"nym-network-defaults",
"nym-sdk",
"nym-sphinx",
@@ -6572,6 +6596,7 @@ dependencies = [
"nym-credentials-interface",
"nym-crypto",
"nym-gateway-requests",
"nym-http-api-client",
"nym-network-defaults",
"nym-ordered-buffer",
"nym-service-providers-common",
@@ -7147,6 +7172,7 @@ dependencies = [
"nym-credentials-interface",
"nym-crypto",
"nym-ecash-time",
"nym-http-api-client",
"nym-network-defaults",
"nym-pemstore",
"nym-serde-helpers",
@@ -7176,7 +7202,9 @@ dependencies = [
"bytes",
"futures",
"humantime",
"nym-api-requests",
"nym-crypto",
"nym-http-api-client",
"nym-task",
"nym-validator-client",
"rand 0.8.5",
@@ -7784,7 +7812,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -7847,7 +7875,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -7876,7 +7904,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -8084,6 +8112,15 @@ dependencies = [
"elliptic-curve",
]
[[package]]
name = "proc-macro-crate"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
@@ -8137,7 +8174,7 @@ dependencies = [
"itertools 0.14.0",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -8252,7 +8289,7 @@ dependencies = [
"once_cell",
"socket2 0.5.10",
"tracing",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -8402,7 +8439,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -8651,7 +8688,7 @@ dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.104",
"syn 2.0.106",
"walkdir",
]
@@ -8736,7 +8773,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -8955,7 +8992,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals 0.29.1",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -8987,7 +9024,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -9008,7 +9045,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -9148,7 +9185,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -9159,7 +9196,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -9170,7 +9207,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -9232,7 +9269,7 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -9245,6 +9282,15 @@ 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"
@@ -9253,7 +9299,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -9306,7 +9352,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -9652,7 +9698,7 @@ dependencies = [
"quote",
"sqlx-core",
"sqlx-macros-core",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -9675,7 +9721,7 @@ dependencies = [
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
"syn 2.0.104",
"syn 2.0.106",
"tokio",
"url",
]
@@ -9884,7 +9930,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -9927,9 +9973,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.104"
version = "2.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
dependencies = [
"proc-macro2",
"quote",
@@ -9959,7 +10005,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -10030,7 +10076,7 @@ dependencies = [
"getrandom 0.3.3",
"once_cell",
"rustix 1.0.8",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -10167,6 +10213,7 @@ dependencies = [
"nym-crypto",
"nym-ecash-contract-common",
"nym-group-contract-common",
"nym-http-api-client",
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-pemstore",
@@ -10222,7 +10269,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -10233,7 +10280,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -10365,7 +10412,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -10686,7 +10733,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -10856,7 +10903,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
"termcolor",
]
@@ -10883,7 +10930,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals 0.28.0",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -11073,7 +11120,7 @@ dependencies = [
"indexmap 2.10.0",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -11088,7 +11135,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 2.0.104",
"syn 2.0.106",
"toml 0.5.11",
"uniffi_meta",
]
@@ -11215,7 +11262,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.104",
"syn 2.0.106",
"uuid",
]
@@ -11254,7 +11301,7 @@ checksum = "268d76aaebb80eba79240b805972e52d7d410d4bcc52321b951318b0f440cd60"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -11265,7 +11312,7 @@ checksum = "382673bda1d05c85b4550d32fd4192ccd4cffe9a908543a0795d1e7682b36246"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
"utoipauto-core",
]
@@ -11289,6 +11336,7 @@ dependencies = [
"clap",
"comfy-table",
"nym-bin-common",
"nym-http-api-client",
"nym-network-defaults",
"nym-validator-client",
"serde",
@@ -11433,7 +11481,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
"wasm-bindgen-shared",
]
@@ -11468,7 +11516,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -11503,7 +11551,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -11519,6 +11567,7 @@ dependencies = [
"nym-credential-storage",
"nym-crypto",
"nym-gateway-client",
"nym-http-api-client",
"nym-sphinx",
"nym-sphinx-acknowledgements",
"nym-statistics-common",
@@ -11711,7 +11760,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@@ -11774,7 +11823,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -11785,7 +11834,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -11804,17 +11853,6 @@ 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"
@@ -12220,7 +12258,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
"synstructure",
]
@@ -12241,7 +12279,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -12261,7 +12299,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
"synstructure",
]
@@ -12282,7 +12320,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
@@ -12315,7 +12353,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
"syn 2.0.106",
]
[[package]]
+3 -1
View File
@@ -57,7 +57,7 @@ members = [
"common/gateway-requests",
"common/gateway-stats-storage",
"common/gateway-storage",
"common/http-api-client",
"common/http-api-client", "common/http-api-client-macro",
"common/http-api-common",
"common/inclusion-probability",
"common/ip-packet-requests",
@@ -271,6 +271,7 @@ hyper = "1.6.0"
hyper-util = "0.1"
indicatif = "0.18.0"
inquire = "0.6.2"
inventory = "0.3.21"
ip_network = "0.4.1"
ipnetwork = "0.20"
itertools = "0.14.0"
@@ -317,6 +318,7 @@ serde_json_path = "0.7.2"
serde_repr = "0.1"
serde_with = "3.9.0"
serde_yaml = "0.9.25"
serde_plain = "1.0.2"
sha2 = "0.10.9"
si-scale = "0.2.3"
snow = "0.9.6"
@@ -5,7 +5,7 @@ use crate::error::BandwidthControllerError;
use crate::utils::{
get_aggregate_verification_key, get_coin_index_signatures, get_expiration_date_signatures,
};
use log::info;
use log::{error, info};
use nym_credential_storage::storage::Storage;
use nym_credentials::ecash::bandwidth::IssuanceTicketBook;
use nym_credentials::ecash::utils::obtain_aggregate_wallet;
@@ -42,7 +42,8 @@ where
deposit_amount.into(),
None,
)
.await?;
.await
.inspect_err(|err| error!("ticketbook deposit fail with: {err}"))?;
let deposit_id = result.parse_singleton_u32_contract_data()?;
+1 -1
View File
@@ -13,7 +13,7 @@ use nym_credentials_interface::{
};
use nym_ecash_time::Date;
use nym_validator_client::coconut::all_ecash_api_clients;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
use nym_validator_client::EcashApiClient;
use rand::prelude::SliceRandom;
+1
View File
@@ -53,6 +53,7 @@ nym-client-core-config-types = { path = "./config-types", features = [
nym-client-core-surb-storage = { path = "./surb-storage" }
nym-client-core-gateways-storage = { path = "./gateways-storage" }
nym-ecash-time = { path = "../ecash-time" }
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
@@ -57,7 +57,7 @@ use nym_task::{TaskClient, TaskHandle};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::HardcodedTopologyProvider;
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, NymApiClient, UserAgent};
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, UserAgent};
use rand::prelude::SliceRandom;
use rand::rngs::OsRng;
use rand::thread_rng;
@@ -566,7 +566,7 @@ where
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
config_topology: config::Topology,
nym_api_urls: Vec<Url>,
nym_api_client: NymApiClient,
nym_api_client: nym_http_api_client::Client,
) -> Box<dyn TopologyProvider + Send + Sync> {
// if no custom provider was ... provided ..., create one using nym-api
custom_provider.unwrap_or_else(|| {
@@ -749,21 +749,31 @@ where
setup_gateway(setup_method, key_store, details_store).await
}
fn construct_nym_api_client(config: &Config, user_agent: Option<UserAgent>) -> NymApiClient {
fn construct_nym_api_client(
config: &Config,
user_agent: Option<UserAgent>,
) -> Result<nym_http_api_client::Client, ClientCoreError> {
let mut nym_api_urls = config.get_nym_api_endpoints();
nym_api_urls.shuffle(&mut thread_rng());
let mut builder = nym_http_api_client::Client::builder(nym_api_urls[0].clone())
.map_err(|source| ClientCoreError::NymApiQueryFailure { source })?;
if let Some(user_agent) = user_agent {
NymApiClient::new_with_user_agent(nym_api_urls[0].clone(), user_agent)
} else {
NymApiClient::new(nym_api_urls[0].clone())
builder = builder.with_user_agent(user_agent);
}
builder = builder.with_bincode();
builder
.build()
.map_err(|source| ClientCoreError::NymApiQueryFailure { source })
}
async fn determine_key_rotation_state(
client: &NymApiClient,
client: &nym_http_api_client::Client,
) -> Result<KeyRotationConfig, ClientCoreError> {
Ok(client.nym_api.get_key_rotation_info().await?.into())
Ok(client.get_key_rotation_info().await?.into())
}
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
@@ -830,7 +840,7 @@ where
.dkg_query_client
.map(|client| BandwidthController::new(credential_store, client));
let nym_api_client = Self::construct_nym_api_client(&self.config, self.user_agent.clone());
let nym_api_client = Self::construct_nym_api_client(&self.config, self.user_agent.clone())?;
let key_rotation_config = Self::determine_key_rotation_state(&nym_api_client).await?;
let topology_provider = Self::setup_topology_provider(
@@ -2,8 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use nym_mixnet_contract_common::EpochRewardedSet;
use nym_topology::provider_trait::{ToTopologyMetadata, TopologyProvider};
use nym_topology::NymTopology;
use nym_validator_client::nym_api::NymApiClientExt;
use rand::prelude::SliceRandom;
use rand::thread_rng;
use std::cmp::min;
@@ -39,30 +41,43 @@ impl Config {
pub struct NymApiTopologyProvider {
config: Config,
validator_client: nym_validator_client::client::NymApiClient,
validator_client: nym_http_api_client::Client,
nym_api_urls: Vec<Url>,
currently_used_api: usize,
use_bincode: bool,
}
impl NymApiTopologyProvider {
pub fn new(
config: impl Into<Config>,
mut nym_api_urls: Vec<Url>,
mut validator_client: nym_validator_client::client::NymApiClient,
validator_client: nym_http_api_client::Client,
) -> Self {
nym_api_urls.shuffle(&mut thread_rng());
validator_client.change_nym_api(nym_api_urls[0].clone());
NymApiTopologyProvider {
let mut provider = NymApiTopologyProvider {
config: config.into(),
validator_client,
nym_api_urls,
currently_used_api: 0,
}
use_bincode: true,
};
// Set all API URLs - the client will try them in order with automatic failover
provider.validator_client.change_base_urls(
provider
.nym_api_urls
.iter()
.map(|u| u.clone().into())
.collect(),
);
provider
}
pub fn disable_bincode(&mut self) {
self.validator_client.use_bincode = false;
self.use_bincode = false;
// Note: The unified client doesn't support toggling bincode after creation.
// This would require recreating the client without bincode.
// For now, we'll track the preference but it won't take effect.
warn!("Disabling bincode on existing client is not currently supported");
}
fn use_next_nym_api(&mut self) {
@@ -72,8 +87,19 @@ impl NymApiTopologyProvider {
}
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
self.validator_client
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
// Provide all URLs starting from the next one in rotation order
// This enables automatic failover to other endpoints
let rotated_urls: Vec<_> = self
.nym_api_urls
.iter()
.cycle()
.skip(self.currently_used_api)
.take(self.nym_api_urls.len())
.map(|u| u.clone().into())
.collect();
self.validator_client.change_base_urls(rotated_urls)
}
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
@@ -99,8 +125,13 @@ impl NymApiTopologyProvider {
.filter(|n| n.performance.round_to_integer() >= self.config.min_node_performance())
.collect::<Vec<_>>();
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
.with_skimmed_nodes(&nodes_filtered)
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
NymTopology::new(
metadata.to_topology_metadata(),
epoch_rewarded_set,
Vec::new(),
)
.with_skimmed_nodes(&nodes_filtered)
} else {
// if we're not using extended topology, we're only getting active set mixnodes and gateways
@@ -148,8 +179,13 @@ impl NymApiTopologyProvider {
}
}
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
.with_skimmed_nodes(&nodes)
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
NymTopology::new(
metadata.to_topology_metadata(),
epoch_rewarded_set,
Vec::new(),
)
.with_skimmed_nodes(&nodes)
};
if !topology.is_minimally_routable() {
+65 -8
View File
@@ -7,7 +7,8 @@ use futures::{SinkExt, StreamExt};
use nym_crypto::asymmetric::ed25519;
use nym_gateway_client::GatewayClient;
use nym_topology::node::RoutingNode;
use nym_validator_client::client::IdentityKeyRef;
use nym_validator_client::client::{IdentityKeyRef, NymApiClientExt};
use nym_validator_client::nym_nodes::SkimmedNodesWithMetadata;
use nym_validator_client::UserAgent;
use rand::{seq::SliceRandom, Rng};
#[cfg(unix)]
@@ -83,6 +84,48 @@ struct GatewayWithLatency<'a, G: ConnectableGateway> {
latency: Duration,
}
// Helper to collect all pages of entry nodes - replicates NymApiClient's convenience method
async fn get_all_basic_entry_nodes_with_metadata(
client: &nym_http_api_client::Client,
use_bincode: bool,
) -> Result<SkimmedNodesWithMetadata, ClientCoreError> {
// Get first page to obtain metadata
let mut page = 0;
let res = client
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
.await?;
let mut nodes = res.nodes.data;
let metadata = res.metadata;
if res.nodes.pagination.total == nodes.len() {
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
}
page += 1;
// Collect remaining pages
loop {
let mut res = client
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
.await?;
if !metadata.consistency_check(&res.metadata) {
return Err(ClientCoreError::ValidatorClientError(
nym_validator_client::ValidatorClientError::InconsistentPagedMetadata,
));
}
nodes.append(&mut res.nodes.data);
if nodes.len() < res.nodes.pagination.total {
page += 1
} else {
break;
}
}
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
}
impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
fn new(gateway: &'a G, latency: Duration) -> Self {
GatewayWithLatency { gateway, latency }
@@ -99,16 +142,30 @@ pub async fn gateways_for_init<R: Rng>(
let nym_api = nym_apis
.choose(rng)
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
let client = if let Some(user_agent) = user_agent {
nym_validator_client::client::NymApiClient::new_with_user_agent(nym_api.clone(), user_agent)
} else {
nym_validator_client::client::NymApiClient::new(nym_api.clone())
};
// Use the unified HTTP client directly with optional user agent
let mut builder = nym_http_api_client::Client::builder(nym_api.clone())
.map_err(|e| {
ClientCoreError::ValidatorClientError(
nym_validator_client::ValidatorClientError::NymAPIError { source: e },
)
})?
.with_bincode(); // Use bincode for better performance
if let Some(user_agent) = user_agent {
builder = builder.with_user_agent(user_agent);
}
let client = builder.build().map_err(|e| {
ClientCoreError::ValidatorClientError(
nym_validator_client::ValidatorClientError::NymAPIError { source: e },
)
})?;
tracing::debug!("Fetching list of gateways from: {nym_api}");
let gateways = client
.get_all_basic_entry_assigned_nodes_with_metadata()
// Use our helper to handle pagination
let gateways = get_all_basic_entry_nodes_with_metadata(&client, true)
.await?
.nodes;
info!("nym api reports {} gateways", gateways.len());
@@ -5,8 +5,8 @@ use crate::nyxd::{self, NyxdClient};
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
use crate::signing::signer::{NoSigner, OfflineSigner};
use crate::{
nym_api, DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient,
ReqwestRpcClient, ValidatorClientError,
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient,
ValidatorClientError,
};
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
@@ -153,7 +153,7 @@ impl Config {
pub struct Client<C, S = NoSigner> {
// ideally they would have been read-only, but unfortunately rust doesn't have such features
// #[deprecated(note = "please use `nym_api_client` instead")]
pub nym_api: nym_api::Client,
pub nym_api: nym_http_api_client::Client,
// pub nym_api_client: NymApiClient,
pub nyxd: NyxdClient<C, S>,
}
@@ -214,7 +214,7 @@ impl Client<ReqwestRpcClient> {
impl<C> Client<C> {
pub fn new_with_rpc_client(config: Config, rpc_client: C) -> Self {
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
Client {
nym_api: nym_api_client,
@@ -228,7 +228,7 @@ impl<C, S> Client<C, S> {
where
S: OfflineSigner,
{
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
Client {
nym_api: nym_api_client,
@@ -385,38 +385,25 @@ impl<C, S> Client<C, S> {
}
}
/// DEPRECATED: Use nym_http_api_client::Client with from_network() or with_bincode() instead
#[deprecated(
since = "1.2.0",
note = "Use nym_http_api_client::Client::from_network() or ClientBuilder::with_bincode() instead"
)]
#[derive(Clone)]
pub struct NymApiClient {
pub use_bincode: bool,
pub nym_api: nym_api::Client,
pub nym_api: nym_http_api_client::Client,
// TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
// we could re-implement the communication with the REST API on port 1317
}
impl From<nym_api::Client> for NymApiClient {
fn from(nym_api: nym_api::Client) -> Self {
NymApiClient {
use_bincode: false,
nym_api,
}
}
}
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
#[allow(deprecated)]
impl NymApiClient {
pub fn new(api_url: Url) -> Self {
let nym_api = nym_api::Client::new(api_url, None);
NymApiClient {
use_bincode: true,
nym_api,
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
let nym_api = nym_api::Client::new(api_url, Some(timeout));
let nym_api = nym_http_api_client::Client::new(api_url, Some(timeout));
NymApiClient {
use_bincode: true,
@@ -431,10 +418,10 @@ impl NymApiClient {
}
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
let nym_api = nym_api::Client::builder::<_, ValidatorClientError>(api_url)
let nym_api = nym_http_api_client::Client::builder(api_url)
.expect("invalid api url")
.with_user_agent(user_agent.into())
.build::<ValidatorClientError>()
.build()
.expect("failed to build nym api client");
NymApiClient {
@@ -3,7 +3,6 @@
use crate::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
use crate::nyxd::error::NyxdError;
use crate::NymApiClient;
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
use nym_coconut_dkg_common::verification_key::ContractVKShare;
use nym_compact_ecash::error::CompactEcashError;
@@ -15,7 +14,7 @@ use url::Url;
// TODO: it really doesn't feel like this should live in this crate.
#[derive(Clone)]
pub struct EcashApiClient {
pub api_client: NymApiClient,
pub api_client: nym_http_api_client::Client,
pub verification_key: VerificationKeyAuth,
pub node_id: NodeIndex,
pub cosmos_address: cosmrs::AccountId,
@@ -25,10 +24,15 @@ impl Display for EcashApiClient {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[id: {}] {} @ {}",
"[id: {}] {} @ ({})",
self.node_id,
self.cosmos_address,
self.api_client.api_url()
self.api_client
.base_urls()
.iter()
.map(|url| url.to_string())
.collect::<Vec<String>>()
.join(", ")
)
}
}
@@ -60,6 +64,9 @@ pub enum EcashApiError {
source: CompactEcashError,
},
#[error("failed to create API client: {0}")]
ClientError(String),
#[error("the provided account address is malformed: {source}")]
MalformedAccountAddress {
#[from]
@@ -89,8 +96,13 @@ impl TryFrom<ContractVKShare> for EcashApiClient {
// In non-client applications this resolver can cause warning logs about H2 connection
// failure. This indicates that the long lived https connection was closed by the remote
// peer and the resolver will have to reconnect. It should not impact actual functionality
let api_client = nym_http_api_client::Client::builder(url_address)
.map_err(|e| EcashApiError::ClientError(e.to_string()))?
.build()
.map_err(|e| EcashApiError::ClientError(e.to_string()))?;
Ok(EcashApiClient {
api_client: NymApiClient::new(url_address),
api_client,
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
node_id: share.node_index,
cosmos_address: share.owner.as_str().parse()?,
@@ -1,7 +1,8 @@
use crate::nym_api::NymApiClientExt;
use crate::nyxd::contract_traits::MixnetQueryClient;
use crate::nyxd::error::NyxdError;
use crate::nyxd::Config as ClientConfig;
use crate::{NymApiClient, QueryHttpRpcNyxdClient, ValidatorClientError};
use crate::{QueryHttpRpcNyxdClient, ValidatorClientError};
use colored::Colorize;
use core::fmt;
use itertools::Itertools;
@@ -87,8 +88,17 @@ fn setup_connection_tests<H: BuildHasher + 'static>(
}
});
let api_connection_test_clients = api_urls.map(|(network, url)| {
ClientForConnectionTest::Api(network, url.clone(), NymApiClient::new(url))
let api_connection_test_clients = api_urls.filter_map(|(network, url)| {
match nym_http_api_client::Client::builder(url.clone()).and_then(|b| b.build()) {
Ok(client) => Some(ClientForConnectionTest::Api(network, url, client)),
Err(err) => {
eprintln!(
"Failed to create API client for {}: {err}",
network.network_name
);
None
}
}
});
nyxd_connection_test_clients.chain(api_connection_test_clients)
@@ -160,7 +170,7 @@ async fn test_nyxd_connection(
async fn test_nym_api_connection(
network: NymNetworkDetails,
url: &Url,
client: &NymApiClient,
client: &nym_http_api_client::Client,
) -> ConnectionResult {
let result = match timeout(
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
@@ -186,7 +196,7 @@ async fn test_nym_api_connection(
enum ClientForConnectionTest {
Nyxd(NymNetworkDetails, Url, Box<QueryHttpRpcNyxdClient>),
Api(NymNetworkDetails, Url, NymApiClient),
Api(NymNetworkDetails, Url, nym_http_api_client::Client),
}
impl ClientForConnectionTest {
@@ -14,7 +14,6 @@ pub mod signing;
pub use crate::error::ValidatorClientError;
pub use crate::rpc::reqwest::ReqwestRpcClient;
pub use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
pub use client::NymApiClient;
pub use client::{Client, Config, EcashApiClient};
pub use nym_api_requests::*;
pub use nym_http_api_client::UserAgent;
@@ -1,7 +1,6 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_api_requests::models::RequestError;
use nym_http_api_client::HttpClientError;
pub type NymAPIError = HttpClientError<RequestError>;
pub type NymAPIError = HttpClientError;
@@ -3,6 +3,7 @@
use crate::nym_api::error::NymAPIError;
use crate::nym_api::routes::{ecash, CORE_STATUS_COUNT, SINCE_ARG};
use crate::nym_nodes::SkimmedNodesWithMetadata;
use async_trait::async_trait;
use nym_api_requests::ecash::models::{
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
@@ -37,7 +38,7 @@ pub use nym_api_requests::{
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
},
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SkimmedNode},
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata, SkimmedNode},
NymNetworkDetailsResponse,
};
use nym_contracts_common::IdentityKey;
@@ -49,8 +50,8 @@ use time::format_description::BorrowedFormatItem;
use time::Date;
use tracing::instrument;
use crate::ValidatorClientError;
pub use nym_coconut_dkg_common::types::EpochId;
pub use nym_http_api_client::Client;
pub mod error;
pub mod routes;
@@ -62,6 +63,9 @@ pub fn rfc_3339_date() -> Vec<BorrowedFormatItem<'static>> {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait NymApiClientExt: ApiClient {
/// Get the current API URL being used by the client
fn api_url(&self) -> &url::Url;
async fn health(&self) -> Result<ApiHealthResponse, NymAPIError> {
self.get_json(
&[
@@ -241,6 +245,156 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn get_current_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
self.get_rewarded_set().await
}
async fn get_all_basic_nodes_with_metadata(
&self,
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
// unroll first loop iteration in order to obtain the metadata
let mut page = 0;
let res = self
.get_basic_nodes_v2(false, Some(page), None, true)
.await?;
let mut nodes = res.nodes.data;
let metadata = res.metadata;
if res.nodes.pagination.total == nodes.len() {
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
}
page += 1;
loop {
let mut res = self
.get_basic_nodes_v2(false, Some(page), None, true)
.await?;
if !metadata.consistency_check(&res.metadata) {
// Create a custom error for inconsistent metadata
return Err(NymAPIError::InternalResponseInconsistency {
url: self.api_url().clone(),
details: "Inconsistent paged metadata".to_string(),
});
}
nodes.append(&mut res.nodes.data);
if nodes.len() >= res.nodes.pagination.total {
break;
} else {
page += 1
}
}
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
}
async fn get_all_basic_active_mixing_assigned_nodes_with_metadata(
&self,
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
// Get all mixing nodes that are in the active/rewarded set
let mut page = 0;
let res = self
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
.await?;
let metadata = res.metadata;
let mut nodes = res.nodes.data;
if res.nodes.pagination.total == nodes.len() {
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
}
page += 1;
loop {
let res = self
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
.await?;
if !metadata.consistency_check(&res.metadata) {
return Err(NymAPIError::InternalResponseInconsistency {
url: self.api_url().clone(),
details: "Inconsistent paged metadata".to_string(),
});
}
nodes.append(&mut res.nodes.data.clone());
// Check if we've got all nodes
if nodes.len() >= res.nodes.pagination.total {
break;
} else {
page += 1;
}
}
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
}
async fn get_all_basic_entry_assigned_nodes_with_metadata(
&self,
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
// Get all nodes that can act as entry gateways
let mut page = 0;
let res = self
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
.await?;
let metadata = res.metadata;
let mut nodes = res.nodes.data;
if res.nodes.pagination.total == nodes.len() {
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
}
page += 1;
loop {
let res = self
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
.await?;
if !metadata.consistency_check(&res.metadata) {
return Err(NymAPIError::InternalResponseInconsistency {
url: self.api_url().clone(),
details: "Inconsistent paged metadata".to_string(),
});
}
nodes.append(&mut res.nodes.data.clone());
// Check if we've got all nodes
if nodes.len() >= res.nodes.pagination.total {
break;
} else {
page += 1;
}
}
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
}
async fn get_all_described_nodes(&self) -> Result<Vec<NymNodeDescription>, NymAPIError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut descriptions = Vec::new();
loop {
let mut res = self.get_nodes_described(Some(page), None).await?;
descriptions.append(&mut res.data);
if descriptions.len() < res.pagination.total {
page += 1
} else {
break;
}
}
Ok(descriptions)
}
#[tracing::instrument(level = "debug", skip_all)]
async fn get_nym_nodes(
&self,
@@ -268,6 +422,25 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn get_all_bonded_nym_nodes(&self) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
let mut page = 0;
let mut bonds = Vec::new();
loop {
let mut res = self.get_nym_nodes(Some(page), None).await?;
bonds.append(&mut res.data);
if bonds.len() < res.pagination.total {
page += 1
} else {
break;
}
}
Ok(bonds)
}
#[deprecated]
#[tracing::instrument(level = "debug", skip_all)]
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
@@ -1371,8 +1544,49 @@ pub trait NymApiClientExt: ApiClient {
)
.await
}
/// Method to change the base API URLs being used by the client
fn change_base_urls(&mut self, urls: Vec<url::Url>);
/// Retrieve expanded information for all bonded nodes on the network
async fn get_all_expanded_nodes(&self) -> Result<SemiSkimmedNodesWithMetadata, NymAPIError> {
// Unroll the first iteration to get the metadata
let mut page = 0;
let res = self.get_expanded_nodes(false, Some(page), None).await?;
let mut nodes = res.nodes.data;
let metadata = res.metadata;
if res.nodes.pagination.total == nodes.len() {
return Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata));
}
page += 1;
loop {
let mut res = self.get_expanded_nodes(false, Some(page), None).await?;
nodes.append(&mut res.nodes.data);
if nodes.len() < res.nodes.pagination.total {
page += 1
} else {
break;
}
}
Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
}
}
// Client is already nym_http_api_client::Client (re-exported above), so just one impl needed
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl NymApiClientExt for Client {}
impl NymApiClientExt for nym_http_api_client::Client {
fn api_url(&self) -> &url::Url {
self.current_url().as_ref()
}
fn change_base_urls(&mut self, urls: Vec<url::Url>) {
self.change_base_urls(urls.into_iter().map(|u| u.into()).collect());
}
}
+1
View File
@@ -38,6 +38,7 @@ cosmrs = { workspace = true }
cosmwasm-std = { workspace = true }
nym-validator-client = { path = "../client-libs/validator-client" }
nym-http-api-client = { path = "../http-api-client" }
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] }
nym-network-defaults = { path = "../network-defaults" }
+1 -1
View File
@@ -2,12 +2,12 @@
// SPDX-License-Identifier: Apache-2.0
use crate::context::errors::ContextError;
pub use nym_http_api_client::Client as NymApiClient;
use nym_network_defaults::{
setup_env,
var_names::{MIXNET_CONTRACT_ADDRESS, NYM_API, NYXD, VESTING_CONTRACT_ADDRESS},
NymNetworkDetails,
};
pub use nym_validator_client::nym_api::Client as NymApiClient;
use nym_validator_client::nyxd::{self, AccountId, NyxdClient};
use nym_validator_client::{
DirectSigningHttpRpcNyxdClient, DirectSigningHttpRpcValidatorClient, QueryHttpRpcNyxdClient,
@@ -14,7 +14,7 @@ use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketB
use nym_credentials_interface::Bandwidth;
use nym_credentials_interface::{ClientTicket, TicketType};
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
use nym_validator_client::nyxd::contract_traits::{
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
};
@@ -354,7 +354,7 @@ impl CredentialHandler {
Err(err) => {
error!("failed to send ticket {ticket_id} for verification to ecash signer '{client}': {err}. if we don't reach quorum, we'll retry later");
Err(EcashTicketError::ApiFailure(EcashApiError::NymApi {
source: err,
source: nym_validator_client::ValidatorClientError::NymAPIError { source: err },
}))
}
}
+1
View File
@@ -22,6 +22,7 @@ nym-ecash-time = { path = "../ecash-time", features = ["expiration"] }
nym-credentials-interface = { path = "../credentials-interface" }
nym-crypto = { path = "../crypto" }
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
nym-http-api-client = { path = "../http-api-client" }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
nym-network-defaults = { path = "../network-defaults" }
@@ -15,7 +15,7 @@ use nym_credentials_interface::{
use nym_crypto::asymmetric::ed25519;
use nym_ecash_contract_common::deposit::DepositId;
use nym_ecash_time::{ecash_default_expiration_date, ecash_today, EcashTime};
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
use serde::{Deserialize, Serialize};
use time::Date;
@@ -116,7 +116,7 @@ impl IssuanceTicketBook {
pub async fn obtain_blinded_credential(
&self,
client: &nym_validator_client::client::NymApiClient,
client: &nym_http_api_client::Client,
request_body: &BlindSignRequestBody,
) -> Result<BlindedSignature, Error> {
let server_response = client.blind_sign(request_body).await?;
@@ -179,7 +179,7 @@ impl IssuanceTicketBook {
// ideally this would have been generic over credential type, but we really don't need secp256k1 keys for bandwidth vouchers
pub async fn obtain_partial_ticketbook_credential(
&self,
client: &nym_validator_client::client::NymApiClient,
client: &nym_http_api_client::Client,
signer_index: u64,
validator_vk: &VerificationKeyAuth,
signing_data: CredentialSigningData,
+1
View File
@@ -10,6 +10,7 @@ use nym_credentials_interface::{
VerificationKeyAuth, WalletSignatures,
};
use nym_validator_client::client::EcashApiClient;
use nym_validator_client::nym_api::NymApiClientExt;
// so we wouldn't break all the existing imports
pub use nym_ecash_time::{cred_exp_date, ecash_date_offset, ecash_today, EcashTime};
+4 -1
View File
@@ -4,7 +4,7 @@
use crate::ecash::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
use nym_credentials_interface::CompactEcashError;
use nym_crypto::asymmetric::x25519::KeyRecoveryError;
use nym_validator_client::ValidatorClientError;
use nym_validator_client::{nym_api::error::NymAPIError, ValidatorClientError};
use thiserror::Error;
#[derive(Debug, Error)]
@@ -37,6 +37,9 @@ pub enum Error {
#[error("Ran into a validator client error - {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("Nym API request failed - {0}")]
NymAPIError(#[from] NymAPIError),
#[error("Bandwidth operation overflowed. {0}")]
BandwidthOverflow(String),
+1
View File
@@ -22,6 +22,7 @@ url = { workspace = true }
nym-validator-client = { path = "../client-libs/validator-client" }
nym-network-defaults = { path = "../network-defaults" }
nym-ecash-signer-check-types = { path = "../ecash-signer-check-types" }
nym-http-api-client = { path = "../http-api-client" }
[lints]
workspace = true
+31 -25
View File
@@ -1,15 +1,14 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{LocalChainStatus, SigningStatus, TypedSignerResult};
use crate::{LocalChainStatus, SignerCheckError, SigningStatus, TypedSignerResult};
use nym_ecash_signer_check_types::dealer_information::RawDealerInformation;
use nym_ecash_signer_check_types::status::{SignerStatus, SignerTestResult};
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::models::BinaryBuildInformationOwned;
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nyxd::contract_traits::dkg_query_client::{
ContractVKShare, DealerDetails,
};
use nym_validator_client::NymApiClient;
use std::time::Duration;
use tracing::{error, warn};
use url::Url;
@@ -32,37 +31,38 @@ pub(crate) mod signing_status {
}
struct ClientUnderTest {
api_client: NymApiClient,
api_client: nym_http_api_client::Client,
build_info: Option<BinaryBuildInformationOwned>,
}
impl ClientUnderTest {
pub(crate) fn new(api_url: &Url) -> Self {
ClientUnderTest {
api_client: NymApiClient::new(api_url.clone()),
pub(crate) fn new(api_url: &Url) -> Result<Self, SignerCheckError> {
// The builder should not fail with a valid URL that's already parsed
// If it does fail, it's an internal error that we can't recover from
let api_client = nym_http_api_client::Client::builder(api_url.clone())?.build()?;
Ok(ClientUnderTest {
api_client,
build_info: None,
}
})
}
pub(crate) async fn try_retrieve_build_information(&mut self) -> bool {
match tokio::time::timeout(
Duration::from_secs(5),
self.api_client.nym_api.build_information(),
)
.await
match tokio::time::timeout(Duration::from_secs(5), self.api_client.build_information())
.await
{
Ok(Ok(build_information)) => {
self.build_info = Some(build_information);
true
}
Ok(Err(err)) => {
warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.api_url());
warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.current_url());
false
}
Err(_timeout) => {
warn!(
"{}: timed out while attempting to retrieve build information",
self.api_client.api_url()
self.api_client.current_url()
);
false
}
@@ -77,7 +77,7 @@ impl ClientUnderTest {
.inspect_err(|err| {
error!(
"ecash signer '{}' reports invalid version {}: {err}",
self.api_client.api_url(),
self.api_client.current_url(),
build_info.build_version
)
})
@@ -121,14 +121,14 @@ impl ClientUnderTest {
// check if it supports the current query
if self.supports_chain_status_query() {
return match self.api_client.nym_api.get_chain_blocks_status().await {
return match self.api_client.get_chain_blocks_status().await {
Ok(status) => LocalChainStatus::Reachable {
response: Box::new(status),
},
Err(err) => {
warn!(
"{}: failed to retrieve local chain status: {err}",
self.api_client.api_url()
self.api_client.current_url()
);
LocalChainStatus::Unreachable
}
@@ -136,14 +136,14 @@ impl ClientUnderTest {
}
// fallback to the legacy query
match self.api_client.nym_api.get_chain_status().await {
match self.api_client.get_chain_status().await {
Ok(status) => LocalChainStatus::ReachableLegacy {
response: Box::new(status),
},
Err(err) => {
warn!(
"{}: failed to retrieve [legacy] local chain status: {err}",
self.api_client.api_url()
self.api_client.current_url()
);
LocalChainStatus::Unreachable
}
@@ -158,14 +158,14 @@ impl ClientUnderTest {
// check if it supports the current query
if self.supports_signing_status_query() {
return match self.api_client.nym_api.get_signer_status().await {
return match self.api_client.get_signer_status().await {
Ok(response) => SigningStatus::Reachable {
response: Box::new(response),
},
Err(err) => {
warn!(
"{}: failed to retrieve signer chain status: {err}",
self.api_client.api_url()
self.api_client.current_url()
);
SigningStatus::Unreachable
}
@@ -173,14 +173,14 @@ impl ClientUnderTest {
}
// fallback to the legacy query
match self.api_client.nym_api.get_signer_information().await {
match self.api_client.get_signer_information().await {
Ok(status) => SigningStatus::ReachableLegacy {
response: Box::new(status),
},
Err(err) => {
warn!(
"{}: failed to retrieve [legacy] signer chain status: {err}",
self.api_client.api_url()
self.api_client.current_url()
);
// NOTE: this might equally mean the signing is disabled
SigningStatus::Unreachable
@@ -201,7 +201,13 @@ pub(crate) async fn check_client(
return SignerStatus::ProvidedInvalidDetails.with_details(dealer_information, dkg_epoch);
};
let mut client = ClientUnderTest::new(&parsed_information.announce_address);
let mut client = match ClientUnderTest::new(&parsed_information.announce_address) {
Ok(client) => client,
Err(err) => {
error!("failed to create client instance: {err}");
return SignerStatus::Unreachable.with_details(dealer_information, dkg_epoch);
}
};
// 8. check basic connection status - can you retrieve build information?
if !client.try_retrieve_build_information().await {
+7
View File
@@ -1,6 +1,7 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_http_api_client::HttpClientError;
use nym_validator_client::nyxd::error::NyxdError;
use thiserror::Error;
@@ -11,6 +12,12 @@ pub enum SignerCheckError {
#[error("failed to query the DKG contract: {source}")]
DKGContractQueryFailure { source: NyxdError },
#[error("failed to build client: {source}")]
HttpClient {
#[from]
source: HttpClientError,
},
}
impl SignerCheckError {
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "nym-http-api-client-macro"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1"
syn = { workspace = true, features = ["full"] }
quote = "1.0.40"
proc-macro-crate = "3"
[dev-dependencies]
nym-http-api-client = { path = "../http-api-client" }
reqwest = { workspace = true }
[lints]
workspace = true
+311
View File
@@ -0,0 +1,311 @@
//! Proc-macros for configuring HTTP clients globally via the `inventory` crate.
//!
//! This crate provides macros that allow any crate in the workspace to contribute
//! configuration modifications to `reqwest::ClientBuilder` instances through a
//! compile-time registry pattern.
//!
//! # Overview
//!
//! The macros work by:
//! 1. Collecting configuration functions from across all crates at compile time
//! 2. Sorting them by priority (lower numbers run first)
//! 3. Applying them sequentially to build HTTP clients with consistent settings
//!
//! # Examples
//!
//! ## Basic Usage with `client_defaults!`
//!
//! ```ignore
//! use nym_http_api_client_macro::client_defaults;
//!
//! // Register default configurations with priority
//! client_defaults!(
//! priority = 10; // Optional, defaults to 0
//! timeout = std::time::Duration::from_secs(30),
//! gzip = true,
//! user_agent = "MyApp/1.0"
//! );
//! ```
//!
//! ## Using `client_cfg!` for one-off configurations
//!
//! ```ignore
//! use nym_http_api_client_macro::client_cfg;
//!
//! let configure = client_cfg!(
//! timeout = std::time::Duration::from_secs(60),
//! default_headers {
//! "X-Custom-Header" => "value",
//! "Authorization" => auth_token
//! }
//! );
//!
//! let builder = reqwest::ClientBuilder::new();
//! let configured = configure(builder);
//! ```
//!
//! # DSL Reference
//!
//! The macro DSL supports several patterns:
//! - `key = value` - Calls `builder.key(value)`
//! - `key(arg1, arg2)` - Calls `builder.key(arg1, arg2)`
//! - `flag` - Calls `builder.flag()` with no arguments
//! - `default_headers { "name" => "value", ... }` - Sets default headers
//!
//! # Priority System
//!
//! Configurations are applied in priority order (lower numbers first):
//! - Negative priorities: Early configuration (e.g., -100 for base settings)
//! - Zero (default): Standard configuration
//! - Positive priorities: Late configuration (e.g., 100 for overrides)
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::{format_ident, quote};
use syn::{
braced,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
token, Expr, Ident, LitInt, Result, Token,
};
// ------------------ core crate path resolution ------------------
fn core_path() -> TokenStream2 {
match crate_name("nym-http-api-client") {
Ok(FoundCrate::Itself) => quote!(crate),
Ok(FoundCrate::Name(name)) => {
let ident = Ident::new(&name, Span::call_site());
quote!( ::#ident )
}
Err(_) => {
// Fallback if the crate is not found by name (unlikely if deps set up correctly)
quote!(::nym_http_api_client)
}
}
}
// ------------------ DSL parsing ------------------
struct Items(Punctuated<Item, Token![,]>);
impl Parse for Items {
fn parse(input: ParseStream<'_>) -> Result<Self> {
Ok(Self(Punctuated::parse_terminated(input)?))
}
}
enum Item {
Assign {
key: Ident,
_eq: Token![=],
value: Expr,
}, // foo = EXPR
Call {
key: Ident,
args: Punctuated<Expr, Token![,]>,
_p: token::Paren,
}, // foo(a,b)
DefaultHeaders {
_key: Ident,
map: HeaderMapInit,
}, // default_headers { ... }
Flag {
key: Ident,
}, // foo
}
impl Parse for Item {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let key: Ident = input.parse()?;
if input.peek(Token![=]) {
let _eq: Token![=] = input.parse()?;
let value: Expr = input.parse()?;
return Ok(Self::Assign { key, _eq, value });
}
if input.peek(token::Paren) {
let content;
let _p = syn::parenthesized!(content in input);
let args = Punctuated::<Expr, Token![,]>::parse_terminated(&content)?;
return Ok(Self::Call { key, args, _p });
}
if input.peek(token::Brace) && key == format_ident!("default_headers") {
let map = input.parse::<HeaderMapInit>()?;
return Ok(Self::DefaultHeaders { _key: key, map });
}
Ok(Self::Flag { key })
}
}
struct HeaderPair {
k: Expr,
_arrow: Token![=>],
v: Expr,
}
impl Parse for HeaderPair {
fn parse(input: ParseStream<'_>) -> Result<Self> {
Ok(Self {
k: input.parse()?,
_arrow: input.parse()?,
v: input.parse()?,
})
}
}
struct HeaderMapInit {
_brace: token::Brace,
pairs: Punctuated<HeaderPair, Token![,]>,
}
impl Parse for HeaderMapInit {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let content;
let _brace = braced!(content in input);
let pairs = Punctuated::<HeaderPair, Token![,]>::parse_terminated(&content)?;
Ok(Self { _brace, pairs })
}
}
// Generate statements that mutate a builder named `b` using the resolved core path.
fn to_stmts(items: Items, core: &TokenStream2) -> TokenStream2 {
let mut stmts = Vec::new();
for it in items.0 {
match it {
Item::Assign { key, value, .. } => {
let m = key;
stmts.push(quote! { b = b.#m(#value); });
}
Item::Call { key, args, .. } => {
let m = key;
let args = args.iter();
stmts.push(quote! { b = b.#m( #( #args ),* ); });
}
Item::DefaultHeaders { map, .. } => {
let (ks, vs): (Vec<_>, Vec<_>) = map.pairs.into_iter().map(|p| (p.k, p.v)).unzip();
stmts.push(quote! {
let mut __cm = #core::reqwest::header::HeaderMap::new();
#(
{
use #core::reqwest::header::{HeaderName, HeaderValue};
let __k = HeaderName::try_from(#ks)
.unwrap_or_else(|e| panic!("Invalid header name: {}", e));
let __v = HeaderValue::try_from(#vs)
.unwrap_or_else(|e| panic!("Invalid header value: {}", e));
__cm.insert(__k, __v);
}
)*
b = b.default_headers(__cm);
});
}
Item::Flag { key } => {
let m = key;
stmts.push(quote! { b = b.#m(); });
}
}
}
quote! { #(#stmts)* }
}
// ------------------ client_cfg! ------------------
/// Creates a closure that configures a `ReqwestClientBuilder`.
///
/// This macro generates a closure that can be used to configure a single
/// `reqwest::ClientBuilder` instance without affecting global defaults.
///
/// # Example
///
/// ```ignore
/// let config = client_cfg!(
/// timeout = Duration::from_secs(30),
/// gzip = true
/// );
/// let client = config(reqwest::ClientBuilder::new()).build()?;
/// ```
#[proc_macro]
pub fn client_cfg(input: TokenStream) -> TokenStream {
let items = parse_macro_input!(input as Items);
let core = core_path();
let body = to_stmts(items, &core);
let out = quote! {
|mut b: #core::ReqwestClientBuilder| { #body b }
};
out.into()
}
// ------------------ client_defaults! with optional priority header ------------------
struct MaybePrioritized {
priority: i32,
items: Items,
}
impl Parse for MaybePrioritized {
fn parse(input: ParseStream<'_>) -> Result<Self> {
// Optional header: `priority = <int> ;`
let fork = input.fork();
let mut priority = 0i32;
if fork.peek(Ident) && fork.parse::<Ident>()? == "priority" && fork.peek(Token![=]) {
// commit
let _ = input.parse::<Ident>()?; // priority
let _ = input.parse::<Token![=]>()?;
let lit: LitInt = input.parse()?;
priority = lit.base10_parse()?;
let _ = input.parse::<Token![;]>()?;
}
let items = input.parse::<Items>()?;
Ok(Self { priority, items })
}
}
/// Registers global default configurations for HTTP clients.
///
/// This macro submits a configuration record to the global registry that will
/// be applied to all HTTP clients created with `default_builder()`.
///
/// # Parameters
///
/// - `priority` (optional): Integer priority for ordering (lower runs first, default: 0)
/// - Configuration items: Any valid `reqwest::ClientBuilder` method calls
///
/// # Example
///
/// ```ignore
/// client_defaults!(
/// priority = -50; // Run early in the configuration chain
/// connect_timeout = Duration::from_secs(10),
/// pool_max_idle_per_host = 32,
/// default_headers {
/// "User-Agent" => "MyApp/1.0",
/// "Accept" => "application/json"
/// }
/// );
/// ```
#[proc_macro]
pub fn client_defaults(input: TokenStream) -> TokenStream {
let MaybePrioritized { priority, items } = parse_macro_input!(input as MaybePrioritized);
let core = core_path();
let body = to_stmts(items, &core);
let out = quote! {
#[allow(non_snake_case)]
mod __client_defaults {
use super::*;
#[allow(unused)]
pub fn __cfg(
mut b: #core::ReqwestClientBuilder
) -> #core::ReqwestClientBuilder {
#body
b
}
#core::inventory::submit! {
#core::registry::ConfigRecord {
priority: #priority,
apply: __cfg,
}
}
}
};
out.into()
}
@@ -0,0 +1,64 @@
use nym_http_api_client_macro::{client_cfg, client_defaults};
use std::time::Duration;
#[test]
fn test_client_cfg_basic() {
// Test that the macro compiles with basic configuration
let _config = client_cfg!(timeout = Duration::from_secs(30), gzip = true);
}
#[test]
fn test_client_cfg_with_headers() {
// Test that the macro compiles with default headers
let _config = client_cfg!(
timeout = Duration::from_secs(30),
default_headers {
"User-Agent" => "TestApp/1.0",
"Accept" => "application/json"
}
);
}
#[test]
fn test_client_cfg_with_method_calls() {
// Test that the macro compiles with method calls
let _config = client_cfg!(
pool_max_idle_per_host = 32,
tcp_nodelay = true,
danger_accept_invalid_certs = true
);
}
#[test]
fn test_client_defaults_with_priority() {
// Test that client_defaults macro compiles with priority
client_defaults!(
priority = -100;
gzip = true,
deflate = true
);
}
#[test]
fn test_client_defaults_without_priority() {
// Test that client_defaults macro compiles without priority (defaults to 0)
client_defaults!(brotli = true, zstd = true);
}
#[test]
fn test_empty_client_cfg() {
// Test that empty configuration compiles
let _config = client_cfg!();
}
// Integration test to verify the closure actually works
#[test]
fn test_client_cfg_closure_application() {
let config = client_cfg!(gzip = true);
// Apply the configuration to a new builder
let builder = reqwest::ClientBuilder::new();
let _configured_builder = config(builder);
// Note: We can't easily test the internal state of the builder,
// but we verify it compiles and runs without panic
}
+6
View File
@@ -13,6 +13,7 @@ license.workspace = true
[features]
default=["tunneling"]
tunneling=[]
network-defaults = ["dep:nym-network-defaults"]
[dependencies]
async-trait = { workspace = true }
@@ -23,9 +24,12 @@ url = { workspace = true }
once_cell = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true}
serde_plain = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
itertools = { workspace = true }
inventory = { workspace = true }
# used for decoding text responses (they were already implicitly included)
bytes = { workspace = true }
@@ -34,6 +38,8 @@ mime = { workspace = true }
nym-http-api-common = { path = "../http-api-common", default-features = false }
nym-bin-common = { path = "../bin-common" }
nym-network-defaults = { path = "../network-defaults", optional = true }
nym-http-api-client-macro = { path = "../http-api-client-macro" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
hickory-resolver = { workspace = true, features = ["https-ring", "tls-ring", "webpki-roots"] }
+7 -3
View File
@@ -54,10 +54,14 @@ impl Front {
#[derive(Debug, Default, PartialEq, Clone)]
#[cfg(feature = "tunneling")]
/// Policy for when to use domain fronting for HTTP requests.
pub enum FrontPolicy {
/// Always use domain fronting for all requests.
Always,
/// Only use domain fronting when retrying failed requests.
OnRetry,
#[default]
/// Never use domain fronting.
Off,
}
@@ -96,14 +100,14 @@ mod tests {
// Some(vec!["https://cdn77.com"]),
// ).unwrap(); // cdn77
let client = ClientBuilder::new::<_, &str>(url1)
let client = ClientBuilder::new(url1)
.expect("bad url")
.with_fronting(FrontPolicy::Always)
.build::<&str>()
.build()
.expect("failed to build client");
let response = client
.send_request::<_, (), &str, &str, &str>(
.send_request::<_, (), &str, &str>(
reqwest::Method::GET,
&["api", "v1", "network", "details"],
NO_PARAMS,
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
//! Global registry for HTTP client configurations.
//!
//! This module provides a compile-time registry system that allows any crate
//! in the workspace to contribute configuration modifications to HTTP clients.
use crate::ReqwestClientBuilder;
/// A configuration record that modifies a `ReqwestClientBuilder`.
///
/// Records are collected at compile-time via the `inventory` crate and
/// applied in priority order when building HTTP clients.
pub struct ConfigRecord {
/// Lower numbers run earlier.
pub priority: i32,
/// A function that takes a builder and returns a mutated builder.
pub apply: fn(ReqwestClientBuilder) -> ReqwestClientBuilder,
}
inventory::collect!(ConfigRecord);
/// Returns the default builder with all registered configurations applied.
pub fn default_builder() -> ReqwestClientBuilder {
let mut b = ReqwestClientBuilder::new();
let mut records: Vec<&'static ConfigRecord> =
inventory::iter::<ConfigRecord>.into_iter().collect();
records.sort_by_key(|r| r.priority); // lower runs first
for r in records {
b = (r.apply)(b);
}
b
}
/// Builds a client using the default builder with all registered configurations.
pub fn build_client() -> reqwest::Result<reqwest::Client> {
default_builder().build()
}
/// Debug function to inspect registered configurations.
/// Returns a vector of (priority, function_pointer) tuples for debugging.
#[cfg(debug_assertions)]
pub fn inspect_registered_configs() -> Vec<(i32, usize)> {
let mut configs: Vec<(i32, usize)> = inventory::iter::<ConfigRecord>
.into_iter()
.map(|record| (record.priority, record.apply as usize))
.collect();
configs.sort_by_key(|(priority, _)| *priority);
configs
}
/// Returns the count of registered configuration records.
pub fn registered_config_count() -> usize {
inventory::iter::<ConfigRecord>.into_iter().count()
}
+6 -9
View File
@@ -95,10 +95,10 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
"http://example.com/".parse()?,
])
.with_retries(3)
.build::<HttpClientError>()?;
.build()?;
let req = client.create_get_request(&["/"], NO_PARAMS);
let resp = client.send::<HttpClientError>(req).await?;
let req = client.create_get_request(&["/"], NO_PARAMS).unwrap();
let resp = client.send(req).await?;
assert_eq!(resp.status(), 200);
@@ -111,10 +111,7 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
#[test]
fn host_updating() {
let url = Url::new("http://example.com", None).unwrap();
let mut client = ClientBuilder::new::<_, &str>(url)
.unwrap()
.build::<&str>()
.unwrap();
let mut client = ClientBuilder::new(url).unwrap().build().unwrap();
// check that the url is set correctly
let current_url = client.current_url();
@@ -171,10 +168,10 @@ fn host_updating() {
#[cfg(feature = "tunneling")]
fn fronted_host_updating() {
let url = Url::new("http://example.com", Some(vec!["http://front1.com"])).unwrap();
let mut client = ClientBuilder::new::<_, &str>(url)
let mut client = ClientBuilder::new(url)
.unwrap()
.with_fronting(crate::fronted::FrontPolicy::Always)
.build::<&str>()
.build()
.unwrap();
// check that the url is set correctly
@@ -0,0 +1,70 @@
use nym_http_api_client::registry;
// Create separate modules to avoid name conflicts
mod config_early {
use nym_http_api_client_macro::client_defaults;
client_defaults!(
priority = -200;
tcp_nodelay = true
);
}
mod config_late {
use nym_http_api_client_macro::client_defaults;
client_defaults!(
priority = 100;
pool_idle_timeout = std::time::Duration::from_secs(90)
);
}
#[test]
fn test_registry_collects_configs() {
// Verify that configurations are being registered
let count = registry::registered_config_count();
// Should have at least the ones we registered above plus the default from lib.rs
assert!(
count >= 3,
"Expected at least 3 registered configs, got {}",
count
);
}
#[test]
fn test_default_builder_applies_configs() {
// Test that default_builder returns a configured builder
let _builder = registry::default_builder();
// The builder should have all configurations applied
// We can't easily inspect the internals, but we verify it doesn't panic
}
#[test]
fn test_build_client_works() {
// Test that we can successfully build a client with all configurations
let result = registry::build_client();
assert!(result.is_ok(), "Failed to build client: {:?}", result.err());
}
#[cfg(debug_assertions)]
#[test]
fn test_inspect_configs() {
// In debug mode, test that we can inspect registered configurations
let configs = registry::inspect_registered_configs();
// Verify configs are sorted by priority
for window in configs.windows(2) {
assert!(window[0].0 <= window[1].0, "Configs not sorted by priority");
}
// Verify we have configs at different priority levels
let priorities: Vec<i32> = configs.iter().map(|(p, _)| *p).collect();
assert!(
priorities.iter().any(|&p| p < 0),
"Expected negative priority configs"
);
assert!(
priorities.iter().any(|&p| p >= 0),
"Expected non-negative priority configs"
);
}
+9 -2
View File
@@ -55,6 +55,7 @@ pub struct ApiUrl {
pub front_hosts: Option<Vec<String>>,
}
#[derive(Copy, Clone)]
pub struct ApiUrlConst<'a> {
pub url: &'a str,
pub front_hosts: Option<&'a [&'a str]>,
@@ -188,8 +189,14 @@ impl NymNetworkDetails {
),
},
nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API),
nym_api_urls: None,
nym_vpn_api_urls: None,
nym_api_urls: Some(mainnet::NYM_APIS.iter().copied().map(Into::into).collect()),
nym_vpn_api_urls: Some(
mainnet::NYM_VPN_APIS
.iter()
.copied()
.map(Into::into)
.collect(),
),
}
}
+2
View File
@@ -25,3 +25,5 @@ url = { workspace = true }
nym-crypto = { path = "../crypto", features = ["asymmetric"] }
nym-task = { path = "../task" }
nym-validator-client = { path = "../client-libs/validator-client" }
nym-http-api-client = { path = "../http-api-client" }
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
+10 -5
View File
@@ -10,7 +10,7 @@ use futures::StreamExt;
use nym_crypto::asymmetric::ed25519;
use nym_task::ShutdownToken;
use nym_validator_client::models::NymNodeDescription;
use nym_validator_client::NymApiClient;
use nym_validator_client::nym_api::NymApiClientExt;
use rand::prelude::SliceRandom;
use rand::thread_rng;
use std::net::SocketAddr;
@@ -135,10 +135,15 @@ impl VerlocMeasurer {
let mut api_endpoints = self.config.nym_api_urls.clone();
api_endpoints.shuffle(&mut thread_rng());
for api_endpoint in api_endpoints {
let client = NymApiClient::new_with_user_agent(
api_endpoint.clone(),
self.config.user_agent.clone(),
);
let client = match nym_http_api_client::Client::builder(api_endpoint.clone())
.and_then(|b| b.with_user_agent(self.config.user_agent.clone()).build())
{
Ok(c) => c,
Err(err) => {
warn!("failed to create client for {api_endpoint}: {err}");
continue;
}
};
match client.get_all_described_nodes().await {
Ok(res) => return Some(res),
Err(err) => {
+1
View File
@@ -34,6 +34,7 @@ nym-statistics-common = { path = "../../statistics" }
nym-task = { path = "../../task" }
nym-topology = { path = "../../topology", features = ["wasm-serde-types"] }
nym-validator-client = { path = "../../client-libs/validator-client", default-features = false }
nym-http-api-client = { path = "../../http-api-client" }
wasm-utils = { path = "../utils" }
wasm-storage = { path = "../storage" }
+6
View File
@@ -37,6 +37,12 @@ pub enum WasmCoreError {
source: ValidatorClientError,
},
#[error("failed to query nym api: {source}")]
NymApiQueryError {
#[from]
source: nym_validator_client::nym_api::error::NymAPIError,
},
#[error("The provided wasm topology was invalid: {source}")]
WasmTopologyError {
#[from]
+20 -6
View File
@@ -19,9 +19,9 @@ use nym_client_core::init::{
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::wasm_helpers::WasmFriendlyNymTopology;
use nym_topology::{NymTopology, RoutingNode};
use nym_topology::{EpochRewardedSet, NymTopology, RoutingNode};
use nym_validator_client::client::IdentityKey;
use nym_validator_client::{NymApiClient, UserAgent};
use nym_validator_client::{nym_api::NymApiClientExt, UserAgent};
use rand::thread_rng;
use url::Url;
use wasm_bindgen::prelude::wasm_bindgen;
@@ -72,7 +72,16 @@ pub async fn current_network_topology_async(
}
};
let api_client = NymApiClient::new(url);
let api_client = nym_http_api_client::Client::builder(url.clone())
.map_err(|_err| WasmCoreError::MalformedUrl {
raw: nym_api_url.to_string(),
source: url::ParseError::EmptyHost,
})?
.build()
.map_err(|_err| WasmCoreError::MalformedUrl {
raw: nym_api_url.to_string(),
source: url::ParseError::EmptyHost,
})?;
let rewarded_set = api_client.get_current_rewarded_set().await?;
let mixnodes_res = api_client
.get_all_basic_active_mixing_assigned_nodes_with_metadata()
@@ -90,9 +99,14 @@ pub async fn current_network_topology_async(
let gateways = gateways_res.nodes;
let topology = NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
.with_skimmed_nodes(&mixnodes)
.with_skimmed_nodes(&gateways);
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
let topology = NymTopology::new(
metadata.to_topology_metadata(),
epoch_rewarded_set,
Vec::new(),
)
.with_skimmed_nodes(&mixnodes)
.with_skimmed_nodes(&gateways);
Ok(topology.into())
}
+1 -1
View File
@@ -21,6 +21,7 @@ pub use nym_client_core::{
pub use nym_gateway_client::{
error::GatewayClientError, GatewayClient, GatewayClientConfig, GatewayConfig,
};
pub use nym_http_api_client::Client as ApiClient;
pub use nym_sphinx::{
addressing::{clients::Recipient, nodes::NodeIdentity},
params::PacketType,
@@ -29,7 +30,6 @@ pub use nym_sphinx::{
pub use nym_statistics_common::clients::ClientStatsSender;
pub use nym_task;
pub use nym_topology::{HardcodedTopologyProvider, MixLayer, NymTopology, TopologyProvider};
pub use nym_validator_client::nym_api::Client as ApiClient;
pub use nym_validator_client::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient};
// TODO: that's a very nasty import path. it should come from contracts instead!
pub use nym_validator_client::client::IdentityKey;
@@ -7,16 +7,14 @@ use tracing::instrument;
use nym_http_api_client::{ApiClient, Client, HttpClientError, NO_PARAMS};
use nym_wireguard_private_metadata_shared::{
routes, Version, {ErrorResponse, Request, Response},
routes, Version, {Request, Response},
};
pub type WireguardMetadataApiClientError = HttpClientError<ErrorResponse>;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait WireguardMetadataApiClient: ApiClient {
#[instrument(level = "debug", skip(self))]
async fn version(&self) -> Result<Version, WireguardMetadataApiClientError> {
async fn version(&self) -> Result<Version, HttpClientError> {
let version: u64 = self
.get_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::VERSION],
@@ -30,7 +28,7 @@ pub trait WireguardMetadataApiClient: ApiClient {
async fn available_bandwidth(
&self,
request_body: &Request,
) -> Result<Response, WireguardMetadataApiClientError> {
) -> Result<Response, HttpClientError> {
self.post_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::AVAILABLE],
NO_PARAMS,
@@ -40,10 +38,7 @@ pub trait WireguardMetadataApiClient: ApiClient {
}
#[instrument(level = "debug", skip(self, request_body))]
async fn topup_bandwidth(
&self,
request_body: &Request,
) -> Result<Response, WireguardMetadataApiClientError> {
async fn topup_bandwidth(&self, request_body: &Request) -> Result<Response, HttpClientError> {
self.post_json(
&[routes::V1_API_VERSION, routes::BANDWIDTH, routes::TOPUP],
NO_PARAMS,
@@ -13,7 +13,7 @@ mod tests {
use nym_wireguard_private_metadata_server::{
AppState, PeerControllerTransceiver, RouterBuilder,
};
use nym_wireguard_private_metadata_shared::{latest, v0, v1, ErrorResponse};
use nym_wireguard_private_metadata_shared::{latest, v0, v1};
use tokio::{net::TcpListener, sync::mpsc};
pub(crate) const VERIFIER_AVAILABLE_BANDWIDTH: i64 = 42;
@@ -140,7 +140,7 @@ mod tests {
.await
.unwrap();
});
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
Client::new_url(addr.to_string(), None).unwrap()
}
#[tokio::test]
@@ -15,7 +15,6 @@ pub(crate) mod test {
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_wireguard::{peer_controller::PeerControlRequest, CONTROL_CHANNEL_SIZE};
use nym_wireguard_private_metadata_server::PeerControllerTransceiver;
use nym_wireguard_private_metadata_shared::ErrorResponse;
use nym_wireguard_private_metadata_shared::{
v0 as latest, AxumErrorResponse, AxumResult, Construct, Extract, Request, Response,
};
@@ -141,6 +140,6 @@ pub(crate) mod test {
.await
.unwrap();
});
Client::new_url::<_, ErrorResponse>(addr.to_string(), None).unwrap()
Client::new_url(addr.to_string(), None).unwrap()
}
}
+1 -1
View File
@@ -744,7 +744,7 @@ version = "0.1.0"
dependencies = [
"cosmwasm-std",
"quote",
"syn 1.0.109",
"syn 2.0.98",
]
[[package]]
+1 -1
View File
@@ -52,7 +52,7 @@ impl TestSetup {
}
}
pub fn query_ctx(&self) -> QueryCtx {
pub fn query_ctx(&self) -> QueryCtx<'_> {
QueryCtx::from((self.deps.as_ref(), self.env.clone()))
}
}
+2 -2
View File
@@ -73,13 +73,13 @@ impl TestSetupSimple {
message_info(&admin, &[])
}
pub fn execute_ctx(&mut self, sender: MessageInfo) -> ExecCtx {
pub fn execute_ctx(&mut self, sender: MessageInfo) -> ExecCtx<'_> {
let env = self.env.clone();
ExecCtx::from((self.deps.as_mut(), env, sender))
}
#[allow(dead_code)]
pub fn query_ctx(&self) -> QueryCtx {
pub fn query_ctx(&self) -> QueryCtx<'_> {
QueryCtx::from((self.deps.as_ref(), self.env.clone()))
}
+2 -2
View File
@@ -139,9 +139,9 @@ mod tests {
fn add_dummy_mixes_with_delegations(test: &mut TestSetup, delegators: usize, mixes: usize) {
for i in 0..mixes {
let mix_id = test.add_legacy_mixnode(&test.make_addr(format!("mix-owner{}", i)), None);
let mix_id = test.add_legacy_mixnode(&test.make_addr(format!("mix-owner{i}")), None);
for delegator in 0..delegators {
let name = &test.make_addr(format!("delegator{}", delegator));
let name = &test.make_addr(format!("delegator{delegator}"));
test.add_immediate_delegation(name, 100_000_000u32, mix_id)
}
}
@@ -300,7 +300,7 @@ mod tests {
let amount1 = coin(100_000_000, TEST_COIN_DENOM);
let sender1 = message_info(owner, &[amount1.clone()]);
let sender1 = message_info(owner, std::slice::from_ref(&amount1));
try_delegate_to_node(test.deps_mut(), env.clone(), sender1, mix_id).unwrap();
+4 -4
View File
@@ -503,8 +503,8 @@ pub(crate) mod tests {
storage,
id,
&UnbondedMixnode {
identity_key: format!("dummy{}", id),
owner: Addr::unchecked(format!("dummy{}", id)),
identity_key: format!("dummy{id}"),
owner: Addr::unchecked(format!("dummy{id}")),
proxy: None,
unbonding_height: 123,
},
@@ -570,7 +570,7 @@ pub(crate) mod tests {
storage,
id,
&UnbondedMixnode {
identity_key: format!("dummy{}", id),
identity_key: format!("dummy{id}"),
owner: owner.clone(),
proxy: None,
unbonding_height: 123,
@@ -817,7 +817,7 @@ pub(crate) mod tests {
id,
&UnbondedMixnode {
identity_key: identity.to_string(),
owner: Addr::unchecked(format!("dummy{}", id)),
owner: Addr::unchecked(format!("dummy{id}")),
proxy: None,
unbonding_height: 123,
},
+7 -7
View File
@@ -165,9 +165,9 @@ pub mod test_helpers {
#[track_caller]
pub fn assert_eq_with_leeway(a: Uint128, b: Uint128, leeway: Uint128) {
if a > b {
assert!(a - b <= leeway, "{} != {}", a, b)
assert!(a - b <= leeway, "{a} != {b}")
} else {
assert!(b - a <= leeway, "{} != {}", a, b)
assert!(b - a <= leeway, "{a} != {b}")
}
}
@@ -175,9 +175,9 @@ pub mod test_helpers {
pub fn assert_decimals(a: Decimal, b: Decimal) {
let epsilon = Decimal::from_ratio(1u128, 100_000_000u128);
if a > b {
assert!(a - b < epsilon, "{} != {}", a, b)
assert!(a - b < epsilon, "{a} != {b}")
} else {
assert!(b - a < epsilon, "{} != {}", a, b)
assert!(b - a < epsilon, "{a} != {b}")
}
}
@@ -1699,7 +1699,7 @@ pub mod test_helpers {
deps.branch(),
&env,
env.block.height,
Addr::unchecked(format!("owner{}", i)),
Addr::unchecked(format!("owner{i}")),
mix_id,
tests::fixtures::good_mixnode_pledge().pop().unwrap(),
)
@@ -1713,7 +1713,7 @@ pub mod test_helpers {
n: usize,
) {
for i in 0..n {
add_unbonded_mixnode(&mut rng, deps.branch(), None, &addr(format!("owner{}", i)));
add_unbonded_mixnode(&mut rng, deps.branch(), None, &addr(format!("owner{i}")));
}
}
@@ -1765,7 +1765,7 @@ pub mod test_helpers {
id,
&UnbondedMixnode {
identity_key: identity_key
.unwrap_or(&*format!("identity{}", id))
.unwrap_or(&*format!("identity{id}"))
.to_string(),
owner: Addr::unchecked(owner),
proxy: None,
+3 -3
View File
@@ -131,7 +131,7 @@ impl PreInitContract {
}
}
pub(crate) fn deps(&self) -> Deps {
pub(crate) fn deps(&self) -> Deps<'_> {
Deps {
storage: &self.storage,
api: &self.api,
@@ -139,7 +139,7 @@ impl PreInitContract {
}
}
pub(crate) fn deps_mut(&mut self) -> DepsMut {
pub(crate) fn deps_mut(&mut self) -> DepsMut<'_> {
DepsMut {
storage: &mut self.storage,
api: &self.api,
@@ -147,7 +147,7 @@ impl PreInitContract {
}
}
pub(crate) fn querier(&self) -> QuerierWrapper {
pub(crate) fn querier(&self) -> QuerierWrapper<'_> {
self.tester_builder.querier()
}
+1 -1
View File
@@ -137,7 +137,7 @@ mod tests {
let response = execute(
deps.as_mut(),
env.clone(),
message_info(&admin, &[amount.clone()]),
message_info(&admin, std::slice::from_ref(&amount)),
msg,
);
assert_eq!(
@@ -20,16 +20,22 @@ use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;
use nym_topology::provider_trait::{async_trait, TopologyProvider};
use nym_topology::{nym_topology_from_detailed, NymTopology};
use nym_validator_client::nym_api::NymApiClientExt;
use url::Url;
struct MyTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient,
validator_client: nym_http_api_client::Client,
}
impl MyTopologyProvider {
fn new(nym_api_url: Url) -> MyTopologyProvider {
let validator_client = nym_http_api_client::Client::builder::<_, nym_validator_client::models::RequestError>(nym_api_url)
.expect("Failed to create API client builder")
.build::<nym_validator_client::models::RequestError>()
.expect("Failed to build API client");
MyTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient::new(nym_api_url),
validator_client,
}
}
+1
View File
@@ -93,6 +93,7 @@ nym-task = { path = "../common/task" }
nym-topology = { path = "../common/topology" }
nym-api-requests = { path = "nym-api-requests" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-http-api-client = { path = "../common/http-api-client" }
nym-bin-common = { path = "../common/bin-common", features = ["output_format", "openapi", "basic_tracing"] }
nym-node-tester-utils = { path = "../common/node-tester-utils" }
nym-node-requests = { path = "../nym-node/nym-node-requests" }
@@ -8,7 +8,9 @@ use nym_crypto::asymmetric::x25519::serde_helpers::bs58_x25519_pubkey;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_mixnet_contract_common::reward_params::Performance;
use nym_mixnet_contract_common::NodeId;
use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT};
use nym_network_defaults::{
DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT, WG_TUNNEL_PORT,
};
use nym_node_requests::api::v1::authenticator::models::Authenticator;
use nym_node_requests::api::v1::gateway::models::Wireguard;
use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
@@ -313,11 +315,20 @@ impl From<Authenticator> for AuthenticatorDetails {
pub struct WireguardDetails {
// NOTE: the port field is deprecated in favour of tunnel_port
pub port: u16,
#[serde(default = "default_tunnel_port")]
pub tunnel_port: u16,
#[serde(default = "default_metadata_port")]
pub metadata_port: u16,
pub public_key: String,
}
fn default_tunnel_port() -> u16 {
WG_TUNNEL_PORT
}
fn default_metadata_port() -> u16 {
WG_METADATA_PORT
}
// works for current simple case.
impl From<Wireguard> for WireguardDetails {
fn from(value: Wireguard) -> Self {
+4
View File
@@ -13,6 +13,7 @@ use nym_dkg::Threshold;
use nym_ecash_contract_common::deposit::DepositId;
use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITLE;
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::error::NymAPIError;
use nym_validator_client::nyxd::error::NyxdError;
use nym_validator_client::nyxd::AccountId;
use thiserror::Error;
@@ -46,6 +47,9 @@ pub enum EcashError {
#[error("coconut api query failure: {0}")]
CoconutApiError(#[from] EcashApiError),
#[error("nym api query failure: {0}")]
NymApiError(#[from] NymAPIError),
#[error(transparent)]
SerdeJsonError(#[from] serde_json::Error),
+1
View File
@@ -46,6 +46,7 @@ use nym_ecash_contract_common::redeem_credential::BATCH_REDEMPTION_PROPOSAL_TITL
use nym_ecash_time::{ecash_default_expiration_date, ecash_today_date};
use nym_task::TaskClient;
use nym_ticketbooks_merkle::{IssuedTicketbook, IssuedTicketbooksFullMerkleProof, MerkleLeaf};
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nyxd::AccountId;
use nym_validator_client::EcashApiClient;
use rand::{thread_rng, RngCore};
+5 -2
View File
@@ -67,7 +67,7 @@ use nym_validator_client::nym_api::routes::{
use nym_validator_client::nyxd::cosmwasm_client::logs::Log;
use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult;
use nym_validator_client::nyxd::{AccountId, ExecTxResult, Fee, Hash, TxResponse};
use nym_validator_client::{EcashApiClient, NymApiClient};
use nym_validator_client::EcashApiClient;
use rand::rngs::OsRng;
use rand::RngCore;
use std::collections::{BTreeMap, HashMap};
@@ -1148,7 +1148,10 @@ impl DummyCommunicationChannel {
cosmos_address: AccountId,
) -> Self {
let client = EcashApiClient {
api_client: NymApiClient::new("http://localhost:1234".parse().unwrap()),
api_client: nym_http_api_client::Client::new(
"http://localhost:1234".parse().unwrap(),
None,
),
verification_key: aggregated_verification_key,
node_id: 1,
cosmos_address,
@@ -26,7 +26,10 @@ pub fn new_client(
base_url: impl IntoUrl,
bearer_token: impl Into<String>,
) -> Result<VpnApiClient, VpnApiClientError> {
let url = base_url.into_url()?;
let raw = base_url.as_str().to_string();
let url = base_url
.into_url()
.map_err(|source| VpnApiClientError::MalformedUrl { raw, source })?;
Ok(VpnApiClient {
inner: Client::builder(url)?
.with_user_agent(format!(
@@ -97,9 +100,14 @@ impl NymVpnApiClient for VpnApiClient {
{
let req = self
.inner
.create_get_request(path, NO_PARAMS)
.create_get_request(path, NO_PARAMS)?
.bearer_auth(&self.bearer_token)
.send();
.build()
.map_err(VpnApiClientError::reqwest_client_build_error)?;
let url = req.url().clone();
let req = reqwest::Client::new().execute(req);
// the only reason for that target lock is so that I could call this method from an ephemeral test
// running in non-wasm mode (since I wanted to use tokio)
@@ -110,8 +118,9 @@ impl NymVpnApiClient for VpnApiClient {
.map_err(|_timeout| HttpClientError::RequestTimeout)??;
#[cfg(not(target_arch = "wasm32"))]
let res = req.await?;
let res = req
.await
.map_err(|source| VpnApiClientError::request_send_error(url, source))?;
parse_response(res, false).await
}
@@ -129,9 +138,14 @@ impl NymVpnApiClient for VpnApiClient {
{
let req = self
.inner
.create_post_request(path, params, json_body)
.create_post_request(path, params, json_body)?
.bearer_auth(&self.bearer_token)
.send();
.build()
.map_err(VpnApiClientError::reqwest_client_build_error)?;
let url = req.url().clone();
let req = reqwest::Client::new().execute(req);
// the only reason for that target lock is so that I could call this method from an ephemeral test
// running in non-wasm mode (since I wanted to use tokio)
@@ -142,7 +156,9 @@ impl NymVpnApiClient for VpnApiClient {
.map_err(|_timeout| HttpClientError::RequestTimeout)??;
#[cfg(not(target_arch = "wasm32"))]
let res = req.await?;
let res = req
.await
.map_err(|source| VpnApiClientError::request_send_error(url, source))?;
parse_response(res, false).await
}
@@ -12,6 +12,7 @@ use nym_credential_proxy_requests::api::v1::ticketbook::models::{
};
use nym_credentials_interface::Base58;
use nym_validator_client::ecash::BlindSignRequestBody;
use nym_validator_client::nym_api::NymApiClientExt;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
@@ -3,7 +3,7 @@
use nym_ecash_signer_check::SignerCheckError;
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nym_api::{error::NymAPIError, EpochId};
use nym_validator_client::nyxd::error::NyxdError;
use std::io;
use std::net::SocketAddr;
@@ -71,6 +71,12 @@ pub enum CredentialProxyError {
source: EcashApiError,
},
#[error("Nym API request failed: {source}")]
NymApiFailure {
#[from]
source: NymAPIError,
},
#[error("Compact ecash internal error: {0}")]
CompactEcashInternalError(#[from] nym_compact_ecash::error::CompactEcashError),
@@ -36,6 +36,7 @@ use nym_ecash_contract_common::deposit::DepositId;
use nym_ecash_contract_common::msg::ExecuteMsg;
use nym_validator_client::coconut::EcashApiError;
use nym_validator_client::nym_api::EpochId;
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
use nym_validator_client::nyxd::contract_traits::{
DkgQueryClient, NymContractsProvider, PagedDkgQueryClient,
+2
View File
@@ -40,3 +40,5 @@ nym-sphinx = { path = "../common/nymsphinx" }
nym-topology = { path = "../common/topology" }
nym-types = { path = "../common/types" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-http-api-client = { path = "../common/http-api-client" }
nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" }
+14 -7
View File
@@ -6,12 +6,15 @@ use log::{info, warn};
use nym_bin_common::bin_info;
use nym_client_core::config::ForgetMe;
use nym_crypto::asymmetric::ed25519::PrivateKey;
use nym_mixnet_contract_common::EpochRewardedSet;
use nym_network_defaults::setup_env;
use nym_network_defaults::var_names::NYM_API;
use nym_sdk::mixnet::{self, MixnetClient};
use nym_sphinx::chunking::monitoring;
use nym_topology::provider_trait::ToTopologyMetadata;
use nym_topology::{HardcodedTopologyProvider, NymTopology};
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::UserAgent;
use std::fs::File;
use std::io::Write;
use std::sync::LazyLock;
@@ -160,10 +163,9 @@ async fn nym_topology_from_env() -> anyhow::Result<NymTopology> {
let api_url = std::env::var(NYM_API)?;
info!("Generating topology from {api_url}");
let client = nym_validator_client::client::NymApiClient::new_with_user_agent(
api_url.parse()?,
bin_info!(),
);
let client = nym_http_api_client::Client::builder(api_url)?
.with_user_agent(UserAgent::from(bin_info!()))
.build()?;
let rewarded_set = client.get_current_rewarded_set().await?;
@@ -172,10 +174,15 @@ async fn nym_topology_from_env() -> anyhow::Result<NymTopology> {
let nodes = nodes_response.nodes;
let metadata = nodes_response.metadata;
Ok(
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
.with_skimmed_nodes(&nodes),
// Convert RewardedSetResponse to EpochRewardedSet which can then be converted to CachedEpochRewardedSet
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
Ok(NymTopology::new(
metadata.to_topology_metadata(),
epoch_rewarded_set,
Vec::new(),
)
.with_skimmed_nodes(&nodes))
}
#[tokio::main]
@@ -5,11 +5,11 @@ use nym_node_requests::api::{client::NymNodeApiClientExt, v1::metrics::models::S
use nym_validator_client::{
client::{NodeId, NymNodeDetails},
models::{DescribedNodeType, NymNodeDescription},
NymApiClient,
};
use time::OffsetDateTime;
use nym_statistics_common::types::SessionType;
use nym_validator_client::client::NymApiClientExt;
use std::collections::HashMap;
use tokio::time::Duration;
use tracing::instrument;
@@ -60,13 +60,11 @@ async fn run(
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
.no_hickory_dns()
.with_timeout(nym_api_client_timeout)
.build::<&str>()?;
let api_client = NymApiClient::from(nym_api);
.build()?;
//SW TBC what nodes exactly need to be scraped, the skimmed node endpoint seems to return more nodes
let bonded_nodes = api_client.get_all_bonded_nym_nodes().await?;
let all_nodes = api_client.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet
let bonded_nodes = nym_api.get_all_bonded_nym_nodes().await?;
let all_nodes = nym_api.get_all_described_nodes().await?; //legacy node that did not upgrade the contract bond yet
tracing::debug!("Fetched {} total nodes", all_nodes.len());
let mut nodes_to_scrape: HashMap<NodeId, MetricsScrapingData> = bonded_nodes
@@ -19,7 +19,7 @@ use nym_validator_client::{
use nym_validator_client::{
nym_nodes::{NodeRole, SkimmedNode},
nyxd::{contract_traits::PagedMixnetQueryClient, AccountId},
NymApiClient, QueryHttpRpcNyxdClient,
QueryHttpRpcNyxdClient,
};
use std::{
collections::{HashMap, HashSet},
@@ -109,11 +109,9 @@ impl Monitor {
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
.no_hickory_dns()
.with_timeout(self.nym_api_client_timeout)
.build::<&str>()?;
.build()?;
let api_client = NymApiClient::from(nym_api);
let described_nodes = api_client
let described_nodes = nym_api
.get_all_described_nodes()
.await
.log_error("get_all_described_nodes")?
@@ -135,7 +133,7 @@ impl Monitor {
tracing::info!("🟣 🚪 gateway nodes: {}", gateways.len());
let bonded_nym_nodes = api_client
let bonded_nym_nodes = nym_api
.get_all_bonded_nym_nodes()
.await?
.into_iter()
@@ -146,10 +144,11 @@ impl Monitor {
tracing::info!("🟣 bonded_nodes: {}", bonded_nym_nodes.len());
// returns only bonded nodes
let nym_nodes = api_client
.get_all_basic_nodes()
let nym_nodes = nym_api
.get_all_basic_nodes_with_metadata()
.await
.log_error("get_all_basic_nodes")?;
.log_error("get_all_basic_nodes")?
.nodes;
let nym_node_count = nym_nodes.len();
tracing::info!("🟣 get_all_basic_nodes: {}", nym_node_count);
@@ -167,8 +166,7 @@ impl Monitor {
self.location_cached(node_description).await;
}
let mixnodes_detailed = api_client
.nym_api
let mixnodes_detailed = nym_api
.get_mixnodes_detailed_unfiltered()
.await
.log_error("get_mixnodes_detailed_unfiltered")?;
@@ -190,15 +188,13 @@ impl Monitor {
})
.collect::<Vec<_>>();
let mixnodes_described = api_client
.nym_api
let mixnodes_described = nym_api
.get_mixnodes_described()
.await
.log_error("get_mixnodes_described")?;
tracing::info!("🟣 mixnodes_described: {}", mixnodes_described.len());
let mixing_assigned_nodes = api_client
.nym_api
let mixing_assigned_nodes = nym_api
.get_basic_active_mixing_assigned_nodes(false, None, None, false)
.await
.log_error("get_basic_active_mixing_assigned_nodes")?
+1 -2
View File
@@ -5,7 +5,6 @@ use crate::api::v1::gateway::models::WebSockets;
use crate::api::v1::node::models::{
AuxiliaryDetails, NodeDescription, NodeRoles, SignedHostInformation,
};
use crate::api::ErrorResponse;
use crate::routes;
use async_trait::async_trait;
use nym_bin_common::build_information::BinaryBuildInformationOwned;
@@ -21,7 +20,7 @@ use crate::api::v1::network_requester::models::NetworkRequester;
use crate::api::v1::node_load::models::NodeLoad;
pub use nym_http_api_client::Client;
pub type NymNodeApiClientError = HttpClientError<ErrorResponse>;
pub type NymNodeApiClientError = HttpClientError;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+25 -16
View File
@@ -10,7 +10,6 @@ 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;
@@ -27,7 +26,7 @@ pub struct NymApisClient {
struct InnerClient {
// NOTE: this was implemented before the internal http client supported multiple URLs
active_client: NymApiClient,
active_client: Client,
available_urls: Vec<Url>,
shutdown_token: ShutdownToken,
currently_used_api: usize,
@@ -45,7 +44,7 @@ impl NymApisClient {
let mut urls = nym_apis.to_vec();
urls.shuffle(&mut thread_rng());
let active_client = nym_http_api_client::Client::builder(urls[0].clone())?
let active_client = Client::builder(urls[0].clone())?
.no_hickory_dns()
.with_user_agent(NymNode::user_agent())
.with_timeout(Duration::from_secs(5))
@@ -53,7 +52,7 @@ impl NymApisClient {
Ok(NymApisClient {
inner: Arc::new(RwLock::new(InnerClient {
active_client: NymApiClient::from(active_client),
active_client: active_client.clone(),
available_urls: urls,
shutdown_token,
currently_used_api: 0,
@@ -88,9 +87,19 @@ 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;
guard.active_client.change_nym_api(next_url);
// 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);
}
Ok(res)
@@ -123,10 +132,10 @@ impl InnerClient {
{
let broadcast_fut =
stream::iter(self.available_urls.clone()).for_each_concurrent(None, |url| {
let nym_api = self
.active_client
.nym_api
.clone_with_new_url(url.clone().into());
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 req_fut = req(nym_api, request_body);
async move {
if let Err(err) = req_fut.await {
@@ -172,10 +181,10 @@ impl InnerClient {
.skip(last_working)
.chain(self.available_urls.iter().enumerate().take(last_working))
{
let nym_api = self
.active_client
.nym_api
.clone_with_new_url(url.clone().into());
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 timeout_fut = sleep(timeout_duration);
let query_fut = req(nym_api);
@@ -216,8 +225,8 @@ impl InnerClient {
}
}
impl AsRef<NymApiClient> for InnerClient {
fn as_ref(&self) -> &NymApiClient {
impl AsRef<Client> for InnerClient {
fn as_ref(&self) -> &Client {
&self.active_client
}
}
+7 -7
View File
@@ -7,6 +7,7 @@ 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;
@@ -20,7 +21,7 @@ use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nym_nodes::{
NodesByAddressesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata,
};
use nym_validator_client::{NymApiClient, ValidatorClientError};
use nym_validator_client::ValidatorClientError;
use std::collections::{HashMap, HashSet};
use std::net::{IpAddr, SocketAddr};
use std::ops::Deref;
@@ -35,7 +36,7 @@ use url::Url;
const LOCAL_NODE_ID: NodeId = 1234567890;
struct NodesQuerier {
client: NymApiClient,
client: Client,
nym_api_urls: Vec<Url>,
currently_used_api: usize,
}
@@ -49,7 +50,7 @@ impl NodesQuerier {
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
self.client
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
.change_base_urls(self.nym_api_urls.iter().map(|u| u.clone().into()).collect())
}
async fn rewarded_set(&mut self) -> Result<EpochRewardedSet, ValidatorClientError> {
@@ -62,7 +63,7 @@ impl NodesQuerier {
if res.is_err() {
self.use_next_nym_api()
}
res
Ok(res?.into())
}
async fn current_nymnodes(
@@ -77,7 +78,7 @@ impl NodesQuerier {
if res.is_err() {
self.use_next_nym_api()
}
res
Ok(res?)
}
async fn query_for_info(
@@ -86,7 +87,6 @@ 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: NymApiClient::from(nym_api),
client: nym_api,
nym_api_urls,
currently_used_api: 0,
},
+8 -10
View File
@@ -6,7 +6,6 @@ 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};
@@ -14,6 +13,8 @@ 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);
@@ -22,7 +23,7 @@ type IpToCountryMap = HashMap<IpAddr, Option<Country>>;
// SW this should use a proper NS API client once it exists
struct NodesQuerier {
client: NymApiClient,
client: Client,
}
impl NodesQuerier {
@@ -99,14 +100,11 @@ impl NetworkRefresher {
this
}
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(),
)
fn build_http_api_client(url: Url) -> Result<Client> {
Ok(Client::builder(url)?
.no_hickory_dns()
.with_user_agent("node-statistics-api")
.build()?)
}
async fn refresh_network_nodes(&mut self) -> Result<()> {
+1
View File
@@ -42,6 +42,7 @@ 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,6 +9,7 @@ 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, NymApiClient};
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;
@@ -139,10 +139,23 @@ 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: NymApiClient::new(api_address),
api_client,
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: NymApiClient,
pub api_client: nym_http_api_client::Client,
pub verification_key: VerificationKeyAuth,
pub node_id: NodeIndex,
}
@@ -16,6 +16,7 @@ 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};
+51
View File
@@ -3384,6 +3384,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "inventory"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e"
dependencies = [
"rustversion",
]
[[package]]
name = "io-uring"
version = "0.7.10"
@@ -4216,20 +4225,34 @@ dependencies = [
"encoding_rs",
"hickory-resolver",
"http 1.3.1",
"inventory",
"itertools 0.14.0",
"mime",
"nym-bin-common",
"nym-http-api-client-macro",
"nym-http-api-common",
"once_cell",
"reqwest 0.12.15",
"serde",
"serde_json",
"serde_plain",
"serde_yaml",
"thiserror 2.0.12",
"tracing",
"url",
"wasmtimer",
]
[[package]]
name = "nym-http-api-client-macro"
version = "0.1.0"
dependencies = [
"proc-macro-crate 3.3.0",
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "nym-http-api-common"
version = "0.1.0"
@@ -6308,6 +6331,15 @@ 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"
@@ -6370,6 +6402,19 @@ 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"
@@ -7859,6 +7904,12 @@ 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"
-6
View File
@@ -2,7 +2,6 @@ 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;
@@ -45,11 +44,6 @@ pub enum BackendError {
source: eyre::Report,
},
#[error(transparent)]
NymApiError {
#[from]
source: NymAPIError,
},
#[error(transparent)]
NymNodeApiError {
#[from]
source: NymNodeApiClientError,
@@ -15,7 +15,6 @@ 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};
@@ -586,14 +585,12 @@ pub async fn get_nym_node_description(
port: u16,
) -> Result<NodeDescription, BackendError> {
Ok(
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?,
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?,
)
}
+1
View File
@@ -38,6 +38,7 @@ 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,18 +4,22 @@
use nym_sdk::mixnet;
use nym_sdk::mixnet::MixnetMessageSender;
use nym_topology::provider_trait::{async_trait, ToTopologyMetadata, TopologyProvider};
use nym_topology::NymTopology;
use nym_topology::{EpochRewardedSet, NymTopology};
use nym_validator_client::nym_api::NymApiClientExt;
use url::Url;
struct MyTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient,
validator_client: nym_http_api_client::Client,
}
impl MyTopologyProvider {
fn new(nym_api_url: Url) -> MyTopologyProvider {
MyTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient::new(nym_api_url),
}
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 }
}
async fn get_topology(&self) -> NymTopology {
@@ -33,7 +37,8 @@ impl MyTopologyProvider {
let metadata = mixnodes_response.metadata.to_topology_metadata();
let mut base_topology = NymTopology::new(metadata, rewarded_set, Vec::new());
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
let mut base_topology = NymTopology::new(metadata, epoch_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,6 +37,7 @@ 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::NymApiClient;
use nym_validator_client::nym_api::NymApiClientExt;
use rand::{thread_rng, RngCore};
use std::fs;
use std::fs::OpenOptions;
@@ -91,7 +91,10 @@ impl NetworkManager {
"⌛waiting for any gateway to appear in the directory ({api_url})..."
));
let api_client = NymApiClient::new(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 wait_fut = async {
let inner_fut = async {
@@ -25,6 +25,7 @@ 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
@@ -1,8 +1,7 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::NymApiClient;
use nym_validator_client::nym_api::NymApiClientExt;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use strum::{Display, EnumProperty};
@@ -48,8 +47,8 @@ impl SignerStatus {
matches!(self.rpc_status, RpcStatus::Up)
}
fn build_api_client(&self) -> Option<NymApiClient> {
let api_endpoint = match self.api_endpoint.as_str().parse() {
fn build_api_client(&self) -> Option<nym_http_api_client::Client> {
let api_endpoint: nym_http_api_client::Url = match self.api_endpoint.as_str().parse() {
Ok(endpoint) => endpoint,
Err(err) => {
error!("{} is not a valid api endpoint: {err}", self.api_endpoint);
@@ -57,14 +56,17 @@ impl SignerStatus {
}
};
Some(NymApiClient::new(api_endpoint))
nym_http_api_client::Client::builder(api_endpoint)
.ok()?
.build()
.ok()
}
pub(crate) async fn try_update_api_version(&mut self) {
let Some(client) = self.build_api_client() else {
return;
};
match client.nym_api.build_information().await {
match client.build_information().await {
Ok(build_info) => {
self.api_version = ApiVersion::Available {
version: build_info.build_version,
@@ -84,7 +86,7 @@ impl SignerStatus {
return;
};
match client.nym_api.get_chain_status().await {
match client.get_chain_status().await {
Ok(chain_status) => {
self.used_rpc_endpoint = RpcEndpoint(chain_status.connected_nyxd);
let last_block =
+2 -2
View File
@@ -1,7 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::vpn_api_client::NymVpnApiClientError;
use nym_http_api_client::HttpClientError;
use thiserror::Error;
use wasm_utils::wasm_error;
@@ -16,7 +16,7 @@ pub enum ZkNymError {
#[error("failed to contact the vpn api")]
HttpClientFailure {
#[from]
source: NymVpnApiClientError,
source: HttpClientError,
},
#[error("the provided shares and issuers are not from the same epoch! {shares} and {issuers}")]
InconsistentEpochId { shares: u64, issuers: u64 },
+9 -11
View File
@@ -1,14 +1,14 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::NymVpnApiClientError;
use crate::error::ZkNymError;
use crate::vpn_api_client::types::{
AttributesResponse, MasterVerificationKeyResponse, PartialVerificationKeysResponse,
};
use async_trait::async_trait;
pub use nym_http_api_client::Client;
use nym_http_api_client::{parse_response, ApiClient, IntoUrl, PathSegments, NO_PARAMS};
use nym_http_api_client::{
parse_response, ApiClient, HttpClientError, IntoUrl, PathSegments, NO_PARAMS,
};
use serde::de::DeserializeOwned;
#[allow(dead_code)]
@@ -34,13 +34,11 @@ pub fn new_client(
#[allow(dead_code)]
#[async_trait(?Send)]
pub trait NymVpnApiClient {
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, NymVpnApiClientError>
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, HttpClientError>
where
T: DeserializeOwned;
async fn get_prehashed_public_attributes(
&self,
) -> Result<AttributesResponse, NymVpnApiClientError> {
async fn get_prehashed_public_attributes(&self) -> Result<AttributesResponse, HttpClientError> {
self.simple_get(&[
"/api",
"/v1",
@@ -52,7 +50,7 @@ pub trait NymVpnApiClient {
async fn get_partial_verification_keys(
&self,
) -> Result<PartialVerificationKeysResponse, NymVpnApiClientError> {
) -> Result<PartialVerificationKeysResponse, HttpClientError> {
self.simple_get(&[
"/api",
"/v1",
@@ -64,7 +62,7 @@ pub trait NymVpnApiClient {
async fn get_master_verification_key(
&self,
) -> Result<MasterVerificationKeyResponse, NymVpnApiClientError> {
) -> Result<MasterVerificationKeyResponse, HttpClientError> {
self.simple_get(&[
"/api",
"/v1",
@@ -77,13 +75,13 @@ pub trait NymVpnApiClient {
#[async_trait(?Send)]
impl NymVpnApiClient for VpnApiClient {
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, NymVpnApiClientError>
async fn simple_get<T>(&self, path: PathSegments<'_>) -> Result<T, HttpClientError>
where
T: DeserializeOwned,
{
let req = self
.inner
.create_get_request(path, NO_PARAMS)
.create_get_request(path, NO_PARAMS)?
.bearer_auth(&self.bearer_token)
.send();
-5
View File
@@ -1,12 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::vpn_api_client::types::ErrorResponse;
use nym_http_api_client::HttpClientError;
#[cfg(test)]
pub(crate) mod client;
pub mod types;
pub type NymVpnApiClientError = HttpClientError<ErrorResponse>;