Compare commits

..

17 Commits

Author SHA1 Message Date
mfahampshire f75cd90092 cargo lock after rebase on develop 2025-03-06 17:32:57 +01:00
mfahampshire 1164f66d7a change hardcoded file to tempdir 2025-03-06 17:32:22 +01:00
Jon Häggblad 8ddadf2aa5 Set DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE to 50 2025-03-06 17:32:22 +01:00
benedettadavico b0234d0140 bump api version 2025-03-06 17:32:22 +01:00
benedettadavico 6c2b917706 update changelog for patched-dorina 2025-03-06 17:32:22 +01:00
benedettadavico 0b8a5d5d86 bumping versions dorina patched 2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński 74623fa2b8 bugfix: make sure to correctly decode response content when putting it into error message (#5571) 2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński 454712b520 chore: additional logs when attempting to load ecash keys (#5567) 2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński 5fe5541d02 fix: gateway protocol negotation for v3/v4 2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński c25c4548b2 feature: v2 authentication request (#5537) (#5563)
* introduced v2 authentication request between clients and gateways

* client to send v2 auth when possible

* added persistence to last used authentication timestamp

* added clients identity to signed plaintext
2025-03-06 17:32:22 +01:00
Jon Häggblad d6e996e8e9 Tweak surb management to be more conservative (#5570)
To reduce the risk of the IPR DoS the client:

- Lower the timeout until the IPR will disconnect a client
- Reduce fewer surbs at a time. Large surb requests increases the
  latency until all fragments in the response have been delivered. The
  efficiency gains of having large surb requests dimishes quickly for
  large sizes as well
2025-03-06 17:32:22 +01:00
Jon Häggblad cf88364dda Deserialize v5 authenticator requests (#5568) 2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński 24d32a64e0 hotfix: ensure we bail on merkle leaves insertion upon missing data (#5565)
* hotfix: ensure we bail on merkle leaves insertion upon missing data

* Update Cargo.toml

---------

Co-authored-by: benedetta davico <46782255+benedettadavico@users.noreply.github.com>
2025-03-06 17:32:22 +01:00
Jędrzej Stuczyński 28d1d9d989 add full response body to error message upon decoding failure (#5566) 2025-03-06 17:32:22 +01:00
Jon Häggblad 74a4197b77 Create authenticator v5 request/response types (#5561)
* Create authenticator v5 request/response types

* Support v5 in the authenticator

* Fix tests

* Bump nym-node version
2025-03-06 17:32:22 +01:00
Jon Häggblad 3bc7301d94 Handle disconnect in IPR (#5547)
* Implement disconnect in the IPR

* Remove unused async
2025-03-06 17:32:22 +01:00
Jon Häggblad cc342458f9 Allow IPR reconnect to session (#5562) 2025-03-06 17:32:22 +01:00
132 changed files with 2054 additions and 5103 deletions
@@ -100,6 +100,7 @@ jobs:
cp target/release/nymvisor $OUTPUT_DIR
cp target/release/nym-node $OUTPUT_DIR
cp target/release/nym-cli $OUTPUT_DIR
cp target/release/explorer-api $OUTPUT_DIR
if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then
cp target/debian/*.deb $OUTPUT_DIR
fi
-2
View File
@@ -6,7 +6,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [2025.4-dorina-patched] (2025-03-06)
- use legacy crypto for constructing SURB headers ([#5579])
- bugfix: make sure to correctly decode response content when putting it into error message ([#5571])
- Tweak surb management to be more conservative ([#5570])
- Deserialize v5 authenticator requests ([#5568])
@@ -16,7 +15,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- feature: v2 authentication request (#5537) ([#5563])
- Create authenticator v5 request/response types ([#5561])
[#5579]: https://github.com/nymtech/nym/pull/5579
[#5571]: https://github.com/nymtech/nym/pull/5571
[#5570]: https://github.com/nymtech/nym/pull/5570
[#5568]: https://github.com/nymtech/nym/pull/5568
Generated
+61 -98
View File
@@ -816,9 +816,9 @@ dependencies = [
[[package]]
name = "bls12_381"
version = "0.8.0"
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd"
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect#22cd0a16b674af1629110a2dc8b6cf6c73ea4cd9"
dependencies = [
"digest 0.10.7",
"digest 0.9.0",
"ff",
"group",
"pairing",
@@ -902,9 +902,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.1"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
dependencies = [
"serde",
]
@@ -941,7 +941,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a"
dependencies = [
"camino",
"cargo-platform",
"semver 1.0.26",
"semver 1.0.25",
"serde",
"serde_json",
"thiserror 1.0.69",
@@ -955,7 +955,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
dependencies = [
"camino",
"cargo-platform",
"semver 1.0.26",
"semver 1.0.25",
"serde",
"serde_json",
"thiserror 1.0.69",
@@ -1587,7 +1587,7 @@ dependencies = [
"bitflags 2.8.0",
"crossterm_winapi",
"parking_lot",
"rustix 0.38.44",
"rustix",
"winapi",
]
@@ -1799,7 +1799,7 @@ dependencies = [
"cosmwasm-std",
"cw2",
"schemars",
"semver 1.0.26",
"semver 1.0.25",
"serde",
"thiserror 1.0.69",
]
@@ -1814,7 +1814,7 @@ dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"schemars",
"semver 1.0.26",
"semver 1.0.25",
"serde",
"thiserror 1.0.69",
]
@@ -2527,9 +2527,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "ff"
version = "0.13.1"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
dependencies = [
"rand_core 0.6.4",
"subtle 2.6.1",
@@ -4144,12 +4144,6 @@ version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
[[package]]
name = "lioness"
version = "0.1.2"
@@ -4786,7 +4780,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "nym-api"
version = "1.1.54"
version = "1.1.53"
dependencies = [
"anyhow",
"async-trait",
@@ -4847,10 +4841,10 @@ dependencies = [
"rand_chacha 0.3.1",
"reqwest 0.12.4",
"schemars",
"semver 1.0.26",
"semver 1.0.25",
"serde",
"serde_json",
"sha2 0.10.8",
"sha2 0.9.9",
"sqlx",
"tempfile",
"tendermint 0.40.1",
@@ -4896,7 +4890,6 @@ dependencies = [
"serde_json",
"sha2 0.10.8",
"tendermint 0.40.1",
"tendermint-rpc",
"thiserror 2.0.12",
"time",
"ts-rs",
@@ -5036,7 +5029,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.51"
version = "1.1.50"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5119,7 +5112,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.51"
version = "1.1.50"
dependencies = [
"bs58",
"clap",
@@ -5331,7 +5324,7 @@ dependencies = [
"bs58",
"cfg-if",
"criterion",
"digest 0.10.7",
"digest 0.9.0",
"ff",
"group",
"itertools 0.14.0",
@@ -5340,7 +5333,7 @@ dependencies = [
"rand 0.8.5",
"rayon",
"serde",
"sha2 0.10.8",
"sha2 0.9.9",
"subtle 2.6.1",
"thiserror 2.0.12",
"zeroize",
@@ -5613,7 +5606,7 @@ dependencies = [
"rand_core 0.6.4",
"serde",
"serde_derive",
"sha2 0.10.8",
"sha2 0.9.9",
"thiserror 2.0.12",
"zeroize",
]
@@ -6062,7 +6055,7 @@ dependencies = [
"nym-contracts-common",
"rand_chacha 0.3.1",
"schemars",
"semver 1.0.26",
"semver 1.0.25",
"serde",
"serde-json-wasm",
"serde_repr",
@@ -6163,7 +6156,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.52"
version = "1.1.51"
dependencies = [
"addr",
"anyhow",
@@ -6214,7 +6207,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.7.0"
version = "1.6.1"
dependencies = [
"anyhow",
"arc-swap",
@@ -6241,7 +6234,6 @@ dependencies = [
"nym-crypto",
"nym-gateway",
"nym-gateway-stats-storage",
"nym-http-api-client",
"nym-http-api-common",
"nym-ip-packet-router",
"nym-metrics",
@@ -6264,7 +6256,7 @@ dependencies = [
"nym-wireguard",
"nym-wireguard-types",
"rand 0.8.5",
"semver 1.0.26",
"semver 1.0.25",
"serde",
"serde_json",
"si-scale",
@@ -6356,7 +6348,6 @@ dependencies = [
"nym-contracts-common",
"nym-crypto",
"nym-explorer-client",
"nym-http-api-client",
"nym-network-defaults",
"nym-node-metrics",
"nym-node-requests",
@@ -6601,7 +6592,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.51"
version = "1.1.50"
dependencies = [
"bs58",
"clap",
@@ -7206,7 +7197,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.16"
version = "0.1.15"
dependencies = [
"anyhow",
"bytes",
@@ -7236,7 +7227,7 @@ dependencies = [
[[package]]
name = "nyx-chain-watcher"
version = "0.1.14"
version = "0.1.11"
dependencies = [
"anyhow",
"async-trait",
@@ -7246,12 +7237,15 @@ dependencies = [
"nym-bin-common",
"nym-config",
"nym-network-defaults",
"nym-node-requests",
"nym-task",
"nym-validator-client",
"nyxd-scraper",
"reqwest 0.12.4",
"rocket",
"schemars",
"serde",
"serde_json",
"sqlx",
"thiserror 2.0.12",
"time",
@@ -8416,9 +8410,9 @@ dependencies = [
[[package]]
name = "ring"
version = "0.17.13"
version = "0.17.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24"
dependencies = [
"cc",
"cfg-if",
@@ -8605,9 +8599,9 @@ dependencies = [
[[package]]
name = "rs_merkle"
version = "1.5.0"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb09b49230ba22e8c676e7b75dfe2887dea8121f18b530ae0ba519ce442d2b21"
checksum = "3b241d2e59b74ef9e98d94c78c47623d04c8392abaf82014dfd372a16041128f"
dependencies = [
"sha2 0.10.8",
]
@@ -8709,7 +8703,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver 1.0.26",
"semver 1.0.25",
]
[[package]]
@@ -8721,20 +8715,7 @@ dependencies = [
"bitflags 2.8.0",
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657"
dependencies = [
"bitflags 2.8.0",
"errno",
"libc",
"linux-raw-sys 0.9.2",
"linux-raw-sys",
"windows-sys 0.59.0",
]
@@ -9010,9 +8991,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.26"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
dependencies = [
"serde",
]
@@ -9025,9 +9006,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.219"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
"serde_derive",
]
@@ -9065,18 +9046,18 @@ dependencies = [
[[package]]
name = "serde_bytes"
version = "0.11.17"
version = "0.11.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96"
checksum = "364fec0df39c49a083c9a8a18a23a6bcfd9af130fe9fe321d18520a0d113e09e"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
"proc-macro2",
"quote",
@@ -9179,9 +9160,9 @@ dependencies = [
[[package]]
name = "serde_repr"
version = "0.1.20"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
@@ -9452,9 +9433,9 @@ dependencies = [
[[package]]
name = "sphinx-packet"
version = "0.3.2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c23047e0cf36ff6904603f499fd13153425cdf5ba47bfbaedbc999da0bd92f4e"
checksum = "535f2c430778bf59c22249fcc1ed6d384129eb2f0f694706015d636c688f9ac6"
dependencies = [
"aes",
"arrayref",
@@ -9973,15 +9954,15 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.18.0"
version = "3.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
dependencies = [
"cfg-if",
"fastrand 2.3.0",
"getrandom 0.3.1",
"once_cell",
"rustix 1.0.1",
"rustix",
"windows-sys 0.59.0",
]
@@ -10109,7 +10090,7 @@ dependencies = [
"pin-project",
"rand 0.8.5",
"reqwest 0.11.27",
"semver 1.0.26",
"semver 1.0.25",
"serde",
"serde_bytes",
"serde_json",
@@ -10269,9 +10250,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.39"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
@@ -10287,15 +10268,15 @@ dependencies = [
[[package]]
name = "time-core"
version = "0.1.3"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.20"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
@@ -10338,9 +10319,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.44.0"
version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
dependencies = [
"backtrace",
"bytes",
@@ -11265,24 +11246,6 @@ dependencies = [
"serde",
]
[[package]]
name = "validator-status-check"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"comfy-table",
"nym-bin-common",
"nym-network-defaults",
"nym-validator-client",
"serde",
"serde_json",
"strum 0.26.3",
"time",
"tokio",
"tracing",
]
[[package]]
name = "valuable"
version = "0.1.1"
@@ -12083,8 +12046,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909"
dependencies = [
"libc",
"linux-raw-sys 0.4.15",
"rustix 0.38.44",
"linux-raw-sys",
"rustix",
]
[[package]]
+13 -13
View File
@@ -137,7 +137,7 @@ members = [
"tools/internal/testnet-manager",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/internal/testnet-manager/dkg-bypass-contract", "tools/internal/validator-status-check",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/nym-cli",
"tools/nym-id-cli",
"tools/nym-nr-query",
@@ -209,7 +209,7 @@ blake3 = "1.6.1"
bloomfilter = "1.0.14"
bs58 = "0.5.1"
bytecodec = "0.4.15"
bytes = "1.10.1"
bytes = "1.7.2"
cargo_metadata = "0.18.1"
celes = "2.5.0"
cfg-if = "1.0.0"
@@ -308,12 +308,12 @@ reqwest = { version = "0.12.4", default-features = false }
rocket = "0.5.0"
rocket_cors = "0.6.0"
rocket_okapi = "0.8.0"
rs_merkle = "1.5.0"
rs_merkle = "1.4.2"
safer-ffi = "0.1.13"
schemars = "0.8.22"
semver = "1.0.26"
serde = "1.0.219"
serde_bytes = "0.11.17"
semver = "1.0.25"
serde = "1.0.217"
serde_bytes = "0.11.16"
serde_derive = "1.0"
serde_json = "1.0.140"
serde_json_path = "0.7.2"
@@ -322,7 +322,7 @@ serde_with = "3.9.0"
serde_yaml = "0.9.25"
sha2 = "0.10.8"
si-scale = "0.2.3"
sphinx-packet = "=0.3.2"
sphinx-packet = "0.3.1"
sqlx = "0.7.4"
strum = "0.26"
strum_macros = "0.26"
@@ -331,10 +331,10 @@ syn = "1"
sysinfo = "0.33.0"
tap = "1.0.1"
tar = "0.4.44"
tempfile = "3.18"
tempfile = "3.15"
thiserror = "2.0"
time = "0.3.39"
tokio = "1.44"
time = "0.3.37"
tokio = "1.43"
tokio-postgres = "0.7"
tokio-stream = "0.1.17"
tokio-test = "0.4.4"
@@ -370,9 +370,9 @@ prometheus = { version = "0.13.0" }
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
# plus to make our live easier we need serde support from https://github.com/zkcrypto/bls12_381/pull/125
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect-updated" }
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect" }
group = { version = "0.13.0", default-features = false }
ff = { version = "0.13.1", default-features = false }
ff = { version = "0.13.0", default-features = false }
subtle = "2.5.0"
# cosmwasm-related
@@ -447,4 +447,4 @@ dbg_macro = "deny"
exit = "deny"
panic = "deny"
unimplemented = "deny"
unreachable = "deny"
unreachable = "deny"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.51"
version = "1.1.50"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.51"
version = "1.1.50"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -658,9 +658,6 @@ pub struct ReplySurbs {
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
/// the default value is used.
pub surb_mix_hops: Option<u8>,
/// Specifies if we should reset all the sender tags on startup
pub fresh_sender_tags: bool,
}
impl Default for ReplySurbs {
@@ -678,7 +675,6 @@ impl Default for ReplySurbs {
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
surb_mix_hops: None,
fresh_sender_tags: false,
}
}
}
@@ -139,8 +139,6 @@ where
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
#[cfg(unix)]
connection_fd_callback: None,
};
let init_details =
@@ -187,8 +187,6 @@ where
let gateway_setup = GatewaySetup::New {
specification: selection_spec,
available_gateways,
#[cfg(unix)]
connection_fd_callback: None,
};
let init_details =
@@ -88,7 +88,7 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
let db_path = db_path.as_ref();
if db_path.exists() {
info!("loading existing surb database");
match fs_backend::Backend::try_load(db_path, surb_config.fresh_sender_tags).await {
match fs_backend::Backend::try_load(db_path).await {
Ok(backend) => Ok(backend),
Err(err) => {
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
+2 -10
View File
@@ -11,8 +11,6 @@ use nym_topology::node::RoutingNode;
use nym_validator_client::client::IdentityKeyRef;
use nym_validator_client::UserAgent;
use rand::{seq::SliceRandom, Rng};
#[cfg(unix)]
use std::os::fd::RawFd;
use std::{sync::Arc, time::Duration};
use tungstenite::Message;
use url::Url;
@@ -315,15 +313,9 @@ pub(super) async fn register_with_gateway(
gateway_id: identity::PublicKey,
gateway_listener: Url,
our_identity: Arc<identity::KeyPair>,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
) -> Result<RegistrationResult, ClientCoreError> {
let mut gateway_client = GatewayClient::new_init(
gateway_listener,
gateway_id,
our_identity.clone(),
#[cfg(unix)]
connection_fd_callback,
);
let mut gateway_client =
GatewayClient::new_init(gateway_listener, gateway_id, our_identity.clone());
gateway_client.establish_connection().await.map_err(|err| {
log::warn!("Failed to establish connection with gateway!");
+4 -22
View File
@@ -23,8 +23,6 @@ use nym_topology::node::RoutingNode;
use rand::rngs::OsRng;
use rand::{CryptoRng, RngCore};
use serde::Serialize;
#[cfg(unix)]
use std::{os::fd::RawFd, sync::Arc};
pub mod helpers;
pub mod types;
@@ -55,7 +53,6 @@ async fn setup_new_gateway<K, D>(
details_store: &D,
selection_specification: GatewaySelectionSpecification,
available_gateways: Vec<RoutingNode>,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
@@ -111,14 +108,9 @@ where
// if we're using a 'normal' gateway setup, do register
let our_identity = client_keys.identity_keypair();
let registration = helpers::register_with_gateway(
gateway_id,
gateway_listener.clone(),
our_identity,
#[cfg(unix)]
connection_fd_callback,
)
.await?;
let registration =
helpers::register_with_gateway(gateway_id, gateway_listener.clone(), our_identity)
.await?;
(
GatewayDetails::new_remote(
gateway_id,
@@ -211,19 +203,9 @@ where
GatewaySetup::New {
specification,
available_gateways,
#[cfg(unix)]
connection_fd_callback,
} => {
log::debug!("GatewaySetup::New with spec: {specification:?}");
setup_new_gateway(
key_store,
details_store,
specification,
available_gateways,
#[cfg(unix)]
connection_fd_callback,
)
.await
setup_new_gateway(key_store, details_store, specification, available_gateways).await
}
GatewaySetup::ReuseConnection {
authenticated_ephemeral_client,
-10
View File
@@ -18,8 +18,6 @@ use nym_validator_client::client::IdentityKey;
use nym_validator_client::nyxd::AccountId;
use serde::Serialize;
use std::fmt::{Debug, Display};
#[cfg(unix)]
use std::os::fd::RawFd;
use std::sync::Arc;
use time::OffsetDateTime;
use url::Url;
@@ -210,10 +208,6 @@ pub enum GatewaySetup {
// TODO: seems to be a bit inefficient to pass them by value
available_gateways: Vec<RoutingNode>,
/// Callback useful for allowing initial connection to gateway
#[cfg(unix)]
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
},
ReuseConnection {
@@ -237,8 +231,6 @@ impl Debug for GatewaySetup {
GatewaySetup::New {
specification,
available_gateways,
#[cfg(unix)]
connection_fd_callback: _,
} => f
.debug_struct("GatewaySetup::New")
.field("specification", specification)
@@ -278,8 +270,6 @@ impl GatewaySetup {
additional_data: None,
},
available_gateways: vec![],
#[cfg(unix)]
connection_fd_callback: None,
}
}
@@ -10,7 +10,7 @@ use crate::{
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
};
use async_trait::async_trait;
use log::{debug, error, info, warn};
use log::{error, info, warn};
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use std::fs;
use std::path::{Path, PathBuf};
@@ -52,10 +52,7 @@ impl Backend {
Ok(backend)
}
pub async fn try_load<P: AsRef<Path>>(
database_path: P,
fresh_sender_tags: bool,
) -> Result<Self, StorageError> {
pub async fn try_load<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
let owned_path: PathBuf = database_path.as_ref().into();
if owned_path.file_name().is_none() {
return Err(StorageError::DatabasePathWithoutFilename {
@@ -121,9 +118,6 @@ impl Backend {
if days > 2 {
info!("it's been over {days} days and {hours} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.");
manager.delete_all_tags().await?;
} else if fresh_sender_tags {
debug!("starting with fresh sender tags");
manager.delete_all_tags().await?;
}
Ok(Backend {
@@ -1065,7 +1065,6 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
gateway_listener: Url,
gateway_identity: identity::PublicKey,
local_identity: Arc<identity::KeyPair>,
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
) -> Self {
log::trace!("Initialising gateway client");
use futures::channel::mpsc;
@@ -1091,7 +1090,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
stats_reporter: ClientStatsSender::new(None, task_client.clone()),
negotiated_protocol: None,
#[cfg(unix)]
connection_fd_callback,
connection_fd_callback: None,
task_client,
}
}
@@ -38,8 +38,7 @@ pub(crate) async fn connect_async(
// Do a DNS lookup for the domain using our custom DNS resolver
resolver
.resolve_str(domain)
.await
.inspect_err(|err| tracing::error!("Resolve error {err}"))?
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
.collect()
@@ -50,27 +49,20 @@ pub(crate) async fn connect_async(
address: endpoint.to_owned(),
});
for sock_addr in sock_addrs {
tracing::info!("Trying with {sock_addr}");
let socket = if sock_addr.is_ipv4() {
TcpSocket::new_v4()
} else {
TcpSocket::new_v6()
}
.map_err(|err| {
tracing::error!("Couldn't create the socket");
GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
source: err.into(),
}
.map_err(|err| GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
source: err.into(),
})?;
tracing::info!("Preparing to call callback");
#[cfg(unix)]
if let Some(callback) = connection_fd_callback.as_ref() {
tracing::info!("Calling callback");
callback.as_ref()(socket.as_raw_fd());
}
tracing::info!("Preparing to connect");
match socket.connect(sock_addr).await {
Ok(s) => {
@@ -83,12 +83,6 @@ impl TryFrom<ContractVKShare> for EcashApiClient {
let url_address = Url::parse(&share.announce_address)?;
// The NymApiClient constructed here uses the default (hickory DoT/DoH) resolver because
// this EcashApiClient is used by both client and non-client applications.
//
// In non-client applications this resolver can cause warning logs about H2 connection
// failure. This indicates that the long lived https connection was closed by the remote
// peer and the resolver will have to reconnect. It should not impact actual functionality
Ok(EcashApiClient {
api_client: NymApiClient::new(url_address),
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
@@ -12,9 +12,8 @@ use nym_api_requests::ecash::models::{
};
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_api_requests::models::{
AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainStatusResponse,
LegacyDescribedMixNode, NodePerformanceResponse, NodeRefreshBody, NymNodeDescription,
PerformanceHistoryResponse, RewardedSetResponse,
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
};
use nym_api_requests::nym_nodes::{
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
@@ -70,19 +69,6 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn build_information(&self) -> Result<BinaryBuildInformationOwned, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
routes::API_STATUS_ROUTES,
routes::BUILD_INFORMATION,
],
NO_PARAMS,
)
.await
}
#[deprecated]
#[instrument(level = "debug", skip(self))]
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
@@ -1057,15 +1043,6 @@ pub trait NymApiClientExt: ApiClient {
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn get_chain_status(&self) -> Result<ChainStatusResponse, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::NETWORK, routes::CHAIN_STATUS],
NO_PARAMS,
)
.await
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -49,8 +49,6 @@ pub mod nym_nodes {
pub const STATUS_ROUTES: &str = "status";
pub const API_STATUS_ROUTES: &str = "api-status";
pub const HEALTH: &str = "health";
pub const BUILD_INFORMATION: &str = "build-information";
pub const MIXNODE: &str = "mixnode";
pub const GATEWAY: &str = "gateway";
pub const NYM_NODES: &str = "nym-nodes";
@@ -72,5 +70,4 @@ pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
pub const SERVICE_PROVIDERS: &str = "services";
pub const DETAILS: &str = "details";
pub const CHAIN_STATUS: &str = "chain-status";
pub const NETWORK: &str = "network";
@@ -62,7 +62,6 @@ pub use cw3;
pub use cw4;
pub use cw_controllers;
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
pub use prost::Name;
pub use tendermint_rpc::endpoint::block::Response as BlockResponse;
pub use tendermint_rpc::{
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
+1 -1
View File
@@ -21,7 +21,7 @@ lazy_static = { workspace = true }
rand = { workspace = true }
rand_chacha = { workspace = true }
rand_core = { workspace = true }
sha2 = { workspace = true }
sha2 = "0.9"
serde = { workspace = true }
serde_derive = { workspace = true }
thiserror = { workspace = true }
+2 -96
View File
@@ -54,12 +54,12 @@ pub(crate) fn hash_to_scalar<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> Scalar {
pub(crate) fn hash_to_scalars<M: AsRef<[u8]>>(msg: M, domain: &[u8], n: usize) -> Vec<Scalar> {
let mut output = vec![Scalar::zero(); n];
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>, _>([msg], domain, &mut output);
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>>(msg.as_ref(), domain, &mut output);
output
}
pub(crate) fn hash_g2<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> G2Projective {
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve([msg], domain)
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve(msg, domain)
}
pub(crate) fn combine_scalar_chunks(chunks: &[Scalar]) -> Scalar {
@@ -112,97 +112,3 @@ pub(crate) fn deserialize_g2(b: &[u8]) -> Option<G2Projective> {
G2Projective::from_bytes(&encoding).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use bls12_381::G2Affine;
#[test]
fn test_hash_to_scalar() {
let msg1 = "foo";
let expected1 = Scalar::from_bytes(&[
253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6,
79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30,
])
.unwrap();
let msg2 = "bar";
let expected2 = Scalar::from_bytes(&[
48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72,
201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26,
])
.unwrap();
let msg3 = [
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
60, 42, 92, 128, 131, 161, 43,
];
let expected3 = Scalar::from_bytes(&[
128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218,
171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4,
])
.unwrap();
assert_eq!(
hash_to_scalar(msg1, b"NYMECASH-V01-CS02-with-expander-SHA256"),
expected1
);
assert_eq!(
hash_to_scalar(msg2, b"NYMECASH-V01-CS02-with-expander-SHA256"),
expected2
);
assert_eq!(
hash_to_scalar(msg3, b"NYMECASH-V01-CS02-with-expander-SHA256"),
expected3
);
}
#[test]
fn test_hash_g2() {
let msg1 = "foo";
let expected1 = G2Affine::from_compressed(&[
175, 187, 62, 7, 29, 17, 42, 93, 28, 93, 234, 253, 101, 166, 158, 187, 153, 82, 93, 18,
11, 233, 36, 107, 51, 117, 30, 127, 32, 254, 210, 77, 133, 12, 253, 255, 84, 128, 36,
214, 234, 103, 50, 21, 26, 78, 112, 49, 20, 69, 19, 109, 7, 78, 33, 227, 196, 180, 168,
219, 73, 251, 192, 221, 41, 138, 160, 131, 191, 186, 156, 117, 179, 179, 191, 235, 171,
26, 219, 148, 170, 179, 11, 38, 137, 14, 95, 115, 171, 186, 163, 82, 158, 6, 239, 88,
])
.unwrap()
.into();
let msg2 = "bar";
let expected2 = G2Affine::from_compressed(&[
183, 25, 90, 187, 34, 184, 30, 182, 215, 242, 158, 83, 116, 34, 210, 96, 188, 79, 83,
255, 100, 122, 90, 188, 196, 93, 164, 253, 20, 106, 205, 33, 48, 140, 60, 149, 66, 246,
121, 244, 146, 66, 170, 60, 113, 95, 102, 237, 25, 231, 8, 42, 121, 124, 180, 140, 34,
104, 173, 251, 89, 189, 28, 196, 49, 66, 101, 38, 68, 44, 40, 235, 21, 35, 204, 123,
218, 238, 216, 92, 134, 217, 212, 246, 176, 77, 187, 0, 245, 134, 132, 73, 31, 44, 137,
197,
])
.unwrap()
.into();
let msg3 = [
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
60, 42, 92, 128, 131, 161, 43,
];
let expected3 = G2Affine::from_compressed(&[
151, 185, 8, 123, 223, 150, 192, 192, 115, 10, 3, 129, 49, 179, 31, 108, 0, 17, 46,
231, 184, 164, 247, 228, 22, 142, 87, 70, 120, 111, 154, 15, 245, 110, 32, 84, 53, 117,
239, 93, 89, 119, 32, 17, 39, 250, 198, 137, 6, 95, 137, 202, 54, 244, 238, 190, 11,
217, 237, 95, 72, 59, 140, 56, 3, 42, 61, 195, 192, 101, 46, 204, 207, 75, 70, 176,
207, 48, 24, 195, 248, 234, 178, 168, 54, 109, 19, 189, 51, 52, 120, 69, 248, 226, 102,
91,
])
.unwrap()
.into();
assert_eq!(hash_g2(msg1, b"DUMMY_TEST_DOMAIN"), expected1);
assert_eq!(hash_g2(msg2, b"DUMMY_TEST_DOMAIN"), expected2);
assert_eq!(hash_g2(msg3, b"DUMMY_TEST_DOMAIN"), expected3);
}
}
+6 -7
View File
@@ -10,7 +10,6 @@ use nym_sphinx::params::packet_sizes::PacketSize;
use serde::{Deserialize, Serialize};
use std::string::FromUtf8Error;
use thiserror::Error;
use time::OffsetDateTime;
// specific errors (that should not be nested!!) for clients to match on
#[derive(Debug, Copy, Clone, Error, Serialize, Deserialize)]
@@ -113,15 +112,15 @@ pub enum AuthenticationFailure {
#[error("failed to verify request signature")]
InvalidSignature(#[from] SignatureError),
#[error("provided request timestamp is in the future")]
RequestTimestampInFuture,
#[error("the client is not registered")]
NotRegistered,
#[error("the provided request timestamp is excessively skewed. got {received} whilst the server time is {server}")]
ExcessiveTimestampSkew {
received: OffsetDateTime,
server: OffsetDateTime,
},
#[error("the provided request is too stale to process")]
StaleRequest,
#[error("the provided request timestamp is smaller or equal to one previously used")]
#[error("the provided request timestamp is smaller or equal to a one previously used")]
RequestReuse,
}
@@ -38,22 +38,13 @@ impl AuthenticateRequest {
})
}
pub fn verify_timestamp(
&self,
max_request_timestamp_skew: Duration,
) -> Result<(), AuthenticationFailure> {
pub fn verify_timestamp(&self, max_request_age: Duration) -> Result<(), AuthenticationFailure> {
let now = OffsetDateTime::now_utc();
if self.content.request_timestamp() < now - max_request_timestamp_skew {
return Err(AuthenticationFailure::ExcessiveTimestampSkew {
received: self.content.request_timestamp(),
server: now,
});
if self.content.request_timestamp() + max_request_age < now {
return Err(AuthenticationFailure::StaleRequest);
}
if self.content.request_timestamp() - max_request_timestamp_skew > now {
return Err(AuthenticationFailure::ExcessiveTimestampSkew {
received: self.content.request_timestamp(),
server: now,
});
if self.content.request_timestamp() > now {
return Err(AuthenticationFailure::RequestTimestampInFuture);
}
Ok(())
}
-23
View File
@@ -1,6 +1,3 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
//! DNS resolver configuration for internal lookups.
//!
//! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints
@@ -12,19 +9,6 @@
//!
//! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the
//! `hickory-resolver` crate
//!
//!
//! Note: The hickory DoH resolver can cause warning logs about H2 connection failure. This
//! indicates that the long lived https connection was closed by the remote peer and the resolver
//! will have to reconnect. It should not impact actual functionality.
//!
//! code ref: https://github.com/hickory-dns/hickory-dns/blob/06a8b1ce9bd9322d8e6accf857d30257e1274427/crates/proto/src/h2/h2_client_stream.rs#L534
//!
//! example log:
//!
//! ```txt
//! WARN /home/ubuntu/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hickory-proto-0.24.3/src/h2/h2_client_stream.rs:493: h2 connection failed: unexpected end of file
//! ```
#![deny(missing_docs)]
use crate::ClientBuilder;
@@ -49,13 +33,6 @@ impl ClientBuilder {
/// Override the DNS resolver implementation used by the underlying http client.
pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> Self {
self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver);
self.use_secure_dns = false;
self
}
/// Override the DNS resolver implementation used by the underlying http client.
pub fn no_hickory_dns(mut self) -> Self {
self.use_secure_dns = false;
self
}
}
+20 -42
View File
@@ -228,8 +228,6 @@ pub struct ClientBuilder {
timeout: Option<Duration>,
custom_user_agent: bool,
reqwest_client_builder: reqwest::ClientBuilder,
#[allow(dead_code)] // not dead code, just unused in wasm
use_secure_dns: bool,
}
impl ClientBuilder {
@@ -241,46 +239,37 @@ impl ClientBuilder {
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();
// a naive check: if the provided URL does not start with http(s), add that scheme
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(alt)
} else {
Ok(Self::new_with_url(url.into_url()?))
}
}
#[cfg(target_arch = "wasm32")]
let reqwest_client_builder = reqwest::ClientBuilder::new();
/// Constructs a new http `ClientBuilder` from a valid url.
pub fn new_with_url(url: Url) -> Self {
if !url.scheme().starts_with("http") {
warn!("the provided url ('{url}') does not use HTTP / HTTPS scheme");
}
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder = {
let r = reqwest::ClientBuilder::new()
.dns_resolver(Arc::new(HickoryDnsResolver::default()));
#[cfg(target_arch = "wasm32")]
let reqwest_client_builder = reqwest::ClientBuilder::new();
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
//
// I am going to leave it here anyways so that gzip decompression is attempted even if
// that feature is removed.
r.gzip(true)
};
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder = {
let r = reqwest::ClientBuilder::new();
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
//
// I am going to leave it here anyways so that gzip decompression is attempted even if
// that feature is removed.
r.gzip(true)
};
ClientBuilder {
url,
timeout: None,
custom_user_agent: false,
reqwest_client_builder,
use_secure_dns: true,
Ok(ClientBuilder {
url: url.into_url()?,
timeout: None,
custom_user_agent: false,
reqwest_client_builder,
})
}
}
@@ -336,18 +325,10 @@ impl ClientBuilder {
let mut builder = self
.reqwest_client_builder
.timeout(self.timeout.unwrap_or(DEFAULT_TIMEOUT));
// if no custom user agent was set, use a default
if !self.custom_user_agent {
builder =
builder.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION")))
}
// unless explicitly disabled use the DoT/DoH enabled resolver
if self.use_secure_dns {
builder = builder.dns_resolver(Arc::new(HickoryDnsResolver::default()));
}
builder.build()?
};
@@ -374,9 +355,6 @@ pub struct Client {
impl Client {
/// Create a new http `Client`
// no timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
//
// In order to prevent interference in API requests at the DNS phase we default to a resolver
// that uses DoT and DoH.
pub fn new(base_url: Url, timeout: Option<Duration>) -> Self {
Self::new_url::<_, String>(base_url, timeout).expect(
"we provided valid url and we were unwrapping previous construction errors anyway",
+2 -2
View File
@@ -15,10 +15,10 @@ bls12_381 = { workspace = true, features = ["alloc", "pairings", "experimental",
bincode.workspace = true
cfg-if.workspace = true
itertools = { workspace = true }
digest = { workspace = true }
digest = "0.9"
rand = { workspace = true }
thiserror = { workspace = true }
sha2 = { workspace = true }
sha2 = "0.9"
bs58 = { workspace = true }
serde = { workspace = true, features = ["derive"] }
rayon = { workspace = true, optional = true }
+6 -73
View File
@@ -113,13 +113,17 @@ const G1_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-BLS12381G1_XMD:SHA-256_SS
const SCALAR_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-expander-SHA256";
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve([msg], G1_HASH_DOMAIN)
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
}
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
let mut output = vec![Scalar::zero()];
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>, _>([msg], SCALAR_HASH_DOMAIN, &mut output);
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
msg.as_ref(),
SCALAR_HASH_DOMAIN,
&mut output,
);
output[0]
}
@@ -397,75 +401,4 @@ mod tests {
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
}
#[test]
fn test_hash_to_scalar() {
let msg1 = "foo";
let expected1 = Scalar::from_bytes(&[
253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6,
79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30,
])
.unwrap();
let msg2 = "bar";
let expected2 = Scalar::from_bytes(&[
48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72,
201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26,
])
.unwrap();
let msg3 = [
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
60, 42, 92, 128, 131, 161, 43,
];
let expected3 = Scalar::from_bytes(&[
128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218,
171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4,
])
.unwrap();
assert_eq!(hash_to_scalar(msg1), expected1);
assert_eq!(hash_to_scalar(msg2), expected2);
assert_eq!(hash_to_scalar(msg3), expected3);
}
#[test]
fn test_hash_to_g1() {
let msg1 = "foo";
let expected1 = G1Affine::from_compressed(&[
161, 109, 186, 0, 192, 221, 83, 87, 71, 31, 120, 201, 185, 35, 62, 239, 46, 120, 117,
150, 191, 227, 128, 161, 78, 201, 207, 167, 86, 181, 229, 115, 2, 6, 178, 16, 251, 118,
219, 115, 184, 96, 2, 10, 31, 63, 150, 70,
])
.unwrap()
.into();
let msg2 = "bar";
let expected2 = G1Affine::from_compressed(&[
135, 102, 204, 42, 221, 49, 209, 192, 250, 87, 59, 255, 197, 93, 37, 113, 38, 2, 154,
233, 68, 234, 206, 182, 121, 212, 166, 210, 74, 155, 190, 33, 203, 237, 176, 60, 249,
241, 53, 170, 18, 168, 49, 35, 1, 151, 205, 174,
])
.unwrap()
.into();
let msg3 = [
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
60, 42, 92, 128, 131, 161, 43,
];
let expected3 = G1Affine::from_compressed(&[
184, 200, 211, 115, 47, 45, 39, 185, 105, 9, 222, 247, 132, 241, 121, 130, 238, 224,
155, 109, 105, 201, 137, 154, 132, 149, 214, 233, 136, 69, 77, 132, 174, 30, 46, 123,
20, 92, 219, 18, 45, 29, 208, 127, 158, 145, 130, 41,
])
.unwrap()
.into();
assert_eq!(hash_g1(msg1), expected1);
assert_eq!(hash_g1(msg2), expected2);
assert_eq!(hash_g1(msg3), expected3);
}
}
+5
View File
@@ -30,6 +30,7 @@ pub use sphinx_packet::{
route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier},
surb::{SURBMaterial, SURB},
version::Version,
version::UPDATED_LEGACY_VERSION,
Error as SphinxError, ProcessedPacket, ProcessedPacketData,
};
@@ -90,8 +91,12 @@ impl NymPacket {
destination: &Destination,
delays: &[Delay],
) -> Result<NymPacket, NymPacketError> {
// FIXME:
// for now explicitly use the legacy version until sufficient number of nodes
// understand both variants
Ok(NymPacket::Sphinx(
SphinxPacketBuilder::new()
.with_version(UPDATED_LEGACY_VERSION)
.with_payload_size(size)
.build_packet(message, route, destination, delays)?,
))
@@ -182,11 +182,9 @@ impl BlockProcessor {
// the ones concerned with individual messages
for (index, msg) in block_tx.tx.body.messages.iter().enumerate() {
for msg_module in &mut self.msg_modules {
if msg.type_url == msg_module.type_url() {
msg_module
.handle_msg(index, msg, &block_tx, &mut tx)
.await?
}
msg_module
.handle_msg(index, msg, &block_tx, &mut tx)
.await?
}
}
}
-9
View File
@@ -83,15 +83,6 @@ pub enum ScraperError {
source: cosmrs::ErrorReport,
},
#[error("could not parse msg in tx {hash} at index {index} into {type_url}: {source}")]
MsgParseFailure {
hash: Hash,
index: usize,
type_url: String,
#[source]
source: cosmrs::ErrorReport,
},
#[error("received an invalid chain subscription event of kind {kind} while we were waiting for new block data (query: '{query}')")]
InvalidSubscriptionEvent { query: String, kind: String },
@@ -9,8 +9,6 @@ use cosmrs::Any;
#[async_trait]
pub trait MsgModule {
fn type_url(&self) -> String;
async fn handle_msg(
&mut self,
index: usize,
-4
View File
@@ -103,8 +103,4 @@ impl LaneQueueLengthsInner {
{
self.map.entry(*lane).and_modify(f);
}
pub fn total(&self) -> usize {
self.map.values().sum()
}
}
@@ -494,9 +494,6 @@ pub struct ReplySurbsWasm {
/// Defines how many mix nodes the reply surb should go through.
/// If not set, the default value is going to be used.
pub surb_mix_hops: Option<u8>,
/// Specifies if we should reset all the sender tags on startup
pub fresh_sender_tags: bool,
}
impl Default for ReplySurbsWasm {
@@ -528,7 +525,6 @@ impl From<ReplySurbsWasm> for ConfigReplySurbs {
reply_surbs.maximum_reply_key_age_ms as u64,
),
surb_mix_hops: reply_surbs.surb_mix_hops,
fresh_sender_tags: reply_surbs.fresh_sender_tags,
}
}
}
@@ -552,7 +548,6 @@ impl From<ConfigReplySurbs> for ReplySurbsWasm {
maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u32,
maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u32,
surb_mix_hops: reply_surbs.surb_mix_hops,
fresh_sender_tags: reply_surbs.fresh_sender_tags,
}
}
}
@@ -378,9 +378,6 @@ pub struct ReplySurbsWasmOverride {
#[tsify(optional)]
pub surb_mix_hops: Option<u8>,
/// Specifies if we should reset all the sender tags on startup
pub fresh_sender_tags: bool,
}
impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
@@ -419,7 +416,6 @@ impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
.maximum_reply_key_age_ms
.unwrap_or(def.maximum_reply_key_age_ms),
surb_mix_hops: value.surb_mix_hops,
fresh_sender_tags: value.fresh_sender_tags,
}
}
}
+12 -50
View File
@@ -1185,7 +1185,6 @@ name = "nym-pemstore"
version = "0.3.0"
dependencies = [
"pem",
"tracing",
]
[[package]]
@@ -1252,12 +1251,6 @@ dependencies = [
"regex",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pkcs8"
version = "0.9.0"
@@ -1529,18 +1522,18 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.26"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.219"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
@@ -1565,9 +1558,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.219"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
@@ -1784,9 +1777,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.39"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
@@ -1801,15 +1794,15 @@ dependencies = [
[[package]]
name = "time-core"
version = "0.1.3"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.20"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
@@ -1847,37 +1840,6 @@ dependencies = [
"winnow",
]
[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "tracing-core"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
]
[[package]]
name = "typenum"
version = "1.18.0"
+1 -3
View File
@@ -7,6 +7,4 @@ package-lock.json
# local env files
.env*.local
.env
scratch.md
.env
@@ -1,10 +0,0 @@
- Run synchronization against [pool.ntp.org](https://www.ntppool.org/en/):
```bash
ntpdate -q pool.ntp.org
```
- Enable `ntp` service, start and review the status:
```bash
systemctl enable --now ntp
service ntp start
service ntp status
```
@@ -1 +1 @@
808_623_916
807_251_217
@@ -1 +1 @@
1_028_488
1_025_628
@@ -1 +1 @@
404_311_958
403_625_608
@@ -1,7 +1,7 @@
| **Item** | **Description** | **Amount in NYM** |
|:-------------------|:------------------------------------------------------|--------------------:|
| Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 |
| Mixmining Reserve | Tokens releasing for operators rewards | 191_376_083 |
| Mixmining Reserve | Tokens releasing for operators rewards | 192_748_782 |
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
| Circulating Supply | Amount of unlocked tokens | 808_623_916 |
| Stake Saturation | Optimal size of node self-bond + delegation | 1_028_488 |
| Circulating Supply | Amount of unlocked tokens | 807_251_217 |
| Stake Saturation | Optimal size of node self-bond + delegation | 1_025_628 |
@@ -1 +1 @@
Tuesday, March 11th 2025, 11:04:18 UTC
Wednesday, February 26th 2025, 16:02:47 UTC
@@ -1,5 +0,0 @@
{
"mainnet":"Mainnet Endpoints",
"sandbox":"Sandbox Endpoints"
}
@@ -1,5 +0,0 @@
{
"mainnet":"Mainnet Endpoints",
"sandbox":"Sandbox Endpoints"
}
+5 -2
View File
@@ -1,8 +1,11 @@
import { Callout } from 'nextra/components'
# Node Status API
The Node Status API serves information about individual `nym-nodes` in the Mixnet, such as which role they are operating in, statistics about them, services such as Network Requesters, as well as summaries of the state of the Mixnet.
The Node Status API contains information about the network, its topology, and the routing scores of all nodes within it. It offers broadly similar information to the experimental [Habourmaster frontend](https://harbourmaster.nymtech.net/) but is stable where the Harbourmaster is subject to sudden changes as we modify and experiment with how we scrape and present data about the Mixnet infrastructure.
<Callout type="info">
We recommend that developers building applications such as explorers or analytics interfaces about the Mixnet run their own instance of the API, in order to promote a robust network of downstream services, and spread the load of API calls amongst as many endpoints as possible.
People building applications or dashboards which requires information about nodes, their uptime, and their delegations should use this instead of Habourmaster.
</Callout>
The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/nym-node-status-api). In the future we will encourage developers to run their own instance of this API in order to distribute endpoints and query load.
@@ -1,5 +0,0 @@
{
"ns-api-run-deploy":"Run Instance",
"mainnet":"Mainnet Endpoints",
"sandbox":"Sandbox Endpoints"
}
@@ -1,11 +1,9 @@
import { RedocStandalone } from 'redoc';
import { Callout } from 'nextra/components'
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://mainnet-node-status-api.nymtech.cc/swagger/](https://mainnet-node-status-api.nymtech.cc/swagger/).
<br /><br />
<RedocStandalone
specUrl="https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json"
options={{
@@ -1,336 +0,0 @@
import { Callout } from 'nextra/components'
import { AccordionTemplate } from 'components/accordion-template.tsx'
# NS API: Deployment Guide
## Components
The Node Status API is made up of 3 components:
- `nym-node-status-api`
- `nym-node-status-client`
- `nym-node-status-agent`
The API stores its data in a local SQLite database. It periodically gets most of its data from a [NymAPI](./nym-api) instance, and for instances also running with the Gateway Probe, uses the stored topology to conduct performance probe tests.
## Gateway Probe
You can run the `nym-node-status-api` alone, but if you want to run probes for `nym-node`s running as Gateways, you have to also run the `nym-node-status-agent` (which consumes the `nym-node-status-client` lib). The probe will run a set of tests per Gateway node, and return the sort of results seen [here](https://harbourmaster.nymtech.net/gateway/23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb).
## UI
The API exposes a [Swagger](https://swagger.io/docs/) documentation UI by default on port `8000` (see an example [here](https://mainnet-node-status-api.nymtech.cc/swagger/)).
Currently we are not shipping a custom UI component for the Node Status API. The [Harbourmaster](https://harbourmaster.nymtech.net/) frontend consumes this API, amongst other things, but this is an internal repo we use to experiment with new APIs and data on, so it is not public yet.
<Callout type="info">
We invite developers to roll their own UI for their Node Status API instance.
</Callout>
## Docker Images
We will ship Docker images for both the `agent` and `api` in the future. There are Docker images for both in root of each corresponding crate ([`agent`](https://github.com/nymtech/nym/blob/09ea406c02e9a3beebc062f525e4ea1b4222dcbb/nym-node-status-api/nym-node-status-agent/Dockerfile), [`api`](https://github.com/nymtech/nym/blob/develop/nym-node-status-api/nym-node-status-api/Dockerfile)) which are used internally, which could be a starting point for developers to dockerize their instance for the moment.
## Build
### Prerequisites
- Rust
- SQLite
- Get an `ipinfo` key following instructions [here](https://github.com/ipinfo/rust?tab=readme-ov-file#getting-started).
### Compilation
```shell
cargo build --release --package nym-node-status-api --package nym-node-status-agent --package nym-node-status-client
```
## Run
Since the Node Status API depends on both flags and environmental variables, it might be easier to run the binary via a script like the one below - this this script essentially just `source`-s the defined `.env` file after exporting certain binary-specific variables, and then runs the binary. You can find the `.env` files [here](https://github.com/nymtech/nym/tree/master/envs).
<Callout type="info">
All CLI flags are configurable as environmental variables and vice versa, so take the following scripts / setups as guides that you can change however best suits your setup. You can see all definitions [here](https://github.com/nymtech/nym/blob/develop/nym-node-status-api/nym-node-status-api/src/cli/mod.rs#L14).
</Callout>
```bash
#!/bin/bash
set -e
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"} # see nym/envs/ for all possible environments
export NYM_API_CLIENT_TIMEOUT=60
export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=120
export IPINFO_API_TOKEN=<YOUR_IPINFO_API_KEY>
monorepo_root=<PATH/TO/NYM/>
set -a
source "${monorepo_root}/envs/${ENVIRONMENT}.env"
echo ${monorepo_root}/envs/${ENVIRONMENT}.env
set +a
export RUST_LOG=${RUST_LOG:-debug} # debug is useful to check everything is working initially, but quite verbose
echo "Verifying environment variables were properly sourced:"
echo "RUST_LOG=${RUST_LOG}"
echo "BECH32_PREFIX=${BECH32_PREFIX}"
echo "NETWORK_NAME=${NETWORK_NAME}"
<PATH/TO/>nym-node-status-api -- --ipinfo-api-token $IPINFO_API_TOKEN
```
### Functionality Without Gateway Probe
Data will be restricted to information that doesn't involve Probe results; `routing` and `config` scores will be `0` and `last_probe` results `null`, as you can see in this snipped output of the `gateways` endpoint:
<AccordionTemplate name="Output">
```shell
{
"gateway_identity_key": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
"bonded": true,
"performance": 99,
"self_described": {
"authenticator": {
"address": "6Gdtw13Fa46AvkqkHELZZCMKWASDodoJeK9APRNpjjdj.7ji8DDkpjA2AdgwK7wbZm8yi4xZGogGJeypBQt4hAw3P@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb"
},
"auxiliary_details": {
"accepted_operator_terms_and_conditions": true,
"announce_ports": {
"mix_port": null,
"verloc_port": null
},
"location": null
},
"build_information": {
"binary_name": "nym-node",
"build_timestamp": "2025-02-13T11:49:34.670488195Z",
"build_version": "1.5.0",
"cargo_profile": "release",
"cargo_triple": "x86_64-unknown-linux-gnu",
"commit_branch": "HEAD",
"commit_sha": "a3e19b4563843055b305ea9a397eb1ad84b5c378",
"commit_timestamp": "2025-02-10T18:14:47.000000000+01:00",
"rustc_channel": "stable",
"rustc_version": "1.84.1"
},
"declared_role": {
"entry": true,
"exit_ipr": true,
"exit_nr": true,
"mixnode": false
},
"host_information": {
"hostname": "bwng1.bwnym.xyz",
"ip_address": [
"95.164.2.86"
],
"keys": {
"ed25519": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
"x25519": "H6pFjqtdSVxkxEQ3wFnuSoobDAUqHx1bYMVJzPZdRByn",
"x25519_noise": null
}
},
"ip_packet_router": {
"address": "7ms2D2uYiTuhX6MKeVL5rz5usgehEoxAAovwYm9nJyBF.3siMjk3wTU7ykaXLNi9c7LpX8yonYKPCA4BQoMwhsfTV@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb"
},
"last_polled": "2025-03-03 09:48:03.635274187 +00:00:00",
"mixnet_websockets": {
"ws_port": 9000,
"wss_port": 9001
},
"network_requester": {
"address": "HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
"uses_exit_policy": true
},
"wireguard": {
"port": 51822,
"public_key": "6o8x9GitFjcrkjrJnivWaQCPnxXykQPYLneNr2FEB8Vq"
}
},
"explorer_pretty_bond": {
"identity_key": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
"location": {
"latitude": 52.5083,
"longitude": 5.475,
"two_letter_iso_country_code": "NL"
},
"owner": "n1cp5gq0apat6c7qmenqp5zjprn2vwvc7jl29j8r",
"pledge_amount": {
"amount": "100000000",
"denom": "unym"
}
},
"description": {
"moniker": "bwn_g1",
"website": "https://bwnym.xyz",
"security_contact": "bwnym@proton.me",
"details": "This gateway is part of the NYM project, which is dedicated to create outstanding privacy software that is legally compliant without sacrificing integrity or having any backdoors."
},
"last_probe_result": null,
"last_probe_log": null,
"last_testrun_utc": null,
"last_updated_utc": "2025-03-03T10:45:48+00:00",
"routing_score": 0.0,
"config_score": 0
},
```
</AccordionTemplate>
If you have already run the API before, make sure to add the following to your script `--database-url "sqlite://node-status-api.sqlite?mode=rwc` so it will continue using the same DB.
### Functionality with Gateway Probe
If you want to enable Gateway node probes and have the NS API store that data, you need to periodically run the `nym-node-status-agent`, authenticated with your `nym-node-status-api` instance. Authentication is to make sure that only the `-agent` you are operating (or others you trust) are submitting data to your API instance.
#### Compile Gateway Probe
The `nym-node-status-agent` is a thin wrapper around the Gateway Probe binary which currently is in the NymVPN repo. `git checkout` to the most recent [release](https://github.com/nymtech/nym-vpn-client/releases), and compile the probe by following the [readme instructions](https://github.com/nymtech/nym-vpn-client/tree/develop/nym-vpn-core/crates/nym-gateway-probe). You will point the `-agent` at this binary when doing Probe testruns.
#### Generate Keypair
```shell
<PATH/TO/>nym-node-status-agent generate-keypair --path <PATH/TO/KEY/FILE/TO/GENERATE>/<KEY_NAME>
# e.g.
# nym-node-status-agent generate-keypair --path ~/.ssh/ns-agent-key
```
You will then want to export the generated `public-key` so its accessible to the `nym-node-status-api` however you are setting your environmental variables, as `NODE_STATUS_API_AGENT_KEY_LIST`:
```bash
export NODE_STATUS_API_AGENT_KEY_LIST=<YOUR_KEY> # e.g. "H4z8kx5Kkf5JNQHfxaE1MwRndjDCD1C7HsVhHTFfBZ4J"
```
In this situation, you are probably only using one key. However, it is possible to set multiple keys as a comma seperated list, in case you wish to whitelist multiple `-agent`s to be able to submit to a single `-api` instance.
#### Run the Node Status Agent
```shell
<PATH/TO/>nym-node-status-agent run-probe --server-address http://127.0.0.1 --server-port 8000 --ns-api-auth-key "<NS_AGENT_PRIVATE_KEY>" --probe-path <PATH/TO/> nym-gateway-probe
```
You will see a lot of output like so:
<AccordionTemplate name="Output">
```shell
listen_port=48586
public_key=3f95caf771b8a63b9bdae6c186f7aba2eae93483f1c82c0243b5e00c85b4ec26
preshared_key=0000000000000000000000000000000000000000000000000000000000000000
protocol_version=1
endpoint=185.186.78.251:51822
last_handshake_time_sec=0
last_handshake_time_nsec=0
tx_bytes=0
rx_bytes=0
persistent_keepalive_interval=0
allowed_ip=0.0.0.0/0
2025/03/03 18:58:28 Pinging nymtech.net seq=0
2025/03/03 18:58:29 Ping latency: 44.503483ms
2025/03/03 18:58:29 Pinging nymtech.net seq=1
2025/03/03 18:58:29 Ping latency: 42.852414ms
2025/03/03 18:58:29 Pinging nymtech.net seq=2
2025/03/03 18:58:29 Ping latency: 43.627256ms
2025/03/03 18:58:29 Pinging nymtech.net seq=3
2025/03/03 18:58:29 Ping latency: 43.638839ms
2025/03/03 18:58:29 Pinging nymtech.net seq=4
2025/03/03 18:58:29 Ping latency: 43.345357ms
2025/03/03 18:58:29 Pinging 1.1.1.1 seq=0
2025/03/03 18:58:29 Ping latency: 46.327233ms
2025/03/03 18:58:34 Pinging 1.1.1.1 seq=1
2025/03/03 18:58:34 Ping latency: 46.273726ms
2025/03/03 18:58:39 Pinging 1.1.1.1 seq=2
2025/03/03 18:58:39 Ping latency: 46.542774ms
2025/03/03 18:58:44 Pinging 1.1.1.1 seq=3
2025/03/03 18:58:44 Ping latency: 45.663545ms
2025/03/03 18:58:49 Pinging 1.1.1.1 seq=4
2025/03/03 18:58:49 Ping latency: 43.803063ms
2025/03/03 18:58:56 Downloaded file content length: 1.00 MB
2025/03/03 18:58:56 Download duration: 1.308072386s
2025/03/03 18:58:56 private_key=1083749e43f4f8fb008f3f7deef9107ef86a68969670ddbb9f07bf85f94fb564
listen_port=39129
public_key=3f95caf771b8a63b9bdae6c186f7aba2eae93483f1c82c0243b5e00c85b4ec26
preshared_key=0000000000000000000000000000000000000000000000000000000000000000
protocol_version=1
endpoint=185.186.78.251:51822
last_handshake_time_sec=0
last_handshake_time_nsec=0
tx_bytes=0
rx_bytes=0
persistent_keepalive_interval=0
allowed_ip=::/0
2025/03/03 18:58:56 Pinging ipv6.google.com seq=0
2025/03/03 18:58:56 Ping latency: 42.839528ms
2025/03/03 18:58:56 Pinging ipv6.google.com seq=1
2025/03/03 18:58:56 Ping latency: 54.844651ms
2025/03/03 18:58:56 Pinging ipv6.google.com seq=2
2025/03/03 18:58:56 Ping latency: 51.23104ms
2025/03/03 18:58:56 Pinging ipv6.google.com seq=3
2025/03/03 18:58:56 Ping latency: 43.320409ms
2025/03/03 18:58:56 Pinging ipv6.google.com seq=4
2025/03/03 18:58:56 Ping latency: 63.517358ms
2025/03/03 18:58:56 Pinging 2001:4860:4860::8888 seq=0
2025/03/03 18:58:56 Ping latency: 54.682534ms
2025/03/03 18:59:01 Pinging 2001:4860:4860::8888 seq=1
2025/03/03 18:59:01 Ping latency: 55.56235ms
2025/03/03 18:59:06 Pinging 2001:4860:4860::8888 seq=2
2025/03/03 18:59:06 Ping latency: 55.970418ms
2025/03/03 18:59:11 Pinging 2001:4860:4860::8888 seq=3
2025/03/03 18:59:14 Failed to send ping: i/o timeout
2025/03/03 18:59:19 Pinging 2001:4860:4860::8888 seq=4
2025/03/03 18:59:22 Failed to send ping: i/o timeout
2025/03/03 18:59:27 Pinging 2606:4700:4700::1111 seq=0
2025/03/03 18:59:27 Ping latency: 45.072616ms
2025/03/03 18:59:32 Pinging 2606:4700:4700::1111 seq=1
2025/03/03 18:59:32 Ping latency: 44.357306ms
2025/03/03 18:59:37 Pinging 2606:4700:4700::1111 seq=2
2025/03/03 18:59:37 Ping latency: 44.013562ms
2025/03/03 18:59:42 Pinging 2606:4700:4700::1111 seq=3
2025/03/03 18:59:42 Ping latency: 46.94342ms
2025/03/03 18:59:47 Pinging 2606:4700:4700::1111 seq=4
2025/03/03 18:59:48 Ping latency: 43.372288ms
2025/03/03 18:59:53 Pinging 2620:fe::fe seq=0
2025/03/03 18:59:53 Ping latency: 42.164952ms
2025/03/03 18:59:58 Pinging 2620:fe::fe seq=1
2025/03/03 18:59:58 Ping latency: 42.295812ms
2025/03/03 19:00:03 Pinging 2620:fe::fe seq=2
2025/03/03 19:00:03 Ping latency: 43.117534ms
2025/03/03 19:00:08 Pinging 2620:fe::fe seq=3
2025/03/03 19:00:08 Ping latency: 44.26068ms
2025/03/03 19:00:13 Pinging 2620:fe::fe seq=4
2025/03/03 19:00:13 Ping latency: 45.29956ms
2025/03/03 19:00:21 Downloaded file content length: 10.00 MB
2025/03/03 19:00:21 Download duration: 3.39529252s
```
</AccordionTemplate>
Whilst you can run the `-agent` directly, it might be easier to run multiple instances in parallel with a script like so:
```bash
#!/bin/bash
set -eu
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"}
probe_git_ref="nym-vpn-core-v1.4.0" # check for the most recent release
monorepo_root=<PATH/TO/NYM/>
set -a
source "${monorepo_root}/envs/${ENVIRONMENT}.env"
set +a
export RUST_LOG="info"
export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1"
export NODE_STATUS_AGENT_SERVER_PORT="8000"
export NODE_STATUS_AGENT_AUTH_KEY=<NS_AGENT_PRIVATE_KEY>
export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1"
workers=${1:-1}
echo "Running $workers workers in parallel"
function swarm() {
local workers=$1
for ((i = 1; i <= workers; i++)); do
${monorepo_root}/target/release/nym-node-status-agent run-probe --probe-path ~/<PATH/TO>/nym-vpn-client/nym-vpn-core/target/debug/nym-gateway-probe &
done
wait
echo "All agents completed"
}
swarm $workers
```
And run specifying the number of workers with `./<SCRIPT_NAME>.sh <NUMBER_OF_WORKERS>`.
<Callout type="info">
When running the probe, use logging level `RUST_LOG=info`. The Node Status API relies on that granularity for parsing probe results, and the logs grow incessantly if a lower level (e.g. `DEBUG`) is used.
</Callout>
### Ports
By default the API listens on `8000`, so you will need to configure this post to be reachable on your remote server. You can modify this with the `--http_port` flag.
@@ -1,3 +0,0 @@
{
"mainnet":"Mainnet Endpoints"
}
@@ -52,22 +52,21 @@ This page displays a full list of all the changes during our release cycle from
Patched version of `dorina` with a few fixes and tweaks to the release. We would like to ask `nym-node` operators to upgrade to this version as quickly as possible to implement the fixes across the network and improve general quality before NymVPN launch.
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.4-dorina-patched)
- [`nym-node`](nodes/nym-node.mdx) version `1.6.2`
- [`nym-node`](nodes/nym-node.mdx) version `1.6.1`
```shell
nym-node
Binary Name: nym-node
Build Timestamp: 2025-03-06T20:32:36.922212778Z
Build Version: 1.6.2
Commit SHA: 247ebb7c4339de0a298a7fcb2574122a8306c3b8
Commit Date: 2025-03-06T21:26:16.000000000+01:00
Build Timestamp: 2025-03-06T14:28:21.408539599Z
Build Version: 1.6.1
Commit SHA: 1fb2ebad7ad1bad455d5b896ea14204211727417
Commit Date: 2025-03-06T15:26:18.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.85.0
rustc Channel: stable
cargo Profile: release
```
- Use legacy crypto for constructing SURB headers ([#5579])
- Bugfix: make sure to correctly decode response content when putting it into error message ([#5571])
- Tweak surb management to be more conservative ([#5570])
- Deserialize v5 authenticator requests ([#5568])
@@ -77,7 +76,6 @@ cargo Profile: release
- Feature: v2 authentication request (#5537) ([#5563])
- Create authenticator v5 request/response types ([#5561])
[#5579]: https://github.com/nymtech/nym/pull/5579
[#5571]: https://github.com/nymtech/nym/pull/5571
[#5570]: https://github.com/nymtech/nym/pull/5570
[#5568]: https://github.com/nymtech/nym/pull/5568
@@ -20,10 +20,10 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2025-03-06T20:32:36.922212778Z
Build Version: 1.6.2
Commit SHA: 247ebb7c4339de0a298a7fcb2574122a8306c3b8
Commit Date: 2025-03-06T21:26:16.000000000+01:00
Build Timestamp: 2025-03-06T14:28:21.408539599Z
Build Version: 1.6.1
Commit SHA: 1fb2ebad7ad1bad455d5b896ea14204211727417
Commit Date: 2025-03-06T15:26:18.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.85.0
rustc Channel: stable
@@ -3,9 +3,8 @@ import { VarInfo } from 'components/variable-info.tsx';
import { Steps } from 'nextra/components';import { Tabs } from 'nextra/components';
import { MyTab } from 'components/generic-tabs.tsx';
import PortsNymNode from 'components/operators/snippets/ports-nym-node.mdx';
import PortsValidator from 'components/operators/snippets/ports-validator.mdx';
import NymNodeSpecs from 'components/operators/snippets/nym-node-specs.mdx';
import NTPSync from 'components/operators/snippets/ntp-time-sync.mdx'
import PortsValidator from 'components/operators/snippets/ports-validator.mdx'
import NymNodeSpecs from 'components/operators/snippets/nym-node-specs.mdx'
# VPS Setup & Configuration
@@ -43,18 +42,14 @@ apt update -y && apt --fix-broken install
```
- Install dependencies
```sh
apt -y install ca-certificates jq curl wget ufw jq tmux pkg-config build-essential libssl-dev git ntp ntpdate
apt -y install ca-certificates jq curl wget ufw jq tmux pkg-config build-essential libssl-dev git
```
- Double check ufw is installed correctly
```sh
apt install ufw --fix-missing
```
###### 2. Synchronize time of your server
<NTPSync />
###### 3. Configure your firewall using Uncomplicated Firewall (UFW)
###### 2. Configure your firewall using Uncomplicated Firewall (UFW)
For a `nym-node` or Nyx validator to recieve traffic, you need to open ports on the server. The following commands will allow you to set up a firewall using `ufw`.
@@ -75,7 +70,7 @@ ufw enable
ufw status
```
###### 4. Open all needed ports to have your firewall for `nym-node` working correctly
###### 3. Open all needed ports to have your firewall for `nym-node` working correctly
<div>
<Tabs items={[
+2 -2
View File
@@ -102,8 +102,8 @@ pub struct Debug {
pub zk_nym_tickets: ZkNymTicketHandlerDebug,
/// Defines the timestamp skew of a signed authentication request before it's deemed too excessive to process.
pub max_request_timestamp_skew: Duration,
/// Defines the maximum age of a signed authentication request before it's deemed too stale to process.
pub maximum_auth_request_age: Duration,
}
#[derive(Debug, Clone)]
@@ -14,11 +14,12 @@ use std::time::Duration;
#[derive(Clone)]
pub(crate) struct Config {
pub(crate) enforce_zk_nym: bool,
pub(crate) max_request_timestamp_skew: Duration,
pub(crate) max_auth_request_age: Duration,
pub(crate) bandwidth: BandwidthFlushingBehaviourConfig,
}
// I can see this being possible expanded with say storage or client store
#[derive(Clone)]
pub(crate) struct CommonHandlerState {
pub(crate) cfg: Config,
@@ -131,6 +131,9 @@ impl<R, S> FreshHandler<R, S> {
// for time being we assume handle is always constructed from raw socket.
// if we decide we want to change it, that's not too difficult
// also at this point I'm not entirely sure how to deal with this warning without
// some considerable refactoring
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
rng: R,
conn: S,
@@ -638,7 +641,7 @@ impl<R, S> FreshHandler<R, S> {
// do cheap checks first
// is the provided timestamp relatively recent (and not in the future?)
request.verify_timestamp(self.shared_state.cfg.max_request_timestamp_skew)?;
request.verify_timestamp(self.shared_state.cfg.max_auth_request_age)?;
// does the message signature verify?
request.verify_signature()?;
@@ -6,8 +6,9 @@ use crate::node::client_handling::websocket::connection_handler::FreshHandler;
use nym_task::TaskClient;
use rand::rngs::OsRng;
use std::net::SocketAddr;
use std::{io, process};
use tokio::net::TcpStream;
use std::process;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::task::JoinHandle;
use tracing::*;
@@ -33,76 +34,6 @@ impl Listener {
}
}
fn active_connections(&self) -> usize {
self.shared_state
.metrics
.network
.active_ingress_websocket_connections_count()
}
fn prepare_connection_handler(
&self,
socket: TcpStream,
remote_address: SocketAddr,
) -> FreshHandler<OsRng, TcpStream> {
let shutdown = self
.shutdown
.fork(format!("websocket_handler_{remote_address}"));
FreshHandler::new(
OsRng,
socket,
self.shared_state.clone(),
remote_address,
shutdown,
)
}
fn try_handle_accepted_connection(&self, accepted: io::Result<(TcpStream, SocketAddr)>) {
match accepted {
Ok((socket, remote_address)) => {
trace!("received a socket connection from {remote_address}");
let active = self.active_connections();
// 1. check if we're within the connection limit
if active >= self.maximum_open_connections {
warn!(
"connection limit exceeded ({}). can't accept request from {remote_address}",
self.maximum_open_connections
);
return;
}
debug!("there are currently {active} connected clients on the gateway websocket");
// 2. prepare shared data for the new connection handler
let handle = self.prepare_connection_handler(socket, remote_address);
// 3. increment the connection counter.
// make sure to do it before spawning the task,
// as another connection might get accepted before the task is scheduled
// for execution
self.shared_state
.metrics
.network
.new_ingress_websocket_client();
// 4. spawn the task handling the client connection
tokio::spawn(async move {
// TODO: refactor it similarly to the mixnet listener on the nym-node
let metrics_ref = handle.shared_state.metrics.clone();
// 4.1. handle all client requests until connection gets terminated
handle.start_handling().await;
// 4.2. decrement the connection counter
metrics_ref.network.disconnected_ingress_websocket_client();
});
}
Err(err) => warn!("failed to accept client connection: {err}"),
}
}
// TODO: change the signature to pub(crate) async fn run(&self, handler: Handler)
pub(crate) async fn run(&mut self) {
@@ -115,6 +46,8 @@ impl Listener {
}
};
let open_connections = Arc::new(AtomicUsize::new(0));
while !self.shutdown.is_shutdown() {
tokio::select! {
biased;
@@ -122,7 +55,38 @@ impl Listener {
trace!("client_handling::Listener: received shutdown");
}
connection = tcp_listener.accept() => {
self.try_handle_accepted_connection(connection)
match connection {
Ok((socket, remote_addr)) => {
let shutdown = self.shutdown.fork(format!("websocket_handler_{remote_addr}"));
trace!("received a socket connection from {remote_addr}");
if open_connections.fetch_add(1, Ordering::SeqCst) >= self.maximum_open_connections {
warn!("connection limit exceeded ({}). can't accept request from {remote_addr}", self.maximum_open_connections);
continue;
}
// TODO: I think we *REALLY* need a mechanism for having a maximum number of connected
// clients or spawned tokio tasks -> perhaps a worker system?
let handle = FreshHandler::new(
OsRng,
socket,
self.shared_state.clone(),
remote_addr,
shutdown,
);
let open_connections = open_connections.clone();
tokio::spawn(async move {
// TODO: refactor it similarly to the mixnet listener on the nym-node
let metrics_ref = handle.shared_state.metrics.clone();
metrics_ref.network.new_ingress_websocket_client();
open_connections.fetch_add(1, Ordering::SeqCst);
handle.start_handling().await;
metrics_ref.network.disconnected_ingress_websocket_client();
open_connections.fetch_sub(1, Ordering::SeqCst);
});
}
Err(err) => warn!("failed to get client: {err}"),
}
}
}
+1 -1
View File
@@ -251,7 +251,7 @@ impl GatewayTasksBuilder {
let shared_state = websocket::CommonHandlerState {
cfg: websocket::Config {
enforce_zk_nym: self.config.gateway.enforce_zk_nyms,
max_request_timestamp_skew: self.config.debug.max_request_timestamp_skew,
max_auth_request_age: self.config.debug.maximum_auth_request_age,
bandwidth: (&self.config).into(),
},
ecash_verifier: self.ecash_manager().await?,
+2 -2
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.54"
version = "1.1.53"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
@@ -141,7 +141,7 @@ tempfile = { workspace = true }
cw3 = { workspace = true }
cw-utils = { workspace = true }
rand_chacha = { workspace = true }
sha2 = { workspace = true }
sha2 = "0.9"
[lints]
workspace = true
-1
View File
@@ -17,7 +17,6 @@ humantime-serde = { workspace = true }
serde_json = { workspace = true }
sha2.workspace = true
tendermint = { workspace = true }
tendermint-rpc = { workspace = true }
thiserror.workspace = true
time = { workspace = true, features = ["serde", "parsing", "formatting"] }
ts-rs = { workspace = true, optional = true }
+4 -296
View File
@@ -27,7 +27,9 @@ use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_
use nym_node_requests::api::v1::authenticator::models::Authenticator;
use nym_node_requests::api::v1::gateway::models::Wireguard;
use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, NodeRoles};
use nym_node_requests::api::v1::node::models::{
AuxiliaryDetails, BinaryBuildInformationOwned, NodeRoles,
};
use schemars::gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject};
use schemars::JsonSchema;
@@ -41,8 +43,6 @@ use thiserror::Error;
use time::{Date, OffsetDateTime};
use utoipa::{IntoParams, ToResponse, ToSchema};
pub use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct RequestError {
message: String,
@@ -1215,7 +1215,6 @@ impl From<Wireguard> for WireguardDetails {
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct ApiHealthResponse {
pub status: ApiStatus,
#[serde(default)]
pub chain_status: ChainStatus,
pub uptime: u64,
}
@@ -1226,11 +1225,10 @@ pub enum ApiStatus {
Up,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Default, schemars::JsonSchema, ToSchema)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ChainStatus {
Synced,
#[default]
Unknown,
Stalled {
#[serde(
@@ -1430,297 +1428,7 @@ impl From<nym_mixnet_contract_common::EpochRewardedSet> for RewardedSetResponse
}
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct ChainStatusResponse {
pub connected_nyxd: String,
pub status: DetailedChainStatus,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct DetailedChainStatus {
pub abci: crate::models::tendermint_types::AbciInfo,
pub latest_block: BlockInfo,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct BlockInfo {
pub block_id: BlockId,
pub block: FullBlockInfo,
// if necessary we might put block data here later too
}
impl From<tendermint_rpc::endpoint::block::Response> for BlockInfo {
fn from(value: tendermint_rpc::endpoint::block::Response) -> Self {
BlockInfo {
block_id: value.block_id.into(),
block: FullBlockInfo {
header: value.block.header.into(),
},
}
}
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct FullBlockInfo {
pub header: BlockHeader,
}
// copy tendermint types definitions whilst deriving schema types on them and dropping unwanted fields
pub mod tendermint_types {
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tendermint::abci::response::Info;
use tendermint::block::header::Version;
use tendermint::{block, Hash};
use utoipa::ToSchema;
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct AbciInfo {
/// Some arbitrary information.
pub data: String,
/// The application software semantic version.
pub version: String,
/// The application protocol version.
pub app_version: u64,
/// The latest block for which the app has called [`Commit`].
pub last_block_height: u64,
/// The latest result of [`Commit`].
pub last_block_app_hash: String,
}
impl From<Info> for AbciInfo {
fn from(value: Info) -> Self {
AbciInfo {
data: value.data,
version: value.version,
app_version: value.app_version,
last_block_height: value.last_block_height.value(),
last_block_app_hash: value.last_block_app_hash.to_string(),
}
}
}
/// `Version` contains the protocol version for the blockchain and the
/// application.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#version>
#[derive(
Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, ToSchema,
)]
pub struct HeaderVersion {
/// Block version
pub block: u64,
/// App version
pub app: u64,
}
impl From<tendermint::block::header::Version> for HeaderVersion {
fn from(value: Version) -> Self {
HeaderVersion {
block: value.block,
app: value.app,
}
}
}
/// Block identifiers which contain two distinct Merkle roots of the block,
/// as well as the number of parts in the block.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#blockid>
///
/// Default implementation is an empty Id as defined by the Go implementation in
/// <https://github.com/tendermint/tendermint/blob/1635d1339c73ae6a82e062cd2dc7191b029efa14/types/block.go#L1204>.
///
/// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None).
/// This is implemented outside of this struct. Use the Default trait to check for an empty BlockId.
/// See: <https://github.com/informalsystems/tendermint-rs/issues/663>
#[derive(
Serialize,
Deserialize,
Copy,
Clone,
Debug,
Default,
Hash,
Eq,
PartialEq,
PartialOrd,
Ord,
JsonSchema,
ToSchema,
)]
pub struct BlockId {
/// The block's main hash is the Merkle root of all the fields in the
/// block header.
#[schemars(with = "String")]
#[schema(value_type = String)]
pub hash: Hash,
/// Parts header (if available) is used for secure gossipping of the block
/// during consensus. It is the Merkle root of the complete serialized block
/// cut into parts.
///
/// PartSet is used to split a byteslice of data into parts (pieces) for
/// transmission. By splitting data into smaller parts and computing a
/// Merkle root hash on the list, you can verify that a part is
/// legitimately part of the complete data, and the part can be forwarded
/// to other peers before all the parts are known. In short, it's a fast
/// way to propagate a large file over a gossip network.
///
/// <https://github.com/tendermint/tendermint/wiki/Block-Structure#partset>
///
/// PartSetHeader in protobuf is defined as never nil using the gogoproto
/// annotations. This does not translate to Rust, but we can indicate this
/// in the domain type.
pub part_set_header: PartSetHeader,
}
impl From<block::Id> for BlockId {
fn from(value: block::Id) -> Self {
BlockId {
hash: value.hash,
part_set_header: value.part_set_header.into(),
}
}
}
/// Block parts header
#[derive(
Clone,
Copy,
Debug,
Default,
Hash,
Eq,
PartialEq,
PartialOrd,
Ord,
Deserialize,
Serialize,
JsonSchema,
ToSchema,
)]
#[non_exhaustive]
pub struct PartSetHeader {
/// Number of parts in this block
pub total: u32,
/// Hash of the parts set header,
#[schemars(with = "String")]
#[schema(value_type = String)]
pub hash: Hash,
}
impl From<tendermint::block::parts::Header> for PartSetHeader {
fn from(value: block::parts::Header) -> Self {
PartSetHeader {
total: value.total,
hash: value.hash,
}
}
}
/// Block `Header` values contain metadata about the block and about the
/// consensus, as well as commitments to the data in the current block, the
/// previous block, and the results returned by the application.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#header>
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct BlockHeader {
/// Header version
pub version: HeaderVersion,
/// Chain ID
pub chain_id: String,
/// Current block height
pub height: u64,
/// Current timestamp
#[schemars(with = "String")]
#[schema(value_type = String)]
pub time: tendermint::Time,
/// Previous block info
pub last_block_id: Option<BlockId>,
/// Commit from validators from the last block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub last_commit_hash: Option<Hash>,
/// Merkle root of transaction hashes
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub data_hash: Option<Hash>,
/// Validators for the current block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub validators_hash: Hash,
/// Validators for the next block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub next_validators_hash: Hash,
/// Consensus params for the current block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub consensus_hash: Hash,
/// State after txs from the previous block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub app_hash: Hash,
/// Root hash of all results from the txs from the previous block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub last_results_hash: Option<Hash>,
/// Hash of evidence included in the block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub evidence_hash: Option<Hash>,
/// Original proposer of the block
#[serde(with = "nym_serde_helpers::hex")]
#[schemars(with = "String")]
#[schema(value_type = String)]
pub proposer_address: Vec<u8>,
}
impl From<block::Header> for BlockHeader {
fn from(value: block::Header) -> Self {
BlockHeader {
version: value.version.into(),
chain_id: value.chain_id.to_string(),
height: value.height.value(),
time: value.time,
last_block_id: value.last_block_id.map(Into::into),
last_commit_hash: value.last_commit_hash,
data_hash: value.data_hash,
validators_hash: value.validators_hash,
next_validators_hash: value.next_validators_hash,
consensus_hash: value.consensus_hash,
app_hash: Hash::try_from(value.app_hash.as_bytes().to_vec()).unwrap_or_default(),
last_results_hash: value.last_results_hash,
evidence_hash: value.evidence_hash,
proposer_address: value.proposer_address.as_bytes().to_vec(),
}
}
}
}
use crate::models::tendermint_types::{BlockHeader, BlockId};
pub use config_score::*;
pub mod config_score {
use nym_contracts_common::NaiveFloat;
use serde::{Deserialize, Serialize};
+1 -1
View File
@@ -109,7 +109,7 @@ impl InternalCounters {
// just hash the current counter
self.tx_hash_counter += 1;
Hash::Sha256(sha2::Sha256::digest(self.tx_hash_counter.to_be_bytes()).into())
Hash::Sha256(sha2::Sha256::digest(&self.tx_hash_counter.to_be_bytes()).into())
}
#[allow(dead_code)]
+1 -2
View File
@@ -1,12 +1,11 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::network::models::{ContractInformation, NetworkDetails};
use crate::network::models::{ChainStatusResponse, ContractInformation, NetworkDetails};
use crate::node_status_api::models::AxumResult;
use crate::support::http::state::AppState;
use axum::extract::State;
use axum::{extract, Json, Router};
use nym_api_requests::models::ChainStatusResponse;
use nym_contracts_common::ContractBuildInformation;
use std::collections::HashMap;
use tower_http::compression::CompressionLayer;
+291
View File
@@ -1,7 +1,9 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::network::models::tendermint_types::{AbciInfo, BlockHeader, BlockId};
use nym_config::defaults::NymNetworkDetails;
use nym_validator_client::nyxd::BlockResponse;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
@@ -27,3 +29,292 @@ pub struct ContractInformation<T> {
pub(crate) address: Option<String>,
pub(crate) details: Option<T>,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct ChainStatusResponse {
pub connected_nyxd: String,
pub status: ChainStatus,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct ChainStatus {
pub abci: AbciInfo,
pub latest_block: BlockInfo,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct BlockInfo {
pub block_id: BlockId,
pub block: FullBlockInfo,
// if necessary we might put block data here later too
}
impl From<BlockResponse> for BlockInfo {
fn from(value: BlockResponse) -> Self {
BlockInfo {
block_id: value.block_id.into(),
block: FullBlockInfo {
header: value.block.header.into(),
},
}
}
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct FullBlockInfo {
pub header: BlockHeader,
}
// copy tendermint types definitions whilst deriving schema types on them and dropping unwanted fields
pub mod tendermint_types {
use nym_validator_client::nyxd::Hash;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tendermint::abci::response::Info;
use tendermint::block;
use tendermint::block::header::Version;
use utoipa::ToSchema;
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct AbciInfo {
/// Some arbitrary information.
pub data: String,
/// The application software semantic version.
pub version: String,
/// The application protocol version.
pub app_version: u64,
/// The latest block for which the app has called [`Commit`].
pub last_block_height: u64,
/// The latest result of [`Commit`].
pub last_block_app_hash: String,
}
impl From<Info> for AbciInfo {
fn from(value: Info) -> Self {
AbciInfo {
data: value.data,
version: value.version,
app_version: value.app_version,
last_block_height: value.last_block_height.value(),
last_block_app_hash: value.last_block_app_hash.to_string(),
}
}
}
/// `Version` contains the protocol version for the blockchain and the
/// application.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#version>
#[derive(
Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, ToSchema,
)]
pub struct HeaderVersion {
/// Block version
pub block: u64,
/// App version
pub app: u64,
}
impl From<tendermint::block::header::Version> for HeaderVersion {
fn from(value: Version) -> Self {
HeaderVersion {
block: value.block,
app: value.app,
}
}
}
/// Block identifiers which contain two distinct Merkle roots of the block,
/// as well as the number of parts in the block.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#blockid>
///
/// Default implementation is an empty Id as defined by the Go implementation in
/// <https://github.com/tendermint/tendermint/blob/1635d1339c73ae6a82e062cd2dc7191b029efa14/types/block.go#L1204>.
///
/// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None).
/// This is implemented outside of this struct. Use the Default trait to check for an empty BlockId.
/// See: <https://github.com/informalsystems/tendermint-rs/issues/663>
#[derive(
Serialize,
Deserialize,
Copy,
Clone,
Debug,
Default,
Hash,
Eq,
PartialEq,
PartialOrd,
Ord,
JsonSchema,
ToSchema,
)]
pub struct BlockId {
/// The block's main hash is the Merkle root of all the fields in the
/// block header.
#[schemars(with = "String")]
#[schema(value_type = String)]
pub hash: Hash,
/// Parts header (if available) is used for secure gossipping of the block
/// during consensus. It is the Merkle root of the complete serialized block
/// cut into parts.
///
/// PartSet is used to split a byteslice of data into parts (pieces) for
/// transmission. By splitting data into smaller parts and computing a
/// Merkle root hash on the list, you can verify that a part is
/// legitimately part of the complete data, and the part can be forwarded
/// to other peers before all the parts are known. In short, it's a fast
/// way to propagate a large file over a gossip network.
///
/// <https://github.com/tendermint/tendermint/wiki/Block-Structure#partset>
///
/// PartSetHeader in protobuf is defined as never nil using the gogoproto
/// annotations. This does not translate to Rust, but we can indicate this
/// in the domain type.
pub part_set_header: PartSetHeader,
}
impl From<block::Id> for BlockId {
fn from(value: block::Id) -> Self {
BlockId {
hash: value.hash,
part_set_header: value.part_set_header.into(),
}
}
}
/// Block parts header
#[derive(
Clone,
Copy,
Debug,
Default,
Hash,
Eq,
PartialEq,
PartialOrd,
Ord,
Deserialize,
Serialize,
JsonSchema,
ToSchema,
)]
#[non_exhaustive]
pub struct PartSetHeader {
/// Number of parts in this block
pub total: u32,
/// Hash of the parts set header,
#[schemars(with = "String")]
#[schema(value_type = String)]
pub hash: Hash,
}
impl From<tendermint::block::parts::Header> for PartSetHeader {
fn from(value: block::parts::Header) -> Self {
PartSetHeader {
total: value.total,
hash: value.hash,
}
}
}
/// Block `Header` values contain metadata about the block and about the
/// consensus, as well as commitments to the data in the current block, the
/// previous block, and the results returned by the application.
///
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#header>
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
pub struct BlockHeader {
/// Header version
pub version: HeaderVersion,
/// Chain ID
pub chain_id: String,
/// Current block height
pub height: u64,
/// Current timestamp
#[schemars(with = "String")]
#[schema(value_type = String)]
pub time: tendermint::Time,
/// Previous block info
pub last_block_id: Option<BlockId>,
/// Commit from validators from the last block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub last_commit_hash: Option<Hash>,
/// Merkle root of transaction hashes
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub data_hash: Option<Hash>,
/// Validators for the current block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub validators_hash: Hash,
/// Validators for the next block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub next_validators_hash: Hash,
/// Consensus params for the current block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub consensus_hash: Hash,
/// State after txs from the previous block
#[schemars(with = "String")]
#[schema(value_type = String)]
pub app_hash: Hash,
/// Root hash of all results from the txs from the previous block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub last_results_hash: Option<Hash>,
/// Hash of evidence included in the block
#[schemars(with = "Option<String>")]
#[schema(value_type = Option<String>)]
pub evidence_hash: Option<Hash>,
/// Original proposer of the block
#[serde(with = "nym_serde_helpers::hex")]
#[schemars(with = "String")]
#[schema(value_type = String)]
pub proposer_address: Vec<u8>,
}
impl From<block::Header> for BlockHeader {
fn from(value: block::Header) -> Self {
BlockHeader {
version: value.version.into(),
chain_id: value.chain_id.to_string(),
height: value.height.value(),
time: value.time,
last_block_id: value.last_block_id.map(Into::into),
last_commit_hash: value.last_commit_hash,
data_hash: value.data_hash,
validators_hash: value.validators_hash,
next_validators_hash: value.next_validators_hash,
consensus_hash: value.consensus_hash,
app_hash: Hash::try_from(value.app_hash.as_bytes().to_vec()).unwrap_or_default(),
last_results_hash: value.last_results_hash,
evidence_hash: value.evidence_hash,
proposer_address: value.proposer_address.as_bytes().to_vec(),
}
}
}
}
-1
View File
@@ -191,7 +191,6 @@ async fn try_get_client(
// if provided host was malformed, no point in continuing
let client = match nym_node_requests::api::Client::builder(address).and_then(|b| {
b.with_timeout(Duration::from_secs(5))
.no_hickory_dns()
.with_user_agent("nym-api-describe-cache")
.build()
}) {
+7 -9
View File
@@ -3,7 +3,7 @@
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
use crate::ecash::state::EcashState;
use crate::network::models::NetworkDetails;
use crate::network::models::{ChainStatus, NetworkDetails};
use crate::node_describe_cache::DescribedNodes;
use crate::node_status_api::handlers::unstable;
use crate::node_status_api::models::AxumErrorResponse;
@@ -15,9 +15,7 @@ use crate::support::caching::Cache;
use crate::support::nyxd::Client;
use crate::support::storage;
use axum::extract::FromRef;
use nym_api_requests::models::{
DetailedChainStatus, GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation,
};
use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation};
use nym_mixnet_contract_common::NodeId;
use nym_task::TaskManager;
use nym_topology::CachedEpochRewardedSet;
@@ -153,7 +151,7 @@ impl ChainStatusCache {
struct ChainStatusCacheInner {
last_refreshed_at: OffsetDateTime,
cache_value: DetailedChainStatus,
cache_value: ChainStatus,
}
impl ChainStatusCacheInner {
@@ -169,7 +167,7 @@ impl ChainStatusCache {
pub(crate) async fn get_or_refresh(
&self,
client: &Client,
) -> Result<DetailedChainStatus, AxumErrorResponse> {
) -> Result<ChainStatus, AxumErrorResponse> {
if let Some(cached) = self.check_cache().await {
return Ok(cached);
}
@@ -177,7 +175,7 @@ impl ChainStatusCache {
self.refresh(client).await
}
async fn check_cache(&self) -> Option<DetailedChainStatus> {
async fn check_cache(&self) -> Option<ChainStatus> {
let guard = self.inner.read().await;
let inner = guard.as_ref()?;
if inner.is_valid(self.cache_ttl) {
@@ -186,7 +184,7 @@ impl ChainStatusCache {
None
}
async fn refresh(&self, client: &Client) -> Result<DetailedChainStatus, AxumErrorResponse> {
async fn refresh(&self, client: &Client) -> Result<ChainStatus, AxumErrorResponse> {
// 1. attempt to get write lock permit
let mut guard = self.inner.write().await;
@@ -203,7 +201,7 @@ impl ChainStatusCache {
.block_info(abci.last_block_height.value() as u32)
.await?;
let status = DetailedChainStatus {
let status = ChainStatus {
abci: abci.into(),
latest_block: block.into(),
};
-6
View File
@@ -1,6 +0,0 @@
# Nym Node Status API
The Node Status API serves information about individual `nym-nodes` in the Mixnet, such as which role they are operating in, statistics about them, services such as Network Requesters, as well as summaries of the state of the Mixnet.
We recommend that developers building applications such as explorers or analytics interfaces about the Mixnet run their own instance of the API, in order to promote a robust network of downstream services, and spread the load of API calls amongst as many endpoints as possible.
You can find build and operation instructions in the [docs](https://nym.com/docs/apis/ns-api).
@@ -10,8 +10,7 @@ pub(crate) fn generate_key_pair(path: impl AsRef<Path>) -> anyhow::Result<()> {
let mut private_key_file = File::create(priv_key_path)?;
private_key_file.write_all(keypair.private_key().to_base58_string().as_bytes())?;
let pub_key_path = priv_key_path.with_file_name("public-key");
let pub_key_path = priv_key_path.with_extension("public");
let mut public_key_file = File::create(&pub_key_path)?;
public_key_file.write_all(keypair.public_key().to_base58_string().as_bytes())?;
@@ -1,4 +1,4 @@
use tracing::{debug, error, info};
use tracing::error;
pub(crate) struct GwProbe {
path: String,
@@ -10,62 +10,21 @@ impl GwProbe {
}
pub(crate) async fn version(&self) -> String {
debug!("Attempting to execute binary at: {}", &self.path);
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
match tokio::fs::metadata(&self.path).await {
Ok(metadata) => {
let perms = metadata.permissions();
let mode = perms.mode();
if mode & 0o111 == 0 {
error!(
"Binary is not executable: {} (mode: {:o})",
&self.path, mode
);
return "Binary is not executable".to_string();
}
debug!("Binary exists with permissions: {:o}", mode);
}
Err(e) => {
error!("Failed to stat binary at {}: {}", &self.path, e);
return format!("Failed to access binary: {}", e);
}
}
}
let mut command = tokio::process::Command::new(&self.path);
command.stdout(std::process::Stdio::piped());
command.arg("--version");
info!("Executing command: {:?} --version", &self.path);
match command.spawn() {
Ok(child) => match child.wait_with_output().await {
Ok(output) => {
if output.status.success() {
String::from_utf8(output.stdout)
.unwrap_or_else(|_| "Unable to parse version output".to_string())
} else {
let stderr = String::from_utf8(output.stderr)
.unwrap_or_else(|_| "Unable to parse error output".to_string());
error!(
"Command failed with exit code {}: {}",
output.status.code().unwrap_or(-1),
stderr
);
format!("Command failed: {}", stderr)
}
Ok(child) => {
if let Ok(output) = child.wait_with_output().await {
return String::from_utf8(output.stdout)
.unwrap_or("Unable to get log from test run".to_string());
}
Err(e) => {
error!("Failed to get command output: {}", e);
format!("Failed to get command output: {}", e)
}
},
"Unable to get probe version".to_string()
}
Err(e) => {
error!("Failed to spawn process: {}", e);
format!("Failed to spawn process: {}", e)
error!("Failed to get probe version: {}", e);
"Failed to get probe version".to_string()
}
}
}
@@ -29,7 +29,6 @@ nym-bin-common = { path = "../../common/bin-common", features = ["models"] }
nym-node-status-client = { path = "../nym-node-status-client" }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
nym-http-api-client = { path = "../../common/http-api-client" }
nym-network-defaults = { path = "../../common/network-defaults" }
nym-serde-helpers = { path = "../../common/serde-helpers"}
nym-statistics-common = { path = "../../common/statistics" }
@@ -97,12 +97,8 @@ impl Monitor {
.clone()
.expect("rust sdk mainnet default missing api_url");
let nym_api = nym_http_api_client::ClientBuilder::new_with_url(default_api_url)
.no_hickory_dns()
.with_timeout(self.nym_api_client_timeout)
.build::<&str>()?;
let api_client = NymApiClient { nym_api };
let api_client =
NymApiClient::new_with_timeout(default_api_url, self.nym_api_client_timeout);
let described_nodes = api_client
.get_all_described_nodes()
@@ -300,14 +296,9 @@ impl Monitor {
Some(location) => return location,
None => {
for ip in node.description.host_information.ip_address.iter() {
match self.ipinfo.locate_ip(ip.to_string()).await {
Ok(location) => {
self.geocache.insert(node_id, location.clone()).await;
return location;
}
Err(err) => {
tracing::warn!("Couldn't locate IP {} due to: {}", ip, err)
}
if let Ok(location) = self.ipinfo.locate_ip(ip.to_string()).await {
self.geocache.insert(node_id, location.clone()).await;
return location;
}
}
// if no data could be retrieved
@@ -57,12 +57,7 @@ async fn run(
.clone()
.expect("rust sdk mainnet default missing api_url");
let nym_api = nym_http_api_client::ClientBuilder::new_with_url(default_api_url)
.no_hickory_dns()
.with_timeout(nym_api_client_timeout)
.build::<&str>()?;
let api_client = NymApiClient { nym_api };
let api_client = NymApiClient::new_with_timeout(default_api_url, nym_api_client_timeout);
//SW TBC what nodes exactly need to be scraped, the skimmed node endpoint seems to return more nodes
let bonded_nodes = api_client.get_all_bonded_nym_nodes().await?;
@@ -175,7 +170,6 @@ impl MetricsScrapingData {
let client = match nym_node_requests::api::Client::builder(address).and_then(|b| {
b.with_timeout(Duration::from_secs(5))
.with_user_agent("node-status-api-metrics-scraper")
.no_hickory_dns()
.build()
}) {
Ok(client) => client,
+2 -2
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node"
version = "1.7.0"
version = "1.6.1"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -71,7 +71,7 @@ nym-verloc = { path = "../common/verloc" }
nym-metrics = { path = "../common/nym-metrics" }
nym-gateway-stats-storage = { path = "../common/gateway-stats-storage" }
nym-topology = { path = "../common/topology" }
nym-http-api-client = { path = "../common/http-api-client" }
# http server
# useful for `#[axum_macros::debug_handler]`
+1 -1
View File
@@ -45,7 +45,7 @@ impl NetworkStats {
pub fn active_ingress_websocket_connections_count(&self) -> usize {
self.active_ingress_websocket_connections
.load(Ordering::SeqCst)
.load(Ordering::Relaxed)
}
pub fn active_egress_mixnet_connections_counter(&self) -> Arc<AtomicUsize> {
+5 -12
View File
@@ -50,13 +50,8 @@ pub struct Debug {
/// The maximum number of client connections the gateway will keep open at once.
pub maximum_open_connections: usize,
/// Specifies the minimum performance of mixnodes in the network that are to be used in internal topologies
/// of the services providers
pub minimum_mix_performance: u8,
/// Defines the timestamp skew of a signed authentication request before it's deemed too excessive to process.
#[serde(alias = "maximum_auth_request_age")]
pub max_request_timestamp_skew: Duration,
/// Defines the maximum age of a signed authentication request before it's deemed too stale to process.
pub maximum_auth_request_age: Duration,
pub stale_messages: StaleMessageDebug,
@@ -66,10 +61,9 @@ pub struct Debug {
}
impl Debug {
pub const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
pub const DEFAULT_MINIMUM_MIX_PERFORMANCE: u8 = 50;
pub const DEFAULT_MAXIMUM_AUTH_REQUEST_TIMESTAMP_SKEW: Duration = Duration::from_secs(120);
pub const DEFAULT_MAXIMUM_OPEN_CONNECTIONS: usize = 8192;
pub const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
pub const DEFAULT_MAXIMUM_AUTH_REQUEST_AGE: Duration = Duration::from_secs(30);
}
impl Default for Debug {
@@ -77,8 +71,7 @@ impl Default for Debug {
Debug {
message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
maximum_open_connections: Self::DEFAULT_MAXIMUM_OPEN_CONNECTIONS,
max_request_timestamp_skew: Self::DEFAULT_MAXIMUM_AUTH_REQUEST_TIMESTAMP_SKEW,
minimum_mix_performance: Self::DEFAULT_MINIMUM_MIX_PERFORMANCE,
maximum_auth_request_age: Self::DEFAULT_MAXIMUM_AUTH_REQUEST_AGE,
stale_messages: Default::default(),
client_bandwidth: Default::default(),
zk_nym_tickets: Default::default(),
+1 -1
View File
@@ -60,7 +60,7 @@ fn ephemeral_gateway_config(config: &Config) -> nym_gateway::config::Config {
.zk_nym_tickets
.maximum_time_between_redemption,
},
max_request_timestamp_skew: config.gateway_tasks.debug.max_request_timestamp_skew,
maximum_auth_request_age: config.gateway_tasks.debug.maximum_auth_request_age,
},
)
}
-7
View File
@@ -3,7 +3,6 @@
use crate::node::http::error::NymNodeHttpError;
use crate::wireguard::error::WireguardError;
use nym_http_api_client::HttpClientError;
use nym_ip_packet_router::error::ClientCoreError;
use nym_validator_client::ValidatorClientError;
use std::io;
@@ -210,9 +209,3 @@ pub enum ServiceProvidersError {
#[error(transparent)]
ExternalClientCore(#[from] ClientCoreError),
}
impl From<HttpClientError> for NymNodeError {
fn from(value: HttpClientError) -> Self {
Self::HttpFailure(NymNodeHttpError::ClientError { source: value })
}
}
-7
View File
@@ -1,7 +1,6 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_http_api_client::HttpClientError;
use std::io;
use std::net::SocketAddr;
use thiserror::Error;
@@ -25,10 +24,4 @@ pub enum NymNodeHttpError {
#[from]
source: nym_crypto::asymmetric::encryption::KeyRecoveryError,
},
#[error("error building or using HTTP client: {source}")]
ClientError {
#[from]
source: HttpClientError,
},
}
+3 -17
View File
@@ -814,23 +814,9 @@ impl NymNode {
return;
}
for nym_api_url in &self.config.mixnet.nym_api_urls {
info!("trying {nym_api_url}...");
let nym_api =
match nym_http_api_client::ClientBuilder::new_with_url(nym_api_url.clone())
.no_hickory_dns()
.with_user_agent(self.user_agent())
.build::<&str>()
{
Ok(b) => b,
Err(e) => {
warn!("failed to build http client for \"{nym_api_url}\": {e}",);
continue;
}
};
let client = NymApiClient { nym_api };
for nym_api in &self.config.mixnet.nym_api_urls {
info!("trying {nym_api}...");
let client = NymApiClient::new_with_user_agent(nym_api.clone(), self.user_agent());
// make new request every time in case previous one takes longer and invalidates the signature
let request = NodeRefreshBody::new(self.ed25519_identity_keys.private_key());
+2 -9
View File
@@ -9,7 +9,6 @@ use nym_node_metrics::prometheus_wrapper::{PrometheusMetric, PROMETHEUS_METRICS}
use nym_task::ShutdownToken;
use nym_topology::node::RoutingNode;
use nym_topology::{EpochRewardedSet, NymTopology, Role, TopologyProvider};
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nym_nodes::{NodesByAddressesResponse, SkimmedNode};
use nym_validator_client::{NymApiClient, ValidatorClientError};
use std::collections::HashSet;
@@ -168,7 +167,6 @@ impl NodesQuerier {
) -> Result<NodesByAddressesResponse, ValidatorClientError> {
let res = self
.client
.nym_api
.nodes_by_addresses(ips)
.await
.inspect_err(|err| error!("failed to obtain node information: {err}"));
@@ -176,7 +174,7 @@ impl NodesQuerier {
if res.is_err() {
self.use_next_nym_api()
}
Ok(res?)
res
}
}
@@ -265,14 +263,9 @@ impl NetworkRefresher {
pending_check_interval: Duration,
shutdown_token: ShutdownToken,
) -> Result<Self, NymNodeError> {
let nym_api = nym_http_api_client::Client::builder(nym_api_urls[0].clone())?
.no_hickory_dns()
.with_user_agent(user_agent)
.build()?;
let mut this = NetworkRefresher {
querier: NodesQuerier {
client: NymApiClient { nym_api },
client: NymApiClient::new_with_user_agent(nym_api_urls[0].clone(), user_agent),
nym_api_urls,
currently_used_api: 0,
},
+40 -42
View File
@@ -385,15 +385,15 @@ dependencies = [
[[package]]
name = "bls12_381"
version = "0.8.0"
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd"
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect#22cd0a16b674af1629110a2dc8b6cf6c73ea4cd9"
dependencies = [
"digest 0.10.7",
"digest 0.9.0",
"ff",
"group",
"pairing",
"rand_core 0.6.4",
"serde",
"serdect 0.3.0",
"serdect 0.3.0-pre.0",
"subtle",
"zeroize",
]
@@ -540,11 +540,11 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.16"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
dependencies = [
"shlex",
"libc",
]
[[package]]
@@ -1583,9 +1583,9 @@ dependencies = [
[[package]]
name = "ff"
version = "0.13.1"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
dependencies = [
"rand_core 0.6.4",
"subtle",
@@ -3293,7 +3293,6 @@ dependencies = [
"serde_json",
"sha2 0.10.8",
"tendermint 0.40.1",
"tendermint-rpc",
"thiserror 2.0.11",
"time",
"utoipa",
@@ -3342,7 +3341,7 @@ dependencies = [
"bls12_381",
"bs58",
"cfg-if",
"digest 0.10.7",
"digest 0.9.0",
"ff",
"group",
"itertools 0.14.0",
@@ -3350,7 +3349,7 @@ dependencies = [
"nym-pemstore",
"rand 0.8.5",
"serde",
"sha2 0.10.8",
"sha2 0.9.9",
"subtle",
"thiserror 2.0.11",
"zeroize",
@@ -3558,7 +3557,6 @@ name = "nym-pemstore"
version = "0.3.0"
dependencies = [
"pem",
"tracing",
]
[[package]]
@@ -4761,16 +4759,16 @@ dependencies = [
[[package]]
name = "ring"
version = "0.17.13"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.10",
"libc",
"spin",
"untrusted",
"windows-sys 0.52.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -4784,9 +4782,9 @@ dependencies = [
[[package]]
name = "rs_merkle"
version = "1.5.0"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb09b49230ba22e8c676e7b75dfe2887dea8121f18b530ae0ba519ce442d2b21"
checksum = "3b241d2e59b74ef9e98d94c78c47623d04c8392abaf82014dfd372a16041128f"
dependencies = [
"sha2 0.10.8",
]
@@ -5064,18 +5062,18 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.26"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.219"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
@@ -5091,18 +5089,18 @@ dependencies = [
[[package]]
name = "serde_bytes"
version = "0.11.17"
version = "0.11.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96"
checksum = "364fec0df39c49a083c9a8a18a23a6bcfd9af130fe9fe321d18520a0d113e09e"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
@@ -5198,9 +5196,9 @@ dependencies = [
[[package]]
name = "serdect"
version = "0.3.0"
version = "0.3.0-pre.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53"
checksum = "791ef964bfaba6be28a5c3f0c56836e17cb711ac009ca1074b9c735a3ebf240a"
dependencies = [
"base16ct",
"serde",
@@ -5271,12 +5269,6 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
@@ -5371,6 +5363,12 @@ dependencies = [
"system-deps 5.0.0",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "spki"
version = "0.7.2"
@@ -6057,9 +6055,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.39"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa 1.0.9",
@@ -6074,15 +6072,15 @@ dependencies = [
[[package]]
name = "time-core"
version = "0.1.3"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.20"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
@@ -6115,9 +6113,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.44.0"
version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
dependencies = [
"backtrace",
"bytes",
@@ -157,6 +157,20 @@ export const SendInputModal = ({
initialValue={userFees?.amount}
fullWidth
/>
<TextField
name="memo"
label="Memo"
onChange={(e) => onMemoChange(e.target.value)}
value={memo}
error={!memoIsValid}
helperText={
!memoIsValid
? ' The text is invalid, only alphanumeric characters and white spaces are allowed'
: undefined
}
InputLabelProps={{ shrink: true }}
fullWidth
/>
</Stack>
)}
</SimpleModal>
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO watcher_execution(start, end, error_message)\n VALUES (?, ?, ?)\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "1aa7733ad4bbf3e6b8db909b8646bee247bc021b9534f1d4b0fcad32e2e56218"
}
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "\n SELECT id, tx_hash, height, message_index, sender, recipient, amount, memo, created_at as \"created_at: ::time::OffsetDateTime\"\n FROM transactions\n WHERE height > ?\n ORDER BY height ASC, message_index ASC\n ",
"query": "\n SELECT * FROM transactions\n WHERE height > ? \n ORDER BY height ASC, message_index ASC\n ",
"describe": {
"columns": [
{
@@ -44,7 +44,7 @@
"type_info": "Text"
},
{
"name": "created_at: ::time::OffsetDateTime",
"name": "created_at",
"ordinal": 8,
"type_info": "Datetime"
}
@@ -64,5 +64,5 @@
true
]
},
"hash": "f69907735e9b1e1572c4bf6fe8d44d4ea4e55c2a9c4d4f7e1c7e57bcb848ee08"
"hash": "7b9abf4ff422b8d7a942955dc4fba380e7d5f0127f4745705b8ac9af6c170d19"
}
+6 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nyx-chain-watcher"
version = "0.1.14"
version = "0.1.11"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -23,11 +23,16 @@ nym-config = { path = "../common/config" }
nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
nym-network-defaults = { path = "../common/network-defaults" }
nym-task = { path = "../common/task" }
nym-node-requests = { path = "../nym-node/nym-node-requests", features = [
"openapi",
] }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nyxd-scraper = { path = "../common/nyxd-scraper" }
reqwest = { workspace = true, features = ["rustls-tls"] }
rocket = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "time"] }
thiserror = { workspace = true }
time = { workspace = true }
@@ -1,11 +0,0 @@
/*
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: GPL-3.0-only
*/
CREATE TABLE watcher_execution
(
start TIMESTAMP WITHOUT TIME ZONE NOT NULL,
end TIMESTAMP WITHOUT TIME ZONE NOT NULL,
error_message TEXT
)
+69 -96
View File
@@ -1,14 +1,12 @@
use crate::config::PaymentWatchersConfig;
use crate::config::PaymentWatcherConfig;
use crate::env::vars::{
NYXD_SCRAPER_START_HEIGHT, NYXD_SCRAPER_UNSAFE_NUKE_DB,
NYXD_SCRAPER_USE_BEST_EFFORT_START_HEIGHT,
};
use crate::http::state::BankScraperModuleState;
use async_trait::async_trait;
use nym_validator_client::nyxd::{Any, Coin, CosmosCoin, Hash, Msg, MsgSend, Name};
use nyxd_scraper::{
error::ScraperError, storage::StorageTransaction, MsgModule, NyxdScraper,
ParsedTransactionResponse, PruningOptions,
error::ScraperError, storage::StorageTransaction, NyxdScraper, ParsedTransactionResponse,
PruningOptions, TxModule,
};
use sqlx::SqlitePool;
use std::fs;
@@ -17,7 +15,6 @@ use tracing::{info, warn};
pub(crate) async fn run_chain_scraper(
config: &crate::config::Config,
db_pool: SqlitePool,
shared_state: BankScraperModuleState,
) -> anyhow::Result<NyxdScraper> {
let websocket_url = std::env::var("NYXD_WS").expect("NYXD_WS not defined");
@@ -61,10 +58,9 @@ pub(crate) async fn run_chain_scraper(
use_best_effort_start_height,
},
})
.with_msg_module(BankScraperModule::new(
.with_tx_module(EventScraperModule::new(
db_pool,
config.payment_watcher_config.clone(),
shared_state,
config.payment_watcher_config.clone().unwrap_or_default(),
));
let instance = scraper.build_and_start().await?;
@@ -75,22 +71,16 @@ pub(crate) async fn run_chain_scraper(
Ok(instance)
}
pub struct BankScraperModule {
pub struct EventScraperModule {
db_pool: SqlitePool,
payment_config: PaymentWatchersConfig,
shared_state: BankScraperModuleState,
payment_config: PaymentWatcherConfig,
}
impl BankScraperModule {
pub fn new(
db_pool: SqlitePool,
payment_config: PaymentWatchersConfig,
shared_state: BankScraperModuleState,
) -> Self {
impl EventScraperModule {
pub fn new(db_pool: SqlitePool, payment_config: PaymentWatcherConfig) -> Self {
Self {
db_pool,
payment_config,
shared_state,
}
}
@@ -118,47 +108,23 @@ impl BankScraperModule {
amount,
memo
)
.execute(&self.db_pool)
.await?;
.execute(&self.db_pool)
.await?;
Ok(())
}
fn get_unym_coin(&self, coins: &[CosmosCoin]) -> Option<Coin> {
coins
.iter()
.find(|coin| coin.denom.as_ref() == "unym")
.map(|c| c.clone().into())
}
// TODO: ideally this should be done by the scraper itself
fn recover_bank_msg(
&self,
tx_hash: Hash,
index: usize,
msg: &Any,
) -> Result<MsgSend, ScraperError> {
MsgSend::from_any(msg).map_err(|source| ScraperError::MsgParseFailure {
hash: tx_hash,
index,
type_url: self.type_url(),
source,
})
}
}
#[async_trait]
impl MsgModule for BankScraperModule {
fn type_url(&self) -> String {
<MsgSend as Msg>::Proto::type_url()
}
async fn handle_msg(
#[async_trait]
impl TxModule for EventScraperModule {
async fn handle_tx(
&mut self,
index: usize,
msg: &Any,
tx: &ParsedTransactionResponse,
_storage_tx: &mut StorageTransaction,
_: &mut StorageTransaction,
) -> Result<(), ScraperError> {
let events = &tx.tx_result.events;
let height = tx.height.value() as i64;
let tx_hash = tx.hash.to_string();
let memo = tx.tx.body.memo.clone();
// Don't process failed transactions
@@ -166,53 +132,60 @@ impl MsgModule for BankScraperModule {
return Ok(());
}
let msg = self.recover_bank_msg(tx.hash, index, msg)?;
// Process each event
for event in events {
// Only process transfer events
if event.kind == "transfer" {
let mut recipient = None;
let mut sender = None;
let mut amount = None;
// TODO: get message index from event
let message_index = 0;
// Check if any watcher is watching this recipient
let is_watched = self
.payment_config
.is_being_watched(msg.to_address.as_ref());
// Extract transfer event attributes
for attr in &event.attributes {
if let (Ok(key), Ok(value)) = (attr.key_str(), attr.value_str()) {
match key {
"recipient" => recipient = Some(value.to_string()),
"sender" => sender = Some(value.to_string()),
"amount" => amount = Some(value.to_string()),
_ => continue,
}
}
}
self.shared_state
.new_bank_msg(tx, index, &msg, is_watched)
.await;
// If we have all required fields, check if recipient is watched and store
if let (Some(recipient), Some(sender), Some(amount)) = (recipient, sender, amount) {
// Check if any watcher is watching this recipient
let is_watched = self.payment_config.watchers.iter().any(|watcher| {
if let Some(watched_accounts) =
&watcher.watch_for_transfer_recipient_accounts
{
watched_accounts
.iter()
.any(|account| account.to_string() == recipient)
} else {
false
}
});
if is_watched {
let Some(unym_coin) = self.get_unym_coin(&msg.amount) else {
let warn = format!(
"{} sent {:?} instead of unym!",
msg.from_address, msg.amount
);
warn!("{warn}");
self.shared_state
.new_rejection(tx.hash.to_string(), tx.height.value(), index as u32, warn)
.await;
// we don't want to fail the whole processing - this is not a failure in that sense!
return Ok(());
};
if let Err(err) = self
.store_transfer_event(
&tx.hash.to_string(),
tx.height.value() as i64,
index as i64,
msg.from_address.to_string(),
msg.to_address.to_string(),
unym_coin.to_string(),
Some(memo.clone()),
)
.await
{
warn!("Failed to store transfer event: {err}");
self.shared_state
.new_rejection(
tx.hash.to_string(),
tx.height.value(),
index as u32,
format!("storage failure: {err}"),
)
.await;
if is_watched {
if let Err(e) = self
.store_transfer_event(
&tx_hash,
height,
message_index,
sender,
recipient,
amount,
Some(memo.clone()),
)
.await
{
warn!("Failed to store transfer event: {}", e);
}
}
}
}
}
+8 -8
View File
@@ -3,8 +3,8 @@
use crate::cli::DEFAULT_NYX_CHAIN_WATCHER_ID;
use crate::config::payments_watcher::HttpAuthenticationOptions::AuthorizationBearerToken;
use crate::config::payments_watcher::PaymentWatcherConfig;
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatchersConfig};
use crate::config::payments_watcher::PaymentWatcherEntry;
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatcherConfig};
use crate::error::NyxChainWatcherError;
use nym_config::save_unformatted_config_to_file;
use nym_validator_client::nyxd::AccountId;
@@ -18,22 +18,22 @@ pub(crate) async fn execute(_args: Args) -> Result<(), NyxChainWatcherError> {
let data_dir = Config::default_data_directory(&config_path)?;
let builder = ConfigBuilder::new(config_path.clone(), data_dir).with_payment_watcher_config(
PaymentWatchersConfig {
watchers: vec![PaymentWatcherConfig {
PaymentWatcherConfig {
watchers: vec![PaymentWatcherEntry {
id: DEFAULT_NYX_CHAIN_WATCHER_ID.to_string(),
webhook_url: "https://webhook.site".to_string(),
watch_for_transfer_recipient_accounts: vec![AccountId::from_str(
watch_for_transfer_recipient_accounts: Some(vec![AccountId::from_str(
"n17g9a2pwwkg8m60wf59pq6mv0c2wusg9ukparkz",
)
.unwrap()],
.unwrap()]),
authentication: Some(AuthorizationBearerToken {
token: "1234".to_string(),
}),
description: None,
watch_for_chain_message_types: vec![
watch_for_chain_message_types: Some(vec![
"/cosmos.bank.v1beta1.MsgSend".to_string(),
"/ibc.applications.transfer.v1.MsgTransfer".to_string(),
],
]),
}],
},
);
@@ -20,7 +20,7 @@ pub(crate) struct Args {
value_delimiter = ',',
env = NYX_CHAIN_WATCHER_WATCH_ACCOUNTS
)]
pub watch_for_transfer_recipient_accounts: Vec<AccountId>,
pub watch_for_transfer_recipient_accounts: Option<Vec<AccountId>>,
/// (Override) Watch for chain messages of these types
#[clap(
@@ -28,7 +28,7 @@ pub(crate) struct Args {
value_delimiter = ',',
env = NYX_CHAIN_WATCHER_WATCH_CHAIN_MESSAGE_TYPES
)]
pub watch_for_chain_message_types: Vec<String>,
pub watch_for_chain_message_types: Option<Vec<String>>,
/// (Override) The webhook to call when we find something
#[clap(
@@ -1,7 +1,7 @@
use crate::cli::commands::run::args::Args;
use crate::cli::DEFAULT_NYX_CHAIN_WATCHER_ID;
use crate::config::payments_watcher::{HttpAuthenticationOptions, PaymentWatcherConfig};
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatchersConfig};
use crate::config::payments_watcher::{HttpAuthenticationOptions, PaymentWatcherEntry};
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatcherConfig};
use crate::error::NyxChainWatcherError;
use tracing::{info, warn};
@@ -18,8 +18,8 @@ pub(crate) fn get_run_config(args: Args) -> Result<Config, NyxChainWatcherError>
} = args;
// if there are no args set, then try load the config
if args.watch_for_transfer_recipient_accounts.is_empty()
&& args.watch_for_transfer_recipient_accounts.is_empty()
if args.watch_for_transfer_recipient_accounts.is_none()
&& args.watch_for_transfer_recipient_accounts.is_none()
&& args.chain_watcher_db_path.is_none()
{
info!("Loading default config file...");
@@ -27,12 +27,12 @@ pub(crate) fn get_run_config(args: Args) -> Result<Config, NyxChainWatcherError>
}
// set default messages
if watch_for_chain_message_types.is_empty() {
watch_for_chain_message_types = vec!["/cosmos.bank.v1beta1.MsgSend".to_string()];
if watch_for_chain_message_types.is_none() {
watch_for_chain_message_types = Some(vec!["/cosmos.bank.v1beta1.MsgSend".to_string()]);
}
// warn if no accounts set
if watch_for_transfer_recipient_accounts.is_empty() {
if watch_for_transfer_recipient_accounts.is_none() {
warn!(
"You did not specify any accounts to watch in {}. Only chain data will be stored.",
crate::env::vars::NYX_CHAIN_WATCHER_WATCH_ACCOUNTS
@@ -58,8 +58,8 @@ pub(crate) fn get_run_config(args: Args) -> Result<Config, NyxChainWatcherError>
let authentication =
webhook_auth.map(|token| HttpAuthenticationOptions::AuthorizationBearerToken { token });
let watcher_config = PaymentWatchersConfig {
watchers: vec![PaymentWatcherConfig {
let watcher_config = PaymentWatcherConfig {
watchers: vec![PaymentWatcherEntry {
id: DEFAULT_NYX_CHAIN_WATCHER_ID.to_string(),
description: None,
watch_for_transfer_recipient_accounts: watch_for_transfer_recipient_accounts
+39 -198
View File
@@ -2,115 +2,19 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::NyxChainWatcherError;
use anyhow::Context;
use std::time::Duration;
use time::OffsetDateTime;
use tokio::task::{JoinHandle, JoinSet};
use tokio_util::sync::CancellationToken;
use tracing::{error, info};
use tokio::join;
use tracing::{error, info, trace};
mod args;
mod config;
use crate::chain_scraper::run_chain_scraper;
use crate::db::DbPool;
use crate::http::state::{BankScraperModuleState, PaymentListenerState, PriceScraperState};
use crate::payment_listener::PaymentListener;
use crate::price_scraper::PriceScraper;
use crate::{db, http};
use crate::{db, http, payment_listener, price_scraper};
pub(crate) use args::Args;
use nym_task::signal::wait_for_signal;
async fn try_insert_watcher_execution_information(
db_pool: DbPool,
start: OffsetDateTime,
end: OffsetDateTime,
error_message: Option<String>,
) {
let _ = sqlx::query!(
r#"
INSERT INTO watcher_execution(start, end, error_message)
VALUES (?, ?, ?)
"#,
start,
end,
error_message
)
.execute(&db_pool)
.await
.inspect_err(|err| error!("failed to insert run information: {err}"));
}
async fn wait_for_shutdown(
db_pool: DbPool,
start: OffsetDateTime,
main_cancellation_token: CancellationToken,
scraper_cancellation_token: CancellationToken,
mut tasks: JoinSet<Option<anyhow::Result<()>>>,
) {
async fn finalize_shutdown(
db_pool: DbPool,
start: OffsetDateTime,
main_cancellation_token: CancellationToken,
scraper_cancellation_token: CancellationToken,
mut tasks: JoinSet<Option<anyhow::Result<()>>>,
error_message: Option<String>,
) {
// cancel all tasks
main_cancellation_token.cancel();
scraper_cancellation_token.cancel();
// stupid nasty and hacky workaround to make sure all relevant tasks have finished before hard aborting them
// nasty stupid and hacky workaround
tokio::time::sleep(Duration::from_secs(1)).await;
tasks.abort_all();
// insert execution result into the db
try_insert_watcher_execution_information(
db_pool,
start,
OffsetDateTime::now_utc(),
error_message,
)
.await
}
tokio::select! {
// graceful shutdown
_ = wait_for_signal() => {
info!("received shutdown signal");
finalize_shutdown(db_pool, start, main_cancellation_token, scraper_cancellation_token, tasks, None).await;
}
_ = scraper_cancellation_token.cancelled() => {
info!("the scraper has issued cancellation");
finalize_shutdown(db_pool, start, main_cancellation_token, scraper_cancellation_token, tasks, Some("unexpected scraper task cancellation".into())).await;
}
_ = main_cancellation_token.cancelled() => {
info!("one of the tasks has cancelled the token");
finalize_shutdown(db_pool, start, main_cancellation_token, scraper_cancellation_token, tasks, Some("unexpected main task cancellation".into())).await;
}
task_result = tasks.join_next() => {
// the first unwrap is fine => join set was not empty
let error_message = match task_result.unwrap() {
Err(_join_err) => Some("unexpected join error".to_string()),
Ok(Some(Ok(_))) => None,
Ok(Some(Err(err))) => Some(err.to_string()),
Ok(None) => {
Some("unexpected task cancellation".to_string())
}
};
error!("unexpected task termination: {error_message:?}");
finalize_shutdown(db_pool, start, main_cancellation_token, scraper_cancellation_token, tasks, error_message).await;
}
}
}
pub(crate) async fn execute(args: Args, http_port: u16) -> Result<(), NyxChainWatcherError> {
let start = OffsetDateTime::now_utc();
info!("passed arguments: {args:#?}");
trace!("passed arguments: {args:#?}");
let config = config::get_run_config(args)?;
@@ -125,7 +29,9 @@ pub(crate) async fn execute(args: Args, http_port: u16) -> Result<(), NyxChainWa
);
info!(
"Chain History Database path is {:?}",
std::path::Path::new(&config.chain_scraper_database_path()).canonicalize()
std::path::Path::new(&config.chain_scraper_database_path())
.canonicalize()
.unwrap_or_default()
);
// Ensure parent directory exists
@@ -135,115 +41,50 @@ pub(crate) async fn execute(args: Args, http_port: u16) -> Result<(), NyxChainWa
let connection_url = format!("sqlite://{}?mode=rwc", db_path);
let storage = db::Storage::init(connection_url).await?;
let watcher_pool = storage.pool_owned();
let watcher_pool = storage.pool_owned().await;
let mut tasks = JoinSet::new();
let cancellation_token = CancellationToken::new();
// Spawn the chain scraper and get its storage
let price_scraper_pool = storage.pool_owned();
let scraper_pool = storage.pool_owned();
let shutdown_pool = storage.pool_owned();
// Spawn the payment listener task
let payment_listener_handle = tokio::spawn({
let price_scraper_pool = storage.pool_owned().await;
let scraper_pool = storage.pool_owned().await;
run_chain_scraper(&config, scraper_pool).await?;
let payment_watcher_config = config.payment_watcher_config.unwrap_or_default();
// construct shared state
let payment_listener_shared_state = PaymentListenerState::new();
let price_scraper_shared_state = PriceScraperState::new();
let bank_scraper_module_shared_state = BankScraperModuleState::new();
// spawn all the tasks
// 1. chain scraper (note: this doesn't really spawn the full scraper on this task, but we don't want to be blocking waiting for its startup)
let scraper_token_handle: JoinHandle<anyhow::Result<CancellationToken>> = tokio::spawn({
let config = config.clone();
let shared_state = bank_scraper_module_shared_state.clone();
async move {
// this only blocks until startup sync is done; it then runs on its own set of tasks
let scraper = run_chain_scraper(&config, scraper_pool, shared_state).await?;
Ok(scraper.cancel_token())
if let Err(e) =
payment_listener::run_payment_listener(payment_watcher_config, price_scraper_pool)
.await
{
error!("Payment listener error: {}", e);
}
Ok::<_, anyhow::Error>(())
}
});
// 2. payment listener
let token = cancellation_token.clone();
let payment_watcher_config = config.payment_watcher_config.clone();
let payment_listener = PaymentListener::new(
price_scraper_pool,
payment_watcher_config,
payment_listener_shared_state.clone(),
)?;
{
tasks.spawn(async move {
token
.run_until_cancelled(async move {
payment_listener.run().await;
Ok(())
})
.await
});
}
// Clone pool for each task that needs it
//let background_pool = db_pool.clone();
// 3. price scraper (note, this task never terminates on its own)
let price_scraper = PriceScraper::new(price_scraper_shared_state.clone(), watcher_pool);
{
let token = cancellation_token.clone();
tasks.spawn(async move {
token
.run_until_cancelled(async move {
price_scraper.run().await;
Ok(())
})
.await
});
}
let price_scraper_handle = tokio::spawn(async move {
price_scraper::run_price_scraper(&watcher_pool).await;
});
// 4. http api
let http_server = http::server::build_http_api(
storage.pool_owned(),
&config,
http_port,
payment_listener_shared_state,
price_scraper_shared_state,
bank_scraper_module_shared_state,
)
.await?;
{
let token = cancellation_token.clone();
tasks.spawn(async move {
info!("Starting HTTP server on port {http_port}",);
async move {
Some(
http_server
.run(token.cancelled_owned())
.await
.context("http server failure"),
)
}
.await
});
}
let shutdown_handles = http::server::start_http_api(storage.pool_owned().await, http_port)
.await
.expect("Failed to start server");
// 1. wait for either shutdown or scraper having finished startup
tokio::select! {
_ = wait_for_signal() => {
info!("received shutdown signal while waiting for scraper to finish its startup");
return Ok(())
}
scraper_token = scraper_token_handle => {
let scraper_token = match scraper_token {
Ok(Ok(token)) => token,
Ok(Err(startup_err)) => {
error!("failed to startup the chain scraper: {startup_err}");
return Err(startup_err.into());
}
Err(runtime_err) => {
error!("failed to finish the scraper startup task: {runtime_err}");
return Ok(())
info!("Started HTTP server on port {}", http_port);
}
};
// Wait for the short-lived tasks to complete
let _ = join!(price_scraper_handle, payment_listener_handle);
wait_for_shutdown(shutdown_pool, start, cancellation_token, scraper_token, tasks).await
}
}
// Wait for a signal to terminate the long-running task
wait_for_signal().await;
if let Err(err) = shutdown_handles.shutdown().await {
error!("{err}");
};
Ok(())
}
+5 -6
View File
@@ -14,7 +14,7 @@ use tracing::{debug, error};
pub(crate) mod payments_watcher;
mod template;
pub use crate::config::payments_watcher::PaymentWatchersConfig;
pub use crate::config::payments_watcher::PaymentWatcherConfig;
use crate::error::NyxChainWatcherError;
const DEFAULT_NYM_CHAIN_WATCHER_DIR: &str = "nym-chain-watcher";
@@ -46,7 +46,7 @@ pub struct ConfigBuilder {
pub chain_scraper_db_path: Option<String>,
pub payment_watcher_config: Option<PaymentWatchersConfig>,
pub payment_watcher_config: Option<PaymentWatcherConfig>,
pub logging: Option<LoggingSettings>,
}
@@ -76,7 +76,7 @@ impl ConfigBuilder {
#[allow(dead_code)]
pub fn with_payment_watcher_config(
mut self,
payment_watcher_config: impl Into<PaymentWatchersConfig>,
payment_watcher_config: impl Into<PaymentWatcherConfig>,
) -> Self {
self.payment_watcher_config = Some(payment_watcher_config.into());
self
@@ -92,7 +92,7 @@ impl ConfigBuilder {
Config {
logging: self.logging.unwrap_or_default(),
save_path: Some(self.config_path),
payment_watcher_config: self.payment_watcher_config.unwrap_or_default(),
payment_watcher_config: self.payment_watcher_config,
data_dir: self.data_dir,
db_path: self.db_path,
chain_scraper_db_path: self.chain_scraper_db_path,
@@ -116,8 +116,7 @@ pub struct Config {
#[serde(skip)]
chain_scraper_db_path: Option<String>,
#[serde(default)]
pub payment_watcher_config: PaymentWatchersConfig,
pub payment_watcher_config: Option<PaymentWatcherConfig>,
#[serde(default)]
pub logging: LoggingSettings,
@@ -1,29 +1,19 @@
use nym_validator_client::nyxd::AccountId;
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct PaymentWatchersConfig {
pub watchers: Vec<PaymentWatcherConfig>,
}
impl PaymentWatchersConfig {
pub fn is_being_watched(&self, account: &str) -> bool {
self.watchers.iter().any(|watcher| {
watcher
.watch_for_transfer_recipient_accounts
.iter()
.any(|acc| acc.as_ref() == account)
})
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PaymentWatcherConfig {
pub watchers: Vec<PaymentWatcherEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentWatcherConfig {
pub struct PaymentWatcherEntry {
pub id: String,
pub description: Option<String>,
pub webhook_url: String,
pub watch_for_transfer_recipient_accounts: Vec<AccountId>,
pub watch_for_chain_message_types: Vec<String>,
pub watch_for_transfer_recipient_accounts: Option<Vec<AccountId>>,
pub watch_for_chain_message_types: Option<Vec<String>>,
pub authentication: Option<HttpAuthenticationOptions>,
}
+1 -1
View File
@@ -34,7 +34,7 @@ impl Storage {
}
/// Cloning pool is cheap, it's the same underlying set of connections
pub fn pool_owned(&self) -> DbPool {
pub async fn pool_owned(&self) -> DbPool {
self.pool.clone()
}
}
+2 -28
View File
@@ -1,11 +1,7 @@
use anyhow::Context;
use nym_validator_client::nyxd::Coin;
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use time::OffsetDateTime;
use utoipa::ToSchema;
#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
#[derive(Clone, Deserialize, Debug, ToSchema)]
pub(crate) struct CurrencyPrices {
pub(crate) chf: f32,
pub(crate) usd: f32,
@@ -15,7 +11,7 @@ pub(crate) struct CurrencyPrices {
}
// Struct to hold Coingecko response
#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
#[derive(Clone, Deserialize, Debug, ToSchema)]
pub(crate) struct CoingeckoPriceResponse {
pub(crate) nym: CurrencyPrices,
}
@@ -45,25 +41,3 @@ pub(crate) struct PaymentRecord {
pub(crate) timestamp: i64,
pub(crate) height: i64,
}
#[derive(Serialize, Deserialize, Debug, FromRow)]
pub(crate) struct Transaction {
pub(crate) id: i64,
pub(crate) tx_hash: String,
pub(crate) height: i64,
pub(crate) message_index: i64,
pub(crate) sender: String,
pub(crate) recipient: String,
pub(crate) amount: String,
pub(crate) memo: Option<String>,
pub(crate) created_at: Option<OffsetDateTime>,
}
impl Transaction {
pub(crate) fn funds(&self) -> anyhow::Result<Coin> {
self.amount
.as_str()
.parse()
.context("failed to parse transaction amount")
}
}
-55
View File
@@ -1,55 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use serde::{Deserialize, Serialize};
use std::collections::vec_deque::{IntoIter, Iter};
use std::collections::VecDeque;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct RingBuffer<T> {
#[serde(flatten)]
inner: VecDeque<T>,
}
impl<T> RingBuffer<T> {
pub fn new(capacity: usize) -> Self {
Self {
inner: VecDeque::with_capacity(capacity),
}
}
pub fn push(&mut self, item: T) {
if self.inner.len() == self.inner.capacity() {
self.inner.pop_front();
self.inner.push_back(item);
debug_assert!(self.inner.len() == self.inner.capacity());
} else {
self.inner.push_back(item);
}
}
pub fn iter(&self) -> Iter<'_, T> {
self.inner.iter()
}
}
impl<T> From<RingBuffer<T>> for VecDeque<T> {
fn from(value: RingBuffer<T>) -> Self {
value.inner
}
}
impl<T> From<RingBuffer<T>> for Vec<T> {
fn from(value: RingBuffer<T>) -> Self {
value.inner.into()
}
}
impl<T> IntoIterator for RingBuffer<T> {
type Item = T;
type IntoIter = IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
+2 -8
View File
@@ -8,7 +8,6 @@ use utoipa_swagger_ui::SwaggerUi;
use crate::http::{api_docs, server::HttpServer, state::AppState};
pub(crate) mod price;
pub(crate) mod status;
pub(crate) mod watcher;
pub(crate) struct RouterBuilder {
@@ -26,13 +25,8 @@ impl RouterBuilder {
"/",
axum::routing::get(|| async { Redirect::permanent("/swagger") }),
)
.nest(
"/v1",
Router::new()
.nest("/status", status::routes())
.nest("/price", price::routes())
.nest("/watcher", watcher::routes()),
);
.nest("/v1", Router::new().nest("/price", price::routes()))
.nest("/v1", Router::new().nest("/watcher", watcher::routes()));
Self {
unfinished_router: router,
-228
View File
@@ -1,228 +0,0 @@
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::http::models::status::{
ActivePaymentWatchersResponse, ApiStatus, BankModuleStatusResponse, BankMsgDetails,
BankMsgRejection, HealthResponse, PaymentListenerFailureDetails, PaymentListenerStatusResponse,
PriceScraperLastError, PriceScraperLastSuccess, PriceScraperStatusResponse, ProcessedPayment,
WatcherFailureDetails, WatcherState,
};
use crate::http::state::{
AppState, BankScraperModuleState, PaymentListenerState, PriceScraperState, StatusState,
};
use axum::extract::State;
use axum::routing::get;
use axum::{Json, Router};
use nym_bin_common::build_information::BinaryBuildInformationOwned;
use std::ops::Deref;
pub(crate) fn routes() -> Router<AppState> {
Router::new()
.route("/health", get(health))
.route("/build-information", get(build_information))
.route("/active-payment-watchers", get(active_payment_watchers))
.route("/payment-listener", get(payment_listener_status))
.route("/price-scraper", get(price_scraper_status))
.route("/bank-module-scraper", get(bank_module_status))
}
#[utoipa::path(
tag = "Status",
get,
path = "/build-information",
context_path = "/v1/status",
responses(
(status = 200, body = BinaryBuildInformationOwned)
)
)]
async fn build_information(State(state): State<StatusState>) -> Json<BinaryBuildInformationOwned> {
Json(state.build_information.to_owned())
}
#[utoipa::path(
tag = "Status",
get,
path = "/health",
context_path = "/v1/status",
responses(
(status = 200, body = HealthResponse)
)
)]
async fn health(State(state): State<StatusState>) -> Json<HealthResponse> {
let uptime = state.startup_time.elapsed();
let health = HealthResponse {
status: ApiStatus::Up,
uptime: uptime.as_secs(),
};
Json(health)
}
#[utoipa::path(
tag = "Status",
get,
path = "/active-payment-watchers",
context_path = "/v1/status",
responses(
(status = 200, body = ActivePaymentWatchersResponse)
)
)]
pub(crate) async fn active_payment_watchers(
State(state): State<AppState>,
) -> Json<ActivePaymentWatchersResponse> {
Json(ActivePaymentWatchersResponse {
watchers: state.registered_payment_watchers.deref().clone(),
})
}
#[utoipa::path(
tag = "Status",
get,
path = "/payment-listener",
context_path = "/v1/status",
responses(
(status = 200, body = PaymentListenerStatusResponse)
)
)]
pub(crate) async fn payment_listener_status(
State(state): State<PaymentListenerState>,
) -> Json<PaymentListenerStatusResponse> {
let guard = state.inner.read().await;
// sorry for the nasty conversion code here, run out of time : (
Json(PaymentListenerStatusResponse {
last_checked: guard.last_checked,
processed_payments_since_startup: guard.processed_payments_since_startup,
watcher_errors_since_startup: guard.watcher_errors_since_startup,
payment_listener_errors_since_startup: guard.payment_listener_errors_since_startup,
last_processed_payment: guard
.last_processed_payment
.as_ref()
.map(|p| ProcessedPayment {
processed_at: p.processed_at,
tx_hash: p.tx_hash.clone(),
message_index: p.message_index,
height: p.height,
sender: p.sender.clone(),
receiver: p.receiver.clone(),
funds: p.funds.clone(),
memo: p.memo.clone(),
}),
latest_failures: guard
.latest_failures
.iter()
.map(|f| PaymentListenerFailureDetails {
timestamp: f.timestamp,
error: f.error.clone(),
})
.collect(),
watchers: guard
.watchers
.iter()
.map(|(w, state)| {
(
w.clone(),
WatcherState {
latest_failures: state
.latest_failures
.iter()
.map(|f| WatcherFailureDetails {
timestamp: f.timestamp,
error: f.error.clone(),
})
.collect(),
},
)
})
.collect(),
})
}
#[utoipa::path(
tag = "Status",
get,
path = "/price-scraper",
context_path = "/v1/status",
responses(
(status = 200, body = PriceScraperStatusResponse)
)
)]
pub(crate) async fn price_scraper_status(
State(state): State<PriceScraperState>,
) -> Json<PriceScraperStatusResponse> {
let guard = state.inner.read().await;
Json(PriceScraperStatusResponse {
last_success: guard
.last_success
.as_ref()
.map(|s| PriceScraperLastSuccess {
timestamp: s.timestamp,
response: s.response.clone(),
}),
last_failure: guard.last_failure.as_ref().map(|f| PriceScraperLastError {
timestamp: f.timestamp,
message: f.message.clone(),
}),
})
}
#[utoipa::path(
tag = "Status",
get,
path = "/bank-module-scraper",
context_path = "/v1/status",
responses(
(status = 200, body = BankModuleStatusResponse)
)
)]
pub(crate) async fn bank_module_status(
State(state): State<BankScraperModuleState>,
) -> Json<BankModuleStatusResponse> {
let guard = state.inner.read().await;
Json(BankModuleStatusResponse {
processed_bank_msgs_since_startup: guard.processed_bank_msgs_since_startup,
processed_bank_msgs_to_watched_addresses_since_startup: guard
.processed_bank_msgs_to_watched_addresses_since_startup,
rejected_bank_msgs_to_watched_addresses_since_startup: guard
.rejected_bank_msgs_to_watched_addresses_since_startup,
last_seen_bank_msgs: guard
.last_seen_bank_msgs
.iter()
.map(|msg| BankMsgDetails {
processed_at: msg.processed_at,
tx_hash: msg.tx_hash.clone(),
height: msg.height,
index: msg.index,
from: msg.from.clone(),
to: msg.to.clone(),
amount: msg.amount.clone(),
memo: msg.memo.clone(),
})
.collect(),
last_seen_watched_bank_msgs: guard
.last_seen_watched_bank_msgs
.iter()
.map(|msg| BankMsgDetails {
processed_at: msg.processed_at,
tx_hash: msg.tx_hash.clone(),
height: msg.height,
index: msg.index,
from: msg.from.clone(),
to: msg.to.clone(),
amount: msg.amount.clone(),
memo: msg.memo.clone(),
})
.collect(),
last_rejected_watched_bank_msgs: guard
.last_rejected_watched_bank_msgs
.iter()
.map(|r| BankMsgRejection {
rejected_at: r.rejected_at,
tx_hash: r.tx_hash.clone(),
height: r.height,
index: r.index,
error: r.error.clone(),
})
.collect(),
})
}

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