Compare commits

...

45 Commits

Author SHA1 Message Date
Jędrzej Stuczyński 9f766549e8 extra logs 2023-10-17 09:59:50 +01:00
Jędrzej Stuczyński 98131d1277 restored and revamped 'force_tls' flag to filter all gateways that support the wss protocol 2023-10-16 10:29:06 +01:00
Jędrzej Stuczyński 42c2d4de7a corrected health endpoint description 2023-10-13 14:58:58 +01:00
Jędrzej Stuczyński 688ffaf59a using camelcase for node status 2023-10-13 14:22:29 +01:00
Jędrzej Stuczyński 270d065661 nym-api trying multiple ports for the client 2023-10-13 13:22:29 +01:00
Jędrzej Stuczyński 25d66b5241 added health endpoint 2023-10-13 13:03:09 +01:00
Jędrzej Stuczyński a5d8a5071f nym-api using new default port for queries 2023-10-13 12:16:12 +01:00
Jędrzej Stuczyński 6c51696cfd changed default http port from 80 to 8080 2023-10-13 10:06:54 +01:00
Jędrzej Stuczyński f2196366e1 reduced severity of gateway details lookup failure 2023-10-12 14:04:38 +01:00
Jędrzej Stuczyński ac22a9d4d9 added default user-agent to http-api-client 2023-10-12 13:55:53 +01:00
Jędrzej Stuczyński d77bcbb241 fixed build and lints of other crates 2023-10-12 11:30:50 +01:00
Jędrzej Stuczyński a6f0fbff3d increased configurability 2023-10-12 11:00:04 +01:00
Jędrzej Stuczyński 07e6d7750a landing page configurability 2023-10-12 10:07:03 +01:00
Jędrzej Stuczyński fcce6b435c fixed the registration test 2023-10-12 10:03:36 +01:00
Jędrzej Stuczyński c7a66fc451 using concrete error type for nym node request error 2023-10-12 09:39:42 +01:00
Jędrzej Stuczyński 6dc7fc92bd output type support for wg routes 2023-10-11 17:52:48 +01:00
Jędrzej Stuczyński 215f927f0d turns out swagger can be happy with strongly typed requests 2023-10-11 17:34:28 +01:00
Jędrzej Stuczyński 7477337807 basic swagger support for wg endpoints 2023-10-11 16:47:00 +01:00
Jędrzej Stuczyński 44db2b8d71 added ErrorResponse for wireguard routes 2023-10-11 16:03:04 +01:00
Jędrzej Stuczyński 3260ba84fe brought in wg routes into nym-node router 2023-10-11 15:30:28 +01:00
Jędrzej Stuczyński 89cddc62cc cargo fmt 2023-10-10 17:11:00 +01:00
Jędrzej Stuczyński fe3b47f103 post rebase fixes 2023-10-10 17:11:00 +01:00
Jędrzej Stuczyński 636bbd9d12 fixed wasm builds 2023-10-10 17:11:00 +01:00
Jędrzej Stuczyński 2a338458b9 fixed request timeouts for wasm 2023-10-10 17:10:59 +01:00
Jędrzej Stuczyński ee2026e53d clients using self-described gateway information 2023-10-10 17:10:54 +01:00
Jędrzej Stuczyński 8f2cb95ffa caching self reported host info 2023-10-10 17:10:54 +01:00
Jędrzej Stuczyński 79ef88dd49 temporarily wired up NymContractCache to NodeDescriptionProvider 2023-10-10 17:10:54 +01:00
Jędrzej Stuczyński 216b32811c removed old cache type 2023-10-10 17:10:54 +01:00
Jędrzej Stuczyński baf7178dc8 nym-api caching node self described information 2023-10-10 17:10:52 +01:00
Jędrzej Stuczyński 00cdc5010a new generic cache and refresher 2023-10-10 17:10:48 +01:00
Jędrzej Stuczyński 04bf153701 routes unification + node api client 2023-10-10 17:10:46 +01:00
Jędrzej Stuczyński e6eb83f350 unified http api client 2023-10-10 17:10:41 +01:00
Jędrzej Stuczyński e8eb031fbd moved all models to separate crate 2023-10-10 17:10:40 +01:00
Jędrzej Stuczyński 0ce5ba2668 signing host information 2023-10-10 17:10:30 +01:00
Jędrzej Stuczyński e8d601df67 expanded on gateway-related endpoints 2023-10-10 17:10:30 +01:00
Jędrzej Stuczyński ca6b09ce8d initial host information endpoint 2023-10-10 17:10:28 +01:00
Jędrzej Stuczyński b8e27c67d9 refactored routes structures 2023-10-10 17:10:23 +01:00
Jędrzej Stuczyński 8dbb72296b fixing some version/feature clashes 2023-10-10 17:10:22 +01:00
Jędrzej Stuczyński c2893e5ca0 running the api as a proper task in gateway 'run' 2023-10-10 17:09:37 +01:00
Jędrzej Stuczyński 0489e5aa65 missing swagger data + using closure capture for immutable state 2023-10-10 17:08:06 +01:00
Jędrzej Stuczyński eda8b6bf85 wip: populating rest of swagger 2023-10-10 17:08:06 +01:00
Jędrzej Stuczyński a1aff09b91 populated build-info endpoint 2023-10-10 17:08:06 +01:00
Jędrzej Stuczyński 5de218bca9 initial empty openapi/swagger 2023-10-10 17:08:05 +01:00
Jędrzej Stuczyński 50960e7bf3 started expanding the API 2023-10-10 17:08:05 +01:00
Jędrzej Stuczyński 723e729f3b initial router 2023-10-10 17:08:03 +01:00
184 changed files with 5464 additions and 1505 deletions
Generated
+183 -49
View File
@@ -2111,6 +2111,7 @@ dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"digest 0.10.7",
"fiat-crypto",
"platforms",
"rustc_version 0.4.0",
@@ -2706,6 +2707,20 @@ dependencies = [
"zeroize",
]
[[package]]
name = "ed25519-dalek"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
dependencies = [
"curve25519-dalek 4.1.0",
"ed25519 2.2.2",
"rand_core 0.6.4",
"serde",
"sha2 0.10.8",
"zeroize",
]
[[package]]
name = "ed25519-zebra"
version = "3.1.0"
@@ -2868,7 +2883,7 @@ dependencies = [
"libp2p 0.51.3",
"libp2p-identity",
"log",
"lru 0.10.0",
"lru 0.10.1",
"nym-config",
"nym-ephemera-common",
"nym-task",
@@ -3847,6 +3862,20 @@ dependencies = [
"itoa",
]
[[package]]
name = "http-api-client"
version = "0.1.0"
dependencies = [
"async-trait",
"reqwest",
"serde",
"serde_json",
"thiserror",
"tracing",
"url",
"wasmtimer",
]
[[package]]
name = "http-body"
version = "0.4.5"
@@ -3858,6 +3887,12 @@ dependencies = [
"pin-project-lite 0.2.12",
]
[[package]]
name = "http-range-header"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
[[package]]
name = "httparse"
version = "1.8.0"
@@ -4109,6 +4144,7 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
"serde",
]
[[package]]
@@ -4244,7 +4280,7 @@ dependencies = [
"socket2 0.4.9",
"widestring",
"winapi",
"winreg",
"winreg 0.10.1",
]
[[package]]
@@ -4610,7 +4646,7 @@ source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c
dependencies = [
"asn1_der",
"bs58 0.4.0",
"ed25519-dalek",
"ed25519-dalek 1.0.1",
"either",
"fnv",
"futures",
@@ -4777,12 +4813,12 @@ dependencies = [
[[package]]
name = "libp2p-identity"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e2d584751cecb2aabaa56106be6be91338a60a0f4e420cf2af639204f596fc1"
checksum = "276bb57e7af15d8f100d3c11cbdd32c6752b7eef4ba7a18ecf464972c07abcce"
dependencies = [
"bs58 0.4.0",
"ed25519-dalek",
"ed25519-dalek 2.0.0",
"log",
"multiaddr 0.17.1",
"multihash",
@@ -5384,9 +5420,9 @@ dependencies = [
[[package]]
name = "lru"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03f1160296536f10c833a82dca22267d5486734230d47bf00bf435885814ba1e"
checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670"
dependencies = [
"hashbrown 0.13.2",
]
@@ -5535,13 +5571,14 @@ dependencies = [
name = "mix-fetch-wasm"
version = "1.2.0-rc.10"
dependencies = [
"async-trait",
"futures",
"http-api-client",
"js-sys",
"nym-ordered-buffer",
"nym-service-providers-common",
"nym-socks5-requests",
"rand 0.7.3",
"reqwest",
"serde",
"serde-wasm-bindgen",
"thiserror",
@@ -5968,6 +6005,7 @@ dependencies = [
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-name-service-common",
"nym-node-requests",
"nym-node-tester-utils",
"nym-pemstore",
"nym-service-provider-directory-common",
@@ -5978,7 +6016,6 @@ dependencies = [
"nym-vesting-contract-common",
"okapi",
"pin-project",
"pretty_env_logger",
"rand 0.7.3",
"rand 0.8.5",
"reqwest",
@@ -6012,6 +6049,7 @@ dependencies = [
"getset",
"nym-coconut-interface",
"nym-mixnet-contract-common",
"nym-node-requests",
"schemars",
"serde",
"ts-rs",
@@ -6045,12 +6083,14 @@ dependencies = [
"opentelemetry",
"opentelemetry-jaeger",
"pretty_env_logger",
"schemars",
"semver 0.11.0",
"serde",
"serde_json",
"tracing-opentelemetry",
"tracing-subscriber",
"tracing-tree",
"utoipa",
"vergen",
]
@@ -6390,7 +6430,7 @@ dependencies = [
"cipher 0.4.4",
"ctr 0.9.2",
"digest 0.10.7",
"ed25519-dalek",
"ed25519-dalek 1.0.1",
"generic-array 0.14.7",
"hkdf 0.12.3",
"hmac 0.12.1",
@@ -6507,6 +6547,7 @@ dependencies = [
"nym-mixnode-common",
"nym-network-defaults",
"nym-network-requester",
"nym-node",
"nym-pemstore",
"nym-sphinx",
"nym-statistics-common",
@@ -6598,21 +6639,6 @@ dependencies = [
"serde",
]
[[package]]
name = "nym-http-requests"
version = "0.1.0"
dependencies = [
"bytecodec",
"bytes",
"http",
"httpcodec",
"nym-ordered-buffer",
"nym-service-providers-common",
"nym-socks5-requests",
"thiserror",
"url",
]
[[package]]
name = "nym-inclusion-probability"
version = "0.1.0"
@@ -6837,6 +6863,57 @@ dependencies = [
"tokio",
]
[[package]]
name = "nym-node"
version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"bytes",
"colored",
"fastrand 2.0.0",
"hmac 0.12.1",
"hyper",
"mime",
"nym-config",
"nym-crypto",
"nym-node-requests",
"nym-task",
"rand 0.7.3",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
"tokio",
"tower",
"tower-http",
"tracing",
"utoipa",
"utoipa-swagger-ui",
"x25519-dalek 2.0.0",
]
[[package]]
name = "nym-node-requests"
version = "0.1.0"
dependencies = [
"async-trait",
"base64 0.21.4",
"hmac 0.12.1",
"http-api-client",
"nym-bin-common",
"nym-crypto",
"rand 0.7.3",
"schemars",
"serde",
"serde_json",
"sha2 0.10.8",
"thiserror",
"tokio",
"utoipa",
"x25519-dalek 2.0.0",
]
[[package]]
name = "nym-node-tester-utils"
version = "0.1.0"
@@ -7326,6 +7403,7 @@ dependencies = [
"async-trait",
"bs58 0.4.0",
"log",
"nym-api-requests",
"nym-bin-common",
"nym-config",
"nym-crypto",
@@ -7386,6 +7464,7 @@ dependencies = [
"eyre",
"flate2",
"futures",
"http-api-client",
"itertools",
"log",
"nym-api-requests",
@@ -8712,9 +8791,9 @@ dependencies = [
[[package]]
name = "refinery"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb0436d0dd7bd8d4fce1e828751fa79742b08e35f27cfea7546f8a322b5ef24"
checksum = "529664dbccc0a296947615c997a857912d72d1c44be1fafb7bae54ecfa7a8c24"
dependencies = [
"refinery-core",
"refinery-macros",
@@ -8722,9 +8801,9 @@ dependencies = [
[[package]]
name = "refinery-core"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19206547cd047e8f4dfa6b20c30d3ecaf24be05841b6aa0aa926a47a3d0662bb"
checksum = "e895cb870cf06e92318cbbeb701f274d022d5ca87a16fa8244e291cd035ef954"
dependencies = [
"async-trait",
"cfg-if",
@@ -8743,9 +8822,9 @@ dependencies = [
[[package]]
name = "refinery-macros"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d94d4b9241859ba19eaa5c04c86e782eb3aa0aae2c5868e0cfa90c856e58a174"
checksum = "123e8b80f8010c3ae38330c81e76938fc7adf6cdbfbaad20295bb8c22718b4f1"
dependencies = [
"proc-macro2",
"quote",
@@ -8800,9 +8879,9 @@ checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
name = "reqwest"
version = "0.11.18"
version = "0.11.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
dependencies = [
"base64 0.21.4",
"bytes",
@@ -8825,6 +8904,7 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
"system-configuration",
"tokio",
"tokio-native-tls",
"tokio-socks",
@@ -8833,7 +8913,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
"winreg 0.50.0",
]
[[package]]
@@ -9639,6 +9719,19 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
dependencies = [
"indexmap 2.0.0",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "sha-1"
version = "0.9.8"
@@ -9757,9 +9850,9 @@ dependencies = [
[[package]]
name = "siphasher"
version = "0.3.10"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
checksum = "54ac45299ccbd390721be55b412d41931911f654fa99e2cb8bfb57184b2061fe"
[[package]]
name = "slab"
@@ -10515,18 +10608,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "thiserror"
version = "1.0.44"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.44"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2",
"quote",
@@ -10885,6 +10978,31 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower-http"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
dependencies = [
"bitflags 2.4.0",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-range-header",
"httpdate",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite 0.2.12",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
@@ -11316,6 +11434,12 @@ dependencies = [
"subtle 2.4.1",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
[[package]]
name = "unsigned-varint"
version = "0.7.1"
@@ -11395,11 +11519,11 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "utoipa"
version = "3.3.0"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ae74ef183fae36d650f063ae7bde1cacbe1cd7e72b617cbe1e985551878b98"
checksum = "d82b1bc5417102a73e8464c686eef947bdfb99fcdfc0a4f228e81afa9526470a"
dependencies = [
"indexmap 1.9.3",
"indexmap 2.0.0",
"serde",
"serde_json",
"utoipa-gen",
@@ -11407,11 +11531,10 @@ dependencies = [
[[package]]
name = "utoipa-gen"
version = "3.3.0"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ea8ac818da7e746a63285594cce8a96f5e00ee31994e655bd827569cb8b137b"
checksum = "05d96dcd6fc96f3df9b3280ef480770af1b7c5d14bc55192baa9b067976d920c"
dependencies = [
"lazy_static",
"proc-macro-error",
"proc-macro2",
"quote",
@@ -11421,11 +11544,12 @@ dependencies = [
[[package]]
name = "utoipa-swagger-ui"
version = "3.1.3"
version = "3.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "062bba5a3568e126ac72049a63254f4cb1da2eb713db0c1ab2a4c76be191db8c"
checksum = "84614caa239fb25b2bb373a52859ffd94605ceb256eeb1d63436325cf81e3653"
dependencies = [
"actix-web",
"axum",
"mime_guess",
"regex",
"rust-embed",
@@ -12236,6 +12360,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "with_builtin_macros"
version = "0.0.3"
+12 -3
View File
@@ -46,7 +46,7 @@ members = [
"common/crypto",
"common/dkg",
"common/execute",
"common/http-requests",
"common/http-api-client",
"common/inclusion-probability",
"common/ledger",
"common/mixnode-common",
@@ -93,6 +93,8 @@ members = [
"nym-api",
"nym-browser-extension/storage",
"nym-api/nym-api-requests",
"nym-node",
"nym-node/nym-node-requests",
"nym-outfox",
"tools/internal/ssl-inject",
"tools/internal/sdk-version-bump",
@@ -129,6 +131,8 @@ license = "Apache-2.0"
[workspace.dependencies]
anyhow = "1.0.71"
async-trait = "0.1.68"
axum = "0.6.20"
base64 = "0.21.4"
bip39 = { version = "2.0.0", features = ["zeroize"] }
cfg-if = "1.0.0"
cosmwasm-derive = "=1.3.0"
@@ -151,22 +155,27 @@ dotenvy = "0.15.6"
futures = "0.3.28"
generic-array = "0.14.7"
getrandom = "0.2.10"
hyper = "0.14.27"
k256 = "0.13"
lazy_static = "1.4.0"
log = "0.4"
once_cell = "1.7.2"
parking_lot = "0.12.1"
rand = "0.8.5"
reqwest = "0.11.18"
reqwest = "0.11.22"
schemars = "0.8.1"
serde = "1.0.152"
serde_json = "1.0.91"
tap = "1.0.1"
tendermint-rpc = "0.32" # same version as used by cosmrs
thiserror = "1.0.38"
thiserror = "1.0.48"
tokio = "1.24.1"
tokio-tungstenite = "0.20.1"
tracing = "0.1.37"
tungstenite = { version = "0.20.1", default-features = false }
ts-rs = "7.0.0"
utoipa = "3.5.0"
utoipa-swagger-ui = "3.1.5"
url = "2.4"
zeroize = "1.6.0"
+1 -1
View File
@@ -8,7 +8,7 @@ edition = "2021"
[dependencies]
bip39 = { workspace = true }
rand = "0.7.3"
thiserror = "1.0"
thiserror = { workspace = true }
url = { workspace = true }
nym-coconut-interface = { path = "../coconut-interface" }
+4
View File
@@ -15,6 +15,7 @@ clap_complete_fig = "4.0"
log = { workspace = true }
pretty_env_logger = "0.4.0"
semver = "0.11"
schemars = { workspace = true, features = ["preserve_order"], optional = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, optional = true }
@@ -29,6 +30,7 @@ opentelemetry-jaeger = { version = "0.18.0", optional = true, features = [
"isahc_collector_client",
] }
tracing-opentelemetry = { version = "0.19.0", optional = true }
utoipa = { workspace = true, optional = true }
opentelemetry = { version = "0.19.0", optional = true, features = ["rt-tokio"] }
@@ -42,7 +44,9 @@ vergen = { version = "=7.4.3", default-features = false, features = [
[features]
default = []
openapi = ["utoipa"]
output_format = ["serde_json"]
bin_info_schema = ["schemars"]
tracing = [
"tracing-subscriber",
"tracing-tree",
@@ -81,6 +81,8 @@ impl BinaryBuildInformation {
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[cfg_attr(feature = "bin_info_schema", derive(schemars::JsonSchema))]
pub struct BinaryBuildInformationOwned {
/// Provides the name of the binary, i.e. the content of `CARGO_PKG_NAME` environmental variable.
pub binary_name: String,
@@ -69,7 +69,7 @@ impl NymApiTopologyProvider {
Ok(mixes) => mixes,
};
let gateways = match self.validator_client.get_cached_gateways().await {
let gateways = match self.validator_client.get_cached_described_gateways().await {
Err(err) => {
error!("failed to get network gateways - {err}");
return None;
+14 -19
View File
@@ -280,29 +280,24 @@ impl GatewayEndpointConfig {
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)
}
pub fn from_node(node: nym_topology::gateway::Node, use_tls: bool) -> Self {
// TODO: in the future this shall return a Result and explicit `use_tls` will be removed in favour of the tls info being available on the struct
if use_tls {
Self::from_topology_node_tls(node)
pub fn from_node(
node: nym_topology::gateway::Node,
must_use_tls: bool,
) -> Result<Self, ClientCoreError> {
let gateway_listener = if must_use_tls {
node.clients_address_tls()
.ok_or(ClientCoreError::UnsupportedWssProtocol {
gateway: node.identity_key.to_base58_string(),
})?
} else {
Self::from_topology_node_no_tls(node)
}
}
node.clients_address()
};
pub fn from_topology_node_no_tls(node: nym_topology::gateway::Node) -> Self {
GatewayEndpointConfig {
Ok(GatewayEndpointConfig {
gateway_id: node.identity_key.to_base58_string(),
gateway_listener: node.clients_address(),
gateway_listener,
gateway_owner: node.owner,
}
}
pub fn from_topology_node_tls(node: nym_topology::gateway::Node) -> Self {
GatewayEndpointConfig {
gateway_id: node.identity_key.to_base58_string(),
gateway_listener: node.clients_address_tls(),
gateway_owner: node.owner,
}
})
}
}
+6
View File
@@ -126,6 +126,12 @@ pub enum ClientCoreError {
#[error("this client has performed gateway initialisation in another session")]
NoInitClientPresent,
#[error("there are no gateways supporting the wss protocol available")]
NoWssGateways,
#[error("the specified gateway '{gateway}' does not support the wss protocol")]
UnsupportedWssProtocol { gateway: String },
}
/// Set of messages that the client can send to listeners via the task manager
+37 -5
View File
@@ -174,7 +174,10 @@ async fn measure_latency(gateway: &gateway::Node) -> Result<GatewayWithLatency,
pub async fn choose_gateway_by_latency<R: Rng>(
rng: &mut R,
gateways: &[gateway::Node],
must_use_tls: bool,
) -> Result<gateway::Node, ClientCoreError> {
let gateways = filter_by_tls(gateways, must_use_tls)?;
info!(
"choosing gateway by latency, pinging {} gateways ...",
gateways.len()
@@ -210,28 +213,57 @@ pub async fn choose_gateway_by_latency<R: Rng>(
Ok(chosen.gateway.clone())
}
fn filter_by_tls(
gateways: &[gateway::Node],
must_use_tls: bool,
) -> Result<Vec<&gateway::Node>, ClientCoreError> {
if must_use_tls {
let filtered = gateways
.iter()
.filter(|g| g.clients_wss_port.is_some())
.collect::<Vec<_>>();
if filtered.is_empty() {
return Err(ClientCoreError::NoWssGateways);
}
Ok(filtered)
} else {
Ok(gateways.iter().collect())
}
}
pub(super) fn uniformly_random_gateway<R: Rng>(
rng: &mut R,
gateways: &[gateway::Node],
must_use_tls: bool,
) -> Result<gateway::Node, ClientCoreError> {
gateways
filter_by_tls(gateways, must_use_tls)?
.choose(rng)
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
.cloned()
.map(|&r| r.clone())
}
pub(super) fn get_specified_gateway(
gateway_identity: IdentityKeyRef,
gateways: &[gateway::Node],
must_use_tls: bool,
) -> Result<gateway::Node, ClientCoreError> {
let user_gateway = identity::PublicKey::from_base58_string(gateway_identity)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
gateways
let gateway = gateways
.iter()
.find(|gateway| gateway.identity_key == user_gateway)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))
.cloned()
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))?;
if must_use_tls && gateway.clients_wss_port.is_none() {
return Err(ClientCoreError::UnsupportedWssProtocol {
gateway: gateway_identity.to_string(),
});
}
Ok(gateway.clone())
}
pub(super) async fn register_with_gateway(
+7 -6
View File
@@ -108,19 +108,20 @@ where
let gateway_details = match selection_specification {
GatewaySelectionSpecification::UniformRemote { must_use_tls } => {
let gateway = uniformly_random_gateway(&mut rng, &available_gateways)?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls))
let gateway = uniformly_random_gateway(&mut rng, &available_gateways, must_use_tls)?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls)?)
}
GatewaySelectionSpecification::RemoteByLatency { must_use_tls } => {
let gateway = choose_gateway_by_latency(&mut rng, &available_gateways).await?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls))
let gateway =
choose_gateway_by_latency(&mut rng, &available_gateways, must_use_tls).await?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls)?)
}
GatewaySelectionSpecification::Specified {
must_use_tls,
identity,
} => {
let gateway = get_specified_gateway(&identity, &available_gateways)?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls))
let gateway = get_specified_gateway(&identity, &available_gateways, must_use_tls)?;
GatewayDetails::Configured(GatewayEndpointConfig::from_node(gateway, must_use_tls)?)
}
GatewaySelectionSpecification::Custom {
gateway_identity,
@@ -24,6 +24,7 @@ nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
http-api-client = { path = "../../../common/http-api-client"}
thiserror = { workspace = true }
log = { workspace = true }
url = { workspace = true, features = ["serde"] }
@@ -11,7 +11,7 @@ use crate::{
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
use nym_api_requests::models::MixNodeBondAnnotated;
use nym_api_requests::models::{DescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::models::{
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
@@ -19,6 +19,7 @@ use nym_api_requests::models::{
use nym_network_defaults::NymNetworkDetails;
use url::Url;
pub use crate::nym_api::NymApiClientExt;
pub use nym_mixnet_contract_common::{
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, MixId,
};
@@ -147,7 +148,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());
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
Client {
nym_api: nym_api_client,
@@ -161,7 +162,7 @@ impl<C, S> Client<C, S> {
where
S: OfflineSigner,
{
let nym_api_client = nym_api::Client::new(config.api_url.clone());
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
Client {
nym_api: nym_api_client,
@@ -177,7 +178,7 @@ impl<C, S> Client<C, S> {
}
pub fn change_nym_api(&mut self, new_endpoint: Url) {
self.nym_api.change_url(new_endpoint)
self.nym_api.change_base_url(new_endpoint)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
@@ -241,7 +242,7 @@ pub struct NymApiClient {
impl NymApiClient {
pub fn new(api_url: Url) -> Self {
let nym_api = nym_api::Client::new(api_url);
let nym_api = nym_api::Client::new(api_url, None);
NymApiClient { nym_api }
}
@@ -251,7 +252,7 @@ impl NymApiClient {
}
pub fn change_nym_api(&mut self, new_endpoint: Url) {
self.nym_api.change_url(new_endpoint);
self.nym_api.change_base_url(new_endpoint);
}
pub async fn get_cached_active_mixnodes(
@@ -274,6 +275,12 @@ impl NymApiClient {
Ok(self.nym_api.get_gateways().await?)
}
pub async fn get_cached_described_gateways(
&self,
) -> Result<Vec<DescribedGateway>, ValidatorClientError> {
Ok(self.nym_api.get_gateways_described().await?)
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
@@ -1,20 +1,7 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use http_api_client::HttpClientError;
use nym_api_requests::models::RequestError;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum NymAPIError {
#[error("There was an issue with the REST request - {source}")]
ReqwestClientError {
#[from]
source: reqwest::Error,
},
#[error("Not found")]
NotFound,
#[error("Request failed with error message - {0}")]
GenericRequestFailure(String),
#[error("The nym API has failed to resolve our request. It returned status code {status} and additional error message: {}", error.message())]
ApiRequestFailure { status: u16, error: RequestError },
}
pub type NymAPIError = HttpClientError<RequestError>;
@@ -3,140 +3,38 @@
use crate::nym_api::error::NymAPIError;
use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use async_trait::async_trait;
use http_api_client::{ApiClient, NO_PARAMS};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
use nym_api_requests::models::{
ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse,
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError, RewardEstimationResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_name_service_common::response::NamesListResponse;
use nym_service_provider_directory_common::response::ServicesListResponse;
use reqwest::{Response, StatusCode};
use serde::{Deserialize, Serialize};
use url::Url;
pub mod error;
pub mod routes;
type PathSegments<'a> = &'a [&'a str];
type Params<'a, K, V> = &'a [(K, V)];
pub use http_api_client::Client;
const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
#[derive(Clone)]
pub struct Client {
url: Url,
reqwest_client: reqwest::Client,
}
impl Client {
pub fn new(url: Url) -> Self {
let reqwest_client = reqwest::Client::new();
Self {
url,
reqwest_client,
}
}
pub fn change_url(&mut self, new_url: Url) {
self.url = new_url
}
pub fn current_url(&self) -> &Url {
&self.url
}
async fn send_get_request<K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<Response, NymAPIError>
where
K: AsRef<str>,
V: AsRef<str>,
{
let url = create_api_url(&self.url, path, params);
Ok(self.reqwest_client.get(url).send().await?)
}
async fn query_nym_api<T, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, NymAPIError>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
{
let res = self.send_get_request(path, params).await?;
if res.status().is_success() {
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(NymAPIError::NotFound)
} else {
Err(NymAPIError::GenericRequestFailure(res.text().await?))
}
}
// This works for endpoints returning Result<Json<T>, ErrorResponse>
async fn query_nym_api_fallible<T, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, NymAPIError>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
{
let res = self.send_get_request(path, params).await?;
let status = res.status();
if res.status().is_success() {
Ok(res.json().await?)
} else {
let request_error: RequestError = res.json().await?;
Err(NymAPIError::ApiRequestFailure {
status: status.as_u16(),
error: request_error,
})
}
}
async fn post_nym_api<B, T, K, V>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, NymAPIError>
where
B: Serialize + ?Sized,
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
{
let url = create_api_url(&self.url, path, params);
let response = self.reqwest_client.post(url).json(json_body).send().await?;
if response.status().is_success() {
Ok(response.json().await?)
} else {
Err(NymAPIError::GenericRequestFailure(response.text().await?))
}
}
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.query_nym_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait NymApiClientExt: ApiClient {
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.get_json(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
.await
}
pub async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.query_nym_api(
async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -148,8 +46,8 @@ impl Client {
.await
}
pub async fn get_gateways_detailed(&self) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
self.query_nym_api(
async fn get_gateways_detailed(&self) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -161,10 +59,10 @@ impl Client {
.await
}
pub async fn get_mixnodes_detailed_unfiltered(
async fn get_mixnodes_detailed_unfiltered(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -176,23 +74,29 @@ impl Client {
.await
}
pub async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
self.query_nym_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
self.get_json(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
.await
}
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.query_nym_api(
async fn get_gateways_described(&self) -> Result<Vec<DescribedGateway>, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::GATEWAYS, routes::DESCRIBED],
NO_PARAMS,
)
.await
}
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
NO_PARAMS,
)
.await
}
pub async fn get_active_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.query_nym_api(
async fn get_active_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -205,19 +109,19 @@ impl Client {
.await
}
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.query_nym_api(
async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_report(
async fn get_mixnode_report(
&self,
mix_id: MixId,
) -> Result<MixnodeStatusReportResponse, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -230,11 +134,11 @@ impl Client {
.await
}
pub async fn get_gateway_report(
async fn get_gateway_report(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<GatewayStatusReportResponse, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -247,11 +151,11 @@ impl Client {
.await
}
pub async fn get_mixnode_history(
async fn get_mixnode_history(
&self,
mix_id: MixId,
) -> Result<MixnodeUptimeHistoryResponse, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -264,11 +168,11 @@ impl Client {
.await
}
pub async fn get_gateway_history(
async fn get_gateway_history(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<GatewayUptimeHistoryResponse, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -281,10 +185,10 @@ impl Client {
.await
}
pub async fn get_rewarded_mixnodes_detailed(
async fn get_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS,
@@ -297,13 +201,13 @@ impl Client {
.await
}
pub async fn get_gateway_core_status_count(
async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<GatewayCoreStatusResponse, NymAPIError> {
if let Some(since) = since {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -315,7 +219,7 @@ impl Client {
)
.await
} else {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -328,13 +232,13 @@ impl Client {
}
}
pub async fn get_mixnode_core_status_count(
async fn get_mixnode_core_status_count(
&self,
mix_id: MixId,
since: Option<i64>,
) -> Result<MixnodeCoreStatusResponse, NymAPIError> {
if let Some(since) = since {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -346,7 +250,7 @@ impl Client {
)
.await
} else {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -360,11 +264,11 @@ impl Client {
}
}
pub async fn get_mixnode_status(
async fn get_mixnode_status(
&self,
mix_id: MixId,
) -> Result<MixnodeStatusResponse, NymAPIError> {
self.query_nym_api(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -377,11 +281,11 @@ impl Client {
.await
}
pub async fn get_mixnode_reward_estimation(
async fn get_mixnode_reward_estimation(
&self,
mix_id: MixId,
) -> Result<RewardEstimationResponse, NymAPIError> {
self.query_nym_api_fallible(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -394,12 +298,12 @@ impl Client {
.await
}
pub async fn compute_mixnode_reward_estimation(
async fn compute_mixnode_reward_estimation(
&self,
mix_id: MixId,
request_body: &ComputeRewardEstParam,
) -> Result<RewardEstimationResponse, NymAPIError> {
self.post_nym_api(
self.post_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -413,11 +317,11 @@ impl Client {
.await
}
pub async fn get_mixnode_stake_saturation(
async fn get_mixnode_stake_saturation(
&self,
mix_id: MixId,
) -> Result<StakeSaturationResponse, NymAPIError> {
self.query_nym_api_fallible(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -430,11 +334,11 @@ impl Client {
.await
}
pub async fn get_mixnode_inclusion_probability(
async fn get_mixnode_inclusion_probability(
&self,
mix_id: MixId,
) -> Result<InclusionProbabilityResponse, NymAPIError> {
self.query_nym_api_fallible(
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -447,11 +351,8 @@ impl Client {
.await
}
pub async fn get_mixnode_avg_uptime(
&self,
mix_id: MixId,
) -> Result<UptimeResponse, NymAPIError> {
self.query_nym_api_fallible(
async fn get_mixnode_avg_uptime(&self, mix_id: MixId) -> Result<UptimeResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
@@ -464,11 +365,11 @@ impl Client {
.await
}
pub async fn blind_sign(
async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
) -> Result<BlindedSignatureResponse, NymAPIError> {
self.post_nym_api(
self.post_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
@@ -481,11 +382,11 @@ impl Client {
.await
}
pub async fn verify_bandwidth_credential(
async fn verify_bandwidth_credential(
&self,
request_body: &VerifyCredentialBody,
) -> Result<VerifyCredentialResponse, NymAPIError> {
self.post_nym_api(
self.post_json(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
@@ -498,118 +399,20 @@ impl Client {
.await
}
pub async fn get_service_providers(&self) -> Result<ServicesListResponse, NymAPIError> {
async fn get_service_providers(&self) -> Result<ServicesListResponse, NymAPIError> {
log::trace!("Getting service providers");
self.query_nym_api(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
self.get_json(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
.await
}
//pub async fn get_registered_names(&self) -> Result<Vec<NameEntry>, NymAPIError> {
pub async fn get_registered_names(&self) -> Result<NamesListResponse, NymAPIError> {
//async fn get_registered_names(&self) -> Result<Vec<NameEntry>, NymAPIError> {
async fn get_registered_names(&self) -> Result<NamesListResponse, NymAPIError> {
log::trace!("Getting registered names");
self.query_nym_api(&[routes::API_VERSION, routes::REGISTERED_NAMES], NO_PARAMS)
self.get_json(&[routes::API_VERSION, routes::REGISTERED_NAMES], NO_PARAMS)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
fn create_api_url<K: AsRef<str>, V: AsRef<str>>(
base: &Url,
segments: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Url {
let mut url = base.clone();
let mut path_segments = url
.path_segments_mut()
.expect("provided validator url does not have a base!");
for segment in segments {
let segment = segment.strip_prefix('/').unwrap_or(segment);
let segment = segment.strip_suffix('/').unwrap_or(segment);
path_segments.push(segment);
}
// I don't understand why compiler couldn't figure out that it's no longer used
// and can be dropped
drop(path_segments);
if !params.is_empty() {
url.query_pairs_mut().extend_pairs(params);
}
url
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creating_api_path() {
let base_url: Url = "http://foomp.com".parse().unwrap();
// works with 1 segment
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["foo"], NO_PARAMS).as_str()
);
// works with 2 segments
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
);
// works with leading slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["/foo"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
);
// works with trailing slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
);
// works with both leading and trailing slash
assert_eq!(
"http://foomp.com/foo",
create_api_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
create_api_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
);
// adds params
assert_eq!(
"http://foomp.com/foo/bar?foomp=baz",
create_api_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar?arg1=val1&arg2=val2",
create_api_url(
&base_url,
&["/foo/", "/bar/"],
&[("arg1", "val1"), ("arg2", "val2")]
)
.as_str()
);
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl NymApiClientExt for Client {}
@@ -6,6 +6,7 @@ use nym_network_defaults::NYM_API_VERSION;
pub const API_VERSION: &str = NYM_API_VERSION;
pub const MIXNODES: &str = "mixnodes";
pub const GATEWAYS: &str = "gateways";
pub const DESCRIBED: &str = "described";
pub const DETAILED: &str = "detailed";
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
+1 -1
View File
@@ -8,6 +8,6 @@ description = "Crutch library until there is proper SerDe support for coconut st
bs58 = "0.4.0"
getset = "0.1.1"
serde = { workspace = true, features = ["derive"] }
thiserror = "1"
thiserror = { workspace = true }
nym-coconut = {path = "../nymcoconut" }
@@ -1,11 +1,11 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use comfy_table::Table;
use crate::context::QueryClientWithNyxd;
use crate::utils::{pretty_cosmwasm_coin, show_error};
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::client::NymApiClientExt;
#[derive(Debug, Parser)]
pub struct Args {
@@ -1,11 +1,11 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use comfy_table::Table;
use crate::context::QueryClientWithNyxd;
use crate::utils::{pretty_decimal_with_denom, show_error};
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::client::NymApiClientExt;
#[derive(Debug, Parser)]
pub struct Args {
@@ -1,12 +1,12 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::nym_api::error::NymAPIError;
use crate::context::QueryClientWithNyxd;
use crate::utils::show_error;
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::nym_api::error::NymAPIError;
#[derive(Debug, Parser)]
pub struct Args {
@@ -1,12 +1,12 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::nym_api::error::NymAPIError;
use crate::context::QueryClientWithNyxd;
use crate::utils::show_error;
use clap::Parser;
use comfy_table::Table;
use nym_validator_client::client::NymApiClientExt;
use nym_validator_client::nym_api::error::NymAPIError;
#[derive(Debug, Parser)]
pub struct Args {
@@ -18,7 +18,7 @@ serde_repr = "0.1"
# we still have to preserve that import for `JsonSchema` for `Layer` type (since we can't use cw_serde macro due to custom serde impl)
schemars = "0.8"
thiserror = "1.0"
thiserror = { workspace = true }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
serde-json-wasm = { workspace = true }
humantime-serde = "1.1.1"
@@ -14,4 +14,4 @@ cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
thiserror = { workspace = true }
@@ -14,7 +14,7 @@ cw2 = { workspace = true, optional = true }
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.6.0" }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.5.0" }
serde = { version = "1.0", features = ["derive"] }
thiserror = "1.0"
thiserror = { workspace = true }
ts-rs = { workspace = true, optional = true}
[features]
+1 -1
View File
@@ -9,7 +9,7 @@ edition = "2021"
async-trait = { workspace = true }
log = { workspace = true }
thiserror = "1.0"
thiserror = { workspace = true }
tokio = { version = "1.24.1", features = ["sync"]}
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
+1 -1
View File
@@ -23,7 +23,7 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"], optional = true }
serde_bytes = { version = "0.11.6", optional = true }
serde_crate = { version = "1.0", optional = true, default_features = false, features = ["derive"], package = "serde" }
subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
thiserror = "1.0.37"
thiserror = { workspace = true }
zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
# internal
+9 -5
View File
@@ -145,8 +145,12 @@ impl PublicKey {
Self::from_bytes(&bytes)
}
pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
self.0.verify(message, &signature.0)
pub fn verify<M: AsRef<[u8]>>(
&self,
message: M,
signature: &Signature,
) -> Result<(), SignatureError> {
self.0.verify(message.as_ref(), &signature.0)
}
}
@@ -239,16 +243,16 @@ impl PrivateKey {
Self::from_bytes(&bytes)
}
pub fn sign(&self, message: &[u8]) -> Signature {
pub fn sign<M: AsRef<[u8]>>(&self, message: M) -> Signature {
let expanded_secret_key = ed25519_dalek::ExpandedSecretKey::from(&self.0);
let public_key: PublicKey = self.into();
let sig = expanded_secret_key.sign(message, &public_key.0);
let sig = expanded_secret_key.sign(message.as_ref(), &public_key.0);
Signature(sig)
}
/// Signs text with the provided Ed25519 private key, returning a base58 signature
pub fn sign_text(&self, text: &str) -> String {
let signature_bytes = self.sign(text.as_ref()).to_bytes();
let signature_bytes = self.sign(text).to_bytes();
bs58::encode(signature_bytes).into_string()
}
}
+3 -3
View File
@@ -21,10 +21,10 @@ rand = { version = "0.8.5", default-features = false}
rand_chacha = "0.3"
rand_core = "0.6.3"
sha2 = "0.9"
serde = "1.0"
serde = { workspace = true }
serde_derive = "1.0"
thiserror = "1.0"
zeroize = { version = "1.4", features = ["zeroize_derive"] }
thiserror = { workspace = true }
zeroize = { workspace = true, features = ["zeroize_derive"] }
nym-pemstore = { path = "../pemstore" }
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "http-api-client"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
url = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
# for request timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
workspace = true
features = ["tokio"]
+512
View File
@@ -0,0 +1,512 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use reqwest::{IntoUrl, Response, StatusCode};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::time::Duration;
use thiserror::Error;
use tracing::warn;
use url::Url;
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3);
pub type PathSegments<'a> = &'a [&'a str];
pub type Params<'a, K, V> = &'a [(K, V)];
pub const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[];
#[derive(Debug, Error)]
pub enum HttpClientError<E: Display = String> {
#[error("there was an issue with the REST request: {source}")]
ReqwestClientError {
#[from]
source: reqwest::Error,
},
#[error("provided url is malformed: {source}")]
MalformedUrl {
#[from]
source: url::ParseError,
},
#[error("the requested resource could not be found")]
NotFound,
#[error("request failed with error message: {0}")]
GenericRequestFailure(String),
#[error("the request failed with status '{status}'. no additional error message provided")]
RequestFailure { status: StatusCode },
#[error("the returned response was empty. status: '{status}'")]
EmptyResponse { status: StatusCode },
#[error("failed to resolve request. status: '{status}', additional error message: {error}")]
EndpointFailure { status: StatusCode, error: E },
#[cfg(target_arch = "wasm32")]
#[error("the request has timed out")]
RequestTimeout,
}
/// A simple extendable client wrapper for http request with extra url sanitization.
#[derive(Debug, Clone)]
pub struct Client {
base_url: Url,
reqwest_client: reqwest::Client,
#[cfg(target_arch = "wasm32")]
request_timeout: Duration,
}
impl Client {
// no timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
pub fn new(base_url: Url, timeout: Option<Duration>) -> Self {
#[cfg(target_arch = "wasm32")]
let reqwest_client = reqwest::Client::new();
// TODO: we should probably be propagating the error rather than panicking,
// but that'd break bunch of things due to type changes
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client = reqwest::ClientBuilder::new()
.timeout(timeout.unwrap_or(DEFAULT_TIMEOUT))
.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION")))
.build()
.expect("Client::new()");
Client {
base_url,
reqwest_client,
#[cfg(target_arch = "wasm32")]
request_timeout: timeout.unwrap_or(DEFAULT_TIMEOUT),
}
}
pub fn new_url<U, E>(url: U, timeout: Option<Duration>) -> Result<Self, HttpClientError<E>>
where
U: IntoUrl,
E: Display,
{
// a naive check: if the provided URL does not start with http(s), add that scheme
let str_url = url.as_str();
if !str_url.starts_with("http") {
let alt = format!("http://{str_url}");
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
// TODO: or should we maybe default to https?
Self::new_url(alt, timeout)
} else {
Ok(Self::new(url.into_url()?, timeout))
}
}
pub fn change_base_url(&mut self, new_url: Url) {
self.base_url = new_url
}
pub fn current_url(&self) -> &Url {
&self.base_url
}
async fn send_get_request<K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<Response, HttpClientError<E>>
where
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
let url = sanitize_url(&self.base_url, path, params);
#[cfg(target_arch = "wasm32")]
{
Ok(
wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client.get(url).send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??,
)
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(self.reqwest_client.get(url).send().await?)
}
}
async fn send_post_request<B, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<Response, HttpClientError<E>>
where
B: Serialize + ?Sized,
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
let url = sanitize_url(&self.base_url, path, params);
#[cfg(target_arch = "wasm32")]
{
Ok(wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client.post(url).json(json_body).send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??)
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(self.reqwest_client.post(url).json(json_body).send().await?)
}
}
pub async fn get_json<T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
E: Display + DeserializeOwned,
{
let res = self.send_get_request(path, params).await?;
parse_response(res, false).await
}
pub async fn post_json<B, T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized,
for<'a> T: Deserialize<'a>,
K: AsRef<str>,
V: AsRef<str>,
E: Display + DeserializeOwned,
{
let res = self.send_post_request(path, params, json_body).await?;
parse_response(res, true).await
}
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str>,
{
#[cfg(target_arch = "wasm32")]
let res = {
wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client
.get(self.base_url.join(endpoint.as_ref())?)
.send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??
};
#[cfg(not(target_arch = "wasm32"))]
let res = {
self.reqwest_client
.get(self.base_url.join(endpoint.as_ref())?)
.send()
.await?
};
parse_response(res, false).await
}
pub async fn post_json_endpoint<B, T, S, E>(
&self,
endpoint: S,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized,
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str>,
{
#[cfg(target_arch = "wasm32")]
let res = {
wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client
.post(self.base_url.join(endpoint.as_ref())?)
.json(json_body)
.send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??
};
#[cfg(not(target_arch = "wasm32"))]
let res = {
self.reqwest_client
.post(self.base_url.join(endpoint.as_ref())?)
.json(json_body)
.send()
.await?
};
parse_response(res, true).await
}
}
// define those methods on the trait for nicer extensions (and not having to type the thing twice)
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait ApiClient {
/// 'get' json data from the segment-defined path, i.e. for example `["api", "v1", "mixnodes"]`,
/// with tuple defined key-value parameters, i.e. for example `[("since", "12345")]`
async fn get_json<T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned;
async fn post_json<B, T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned;
/// `get` json data from the provided absolute endpoint, i.e. for example `"/api/v1/mixnodes?since=12345"`
async fn get_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send;
async fn post_json_data_to<B, T, S, E>(
&self,
endpoint: S,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send;
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl ApiClient for Client {
async fn get_json<T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
self.get_json(path, params).await
}
async fn post_json<B, T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
self.post_json(path, params, json_body).await
}
async fn get_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send,
{
self.get_json_endpoint(endpoint).await
}
async fn post_json_data_to<B, T, S, E>(
&self,
endpoint: S,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send,
{
self.post_json_endpoint(endpoint, json_body).await
}
}
// utility function that should solve the double slash problem in API urls forever.
pub fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
base: &Url,
segments: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Url {
let mut url = base.clone();
let mut path_segments = url
.path_segments_mut()
.expect("provided validator url does not have a base!");
for segment in segments {
let segment = segment.strip_prefix('/').unwrap_or(segment);
let segment = segment.strip_suffix('/').unwrap_or(segment);
path_segments.push(segment);
}
// I don't understand why compiler couldn't figure out that it's no longer used
// and can be dropped
drop(path_segments);
if !params.is_empty() {
url.query_pairs_mut().extend_pairs(params);
}
url
}
async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
where
T: DeserializeOwned,
E: DeserializeOwned + Display,
{
let status = res.status();
if !allow_empty {
if let Some(0) = res.content_length() {
return Err(HttpClientError::EmptyResponse { status });
}
}
if res.status().is_success() {
Ok(res.json().await?)
} else if res.status() == StatusCode::NOT_FOUND {
Err(HttpClientError::NotFound)
} else {
let Ok(plaintext) = res.text().await else {
return Err(HttpClientError::RequestFailure { status });
};
if let Ok(request_error) = serde_json::from_str(&plaintext) {
Err(HttpClientError::EndpointFailure {
status,
error: request_error,
})
} else {
Err(HttpClientError::GenericRequestFailure(plaintext))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitizing_urls() {
let base_url: Url = "http://foomp.com".parse().unwrap();
// works with 1 segment
assert_eq!(
"http://foomp.com/foo",
sanitize_url(&base_url, &["foo"], NO_PARAMS).as_str()
);
// works with 2 segments
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
);
// works with leading slash
assert_eq!(
"http://foomp.com/foo",
sanitize_url(&base_url, &["/foo"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
);
// works with trailing slash
assert_eq!(
"http://foomp.com/foo",
sanitize_url(&base_url, &["foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
);
// works with both leading and trailing slash
assert_eq!(
"http://foomp.com/foo",
sanitize_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar",
sanitize_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
);
// adds params
assert_eq!(
"http://foomp.com/foo/bar?foomp=baz",
sanitize_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
);
assert_eq!(
"http://foomp.com/foo/bar?arg1=val1&arg2=val2",
sanitize_url(
&base_url,
&["/foo/", "/bar/"],
&[("arg1", "val1"), ("arg2", "val2")]
)
.as_str()
);
}
}
-19
View File
@@ -1,19 +0,0 @@
[package]
name = "nym-http-requests"
version = "0.1.0"
description = "Helper library for sending HTTP requesters over the Nym mixnet"
edition = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
[dependencies]
nym-socks5-requests = { path = "../socks5/requests" }
nym-ordered-buffer = { path = "../socks5/ordered-buffer" }
nym-service-providers-common = { path = "../../service-providers/common" }
bytecodec = "0.4.15"
httpcodec = "0.2.3"
bytes = "1"
http = "0.2.9"
thiserror = "1"
url = "2"
-23
View File
@@ -1,23 +0,0 @@
use std::io;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum MixHttpRequestError {
#[error("invalid Socks5 response")]
InvalidSocks5Response,
#[error("the received Socks5 response was empty")]
EmptySocks5Response,
#[error("bytecodec Error: {0}")]
ByteCodecError(#[from] bytecodec::Error),
#[error("Url parse error: {0}")]
UrlParseError(#[from] url::ParseError),
#[error("could not resolve socket address from the provided URL")]
SocketAddrResolveError {
#[source]
source: io::Error,
},
}
-63
View File
@@ -1,63 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bytecodec::bytes::BytesEncoder;
use bytecodec::io::IoEncodeExt;
use bytecodec::Encode;
use httpcodec::{BodyEncoder, Request, RequestEncoder};
pub mod error;
pub mod socks;
pub fn encode_http_request_as_string(
request: Request<Vec<u8>>,
) -> Result<String, error::MixHttpRequestError> {
// Encode HTTP request as bytes
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
encoder.start_encoding(request)?;
let mut buf = Vec::new();
encoder.encode_all(&mut buf)?;
Ok(String::from_utf8_lossy(&buf).to_string())
}
#[cfg(test)]
mod http_requests_tests {
use super::*;
use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
fn create_http_get_request() -> Request<Vec<u8>> {
let mut request = Request::new(
Method::new("GET").unwrap(),
RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
HttpVersion::V1_1,
b"".to_vec(),
);
let mut headers = request.header_mut();
headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
request
}
#[test]
fn http_request_ok() {
// Encode HTTP request as bytes
let request = create_http_get_request();
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
encoder.start_encoding(request).unwrap();
let mut buf = Vec::new();
encoder.encode_all(&mut buf).unwrap();
let body_as_string = String::from_utf8(buf).unwrap();
// replace newlines with \r\n
let expected = r"GET /.wellknown/wallet/validators.json HTTP/1.1
Host: nymtech.net
Content-Length: 0
"
.replace('\n', "\r\n");
assert_eq!(expected, body_as_string);
}
}
-214
View File
@@ -1,214 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error;
use bytecodec::bytes::BytesEncoder;
use bytecodec::bytes::RemainingBytesDecoder;
use bytecodec::io::IoEncodeExt;
use bytecodec::{DecodeExt, Encode};
use httpcodec::{BodyDecoder, ResponseDecoder};
use httpcodec::{BodyEncoder, Request, RequestEncoder};
use nym_service_providers_common::interface::ProviderInterfaceVersion;
use nym_socks5_requests::{SocketData, Socks5ProtocolVersion, Socks5ProviderRequest};
pub fn encode_http_request_as_socks_send_request(
provider_interface: ProviderInterfaceVersion,
socks5_protocol: Socks5ProtocolVersion,
conn_id: u64,
request: Request<Vec<u8>>,
seq: Option<u64>,
local_closed: bool,
) -> Result<nym_socks5_requests::Socks5ProviderRequest, error::MixHttpRequestError> {
// Encode HTTP request as bytes
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
encoder.start_encoding(request)?;
let mut buf = Vec::new();
encoder.encode_all(&mut buf)?;
// Wrap it as SOCKS send request
let request_content = nym_socks5_requests::request::Socks5Request::new_send(
socks5_protocol,
SocketData::new(seq.unwrap_or_default(), conn_id, local_closed, buf),
);
// and wrap it in provider request
Ok(Socks5ProviderRequest::new_provider_data(
provider_interface,
request_content,
))
}
#[derive(Debug)]
pub struct MixHttpResponse {
// pub connection_id: u64,
// #[deprecated]
// pub is_closed: bool,
pub http_response: httpcodec::Response<Vec<u8>>,
// #[deprecated]
// pub seq: u64,
}
impl MixHttpResponse {
pub fn try_from_bytes(b: &[u8]) -> Result<MixHttpResponse, error::MixHttpRequestError> {
if b.is_empty() {
Err(error::MixHttpRequestError::EmptySocks5Response)
} else {
let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
let http_response = decoder.decode_from_bytes(b)?;
Ok(MixHttpResponse { http_response })
}
}
}
// impl TryFrom<Socks5Response> for MixHttpResponse {
// type Error = error::MixHttpRequestError;
//
// fn try_from(value: Socks5Response) -> Result<Self, Self::Error> {
// if let Socks5ResponseContent::NetworkData { content } = value.content {
// content.try_into()
// } else {
// Err(error::MixHttpRequestError::InvalidSocks5Response)
// }
// }
// }
//
// impl TryFrom<SocketData> for MixHttpResponse {
// type Error = error::MixHttpRequestError;
//
// fn try_from(value: SocketData) -> Result<Self, Self::Error> {
// if value.data.is_empty() {
// Err(error::MixHttpRequestError::EmptySocks5Response)
// } else {
// let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
// let http_response = decoder.decode_from_bytes(value.data.as_ref())?;
//
// Ok(MixHttpResponse {
// connection_id: value.header.connection_id,
// is_closed: value.header.local_socket_closed,
// http_response,
// seq: value.header.seq,
// })
// }
// }
// }
// pub fn decode_socks_response_as_http_response(
// socks5_response: Socks5Response,
// ) -> Result<MixHttpResponse, error::MixHttpRequestError> {
// socks5_response.try_into()
// }
//
// #[cfg(test)]
// mod http_requests_tests {
// use super::*;
// use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
// use nym_service_providers_common::interface::Serializable;
// use nym_socks5_requests::Socks5Response;
//
// fn create_http_get_request() -> Request<Vec<u8>> {
// let mut request = Request::new(
// Method::new("GET").unwrap(),
// RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
// HttpVersion::V1_1,
// b"".to_vec(),
// );
// let mut headers = request.header_mut();
// headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
//
// request
// }
//
// fn create_socks5_request_buffer() -> Vec<u8> {
// let request = create_http_get_request();
// let socks5_request = encode_http_request_as_socks_send_request(
// ProviderInterfaceVersion::new_current(),
// Socks5ProtocolVersion::new_current(),
// 99u64,
// request,
// Some(42u64),
// true,
// )
// .unwrap();
// socks5_request.into_bytes()
// }
//
// #[test]
// fn request_http_request_content_ok() {
// let buffer = create_socks5_request_buffer();
//
// // HTTP request string content is as expected
// assert_eq!(
// [71u8, 69u8, 84u8, 32u8, 47u8, 46u8, 119u8, 101u8],
// buffer[19..27]
// );
// }
//
// /// This test will fail if the framing of the request buffer changes, e.g. when OrderedMessage
// /// changes to have the `index` value as a field, instead of packed with the `data`
// #[test]
// fn request_size_as_expected_ok() {
// let buffer = create_socks5_request_buffer();
// // println!("{:?}", buffer) // uncomment and run `cargo test -- --nocapture` to view
//
// assert_eq!(108, buffer.len()); // version set to SOCKS5
// }
//
// #[test]
// fn request_socks5_headers_ok() {
// let buffer = create_socks5_request_buffer();
//
// assert_eq!(5u8, buffer[0]); // version set to SOCKS5
// assert_eq!(1u8, buffer[1]); // type is SEND
// assert_eq!(99u8, buffer[9]); // ConnectionId is correct
// assert_eq!(1u8, buffer[10]); // local_closed is true
// }
//
// #[test]
// fn request_ordered_message_ok() {
// let buffer = create_socks5_request_buffer();
//
// // OrderedMessage index is correct
// assert_eq!(42u8, buffer[18]);
// }
//
// fn create_socks_response() -> Socks5Response {
// // HTTP response is just a string
// let http_response_string = "HTTP/1.1 200 OK\r\nServer: foo/0.0.1\r\n\r\n";
//
// let data = http_response_string.as_bytes().to_vec();
//
// // wrap in `NetworkData`, then Socks5Response
// Socks5Response::new(
// Socks5ProtocolVersion::new_current(),
// Socks5ResponseContent::NetworkData {
// content: SocketData::new(42, 99u64, false, data),
// },
// )
// }
//
// /// This test will fail is anything in the framing of the socks5_response byte
// /// representation changes
// #[test]
// fn response_byte_size_is_as_expected() {
// let socks5_response = create_socks_response();
// let buf = socks5_response.into_bytes();
//
// assert_eq!(57, buf.len());
// }
//
// #[test]
// fn response_parses() {
// unimplemented!()
// // let socks5_response = create_socks_response();
// // let response = decode_socks_response_as_http_response(socks5_response).unwrap();
// //
// // assert_eq!(42u64, response.seq); // OrderedMessage index as expected
// // assert_eq!(HttpVersion::V1_1, response.http_response.http_version());
// // assert_eq!(200u16, response.http_response.status_code().as_u16());
// // assert_eq!(
// // "foo/0.0.1",
// // response.http_response.header().get_field("Server").unwrap()
// // );
// }
// }
+1 -1
View File
@@ -10,4 +10,4 @@ bip32 = "0.5.1"
k256 = { workspace = true }
ledger-transport = "0.10.0"
ledger-transport-hid = "0.10.0"
thiserror = "1"
thiserror = { workspace = true }
+2 -2
View File
@@ -23,7 +23,7 @@ impl EchoPacket {
.chain(keys.public_key().to_bytes().iter().cloned())
.collect::<Vec<_>>();
let signature = keys.private_key().sign(&bytes_to_sign);
let signature = keys.private_key().sign(bytes_to_sign);
EchoPacket {
sequence_number,
@@ -67,7 +67,7 @@ impl EchoPacket {
pub(crate) fn construct_reply(self, private_key: &identity::PrivateKey) -> ReplyPacket {
let bytes = self.to_bytes();
let signature = private_key.sign(&bytes);
let signature = private_key.sign(bytes);
ReplyPacket {
base_packet: self,
signature,
+1 -1
View File
@@ -7,5 +7,5 @@ edition = "2021"
[dependencies]
async-trait = { workspace = true }
thiserror = "1.0"
thiserror = { workspace = true }
+1 -1
View File
@@ -12,7 +12,7 @@ cfg-if = { workspace = true }
dotenvy = { workspace = true }
hex-literal = "0.3.3"
once_cell = { workspace = true }
schemars = { version = "0.8", features = ["preserve_order"] }
schemars = { workspace = true, features = ["preserve_order"] }
serde = { workspace = true, features = ["derive"]}
thiserror = { workspace = true }
url = { workspace = true }
+3
View File
@@ -462,6 +462,9 @@ pub const DEFAULT_NYM_API_PORT: u16 = 8080;
pub const NYM_API_VERSION: &str = "v1";
// NYM-NODE
pub const DEFAULT_NYM_NODE_HTTP_PORT: u16 = 8080;
// REWARDING
/// We'll be assuming a few more things, profit margin and cost function. Since we don't have reliable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
+2 -2
View File
@@ -11,8 +11,8 @@ bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch ="gt-seriali
itertools = "0.10"
digest = "0.9"
rand = "0.8"
thiserror = "1.0"
serde = "1.0"
thiserror = { workspace = true }
serde = { workspace = true }
serde_derive = "1.0"
bs58 = "0.4.0"
sha2 = "0.9"
+1 -1
View File
@@ -11,7 +11,7 @@ repository = { workspace = true }
nym-crypto = { path = "../../crypto", features = ["asymmetric"] } # all addresses are expressed in terms on their crypto keys
nym-sphinx-types = { path = "../types", features = ["sphinx"] } # we need to be able to refer to some types defined inside sphinx crate
serde = "1.0" # implementing serialization/deserialization for some types, like `Recipient`
thiserror = "1.0.37"
thiserror = { workspace = true }
[dev-dependencies]
rand = "0.7"
@@ -10,8 +10,8 @@ repository = { workspace = true }
[dependencies]
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
bs58 = "0.4"
serde = "1.0"
thiserror = "1"
serde = { workspace = true }
thiserror = { workspace = true }
nym-crypto = { path = "../../crypto", features = ["symmetric", "rand"] }
nym-sphinx-addressing = { path = "../addressing" }
+1 -1
View File
@@ -12,7 +12,7 @@ repository = { workspace = true }
[dependencies]
log = { workspace = true }
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
thiserror = "1.0.37"
thiserror = { workspace = true }
nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-params = { path = "../params" }
+1 -1
View File
@@ -9,7 +9,7 @@ repository = { workspace = true }
[dependencies]
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
thiserror = "1.0.37"
thiserror = { workspace = true }
nym-crypto = { path = "../../crypto" }
nym-sphinx-acknowledgements = { path = "../acknowledgements" }
+1 -1
View File
@@ -12,4 +12,4 @@ nym-sphinx-addressing = { path = "../addressing" }
nym-sphinx-params = { path = "../params" }
nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] }
nym-outfox = { path = "../../../nym-outfox" }
thiserror = "1"
thiserror = { workspace = true }
+1 -1
View File
@@ -10,7 +10,7 @@ repository = { workspace = true }
[dependencies]
bytes = "1.0"
tokio-util = { version = "0.7.4", features = ["codec"] }
thiserror = "1.0.37"
thiserror = { workspace = true }
nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] }
nym-sphinx-params = { path = "../params", features = ["sphinx", "outfox"] }
+1 -1
View File
@@ -8,7 +8,7 @@ license = { workspace = true }
repository = { workspace = true }
[dependencies]
thiserror = "1.0.37"
thiserror = { workspace = true }
serde = { workspace = true, features = ["derive"] }
nym-crypto = { path = "../../crypto", features = ["hashing", "symmetric"] }
+2 -1
View File
@@ -294,7 +294,8 @@ mod message_receiver {
owner: "foomp4".to_string(),
host: "1.2.3.4".parse().unwrap(),
mix_host: "1.2.3.4:1789".parse().unwrap(),
clients_port: 9000,
clients_ws_port: 9000,
clients_wss_port: None,
identity_key: identity::PublicKey::from_base58_string(
"FioFa8nMmPpQnYi7JyojoTuwGLeyNS8BF4ChPr29zUML",
)
+1 -1
View File
@@ -10,7 +10,7 @@ repository = { workspace = true }
[dependencies]
sphinx-packet = { version = "0.1.0", optional = true }
nym-outfox = { path = "../../../nym-outfox", optional = true }
thiserror = "1"
thiserror = { workspace = true }
[features]
default = ["sphinx"]
+5 -1
View File
@@ -51,7 +51,11 @@ where
if T::pem_type() != key_pem.tag {
return Err(io::Error::new(
io::ErrorKind::Other,
"unexpected key pem tag",
format!(
"unexpected key pem tag. Got '{}', expected: '{}'",
key_pem.tag,
T::pem_type()
),
));
}
+2 -2
View File
@@ -13,10 +13,10 @@ log = { workspace = true }
pin-project = "1.0"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
reqwest = { workspace = true }
schemars = { version = "0.8", features = ["preserve_order"] }
schemars = { workspace = true, features = ["preserve_order"] }
serde = { workspace = true, features = ["derive"] } # for config serialization/deserialization
tap = "1.0.1"
thiserror = "1.0.34"
thiserror = { workspace = true }
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
url = { workspace = true }
+1 -1
View File
@@ -8,4 +8,4 @@ edition = "2021"
[dependencies]
log = { workspace = true }
thiserror = "1.0.37"
thiserror = { workspace = true }
+1 -1
View File
@@ -15,5 +15,5 @@ reqwest = { workspace = true, features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "chrono"]}
thiserror = "1"
thiserror = { workspace = true }
tokio = { version = "1.24.1", features = [ "time" ] }
+5 -1
View File
@@ -15,7 +15,7 @@ documentation = { workspace = true }
bs58 = "0.4"
log = { workspace = true }
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
thiserror = "1.0.37"
thiserror = { workspace = true }
async-trait = { workspace = true, optional = true }
semver = "0.11"
@@ -35,6 +35,10 @@ nym-sphinx-types = { path = "../nymsphinx/types", features = ["sphinx", "outfox"
nym-sphinx-routing = { path = "../nymsphinx/routing" }
nym-bin-common = { path = "../bin-common" }
# I'm not sure how to feel about pulling in this dependency here...
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
# 'serializable' feature
nym-config = { path = "../config", optional = true }
+86 -6
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{filter, NetworkAddress, NodeVersion};
use nym_api_requests::models::DescribedGateway;
use nym_crypto::asymmetric::{encryption, identity};
use nym_mixnet_contract_common::GatewayBond;
use nym_sphinx_addressing::nodes::{NodeIdentity, NymNodeRoutingAddress};
@@ -10,6 +11,7 @@ use std::convert::{TryFrom, TryInto};
use std::fmt;
use std::fmt::Formatter;
use std::io;
use std::net::AddrParseError;
use std::net::SocketAddr;
use thiserror::Error;
@@ -27,6 +29,17 @@ pub enum GatewayConversionError {
#[source]
source: io::Error,
},
#[error("'{gateway}' has not provided any valid ip addresses")]
NoIpAddressesProvided { gateway: String },
#[error("'{gateway}' has provided a malformed ip address: {err}")]
MalformedIpAddress {
gateway: String,
#[source]
err: AddrParseError,
},
}
#[derive(Clone)]
@@ -36,7 +49,13 @@ pub struct Node {
// we're keeping this as separate resolved field since we do not want to be resolving the potential
// hostname every time we want to construct a path via this node
pub mix_host: SocketAddr,
pub clients_port: u16,
// #[serde(alias = "clients_port")]
pub clients_ws_port: u16,
// #[serde(default)]
pub clients_wss_port: Option<u16>,
pub identity_key: identity::PublicKey,
pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519
pub version: NodeVersion,
@@ -48,7 +67,8 @@ impl std::fmt::Debug for Node {
.field("host", &self.host)
.field("owner", &self.owner)
.field("mix_host", &self.mix_host)
.field("clients_port", &self.clients_port)
.field("clients_ws_port", &self.clients_ws_port)
.field("clients_wss_port", &self.clients_wss_port)
.field("identity_key", &self.identity_key.to_base58_string())
.field("sphinx_key", &self.sphinx_key.to_base58_string())
.field("version", &self.version)
@@ -82,11 +102,17 @@ impl Node {
}
pub fn clients_address(&self) -> String {
format!("ws://{}:{}", self.host, self.clients_port)
self.clients_address_tls()
.unwrap_or_else(|| self.clients_address_no_tls())
}
pub fn clients_address_tls(&self) -> String {
format!("wss://{}:443", self.host)
pub fn clients_address_no_tls(&self) -> String {
format!("ws://{}:{}", self.host, self.clients_ws_port)
}
pub fn clients_address_tls(&self) -> Option<String> {
self.clients_wss_port
.map(|p| format!("wss://{}:{p}", self.host))
}
}
@@ -131,7 +157,8 @@ impl<'a> TryFrom<&'a GatewayBond> for Node {
owner: bond.owner.as_str().to_owned(),
host,
mix_host,
clients_port: bond.gateway.clients_port,
clients_ws_port: bond.gateway.clients_port,
clients_wss_port: None,
identity_key: identity::PublicKey::from_base58_string(&bond.gateway.identity_key)?,
sphinx_key: encryption::PublicKey::from_base58_string(&bond.gateway.sphinx_key)?,
version: bond.gateway.version.as_str().into(),
@@ -146,3 +173,56 @@ impl TryFrom<GatewayBond> for Node {
Node::try_from(&bond)
}
}
impl<'a> TryFrom<&'a DescribedGateway> for Node {
type Error = GatewayConversionError;
fn try_from(value: &'a DescribedGateway) -> Result<Self, Self::Error> {
let Some(ref self_described) = value.self_described else {
return (&value.bond).try_into();
};
let ips = &self_described.host_information.ip_address;
if ips.is_empty() {
return Err(GatewayConversionError::NoIpAddressesProvided {
gateway: value.bond.gateway.identity_key.clone(),
});
}
let host = match &self_described.host_information.hostname {
None => NetworkAddress::IpAddr(ips[0]),
Some(hostname) => NetworkAddress::Hostname(hostname.clone()),
};
// get ip from the self-reported values so we wouldn't need to do any hostname resolution
// (which doesn't really work in wasm)
let mix_host = SocketAddr::new(ips[0], value.bond.gateway.mix_port);
Ok(Node {
owner: value.bond.owner.as_str().to_owned(),
host,
mix_host,
clients_ws_port: self_described.mixnet_websockets.ws_port,
clients_wss_port: self_described.mixnet_websockets.wss_port,
identity_key: identity::PublicKey::from_base58_string(
&self_described.host_information.keys.ed25519,
)?,
sphinx_key: encryption::PublicKey::from_base58_string(
&self_described.host_information.keys.x25519,
)?,
version: self_described
.build_information
.build_version
.as_str()
.into(),
})
}
}
impl TryFrom<DescribedGateway> for Node {
type Error = GatewayConversionError;
fn try_from(value: DescribedGateway) -> Result<Self, Self::Error> {
Node::try_from(&value)
}
}
+28 -4
View File
@@ -19,6 +19,7 @@ use std::str::FromStr;
#[cfg(feature = "serializable")]
use ::serde::{Deserialize, Deserializer, Serialize, Serializer};
use nym_api_requests::models::DescribedGateway;
pub mod error;
pub mod filter;
@@ -403,10 +404,33 @@ impl<'de> Deserialize<'de> for NymTopology {
}
}
pub fn nym_topology_from_detailed(
pub trait IntoGatewayNode: TryInto<gateway::Node>
where
<Self as TryInto<gateway::Node>>::Error: Display,
{
fn identity(&self) -> IdentityKeyRef;
}
impl IntoGatewayNode for GatewayBond {
fn identity(&self) -> IdentityKeyRef {
&self.gateway.identity_key
}
}
impl IntoGatewayNode for DescribedGateway {
fn identity(&self) -> IdentityKeyRef {
&self.bond.gateway.identity_key
}
}
pub fn nym_topology_from_detailed<G>(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<GatewayBond>,
) -> NymTopology {
gateway_bonds: Vec<G>,
) -> NymTopology
where
G: IntoGatewayNode,
<G as TryInto<gateway::Node>>::Error: Display,
{
let mut mixes = BTreeMap::new();
for bond in mix_details
.into_iter()
@@ -435,7 +459,7 @@ pub fn nym_topology_from_detailed(
let mut gateways = Vec::with_capacity(gateway_bonds.len());
for bond in gateway_bonds.into_iter() {
let gate_id = bond.gateway.identity_key.clone();
let gate_id = bond.identity().to_owned();
match bond.try_into() {
Ok(gate) => gateways.push(gate),
Err(err) => {
+13 -4
View File
@@ -190,7 +190,12 @@ pub struct SerializableGateway {
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "clients_port")]
pub clients_port: Option<u16>,
#[serde(alias = "clients_ws_port")]
pub clients_ws_port: Option<u16>,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "clients_wss_port")]
pub clients_wss_port: Option<u16>,
#[serde(alias = "identity_key")]
pub identity_key: String,
@@ -209,7 +214,9 @@ impl TryFrom<SerializableGateway> for gateway::Node {
let host = gateway::Node::parse_host(&value.host)?;
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
let clients_port = value.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT);
let clients_ws_port = value
.clients_ws_port
.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT);
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
// try to completely resolve the host in the mix situation to avoid doing it every
@@ -224,7 +231,8 @@ impl TryFrom<SerializableGateway> for gateway::Node {
owner: value.owner,
host,
mix_host,
clients_port,
clients_ws_port,
clients_wss_port: value.clients_wss_port,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(GatewayConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
@@ -241,7 +249,8 @@ impl<'a> From<&'a gateway::Node> for SerializableGateway {
host: value.host.to_string(),
explicit_ip: Some(value.mix_host.ip()),
mix_port: Some(value.mix_host.port()),
clients_port: Some(value.clients_port),
clients_ws_port: Some(value.clients_ws_port),
clients_wss_port: value.clients_wss_port,
identity_key: value.identity_key.to_base58_string(),
sphinx_key: value.sphinx_key.to_base58_string(),
version: Some(value.version.to_string()),
+1 -1
View File
@@ -14,7 +14,7 @@ js-sys = { workspace = true }
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { workspace = true, features = ["derive"] }
serde-wasm-bindgen = { workspace = true }
thiserror = { workspace = true }
thiserror = { workspace = true }
tsify = { workspace = true, features = ["js"] }
url = { workspace = true }
wasm-bindgen = { workspace = true }
+13 -13
View File
@@ -810,7 +810,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.12",
"syn 2.0.32",
]
[[package]]
@@ -1628,9 +1628,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.26"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
checksum = "5907a1b7c277254a8b15170f6e7c97cfa60ee7872a3217663bb81151e48184bb"
dependencies = [
"proc-macro2",
]
@@ -1894,7 +1894,7 @@ checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.12",
"syn 2.0.32",
]
[[package]]
@@ -1927,7 +1927,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.12",
"syn 2.0.32",
]
[[package]]
@@ -2041,9 +2041,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.12"
version = "2.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927"
checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
dependencies = [
"proc-macro2",
"quote",
@@ -2065,22 +2065,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.40"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.12",
"syn 2.0.32",
]
[[package]]
@@ -2427,5 +2427,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.12",
"syn 2.0.32",
]
+2
View File
@@ -48,3 +48,5 @@ cw3 = "=1.1.0"
cw3-fixed-multisig = "=1.1.0"
cw4 = "=1.1.0"
cw20 = "=1.1.0"
thiserror = "1.0.48"
+1 -1
View File
@@ -23,7 +23,7 @@ cw-storage-plus = { workspace = true }
cw-controllers = { workspace = true }
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0.23"
thiserror = { workspace = true }
[features]
schema-gen = ["nym-coconut-bandwidth-contract-common/schema", "cosmwasm-schema"]
+1 -1
View File
@@ -22,7 +22,7 @@ cw-storage-plus = { workspace = true }
cw-controllers = { workspace = true }
cw4 = { workspace = true }
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0.23"
thiserror = { workspace = true }
[dev-dependencies]
cw-multi-test = { workspace = true }
+1 -1
View File
@@ -22,7 +22,7 @@ cw-utils = { workspace = true }
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0.23"
thiserror = { workspace = true }
nym-coconut-bandwidth = { path = "../coconut-bandwidth" }
nym-coconut-dkg = { path = "../coconut-dkg" }
+1 -1
View File
@@ -23,7 +23,7 @@ cw-controllers = { workspace = true }
cw4 = { workspace = true }
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = "1.0.23"
thiserror = { workspace = true }
[dev-dependencies]
cw-multi-test = { workspace = true }
@@ -243,7 +243,7 @@ impl TestSetup {
ContractMessageContent::new(Addr::unchecked(owner), proxy, vec![stake], payload);
let sign_payload = SignableMixNodeBondingMsg::new(signing_nonce, content);
let plaintext = sign_payload.to_plaintext().unwrap();
let signature = keypair.private_key().sign(&plaintext);
let signature = keypair.private_key().sign(plaintext);
let msg_signature = MessageSignature::from(signature.to_bytes().as_ref());
(mixnode, msg_signature)
+1 -1
View File
@@ -39,7 +39,7 @@ cw-storage-plus = { workspace = true }
bs58 = "0.4.0"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
thiserror = { workspace = true }
time = { version = "0.3", features = ["macros"] }
semver = { version = "1.0.16", default-features = false }
+2 -2
View File
@@ -206,7 +206,7 @@ pub mod test_helpers {
let sig_bytes = family_owner_keys
.private_key()
.sign(&msg.to_plaintext().unwrap())
.sign(msg.to_plaintext().unwrap())
.to_bytes();
MessageSignature::from(sig_bytes.as_ref())
}
@@ -956,7 +956,7 @@ pub mod test_helpers {
match message.algorithm {
SigningAlgorithm::Ed25519 => {
let plaintext = message.to_plaintext().unwrap();
let signature = private_key.sign(&plaintext);
let signature = private_key.sign(plaintext);
MessageSignature::from(signature.to_bytes().as_ref())
}
SigningAlgorithm::Secp256k1 => {
+1 -1
View File
@@ -41,5 +41,5 @@ cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
thiserror = { workspace = true }
+1 -1
View File
@@ -22,7 +22,7 @@ nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts
nym-name-service-common = { path = "../../common/cosmwasm-smart-contracts/name-service" }
semver = { version = "1.0.16", default-features = false }
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
thiserror = "1.0.39"
thiserror = { workspace = true }
[build-dependencies]
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] }
@@ -29,7 +29,7 @@ pub fn ed25519_sign_message<T: Serialize + SigningPurpose>(
match message.algorithm {
SigningAlgorithm::Ed25519 => {
let plaintext = message.to_plaintext().unwrap();
let signature = private_key.sign(&plaintext);
let signature = private_key.sign(plaintext);
MessageSignature::from(signature.to_bytes().as_ref())
}
SigningAlgorithm::Secp256k1 => {
@@ -22,7 +22,7 @@ nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts
nym-service-provider-directory-common = { path = "../../common/cosmwasm-smart-contracts/service-provider-directory" }
semver = { version = "1.0.16", default-features = false }
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
thiserror = "1.0.39"
thiserror = { workspace = true }
[build-dependencies]
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] }
@@ -31,7 +31,7 @@ pub fn ed25519_sign_message<T: Serialize + SigningPurpose>(
match message.algorithm {
SigningAlgorithm::Ed25519 => {
let plaintext = message.to_plaintext().unwrap();
let signature = private_key.sign(&plaintext);
let signature = private_key.sign(plaintext);
MessageSignature::from(signature.to_bytes().as_ref())
}
SigningAlgorithm::Secp256k1 => {
+1 -1
View File
@@ -35,7 +35,7 @@ cw2 = { workspace = true }
cw-storage-plus = { workspace = true, features = ["iterator"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror = { version = "1.0" }
thiserror ={ workspace = true }
semver = { version = "1.0.16", default-features = false }
[dev-dependencies]
+4 -4
View File
@@ -36,7 +36,7 @@ nym-config = { path = "../common/config" }
nym-ephemera-common = { path = "../common/cosmwasm-smart-contracts/ephemera" }
pretty_env_logger = "0.4"
refinery = { version = "0.8.7", features = ["rusqlite"], optional = true }
reqwest = { version = "0.11.6", features = ["json"] }
reqwest = { version = "0.11.22", features = ["json"] }
# Rocksdb kills compilation times and we're not currently using it. The reason
# we comment it out is that rust-analyzer runs with --all-features
#rocksdb = { version = "0.21.0", optional = true }
@@ -44,14 +44,14 @@ rusqlite = { version = "0.27.0", features = ["bundled"], optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0.149"
serde_json = "1.0.91"
thiserror = "1.0.37"
thiserror = { workspace = true }
tokio = { version = "1", features = ["macros", "net","rt-multi-thread"] }
tokio-tungstenite = { workspace = true }
tokio-util = { version = "0.7.4", features = ["full"] }
toml = "0.7.0"
unsigned-varint = "0.7.1"
utoipa = { version = "3.0.1", features = ["actix_extras"] }
utoipa-swagger-ui = { version = "3.0.2", features = ["actix-web"] }
utoipa = { workspace = true, features = ["actix_extras"] }
utoipa-swagger-ui = { workspace = true, features = ["actix-web"] }
uuid = { version = "1.2.2", features = ["v4"] }
# Temporary fix to https://github.com/bluejekyll/trust-dns/issues/1946
+4 -4
View File
@@ -24,10 +24,10 @@ reqwest = { workspace = true }
rocket = { version = "0.5.0-rc.2", features = ["json"] }
rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" }
rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] }
schemars = { version = "0.8", features = ["preserve_order"] }
serde = "1.0.126"
serde_json = "1.0.66"
thiserror = "1.0.29"
schemars = { workspace = true, features = ["preserve_order"] }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = {version = "1.21.2", features = ["full"] }
nym-bin-common = { path = "../common/bin-common"}
@@ -7,6 +7,6 @@ edition = "2021"
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
schemars = { version = "0.8", features = ["preserve_order"] }
schemars = { workspace = true, features = ["preserve_order"] }
serde = { version = "1.0", features = ["derive"] }
ts-rs = { workspace = true, optional = true }
+1
View File
@@ -6,6 +6,7 @@ use crate::helpers::best_effort_small_dec_to_f64;
use crate::mix_node::models::EconomicDynamicsStats;
use nym_contracts_common::truncate_decimal;
use nym_mixnet_contract_common::MixId;
use nym_validator_client::client::NymApiClientExt;
pub(crate) async fn retrieve_mixnode_econ_stats(
client: &ThreadsafeValidatorClient,
+5 -1
View File
@@ -22,6 +22,8 @@ axum-macros = "0.3.8" # Useful for debugging axum Handler trait errors
fastrand = "2"
x25519-dalek = { version = "2.0.0", features = ["static_secrets"] }
base64 = "0.21.4"
anyhow = { workspace = true }
async-trait = { workspace = true }
atty = "0.2"
@@ -48,7 +50,7 @@ sqlx = { version = "0.5", features = [
"migrate",
] }
subtle-encoding = { version = "0.5", features = ["bech32-preview"] }
thiserror = "1"
thiserror = { workspace = true }
tokio = { workspace = true, features = [
"rt-multi-thread",
"net",
@@ -63,6 +65,8 @@ url = { version = "2.2", features = ["serde"] }
zeroize = { workspace = true }
# internal
nym-node = { path = "../nym-node" }
nym-api-requests = { path = "../nym-api/nym-api-requests" }
nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
nym-coconut-interface = { path = "../common/coconut-interface" }
+1 -1
View File
@@ -17,7 +17,7 @@ log = { workspace = true }
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = "1.0"
thiserror = { workspace = true }
zeroize = { workspace = true }
nym-crypto = { path = "../../common/crypto" }
@@ -137,7 +137,7 @@ impl<'a, S> State<'a, S> {
.chain(remote_ephemeral_key.to_bytes().iter().cloned())
.collect();
let signature = self.identity.private_key().sign(&message);
let signature = self.identity.private_key().sign(message);
let zero_iv = stream_cipher::zero_iv::<GatewayEncryptionAlgorithm>();
stream_cipher::encrypt::<GatewayEncryptionAlgorithm>(
self.derived_shared_keys.as_ref().unwrap().encryption_key(),
@@ -191,7 +191,7 @@ impl<'a, S> State<'a, S> {
self.remote_pubkey
.as_ref()
.unwrap()
.verify(&signed_payload, &signature)
.verify(signed_payload, &signature)
.map_err(|_| HandshakeError::InvalidSignature)
}
+1 -1
View File
@@ -41,7 +41,7 @@ pub(crate) struct OverrideConfig {
impl OverrideConfig {
pub(crate) fn do_override(self, mut config: Config) -> Result<Config, GatewayError> {
config = config
.with_optional(Config::with_listening_address, self.host)
.with_optional(Config::with_host, self.host)
.with_optional(Config::with_mix_port, self.mix_port)
.with_optional(Config::with_clients_port, self.clients_port)
.with_optional_custom_env(
+29 -1
View File
@@ -3,6 +3,7 @@
use crate::config::old_config_v1_1_20::ConfigV1_1_20;
use crate::config::old_config_v1_1_28::ConfigV1_1_28;
use crate::config::old_config_v1_1_29::ConfigV1_1_29;
use crate::config::{default_config_filepath, Config};
use crate::error::GatewayError;
use log::info;
@@ -20,7 +21,8 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, GatewayError> {
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_28 = old_config.into();
let updated: Config = updated_step1.into();
let updated_step2: ConfigV1_1_29 = updated_step1.into();
let updated: Config = updated_step2.into();
updated
.save_to_default_location()
.map_err(|err| GatewayError::ConfigSaveFailure {
@@ -42,6 +44,29 @@ fn try_upgrade_v1_1_28_config(id: &str) -> Result<bool, GatewayError> {
info!("It seems the gateway is using <= v1.1.28 config template.");
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_29 = old_config.into();
let updated: Config = updated_step1.into();
updated
.save_to_default_location()
.map_err(|err| GatewayError::ConfigSaveFailure {
path: default_config_filepath(id),
id: id.to_string(),
source: err,
})?;
Ok(true)
}
fn try_upgrade_v1_1_29_config(id: &str) -> Result<bool, GatewayError> {
// explicitly load it as v1.1.29 (which is incompatible with the current, i.e. 1.1.30+)
let Ok(old_config) = ConfigV1_1_29::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the gateway is using <= v1.1.29 config template.");
info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into();
updated
.save_to_default_location()
@@ -61,6 +86,9 @@ pub(crate) fn try_upgrade_config(id: &str) -> Result<(), GatewayError> {
if try_upgrade_v1_1_28_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_29_config(id)? {
return Ok(());
}
Ok(())
}
+48 -4
View File
@@ -12,9 +12,10 @@ use nym_config::{
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
};
use nym_network_defaults::mainnet;
use serde::{Deserialize, Serialize};
use nym_node::config;
use serde::{Deserialize, Deserializer, Serialize};
use std::io;
use std::net::IpAddr;
use std::net::{IpAddr, SocketAddr};
use std::path::{Path, PathBuf};
use std::time::Duration;
use url::Url;
@@ -22,6 +23,7 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
pub(crate) mod old_config_v1_1_20;
pub(crate) mod old_config_v1_1_28;
pub(crate) mod old_config_v1_1_29;
pub mod persistence;
mod template;
@@ -38,6 +40,18 @@ const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
fn de_maybe_port<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
where
D: Deserializer<'de>,
{
let port = u16::deserialize(deserializer)?;
if port == 0 {
Ok(None)
} else {
Ok(Some(port))
}
}
/// Derive default path to gateway's config directory.
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
@@ -71,8 +85,17 @@ pub struct Config {
#[serde(skip)]
pub(crate) save_path: Option<PathBuf>,
pub host: config::Host,
#[serde(default)]
pub http: config::Http,
pub gateway: Gateway,
#[serde(default)]
// currently not really used for anything useful
pub wireguard: config::Wireguard,
pub storage_paths: GatewayPaths,
pub network_requester: NetworkRequester,
@@ -92,9 +115,16 @@ impl NymConfigTemplate for Config {
impl Config {
pub fn new<S: AsRef<str>>(id: S) -> Self {
let default_gateway = Gateway::new_default(id.as_ref());
Config {
save_path: None,
gateway: Gateway::new_default(id.as_ref()),
host: config::Host {
public_ips: vec![default_gateway.listening_address],
..Default::default()
},
http: Default::default(),
gateway: default_gateway,
wireguard: Default::default(),
storage_paths: GatewayPaths::new_default(id.as_ref()),
network_requester: Default::default(),
logging: Default::default(),
@@ -182,8 +212,16 @@ impl Config {
self
}
pub fn with_listening_address(mut self, listening_address: IpAddr) -> Self {
pub fn with_host(mut self, listening_address: IpAddr) -> Self {
self.gateway.listening_address = listening_address;
// temporary workaround
self.host.public_ips = vec![listening_address];
let http_port = self.http.bind_address.port();
self.http.bind_address = SocketAddr::new(listening_address, http_port);
let wg_port = self.wireguard.bind_address.port();
self.wireguard.bind_address = SocketAddr::new(listening_address, wg_port);
self
}
@@ -245,6 +283,11 @@ pub struct Gateway {
/// (default: 9000)
pub clients_port: u16,
/// If applicable, announced port for listening for secure websocket client traffic.
/// (default: None)
#[serde(deserialize_with = "de_maybe_port")]
pub clients_wss_port: Option<u16>,
/// Whether gateway collects and sends anonymized statistics
pub enabled_statistics: bool,
@@ -279,6 +322,7 @@ impl Gateway {
listening_address: inaddr_any(),
mix_port: DEFAULT_MIX_LISTENING_PORT,
clients_port: DEFAULT_CLIENT_LISTENING_PORT,
clients_wss_port: None,
enabled_statistics: false,
statistics_service_url: mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS
.parse()
+19 -10
View File
@@ -1,9 +1,10 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::paths::{GatewayPaths, KeysPaths};
use crate::config::{Config, Debug, Gateway};
use nym_bin_common::logging::LoggingSettings;
use crate::config::old_config_v1_1_29::{
ConfigV1_1_29, DebugV1_1_29, GatewayPathsV1_1_29, GatewayV1_1_29, KeysPathsV1_1_29,
LoggingSettingsV1_1_29,
};
use nym_config::{
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
};
@@ -84,11 +85,13 @@ impl ConfigV1_1_28 {
}
}
impl From<ConfigV1_1_28> for Config {
impl From<ConfigV1_1_28> for ConfigV1_1_29 {
fn from(value: ConfigV1_1_28) -> Self {
Config {
ConfigV1_1_29 {
// \/ ADDED
save_path: None,
gateway: Gateway {
// /\ ADDED
gateway: GatewayV1_1_29 {
version: value.gateway.version,
id: value.gateway.id,
only_coconut_credentials: value.gateway.only_coconut_credentials,
@@ -101,19 +104,25 @@ impl From<ConfigV1_1_28> for Config {
statistics_service_url: value.gateway.statistics_service_url,
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
},
storage_paths: GatewayPaths {
keys: KeysPaths {
storage_paths: GatewayPathsV1_1_29 {
keys: KeysPathsV1_1_29 {
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
},
clients_storage: value.storage_paths.clients_storage,
// \/ ADDED
network_requester_config: None,
// /\ ADDED
},
// \/ ADDED
network_requester: Default::default(),
logging: LoggingSettings {},
debug: Debug {
// /\ ADDED
logging: LoggingSettingsV1_1_29 {},
debug: DebugV1_1_29 {
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
initial_connection_timeout: value.debug.initial_connection_timeout,
+278
View File
@@ -0,0 +1,278 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::persistence::paths::{GatewayPaths, KeysPaths};
use crate::config::{Config, Debug, Gateway, NetworkRequester};
use nym_bin_common::logging::LoggingSettings;
use nym_config::{
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
};
use serde::{Deserialize, Deserializer, Serialize};
use std::io;
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use std::time::Duration;
use url::Url;
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
// 'DEBUG'
// where applicable, the below are defined in milliseconds
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
/// Derive default path to gateway's config directory.
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_GATEWAYS_DIR)
.join(id)
.join(DEFAULT_CONFIG_DIR)
}
/// Derive default path to gateways's config file.
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct KeysPathsV1_1_29 {
pub private_identity_key_file: PathBuf,
pub public_identity_key_file: PathBuf,
pub private_sphinx_key_file: PathBuf,
pub public_sphinx_key_file: PathBuf,
}
fn de_maybe_path<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
where
D: Deserializer<'de>,
{
let path = PathBuf::deserialize(deserializer)?;
if path.as_os_str().is_empty() {
Ok(None)
} else {
Ok(Some(path))
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct GatewayPathsV1_1_29 {
pub keys: KeysPathsV1_1_29,
#[serde(alias = "persistent_storage")]
pub clients_storage: PathBuf,
#[serde(deserialize_with = "de_maybe_path")]
pub network_requester_config: Option<PathBuf>,
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LoggingSettingsV1_1_29 {}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_29 {
#[serde(skip)]
pub save_path: Option<PathBuf>,
pub gateway: GatewayV1_1_29,
pub storage_paths: GatewayPathsV1_1_29,
pub network_requester: NetworkRequesterV1_1_29,
#[serde(default)]
pub logging: LoggingSettingsV1_1_29,
#[serde(default)]
pub debug: DebugV1_1_29,
}
impl ConfigV1_1_29 {
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
read_config_from_toml_file(default_config_filepath(id))
}
}
impl From<ConfigV1_1_29> for Config {
fn from(value: ConfigV1_1_29) -> Self {
Config {
save_path: value.save_path,
// \/ ADDED
host: nym_node::config::Host {
public_ips: vec![value.gateway.listening_address],
..Default::default()
},
// /\ ADDED
// \/ ADDED
http: Default::default(),
// /\ ADDED
gateway: Gateway {
version: value.gateway.version,
id: value.gateway.id,
only_coconut_credentials: value.gateway.only_coconut_credentials,
listening_address: value.gateway.listening_address,
mix_port: value.gateway.mix_port,
clients_port: value.gateway.clients_port,
// \/ ADDED
clients_wss_port: None,
// /\ ADDED
enabled_statistics: value.gateway.enabled_statistics,
nym_api_urls: value.gateway.nym_api_urls,
nyxd_urls: value.gateway.nyxd_urls,
statistics_service_url: value.gateway.statistics_service_url,
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
},
// \/ ADDED
wireguard: Default::default(),
// /\ ADDED
storage_paths: GatewayPaths {
keys: KeysPaths {
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
},
clients_storage: value.storage_paths.clients_storage,
network_requester_config: value.storage_paths.network_requester_config,
},
network_requester: NetworkRequester {
enabled: value.network_requester.enabled,
},
logging: LoggingSettings {},
debug: Debug {
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
initial_connection_timeout: value.debug.initial_connection_timeout,
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
presence_sending_delay: value.debug.presence_sending_delay,
stored_messages_filename_length: value.debug.stored_messages_filename_length,
message_retrieval_limit: value.debug.message_retrieval_limit,
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
},
}
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayV1_1_29 {
/// Version of the gateway for which this configuration was created.
pub version: String,
/// ID specifies the human readable ID of this particular gateway.
pub id: String,
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
/// the mixnet, or if it also accepts non-paying clients
#[serde(default)]
pub only_coconut_credentials: bool,
/// Address to which this mixnode will bind to and will be listening for packets.
pub listening_address: IpAddr,
/// Port used for listening for all mixnet traffic.
/// (default: 1789)
pub mix_port: u16,
/// Port used for listening for all client-related traffic.
/// (default: 9000)
pub clients_port: u16,
/// Whether gateway collects and sends anonymized statistics
pub enabled_statistics: bool,
/// Domain address of the statistics service
pub statistics_service_url: Url,
/// Addresses to APIs from which the node gets the view of the network.
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
#[serde(alias = "validator_nymd_urls")]
pub nyxd_urls: Vec<Url>,
/// Mnemonic of a cosmos wallet used in checking for double spending.
// #[deprecated(note = "move to storage")]
// TODO: I don't think this should be stored directly in the config...
pub cosmos_mnemonic: bip39::Mnemonic,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct NetworkRequesterV1_1_29 {
pub enabled: bool,
}
#[allow(clippy::derivable_impls)]
impl Default for NetworkRequesterV1_1_29 {
fn default() -> Self {
NetworkRequesterV1_1_29 { enabled: false }
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct DebugV1_1_29 {
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
pub packet_forwarding_initial_backoff: Duration,
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
pub packet_forwarding_maximum_backoff: Duration,
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
#[serde(with = "humantime_serde")]
pub initial_connection_timeout: Duration,
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
pub maximum_connection_buffer_size: usize,
/// Delay between each subsequent presence data being sent.
#[serde(with = "humantime_serde")]
pub presence_sending_delay: Duration,
/// Length of filenames for new client messages.
pub stored_messages_filename_length: u16,
/// Number of messages from offline client that can be pulled at once from the storage.
pub message_retrieval_limit: i64,
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
// it's set to true by default. The reason for that decision is to preserve compatibility with the
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
// It shall be disabled in the subsequent releases.
pub use_legacy_framed_packet_version: bool,
}
impl Default for DebugV1_1_29 {
fn default() -> Self {
DebugV1_1_29 {
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
use_legacy_framed_packet_version: false,
}
}
}
+24 -1
View File
@@ -9,7 +9,18 @@ pub(crate) const CONFIG_TEMPLATE: &str = r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
##### main base mixnode config options #####
##### main base gateway config options #####
[host]
# Ip address(es) of this host, such as 1.1.1.1 that external clients will use for connections.
public_ips = [
{{#each host.public_ips }}
'{{this}}',
{{/each}}
]
# (temporary) Optional hostname of this node, for example nymtech.net.
hostname = '{{ host.hostname }}'
[gateway]
# Version of the gateway for which this configuration was created.
@@ -33,6 +44,10 @@ mix_port = {{ gateway.mix_port }}
# (default: 9000)
clients_port = {{ gateway.clients_port }}
# If applicable, announced port for listening for secure websocket client traffic.
# (default: 0 - disabled)
clients_wss_port ={{#if gateway.clients_wss_port }} {{ gateway.clients_wss_port }} {{else}} 0 {{/if}}
# Wheather gateway collects and sends anonymized statistics
enabled_statistics = {{ gateway.enabled_statistics }}
@@ -55,6 +70,14 @@ nyxd_urls = [
cosmos_mnemonic = '{{ gateway.cosmos_mnemonic }}'
[http]
# Socket address this node will use for binding its http API.
# default: `0.0.0.0:8080`
bind_address = '{{ http.bind_address }}'
# Path to assets directory of custom landing page of this node
landing_page_assets_path = '{{ http.landing_page_assets_path }}'
[network_requester]
# Specifies whether network requester service is enabled in this process.
enabled = {{ network_requester.enabled }}
+5
View File
@@ -106,6 +106,11 @@ pub(crate) enum GatewayError {
#[from]
source: NyxdError,
},
// TODO: in the future this should work the other way, i.e. NymNode depending on Gateway errors
#[error(transparent)]
NymNodeError(#[from] nym_node::error::NymNodeError),
#[error("Error verifying hmac digest")]
HmacDigestError {
#[from]
+143
View File
@@ -0,0 +1,143 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config::Config;
use crate::error::GatewayError;
use crate::node::helpers::load_public_key;
use nym_bin_common::bin_info_owned;
use nym_crypto::asymmetric::{encryption, identity};
use nym_node::error::NymNodeError;
use nym_node::http::api::api_requests;
use nym_node::http::api::api_requests::SignedHostInformation;
use nym_node::http::router::WireguardAppState;
use nym_node::wireguard::types::ClientRegistry;
use nym_sphinx::addressing::clients::Recipient;
use nym_task::TaskClient;
use std::sync::Arc;
use tokio::sync::RwLock;
fn load_gateway_details(
config: &Config,
) -> Result<api_requests::v1::gateway::models::Gateway, GatewayError> {
let wireguard = if config.wireguard.enabled {
Some(api_requests::v1::gateway::models::Wireguard {
port: config.wireguard.announced_port,
public_key: "placeholder key value".to_string(),
})
} else {
None
};
Ok(api_requests::v1::gateway::models::Gateway {
client_interfaces: api_requests::v1::gateway::models::ClientInterfaces {
wireguard,
mixnet_websockets: Some(api_requests::v1::gateway::models::WebSockets {
ws_port: config.gateway.clients_port,
wss_port: config.gateway.clients_wss_port,
}),
},
})
}
fn load_host_details(
config: &Config,
sphinx_key: &encryption::PublicKey,
identity_keypair: &identity::KeyPair,
) -> Result<api_requests::v1::node::models::SignedHostInformation, GatewayError> {
let host_info = api_requests::v1::node::models::HostInformation {
// TODO: this should be extracted differently, i.e. it's the issue of the public/private address
ip_address: config.host.public_ips.clone(),
hostname: config.host.hostname.clone(),
keys: api_requests::v1::node::models::HostKeys {
ed25519: identity_keypair.public_key().to_base58_string(),
x25519: sphinx_key.to_base58_string(),
},
};
let signed_info = SignedHostInformation::new(host_info, identity_keypair.private_key())
.map_err(NymNodeError::from)?;
Ok(signed_info)
}
fn load_network_requester_details(
config: &Config,
network_requester_config: &nym_network_requester::Config,
) -> Result<api_requests::v1::network_requester::models::NetworkRequester, GatewayError> {
let identity_public_key: identity::PublicKey = load_public_key(
&network_requester_config
.storage_paths
.common_paths
.keys
.public_identity_key_file,
"network requester identity",
)?;
let dh_public_key: encryption::PublicKey = load_public_key(
&network_requester_config
.storage_paths
.common_paths
.keys
.public_encryption_key_file,
"network requester diffie hellman",
)?;
let gateway_identity_public_key: identity::PublicKey = load_public_key(
&config.storage_paths.keys.public_identity_key_file,
"gateway identity",
)?;
Ok(
api_requests::v1::network_requester::models::NetworkRequester {
encoded_identity_key: identity_public_key.to_base58_string(),
encoded_x25519_key: dh_public_key.to_base58_string(),
address: Recipient::new(
identity_public_key,
dh_public_key,
gateway_identity_public_key,
)
.to_string(),
},
)
}
pub(crate) fn start_http_api(
gateway_config: &Config,
network_requester_config: Option<&nym_network_requester::Config>,
client_registry: Arc<RwLock<ClientRegistry>>,
identity_keypair: &identity::KeyPair,
// TODO: this should be a wg specific key and not re-used sphinx
sphinx_keypair: Arc<encryption::KeyPair>,
task_client: TaskClient,
) -> Result<(), GatewayError> {
// is it suboptimal to load all the keys, etc for the second time after they've already been
// retrieved during startup of the rest of the components?
// yes, a bit.
// but in the grand scheme of things performance penalty is negligible since it's only happening on startup
// and makes the code a bit nicer to manage. on top of it, all of it will refactored anyway at some point
// (famous last words, eh? - 22.09.23)
let mut config = nym_node::http::Config::new(
bin_info_owned!(),
load_host_details(
gateway_config,
sphinx_keypair.public_key(),
identity_keypair,
)?,
)
.with_gateway(load_gateway_details(gateway_config)?)
.with_landing_page_assets(gateway_config.http.landing_page_assets_path.as_ref());
if let Some(nr_config) = network_requester_config {
config = config
.with_network_requester(load_network_requester_details(gateway_config, nr_config)?)
}
let wg_state = WireguardAppState::new(sphinx_keypair, client_registry, Default::default());
let router = nym_node::http::NymNodeRouter::new(config, Some(wg_state));
let server = router
.build_server(&gateway_config.http.bind_address)?
.with_task_client(task_client);
tokio::spawn(async move { server.run().await });
Ok(())
}
+3 -1
View File
@@ -17,6 +17,7 @@ use std::error::Error;
mod commands;
mod config;
pub(crate) mod error;
mod http;
mod node;
pub(crate) mod support;
@@ -46,13 +47,14 @@ struct Cli {
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
setup_logging();
let args = Cli::parse();
setup_env(args.config_env_file.as_ref());
if !args.no_banner {
maybe_print_banner(crate_name!(), crate_version!());
}
setup_logging();
commands::execute(args).await.map_err(|err| {
if atty::is(atty::Stream::Stdout) {
@@ -1,181 +1,182 @@
use std::{
collections::HashMap,
fmt,
hash::{Hash, Hasher},
net::SocketAddr,
ops::Deref,
str::FromStr,
};
use base64::{engine::general_purpose, Engine};
use hmac::{Hmac, Mac};
use nym_crypto::asymmetric::encryption::PrivateKey;
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use x25519_dalek::StaticSecret;
use crate::error::GatewayError;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) enum ClientMessage {
Init(InitMessage),
Final(Client),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct InitMessage {
pub_key: ClientPublicKey,
}
impl InitMessage {
pub fn pub_key(&self) -> &ClientPublicKey {
&self.pub_key
}
#[allow(dead_code)]
pub fn new(pub_key: ClientPublicKey) -> Self {
InitMessage { pub_key }
}
}
// Client that wants to register sends its PublicKey and SocketAddr bytes mac digest encrypted with a DH shared secret.
// Gateway can then verify pub_key payload using the sme process
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct Client {
// base64 encoded public key, using x25519-dalek for impl
pub(crate) pub_key: ClientPublicKey,
pub(crate) socket: SocketAddr,
pub(crate) mac: ClientMac,
}
pub type HmacSha256 = Hmac<Sha256>;
impl Client {
// Reusable secret should be gateways Wireguard PK
// Client should perform this step when generating its payload, using its own WG PK
pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), GatewayError> {
#[allow(clippy::expect_used)]
let static_secret =
StaticSecret::try_from(gateway_key.to_bytes()).expect("This is infalliable");
let dh = static_secret.diffie_hellman(&self.pub_key);
let mut mac = HmacSha256::new_from_slice(dh.as_bytes())?;
mac.update(self.pub_key.as_bytes());
mac.update(self.socket.ip().to_string().as_bytes());
mac.update(self.socket.port().to_string().as_bytes());
mac.update(&nonce.to_le_bytes());
Ok(mac.verify_slice(&self.mac)?)
}
pub fn pub_key(&self) -> &ClientPublicKey {
&self.pub_key
}
pub fn socket(&self) -> SocketAddr {
self.socket
}
}
// This should go into nym-wireguard crate
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ClientPublicKey(x25519_dalek::PublicKey);
#[derive(Debug, Clone)]
pub(crate) struct ClientMac(Vec<u8>);
impl ClientMac {
#[allow(dead_code)]
pub fn new(mac: Vec<u8>) -> Self {
ClientMac(mac)
}
}
impl Deref for ClientMac {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl fmt::Display for ClientPublicKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", general_purpose::STANDARD.encode(self.0.as_bytes()))
}
}
impl Hash for ClientPublicKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.as_bytes().hash(state)
}
}
impl FromStr for ClientMac {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mac_bytes: Vec<u8> = general_purpose::STANDARD
.decode(s)
.map_err(|_| "Could not base64 decode public key mac representation".to_string())?;
Ok(ClientMac(mac_bytes))
}
}
impl Serialize for ClientMac {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
serializer.serialize_str(&encoded_key)
}
}
impl<'de> Deserialize<'de> for ClientMac {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded_key = String::deserialize(deserializer)?;
ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
}
}
impl ClientPublicKey {
#[allow(dead_code)]
pub fn new(key: x25519_dalek::PublicKey) -> Self {
ClientPublicKey(key)
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl Deref for ClientPublicKey {
type Target = x25519_dalek::PublicKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FromStr for ClientPublicKey {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let key_bytes: [u8; 32] = general_purpose::STANDARD
.decode(s)
.map_err(|_| "Could not base64 decode public key representation".to_string())?
.try_into()
.map_err(|_| "Invalid key length".to_string())?;
Ok(ClientPublicKey(x25519_dalek::PublicKey::from(key_bytes)))
}
}
impl Serialize for ClientPublicKey {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let encoded_key = general_purpose::STANDARD.encode(self.0.as_bytes());
serializer.serialize_str(&encoded_key)
}
}
impl<'de> Deserialize<'de> for ClientPublicKey {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded_key = String::deserialize(deserializer)?;
Ok(ClientPublicKey::from_str(&encoded_key).map_err(serde::de::Error::custom))?
}
}
pub(crate) type ClientRegistry = HashMap<SocketAddr, Client>;
// use std::{
// collections::HashMap,
// fmt,
// hash::{Hash, Hasher},
// net::SocketAddr,
// ops::Deref,
// str::FromStr,
// };
//
// use base64::{engine::general_purpose, Engine};
// use hmac::{Hmac, Mac};
// use nym_crypto::asymmetric::encryption::PrivateKey;
// use serde::{Deserialize, Serialize};
// use sha2::Sha256;
// use x25519_dalek::StaticSecret;
//
// use crate::error::GatewayError;
//
// #[derive(Debug, Clone, Deserialize, Serialize)]
// pub(crate) enum ClientMessage {
// Init(InitMessage),
// Final(Client),
// }
//
// #[derive(Debug, Clone, Deserialize, Serialize)]
// pub(crate) struct InitMessage {
// pub_key: ClientPublicKey,
// }
//
// impl InitMessage {
// pub fn pub_key(&self) -> &ClientPublicKey {
// &self.pub_key
// }
// #[allow(dead_code)]
// pub fn new(pub_key: ClientPublicKey) -> Self {
// InitMessage { pub_key }
// }
// }
//
// // Client that wants to register sends its PublicKey and SocketAddr bytes mac digest encrypted with a DH shared secret.
// // Gateway can then verify pub_key payload using the sme process
// #[derive(Debug, Clone, Deserialize, Serialize)]
// pub(crate) struct Client {
// // base64 encoded public key, using x25519-dalek for impl
// pub(crate) pub_key: ClientPublicKey,
// pub(crate) socket: SocketAddr,
// pub(crate) mac: ClientMac,
// }
//
// pub type HmacSha256 = Hmac<Sha256>;
//
// impl Client {
// // Reusable secret should be gateways Wireguard PK
// // Client should perform this step when generating its payload, using its own WG PK
// pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), GatewayError> {
// #[allow(clippy::expect_used)]
// let static_secret =
// StaticSecret::try_from(gateway_key.to_bytes()).expect("This is infalliable");
// let dh = static_secret.diffie_hellman(&self.pub_key);
// let mut mac = HmacSha256::new_from_slice(dh.as_bytes())?;
// mac.update(self.pub_key.as_bytes());
// mac.update(self.socket.ip().to_string().as_bytes());
// mac.update(self.socket.port().to_string().as_bytes());
// mac.update(&nonce.to_le_bytes());
// Ok(mac.verify_slice(&self.mac)?)
// }
//
// pub fn pub_key(&self) -> &ClientPublicKey {
// &self.pub_key
// }
//
// pub fn socket(&self) -> SocketAddr {
// self.socket
// }
// }
//
// // This should go into nym-wireguard crate
// #[derive(Debug, Clone, Eq, PartialEq)]
// pub struct ClientPublicKey(x25519_dalek::PublicKey);
//
// #[derive(Debug, Clone)]
// pub(crate) struct ClientMac(Vec<u8>);
//
// impl ClientMac {
// #[allow(dead_code)]
// pub fn new(mac: Vec<u8>) -> Self {
// ClientMac(mac)
// }
// }
//
// impl Deref for ClientMac {
// type Target = Vec<u8>;
//
// fn deref(&self) -> &Self::Target {
// &self.0
// }
// }
//
// impl fmt::Display for ClientPublicKey {
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// write!(f, "{}", general_purpose::STANDARD.encode(self.0.as_bytes()))
// }
// }
//
// impl Hash for ClientPublicKey {
// fn hash<H: Hasher>(&self, state: &mut H) {
// self.0.as_bytes().hash(state)
// }
// }
//
// impl FromStr for ClientMac {
// type Err = String;
//
// fn from_str(s: &str) -> Result<Self, Self::Err> {
// let mac_bytes: Vec<u8> = general_purpose::STANDARD
// .decode(s)
// .map_err(|_| "Could not base64 decode public key mac representation".to_string())?;
// Ok(ClientMac(mac_bytes))
// }
// }
//
// impl Serialize for ClientMac {
// fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
// let encoded_key = general_purpose::STANDARD.encode(self.0.clone());
// serializer.serialize_str(&encoded_key)
// }
// }
//
// impl<'de> Deserialize<'de> for ClientMac {
// fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
// let encoded_key = String::deserialize(deserializer)?;
// ClientMac::from_str(&encoded_key).map_err(serde::de::Error::custom)
// }
// }
//
// impl ClientPublicKey {
// #[allow(dead_code)]
// pub fn new(key: x25519_dalek::PublicKey) -> Self {
// ClientPublicKey(key)
// }
//
// pub fn as_bytes(&self) -> &[u8] {
// self.0.as_bytes()
// }
// }
//
// impl Deref for ClientPublicKey {
// type Target = x25519_dalek::PublicKey;
//
// fn deref(&self) -> &Self::Target {
// &self.0
// }
// }
//
// impl FromStr for ClientPublicKey {
// type Err = String;
//
// fn from_str(s: &str) -> Result<Self, Self::Err> {
// let key_bytes: [u8; 32] = general_purpose::STANDARD
// .decode(s)
// .map_err(|_| "Could not base64 decode public key representation".to_string())?
// .try_into()
// .map_err(|_| "Invalid key length".to_string())?;
// Ok(ClientPublicKey(x25519_dalek::PublicKey::from(key_bytes)))
// }
// }
//
// impl Serialize for ClientPublicKey {
// fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
// let encoded_key = general_purpose::STANDARD.encode(self.0.as_bytes());
// serializer.serialize_str(&encoded_key)
// }
// }
//
// impl<'de> Deserialize<'de> for ClientPublicKey {
// fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
// let encoded_key = String::deserialize(deserializer)?;
// Ok(ClientPublicKey::from_str(&encoded_key).map_err(serde::de::Error::custom))?
// }
// }
//
// pub(crate) type ClientRegistry = HashMap<SocketAddr, Client>;
-1
View File
@@ -1 +0,0 @@
pub(crate) mod v1;
@@ -1,105 +0,0 @@
use axum::extract::Path;
use std::sync::Arc;
use axum::http::StatusCode;
use axum::{extract::State, Json};
use std::str::FromStr;
// use axum_macros::debug_handler;
use crate::node::client_handling::client_registration::{
Client, ClientMessage, ClientPublicKey, InitMessage,
};
use crate::node::http::ApiState;
async fn process_final_message(client: Client, state: Arc<ApiState>) -> StatusCode {
let preshared_nonce = {
let in_progress_ro = state.registration_in_progress.read().await;
if let Some(nonce) = in_progress_ro.get(client.pub_key()) {
*nonce
} else {
return StatusCode::BAD_REQUEST;
}
};
if client
.verify(state.sphinx_key_pair.private_key(), preshared_nonce)
.is_ok()
{
{
let mut in_progress_rw = state.registration_in_progress.write().await;
in_progress_rw.remove(client.pub_key());
}
{
let mut registry_rw = state.client_registry.write().await;
registry_rw.insert(client.socket(), client);
}
return StatusCode::OK;
}
StatusCode::BAD_REQUEST
}
async fn process_init_message(init_message: InitMessage, state: Arc<ApiState>) -> u64 {
let nonce: u64 = fastrand::u64(..);
let mut registry_rw = state.registration_in_progress.write().await;
registry_rw.insert(init_message.pub_key().clone(), nonce);
nonce
}
// #[debug_handler]
pub(crate) async fn register_client(
State(state): State<Arc<ApiState>>,
Json(payload): Json<ClientMessage>,
) -> (StatusCode, Json<Option<u64>>) {
match payload {
ClientMessage::Init(i) => (
StatusCode::OK,
Json(Some(process_init_message(i, Arc::clone(&state)).await)),
),
ClientMessage::Final(client) => (
process_final_message(client, Arc::clone(&state)).await,
Json(None),
),
}
}
pub(crate) async fn get_all_clients(
State(state): State<Arc<ApiState>>,
) -> (StatusCode, Json<Vec<ClientPublicKey>>) {
let registry_ro = state.client_registry.read().await;
(
StatusCode::OK,
Json(
registry_ro
.values()
.map(|c| c.pub_key().clone())
.collect::<Vec<ClientPublicKey>>(),
),
)
}
pub(crate) async fn get_client(
Path(pub_key): Path<String>,
State(state): State<Arc<ApiState>>,
) -> (StatusCode, Json<Vec<Client>>) {
let pub_key = match ClientPublicKey::from_str(&pub_key) {
Ok(pub_key) => pub_key,
Err(_) => return (StatusCode::BAD_REQUEST, Json(vec![])),
};
let registry_ro = state.client_registry.read().await;
let clients = registry_ro
.iter()
.filter_map(|(_, c)| {
if c.pub_key() == &pub_key {
Some(c.clone())
} else {
None
}
})
.collect::<Vec<Client>>();
if clients.is_empty() {
return (StatusCode::NOT_FOUND, Json(clients));
}
(StatusCode::OK, Json(clients))
}
-1
View File
@@ -1 +0,0 @@
pub(crate) mod client_registry;
+11 -9
View File
@@ -1,11 +1,11 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use self::client_handling::client_registration::ClientRegistry;
use self::storage::PersistentStorage;
use crate::commands::helpers::{override_network_requester_config, OverrideNetworkRequesterConfig};
use crate::config::Config;
use crate::error::GatewayError;
use crate::http::start_http_api;
use crate::node::client_handling::active_clients::ActiveClientsStore;
use crate::node::client_handling::embedded_network_requester::{
LocalNetworkRequesterHandle, MessageRouter,
@@ -13,7 +13,6 @@ use crate::node::client_handling::embedded_network_requester::{
use crate::node::client_handling::websocket;
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
use crate::node::helpers::{initialise_main_storage, load_network_requester_config};
use crate::node::http::start_http_api;
use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler;
use crate::node::statistics::collector::GatewayStatisticsCollector;
use crate::node::storage::Storage;
@@ -24,6 +23,7 @@ use nym_crypto::asymmetric::{encryption, identity};
use nym_mixnet_client::forwarder::{MixForwardingSender, PacketForwarder};
use nym_network_defaults::NymNetworkDetails;
use nym_network_requester::{LocalGateway, NRServiceProviderBuilder};
use nym_node::wireguard::types::ClientRegistry;
use nym_statistics_common::collector::StatisticsSender;
use nym_task::{TaskClient, TaskManager};
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
@@ -38,7 +38,6 @@ use tokio::sync::RwLock;
pub(crate) mod client_handling;
pub(crate) mod helpers;
pub(crate) mod http;
pub(crate) mod mixnet_handling;
pub(crate) mod statistics;
pub(crate) mod storage;
@@ -333,6 +332,15 @@ impl<St> Gateway<St> {
CoconutVerifier::new(nyxd_client)
};
start_http_api(
&self.config,
self.network_requester_opts.as_ref().map(|o| &o.config),
self.client_registry.clone(),
self.identity_keypair.as_ref(),
self.sphinx_keypair.clone(),
shutdown.subscribe().named("http-api"),
)?;
let mix_forwarding_channel =
self.start_packet_forwarder(shutdown.subscribe().named("PacketForwarder"));
@@ -384,12 +392,6 @@ impl<St> Gateway<St> {
bail!("{err}")
}
// This should likely be wireguard feature gated, but its easier to test if it hangs in here
tokio::spawn(start_http_api(
Arc::clone(&self.client_registry),
Arc::clone(&self.sphinx_keypair),
));
info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!");
if let Err(err) = Self::wait_for_interrupt(shutdown).await {
+1 -1
View File
@@ -7,7 +7,7 @@ rust-version = "1.56"
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1.0"
thiserror = { workspace = true }
k256 = { workspace = true, features = ["ecdsa", "sha256"] }
eyre = "0.6.5"
+5 -5
View File
@@ -16,7 +16,7 @@ rust-version = "1.56"
[dependencies]
async-trait = { workspace = true }
bs58 = {version = "0.4.0" }
bs58 = { version = "0.4.0" }
bip39 = { workspace = true }
cfg-if = "1.0"
clap = { version = "4.0", features = ["cargo", "derive"] }
@@ -27,7 +27,6 @@ humantime-serde = "1.0"
lazy_static = "1.4.0"
log = { workspace = true }
pin-project = "1.0"
pretty_env_logger = "0.4.0"
rand = "0.8.5"
rand-07 = { package = "rand", version = "0.7.3" } # required for compatibility
reqwest = { workspace = true, features = ["json"] }
@@ -49,7 +48,7 @@ url = { workspace = true }
ts-rs = { workspace = true, optional = true}
anyhow = "1.0"
anyhow = { workspace = true }
getset = "0.1.1"
sqlx = { version = "0.6.2", features = [
@@ -61,7 +60,7 @@ sqlx = { version = "0.6.2", features = [
okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] }
rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] }
schemars = { version = "0.8", features = ["preserve_order"] }
schemars = { workspace = true, features = ["preserve_order"] }
zeroize = { workspace = true }
## ephemera-specific
@@ -106,13 +105,14 @@ nym-api-requests = { path = "nym-api-requests" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-bin-common = { path = "../common/bin-common" }
nym-node-tester-utils = { path = "../common/node-tester-utils" }
nym-node-requests = { path = "../nym-node/nym-node-requests" }
[features]
no-reward = []
generate-ts = ["ts-rs"]
[build-dependencies]
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
sqlx = { version = "0.6.2", features = [
"runtime-tokio-rustls",
"sqlite",
+4 -3
View File
@@ -10,13 +10,14 @@ bs58 = "0.4.0"
cosmrs = { workspace = true }
cosmwasm-std = { workspace = true }
getset = "0.1.1"
schemars = { version = "0.8", features = ["preserve_order"] }
serde = { version = "1.0", features = ["derive"] }
schemars = { workspace = true, features = ["preserve_order"] }
serde = { workspace = true, features = ["derive"] }
ts-rs = { workspace = true, optional = true }
nym-coconut-interface = { path = "../../common/coconut-interface" }
nym-mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" }
nym-node-requests = { path = "../../nym-node/nym-node-requests", default-features = false }
[features]
default = []
generate-ts = ["ts-rs"]
generate-ts = ["ts-rs", "nym-mixnet-contract-common/generate-ts"]
+44
View File
@@ -9,8 +9,11 @@ use nym_mixnet_contract_common::rewarding::RewardEstimate;
use nym_mixnet_contract_common::{
GatewayBond, IdentityKey, Interval, MixId, MixNode, Percent, RewardedSetNodeStatus,
};
use nym_node_requests::api::v1::gateway::models::WebSockets;
use nym_node_requests::api::v1::node::models::{BinaryBuildInformationOwned, HostInformation};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::{fmt, time::Duration};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -30,6 +33,12 @@ impl RequestError {
}
}
impl Display for RequestError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.message.fmt(f)
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
@@ -135,6 +144,10 @@ impl MixNodeBondAnnotated {
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct GatewayBondAnnotated {
pub gateway_bond: GatewayBond,
#[serde(default)]
pub self_described: Option<GatewayDescription>,
// NOTE: the performance field is deprecated in favour of node_performance
pub performance: Performance,
pub node_performance: NodePerformance,
@@ -151,6 +164,11 @@ impl GatewayBondAnnotated {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct GatewayDescription {
// for now only expose what we need. this struct will evolve in the future (or be incorporated into nym-node properly)
}
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ComputeRewardEstParam {
pub performance: Option<Performance>,
@@ -337,3 +355,29 @@ pub struct CirculatingSupplyResponse {
pub vesting_tokens: Coin,
pub circulating_supply: Coin,
}
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct NymNodeDescription {
pub host_information: HostInformation,
// TODO: do we really care about ALL build info or just the version?
pub build_information: BinaryBuildInformationOwned,
// for now we only care about their ws/wss situation, nothing more
pub mixnet_websockets: WebSockets,
}
#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct DescribedGateway {
pub bond: GatewayBond,
pub self_described: Option<NymNodeDescription>,
}
impl From<GatewayBond> for DescribedGateway {
fn from(bond: GatewayBond) -> Self {
DescribedGateway {
bond,
self_described: None,
}
}
}
+3 -3
View File
@@ -30,9 +30,9 @@ impl<'a> From<&'a CirculatingSupplyCacheData> for CirculatingSupplyResponse {
fn from(value: &'a CirculatingSupplyCacheData) -> Self {
CirculatingSupplyResponse {
total_supply: value.total_supply.clone().into(),
mixmining_reserve: value.mixmining_reserve.clone().into_inner().into(),
vesting_tokens: value.vesting_tokens.clone().into_inner().into(),
circulating_supply: value.circulating_supply.clone().into_inner().into(),
mixmining_reserve: value.mixmining_reserve.clone().into(),
vesting_tokens: value.vesting_tokens.clone().into(),
circulating_supply: value.circulating_supply.clone().into(),
}
}
}
+5 -3
View File
@@ -85,8 +85,10 @@ impl CirculatingSupplyCache {
log::info!("the number of tokens still vesting is now {vesting_tokens}");
log::info!("the circulating supply is now {circulating_supply}");
cache.mixmining_reserve.update(mixmining_reserve);
cache.vesting_tokens.update(vesting_tokens);
cache.circulating_supply.update(circulating_supply);
cache.mixmining_reserve.unchecked_update(mixmining_reserve);
cache.vesting_tokens.unchecked_update(vesting_tokens);
cache
.circulating_supply
.unchecked_update(circulating_supply);
}
}
+18
View File
@@ -6,7 +6,9 @@ extern crate rocket;
use crate::epoch_operations::RewardedSetUpdater;
use crate::network::models::NetworkDetails;
use crate::node_describe_cache::DescribedNodes;
use crate::node_status_api::uptime_updater::HistoricalUptimeUpdater;
use crate::support::caching::cache::SharedCache;
use crate::support::cli;
use crate::support::cli::CliArgs;
use crate::support::config::Config;
@@ -34,8 +36,10 @@ mod ephemera;
mod epoch_operations;
pub(crate) mod network;
mod network_monitor;
pub(crate) mod node_describe_cache;
pub(crate) mod node_status_api;
pub(crate) mod nym_contract_cache;
pub(crate) mod nym_nodes;
pub(crate) mod support;
struct ShutdownHandles {
@@ -90,6 +94,19 @@ async fn start_nym_api_tasks(
let node_status_cache_state = rocket.state::<NodeStatusCache>().unwrap();
let circulating_supply_cache_state = rocket.state::<CirculatingSupplyCache>().unwrap();
let maybe_storage = rocket.state::<NymApiStorage>();
let described_nodes_state = rocket.state::<SharedCache<DescribedNodes>>().unwrap();
// start note describe cache refresher
// we should be doing the below, but can't due to our current startup structure
// let refresher = node_describe_cache::new_refresher(&config.topology_cacher);
// let cache = refresher.get_shared_cache();
node_describe_cache::new_refresher_with_initial_value(
&config.topology_cacher,
nym_contract_cache_state.clone(),
described_nodes_state.to_owned(),
)
.named("node-self-described-data-refresher")
.start(shutdown.subscribe_named("node-self-described-data-refresher"));
// start all the caches first
let nym_contract_cache_listener = nym_contract_cache::start_refresher(
@@ -98,6 +115,7 @@ async fn start_nym_api_tasks(
nyxd_client.clone(),
&shutdown,
);
node_status_api::start_cache_refresh(
&config.node_status_api,
nym_contract_cache_state,

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