Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b83fdd34af | |||
| 356cd2eeac | |||
| 0f6ec8610e | |||
| c3b8c4b2f7 | |||
| 271b9e545c | |||
| 9641f01670 | |||
| a7bb3e8d91 | |||
| 79ce611d21 | |||
| 960e817b8f | |||
| 8b03e66ba7 | |||
| 6a35581299 | |||
| ce124a29a7 | |||
| f62d8813e0 | |||
| a9cf016af2 | |||
| a8403b585b | |||
| e9a7b48da0 | |||
| 66792f57ed | |||
| f8d863249e | |||
| 7d59a2477a | |||
| eca88b0fa4 | |||
| b80a4c8614 | |||
| ec5d342e3a | |||
| 6565655861 | |||
| 5aba886f14 | |||
| 3ee73d541e | |||
| 4588a3036e | |||
| 6194ac07b8 | |||
| a7fcfef5a3 | |||
| fa927b82d8 | |||
| f724478763 | |||
| 040f4f2500 | |||
| 63002e784a | |||
| 4a0b683b70 | |||
| 9e84b1f0c1 | |||
| bf031ad6de | |||
| 933769401c | |||
| ddd85704bb | |||
| 17860c809f | |||
| 2d00fcd934 | |||
| c2c3df98cb | |||
| f429092e21 | |||
| d7ef68d8d1 | |||
| 1a334b575d | |||
| 2126736aff | |||
| a69aa23609 | |||
| 8a2d98e3ce | |||
| 9c4243914e | |||
| 143ede268d | |||
| 81bddb5f6d | |||
| 247ebb7c43 | |||
| 01c052e9a4 | |||
| 3880971e57 | |||
| 6bd31b9521 | |||
| 430c33eb04 | |||
| d45d1eb313 | |||
| b42e5b063e | |||
| f6b30d0db6 | |||
| c33e4c0836 | |||
| be92ccf0da | |||
| 35bf49c48c | |||
| 7335a3dad4 | |||
| 698883c03f | |||
| 8ddef08c72 | |||
| 0d8b3abc6f | |||
| aa2f336904 | |||
| eacaf84430 | |||
| c284b1e8b1 | |||
| 7785d085cf | |||
| bb5b2eafcf |
@@ -100,7 +100,6 @@ 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
|
||||
|
||||
@@ -6,6 +6,7 @@ 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])
|
||||
@@ -15,6 +16,7 @@ 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
+98
-61
@@ -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#22cd0a16b674af1629110a2dc8b6cf6c73ea4cd9"
|
||||
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
"group",
|
||||
"pairing",
|
||||
@@ -902,9 +902,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -941,7 +941,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
@@ -955,7 +955,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
@@ -1587,7 +1587,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"crossterm_winapi",
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"rustix 0.38.44",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -1799,7 +1799,7 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw2",
|
||||
"schemars",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
@@ -1814,7 +1814,7 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"schemars",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
@@ -2527,9 +2527,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.13.0"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
|
||||
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"subtle 2.6.1",
|
||||
@@ -4144,6 +4144,12 @@ 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"
|
||||
@@ -4780,7 +4786,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.53"
|
||||
version = "1.1.54"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4841,10 +4847,10 @@ dependencies = [
|
||||
"rand_chacha 0.3.1",
|
||||
"reqwest 0.12.4",
|
||||
"schemars",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.9.9",
|
||||
"sha2 0.10.8",
|
||||
"sqlx",
|
||||
"tempfile",
|
||||
"tendermint 0.40.1",
|
||||
@@ -4890,6 +4896,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
"tendermint 0.40.1",
|
||||
"tendermint-rpc",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"ts-rs",
|
||||
@@ -5029,7 +5036,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.50"
|
||||
version = "1.1.51"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -5112,7 +5119,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.50"
|
||||
version = "1.1.51"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -5324,7 +5331,7 @@ dependencies = [
|
||||
"bs58",
|
||||
"cfg-if",
|
||||
"criterion",
|
||||
"digest 0.9.0",
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
"group",
|
||||
"itertools 0.14.0",
|
||||
@@ -5333,7 +5340,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"rayon",
|
||||
"serde",
|
||||
"sha2 0.9.9",
|
||||
"sha2 0.10.8",
|
||||
"subtle 2.6.1",
|
||||
"thiserror 2.0.12",
|
||||
"zeroize",
|
||||
@@ -5606,7 +5613,7 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"sha2 0.9.9",
|
||||
"sha2 0.10.8",
|
||||
"thiserror 2.0.12",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -6055,7 +6062,7 @@ dependencies = [
|
||||
"nym-contracts-common",
|
||||
"rand_chacha 0.3.1",
|
||||
"schemars",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"serde_repr",
|
||||
@@ -6156,7 +6163,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.51"
|
||||
version = "1.1.52"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -6207,7 +6214,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.6.1"
|
||||
version = "1.7.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -6234,6 +6241,7 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-gateway",
|
||||
"nym-gateway-stats-storage",
|
||||
"nym-http-api-client",
|
||||
"nym-http-api-common",
|
||||
"nym-ip-packet-router",
|
||||
"nym-metrics",
|
||||
@@ -6256,7 +6264,7 @@ dependencies = [
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-types",
|
||||
"rand 0.8.5",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"si-scale",
|
||||
@@ -6348,6 +6356,7 @@ dependencies = [
|
||||
"nym-contracts-common",
|
||||
"nym-crypto",
|
||||
"nym-explorer-client",
|
||||
"nym-http-api-client",
|
||||
"nym-network-defaults",
|
||||
"nym-node-metrics",
|
||||
"nym-node-requests",
|
||||
@@ -6592,7 +6601,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.50"
|
||||
version = "1.1.51"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -7197,7 +7206,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.15"
|
||||
version = "0.1.16"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7227,7 +7236,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nyx-chain-watcher"
|
||||
version = "0.1.11"
|
||||
version = "0.1.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -7237,15 +7246,12 @@ 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",
|
||||
@@ -8410,9 +8416,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.9"
|
||||
version = "0.17.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24"
|
||||
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
@@ -8599,9 +8605,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rs_merkle"
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b241d2e59b74ef9e98d94c78c47623d04c8392abaf82014dfd372a16041128f"
|
||||
checksum = "bb09b49230ba22e8c676e7b75dfe2887dea8121f18b530ae0ba519ce442d2b21"
|
||||
dependencies = [
|
||||
"sha2 0.10.8",
|
||||
]
|
||||
@@ -8703,7 +8709,7 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8715,7 +8721,20 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"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",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -8991,9 +9010,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -9006,9 +9025,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.218"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -9046,18 +9065,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.16"
|
||||
version = "0.11.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "364fec0df39c49a083c9a8a18a23a6bcfd9af130fe9fe321d18520a0d113e09e"
|
||||
checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.218"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9160,9 +9179,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.19"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9433,9 +9452,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-packet"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "535f2c430778bf59c22249fcc1ed6d384129eb2f0f694706015d636c688f9ac6"
|
||||
checksum = "c23047e0cf36ff6904603f499fd13153425cdf5ba47bfbaedbc999da0bd92f4e"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arrayref",
|
||||
@@ -9954,15 +9973,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.17.1"
|
||||
version = "3.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
||||
checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand 2.3.0",
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"rustix 1.0.1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -10090,7 +10109,7 @@ dependencies = [
|
||||
"pin-project",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.11.27",
|
||||
"semver 1.0.25",
|
||||
"semver 1.0.26",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
@@ -10250,9 +10269,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.37"
|
||||
version = "0.3.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -10268,15 +10287,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.19"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -10319,9 +10338,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.43.0"
|
||||
version = "1.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
|
||||
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@@ -11246,6 +11265,24 @@ 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"
|
||||
@@ -12046,8 +12083,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"rustix",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"rustix 0.38.44",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+13
-13
@@ -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/testnet-manager/dkg-bypass-contract", "tools/internal/validator-status-check",
|
||||
"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.7.2"
|
||||
bytes = "1.10.1"
|
||||
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.4.2"
|
||||
rs_merkle = "1.5.0"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.22"
|
||||
semver = "1.0.25"
|
||||
serde = "1.0.217"
|
||||
serde_bytes = "0.11.16"
|
||||
semver = "1.0.26"
|
||||
serde = "1.0.219"
|
||||
serde_bytes = "0.11.17"
|
||||
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.1"
|
||||
sphinx-packet = "=0.3.2"
|
||||
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.15"
|
||||
tempfile = "3.18"
|
||||
thiserror = "2.0"
|
||||
time = "0.3.37"
|
||||
tokio = "1.43"
|
||||
time = "0.3.39"
|
||||
tokio = "1.44"
|
||||
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" }
|
||||
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect-updated" }
|
||||
group = { version = "0.13.0", default-features = false }
|
||||
ff = { version = "0.13.0", default-features = false }
|
||||
ff = { version = "0.13.1", 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,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.50"
|
||||
version = "1.1.51"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.50"
|
||||
version = "1.1.51"
|
||||
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,6 +658,9 @@ 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 {
|
||||
@@ -675,6 +678,7 @@ 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,6 +139,8 @@ where
|
||||
let gateway_setup = GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
};
|
||||
|
||||
let init_details =
|
||||
|
||||
@@ -187,6 +187,8 @@ 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).await {
|
||||
match fs_backend::Backend::try_load(db_path, surb_config.fresh_sender_tags).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");
|
||||
|
||||
@@ -11,6 +11,8 @@ 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;
|
||||
@@ -313,9 +315,15 @@ 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());
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway_listener,
|
||||
gateway_id,
|
||||
our_identity.clone(),
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
);
|
||||
|
||||
gateway_client.establish_connection().await.map_err(|err| {
|
||||
log::warn!("Failed to establish connection with gateway!");
|
||||
|
||||
@@ -23,6 +23,8 @@ 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;
|
||||
@@ -53,6 +55,7 @@ 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,
|
||||
@@ -108,9 +111,14 @@ 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)
|
||||
.await?;
|
||||
let registration = helpers::register_with_gateway(
|
||||
gateway_id,
|
||||
gateway_listener.clone(),
|
||||
our_identity,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
GatewayDetails::new_remote(
|
||||
gateway_id,
|
||||
@@ -203,9 +211,19 @@ 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).await
|
||||
setup_new_gateway(
|
||||
key_store,
|
||||
details_store,
|
||||
specification,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
)
|
||||
.await
|
||||
}
|
||||
GatewaySetup::ReuseConnection {
|
||||
authenticated_ephemeral_client,
|
||||
|
||||
@@ -18,6 +18,8 @@ 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;
|
||||
@@ -208,6 +210,10 @@ 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 {
|
||||
@@ -231,6 +237,8 @@ impl Debug for GatewaySetup {
|
||||
GatewaySetup::New {
|
||||
specification,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: _,
|
||||
} => f
|
||||
.debug_struct("GatewaySetup::New")
|
||||
.field("specification", specification)
|
||||
@@ -270,6 +278,8 @@ 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::{error, info, warn};
|
||||
use log::{debug, error, info, warn};
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -52,7 +52,10 @@ impl Backend {
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
pub async fn try_load<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
|
||||
pub async fn try_load<P: AsRef<Path>>(
|
||||
database_path: P,
|
||||
fresh_sender_tags: bool,
|
||||
) -> Result<Self, StorageError> {
|
||||
let owned_path: PathBuf = database_path.as_ref().into();
|
||||
if owned_path.file_name().is_none() {
|
||||
return Err(StorageError::DatabasePathWithoutFilename {
|
||||
@@ -118,6 +121,9 @@ 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,6 +1065,7 @@ 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;
|
||||
@@ -1090,7 +1091,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
stats_reporter: ClientStatsSender::new(None, task_client.clone()),
|
||||
negotiated_protocol: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
connection_fd_callback,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ pub(crate) async fn connect_async(
|
||||
// Do a DNS lookup for the domain using our custom DNS resolver
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.await
|
||||
.inspect_err(|err| tracing::error!("Resolve error {err}"))?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
@@ -49,20 +50,27 @@ 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| GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: err.into(),
|
||||
.map_err(|err| {
|
||||
tracing::error!("Couldn't create the socket");
|
||||
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,6 +83,12 @@ 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,8 +12,9 @@ use nym_api_requests::ecash::models::{
|
||||
};
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_api_requests::models::{
|
||||
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
||||
AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainStatusResponse,
|
||||
LegacyDescribedMixNode, NodePerformanceResponse, NodeRefreshBody, NymNodeDescription,
|
||||
PerformanceHistoryResponse, RewardedSetResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
|
||||
@@ -69,6 +70,19 @@ 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> {
|
||||
@@ -1043,6 +1057,15 @@ 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,6 +49,8 @@ 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";
|
||||
@@ -70,4 +72,5 @@ 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,6 +62,7 @@ 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},
|
||||
|
||||
@@ -21,7 +21,7 @@ lazy_static = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
rand_core = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
sha2 = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
+96
-2
@@ -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.as_ref(), domain, &mut output);
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>, _>([msg], 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,3 +112,97 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ 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)]
|
||||
@@ -112,15 +113,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 is too stale to process")]
|
||||
StaleRequest,
|
||||
#[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 timestamp is smaller or equal to a one previously used")]
|
||||
#[error("the provided request timestamp is smaller or equal to one previously used")]
|
||||
RequestReuse,
|
||||
}
|
||||
|
||||
@@ -38,13 +38,22 @@ impl AuthenticateRequest {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify_timestamp(&self, max_request_age: Duration) -> Result<(), AuthenticationFailure> {
|
||||
pub fn verify_timestamp(
|
||||
&self,
|
||||
max_request_timestamp_skew: Duration,
|
||||
) -> Result<(), AuthenticationFailure> {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
if self.content.request_timestamp() + max_request_age < now {
|
||||
return Err(AuthenticationFailure::StaleRequest);
|
||||
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() > now {
|
||||
return Err(AuthenticationFailure::RequestTimestampInFuture);
|
||||
if self.content.request_timestamp() - max_request_timestamp_skew > now {
|
||||
return Err(AuthenticationFailure::ExcessiveTimestampSkew {
|
||||
received: self.content.request_timestamp(),
|
||||
server: now,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 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
|
||||
@@ -9,6 +12,19 @@
|
||||
//!
|
||||
//! 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;
|
||||
@@ -33,6 +49,13 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,8 @@ 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 {
|
||||
@@ -239,37 +241,46 @@ 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 {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client_builder = reqwest::ClientBuilder::new();
|
||||
Ok(Self::new_with_url(url.into_url()?))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client_builder = {
|
||||
let r = reqwest::ClientBuilder::new()
|
||||
.dns_resolver(Arc::new(HickoryDnsResolver::default()));
|
||||
/// 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");
|
||||
}
|
||||
|
||||
// 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(target_arch = "wasm32")]
|
||||
let reqwest_client_builder = reqwest::ClientBuilder::new();
|
||||
|
||||
Ok(ClientBuilder {
|
||||
url: url.into_url()?,
|
||||
timeout: None,
|
||||
custom_user_agent: false,
|
||||
reqwest_client_builder,
|
||||
})
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,10 +336,18 @@ 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()?
|
||||
};
|
||||
|
||||
@@ -355,6 +374,9 @@ 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",
|
||||
|
||||
@@ -15,10 +15,10 @@ bls12_381 = { workspace = true, features = ["alloc", "pairings", "experimental",
|
||||
bincode.workspace = true
|
||||
cfg-if.workspace = true
|
||||
itertools = { workspace = true }
|
||||
digest = "0.9"
|
||||
digest = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
sha2 = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
rayon = { workspace = true, optional = true }
|
||||
|
||||
@@ -113,17 +113,13 @@ 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.as_ref(),
|
||||
SCALAR_HASH_DOMAIN,
|
||||
&mut output,
|
||||
);
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>, _>([msg], SCALAR_HASH_DOMAIN, &mut output);
|
||||
output[0]
|
||||
}
|
||||
|
||||
@@ -401,4 +397,75 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ pub use sphinx_packet::{
|
||||
route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier},
|
||||
surb::{SURBMaterial, SURB},
|
||||
version::Version,
|
||||
version::UPDATED_LEGACY_VERSION,
|
||||
Error as SphinxError, ProcessedPacket, ProcessedPacketData,
|
||||
};
|
||||
|
||||
@@ -91,12 +90,8 @@ 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,9 +182,11 @@ 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 {
|
||||
msg_module
|
||||
.handle_msg(index, msg, &block_tx, &mut tx)
|
||||
.await?
|
||||
if msg.type_url == msg_module.type_url() {
|
||||
msg_module
|
||||
.handle_msg(index, msg, &block_tx, &mut tx)
|
||||
.await?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,15 @@ 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,6 +9,8 @@ use cosmrs::Any;
|
||||
|
||||
#[async_trait]
|
||||
pub trait MsgModule {
|
||||
fn type_url(&self) -> String;
|
||||
|
||||
async fn handle_msg(
|
||||
&mut self,
|
||||
index: usize,
|
||||
|
||||
@@ -103,4 +103,8 @@ impl LaneQueueLengthsInner {
|
||||
{
|
||||
self.map.entry(*lane).and_modify(f);
|
||||
}
|
||||
|
||||
pub fn total(&self) -> usize {
|
||||
self.map.values().sum()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,6 +494,9 @@ 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 {
|
||||
@@ -525,6 +528,7 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -548,6 +552,7 @@ 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,6 +378,9 @@ 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 {
|
||||
@@ -416,6 +419,7 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+50
-12
@@ -1185,6 +1185,7 @@ name = "nym-pemstore"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1251,6 +1252,12 @@ 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"
|
||||
@@ -1522,18 +1529,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -1558,9 +1565,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1777,9 +1784,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.37"
|
||||
version = "0.3.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -1794,15 +1801,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.19"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -1840,6 +1847,37 @@ 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"
|
||||
|
||||
@@ -7,4 +7,6 @@ package-lock.json
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env
|
||||
.env
|
||||
|
||||
scratch.md
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
- 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
@@ -1 +1 @@
|
||||
807_251_217
|
||||
808_623_916
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.64%
|
||||
0.68%
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
44.811
|
||||
42.157
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1_025_628
|
||||
1_028_488
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
403_625_608
|
||||
404_311_958
|
||||
|
||||
+3
-3
@@ -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 | 192_748_782 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 191_376_083 |
|
||||
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 807_251_217 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_025_628 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 808_623_916 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_028_488 |
|
||||
|
||||
@@ -1 +1 @@
|
||||
Wednesday, February 26th 2025, 16:02:47 UTC
|
||||
Tuesday, March 11th 2025, 11:04:18 UTC
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
{
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
{
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# Node Status API
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
<Callout type="info">
|
||||
People building applications or dashboards which requires information about nodes, their uptime, and their delegations should use this instead of Habourmaster.
|
||||
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.
|
||||
</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.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ns-api-run-deploy":"Run Instance",
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
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={{
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
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.
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"mainnet":"Mainnet Endpoints"
|
||||
}
|
||||
@@ -52,21 +52,22 @@ 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.1`
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.6.2`
|
||||
|
||||
```shell
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
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
|
||||
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
|
||||
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])
|
||||
@@ -76,6 +77,7 @@ 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-06T14:28:21.408539599Z
|
||||
Build Version: 1.6.1
|
||||
Commit SHA: 1fb2ebad7ad1bad455d5b896ea14204211727417
|
||||
Commit Date: 2025-03-06T15:26:18.000000000+01:00
|
||||
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
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.85.0
|
||||
rustc Channel: stable
|
||||
|
||||
@@ -3,8 +3,9 @@ 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 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'
|
||||
|
||||
# VPS Setup & Configuration
|
||||
|
||||
@@ -42,14 +43,18 @@ 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
|
||||
apt -y install ca-certificates jq curl wget ufw jq tmux pkg-config build-essential libssl-dev git ntp ntpdate
|
||||
```
|
||||
- Double check ufw is installed correctly
|
||||
```sh
|
||||
apt install ufw --fix-missing
|
||||
```
|
||||
|
||||
###### 2. Configure your firewall using Uncomplicated Firewall (UFW)
|
||||
###### 2. Synchronize time of your server
|
||||
|
||||
<NTPSync />
|
||||
|
||||
###### 3. 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`.
|
||||
|
||||
@@ -70,7 +75,7 @@ ufw enable
|
||||
ufw status
|
||||
```
|
||||
|
||||
###### 3. Open all needed ports to have your firewall for `nym-node` working correctly
|
||||
###### 4. Open all needed ports to have your firewall for `nym-node` working correctly
|
||||
|
||||
<div>
|
||||
<Tabs items={[
|
||||
|
||||
@@ -102,8 +102,8 @@ pub struct Debug {
|
||||
|
||||
pub zk_nym_tickets: ZkNymTicketHandlerDebug,
|
||||
|
||||
/// Defines the maximum age of a signed authentication request before it's deemed too stale to process.
|
||||
pub maximum_auth_request_age: Duration,
|
||||
/// Defines the timestamp skew of a signed authentication request before it's deemed too excessive to process.
|
||||
pub max_request_timestamp_skew: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -14,12 +14,11 @@ use std::time::Duration;
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Config {
|
||||
pub(crate) enforce_zk_nym: bool,
|
||||
pub(crate) max_auth_request_age: Duration,
|
||||
pub(crate) max_request_timestamp_skew: 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,9 +131,6 @@ 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,
|
||||
@@ -641,7 +638,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_auth_request_age)?;
|
||||
request.verify_timestamp(self.shared_state.cfg.max_request_timestamp_skew)?;
|
||||
|
||||
// does the message signature verify?
|
||||
request.verify_signature()?;
|
||||
|
||||
@@ -6,9 +6,8 @@ use crate::node::client_handling::websocket::connection_handler::FreshHandler;
|
||||
use nym_task::TaskClient;
|
||||
use rand::rngs::OsRng;
|
||||
use std::net::SocketAddr;
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::{io, process};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::*;
|
||||
|
||||
@@ -34,6 +33,76 @@ 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) {
|
||||
@@ -46,8 +115,6 @@ impl Listener {
|
||||
}
|
||||
};
|
||||
|
||||
let open_connections = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
@@ -55,38 +122,7 @@ impl Listener {
|
||||
trace!("client_handling::Listener: received shutdown");
|
||||
}
|
||||
connection = tcp_listener.accept() => {
|
||||
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}"),
|
||||
}
|
||||
self.try_handle_accepted_connection(connection)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ impl GatewayTasksBuilder {
|
||||
let shared_state = websocket::CommonHandlerState {
|
||||
cfg: websocket::Config {
|
||||
enforce_zk_nym: self.config.gateway.enforce_zk_nyms,
|
||||
max_auth_request_age: self.config.debug.maximum_auth_request_age,
|
||||
max_request_timestamp_skew: self.config.debug.max_request_timestamp_skew,
|
||||
bandwidth: (&self.config).into(),
|
||||
},
|
||||
ecash_verifier: self.ecash_manager().await?,
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.53"
|
||||
version = "1.1.54"
|
||||
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 = "0.9"
|
||||
sha2 = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -17,6 +17,7 @@ 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 }
|
||||
|
||||
@@ -27,9 +27,7 @@ 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, BinaryBuildInformationOwned, NodeRoles,
|
||||
};
|
||||
use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, NodeRoles};
|
||||
use schemars::gen::SchemaGenerator;
|
||||
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
||||
use schemars::JsonSchema;
|
||||
@@ -43,6 +41,8 @@ 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,6 +1215,7 @@ 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,
|
||||
}
|
||||
@@ -1225,10 +1226,11 @@ pub enum ApiStatus {
|
||||
Up,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Default, schemars::JsonSchema, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ChainStatus {
|
||||
Synced,
|
||||
#[default]
|
||||
Unknown,
|
||||
Stalled {
|
||||
#[serde(
|
||||
@@ -1428,7 +1430,297 @@ 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};
|
||||
|
||||
@@ -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,11 +1,12 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::network::models::{ChainStatusResponse, ContractInformation, NetworkDetails};
|
||||
use crate::network::models::{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;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// 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;
|
||||
@@ -29,292 +27,3 @@ 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,7 @@ 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()
|
||||
}) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::network::models::{ChainStatus, NetworkDetails};
|
||||
use crate::network::models::NetworkDetails;
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::handlers::unstable;
|
||||
use crate::node_status_api::models::AxumErrorResponse;
|
||||
@@ -15,7 +15,9 @@ use crate::support::caching::Cache;
|
||||
use crate::support::nyxd::Client;
|
||||
use crate::support::storage;
|
||||
use axum::extract::FromRef;
|
||||
use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation};
|
||||
use nym_api_requests::models::{
|
||||
DetailedChainStatus, GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation,
|
||||
};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_task::TaskManager;
|
||||
use nym_topology::CachedEpochRewardedSet;
|
||||
@@ -151,7 +153,7 @@ impl ChainStatusCache {
|
||||
|
||||
struct ChainStatusCacheInner {
|
||||
last_refreshed_at: OffsetDateTime,
|
||||
cache_value: ChainStatus,
|
||||
cache_value: DetailedChainStatus,
|
||||
}
|
||||
|
||||
impl ChainStatusCacheInner {
|
||||
@@ -167,7 +169,7 @@ impl ChainStatusCache {
|
||||
pub(crate) async fn get_or_refresh(
|
||||
&self,
|
||||
client: &Client,
|
||||
) -> Result<ChainStatus, AxumErrorResponse> {
|
||||
) -> Result<DetailedChainStatus, AxumErrorResponse> {
|
||||
if let Some(cached) = self.check_cache().await {
|
||||
return Ok(cached);
|
||||
}
|
||||
@@ -175,7 +177,7 @@ impl ChainStatusCache {
|
||||
self.refresh(client).await
|
||||
}
|
||||
|
||||
async fn check_cache(&self) -> Option<ChainStatus> {
|
||||
async fn check_cache(&self) -> Option<DetailedChainStatus> {
|
||||
let guard = self.inner.read().await;
|
||||
let inner = guard.as_ref()?;
|
||||
if inner.is_valid(self.cache_ttl) {
|
||||
@@ -184,7 +186,7 @@ impl ChainStatusCache {
|
||||
None
|
||||
}
|
||||
|
||||
async fn refresh(&self, client: &Client) -> Result<ChainStatus, AxumErrorResponse> {
|
||||
async fn refresh(&self, client: &Client) -> Result<DetailedChainStatus, AxumErrorResponse> {
|
||||
// 1. attempt to get write lock permit
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
@@ -201,7 +203,7 @@ impl ChainStatusCache {
|
||||
.block_info(abci.last_block_height.value() as u32)
|
||||
.await?;
|
||||
|
||||
let status = ChainStatus {
|
||||
let status = DetailedChainStatus {
|
||||
abci: abci.into(),
|
||||
latest_block: block.into(),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# 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,7 +10,8 @@ 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_extension("public");
|
||||
let pub_key_path = priv_key_path.with_file_name("public-key");
|
||||
|
||||
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::error;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub(crate) struct GwProbe {
|
||||
path: String,
|
||||
@@ -10,21 +10,62 @@ 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) => {
|
||||
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());
|
||||
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)
|
||||
}
|
||||
}
|
||||
"Unable to get probe version".to_string()
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get command output: {}", e);
|
||||
format!("Failed to get command output: {}", e)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to get probe version: {}", e);
|
||||
"Failed to get probe version".to_string()
|
||||
error!("Failed to spawn process: {}", e);
|
||||
format!("Failed to spawn process: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ 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,8 +97,12 @@ impl Monitor {
|
||||
.clone()
|
||||
.expect("rust sdk mainnet default missing api_url");
|
||||
|
||||
let api_client =
|
||||
NymApiClient::new_with_timeout(default_api_url, self.nym_api_client_timeout);
|
||||
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 described_nodes = api_client
|
||||
.get_all_described_nodes()
|
||||
@@ -296,9 +300,14 @@ impl Monitor {
|
||||
Some(location) => return location,
|
||||
None => {
|
||||
for ip in node.description.host_information.ip_address.iter() {
|
||||
if let Ok(location) = self.ipinfo.locate_ip(ip.to_string()).await {
|
||||
self.geocache.insert(node_id, location.clone()).await;
|
||||
return location;
|
||||
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 no data could be retrieved
|
||||
|
||||
@@ -57,7 +57,12 @@ async fn run(
|
||||
.clone()
|
||||
.expect("rust sdk mainnet default missing api_url");
|
||||
|
||||
let api_client = NymApiClient::new_with_timeout(default_api_url, nym_api_client_timeout);
|
||||
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 };
|
||||
|
||||
//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?;
|
||||
@@ -170,6 +175,7 @@ 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
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node"
|
||||
version = "1.6.1"
|
||||
version = "1.7.0"
|
||||
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]`
|
||||
|
||||
@@ -45,7 +45,7 @@ impl NetworkStats {
|
||||
|
||||
pub fn active_ingress_websocket_connections_count(&self) -> usize {
|
||||
self.active_ingress_websocket_connections
|
||||
.load(Ordering::Relaxed)
|
||||
.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn active_egress_mixnet_connections_counter(&self) -> Arc<AtomicUsize> {
|
||||
|
||||
@@ -50,8 +50,13 @@ pub struct Debug {
|
||||
/// The maximum number of client connections the gateway will keep open at once.
|
||||
pub maximum_open_connections: usize,
|
||||
|
||||
/// Defines the maximum age of a signed authentication request before it's deemed too stale to process.
|
||||
pub maximum_auth_request_age: Duration,
|
||||
/// 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,
|
||||
|
||||
pub stale_messages: StaleMessageDebug,
|
||||
|
||||
@@ -61,9 +66,10 @@ pub struct Debug {
|
||||
}
|
||||
|
||||
impl Debug {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
impl Default for Debug {
|
||||
@@ -71,7 +77,8 @@ impl Default for Debug {
|
||||
Debug {
|
||||
message_retrieval_limit: Self::DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
maximum_open_connections: Self::DEFAULT_MAXIMUM_OPEN_CONNECTIONS,
|
||||
maximum_auth_request_age: Self::DEFAULT_MAXIMUM_AUTH_REQUEST_AGE,
|
||||
max_request_timestamp_skew: Self::DEFAULT_MAXIMUM_AUTH_REQUEST_TIMESTAMP_SKEW,
|
||||
minimum_mix_performance: Self::DEFAULT_MINIMUM_MIX_PERFORMANCE,
|
||||
stale_messages: Default::default(),
|
||||
client_bandwidth: Default::default(),
|
||||
zk_nym_tickets: Default::default(),
|
||||
|
||||
@@ -60,7 +60,7 @@ fn ephemeral_gateway_config(config: &Config) -> nym_gateway::config::Config {
|
||||
.zk_nym_tickets
|
||||
.maximum_time_between_redemption,
|
||||
},
|
||||
maximum_auth_request_age: config.gateway_tasks.debug.maximum_auth_request_age,
|
||||
max_request_timestamp_skew: config.gateway_tasks.debug.max_request_timestamp_skew,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
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;
|
||||
@@ -209,3 +210,9 @@ pub enum ServiceProvidersError {
|
||||
#[error(transparent)]
|
||||
ExternalClientCore(#[from] ClientCoreError),
|
||||
}
|
||||
|
||||
impl From<HttpClientError> for NymNodeError {
|
||||
fn from(value: HttpClientError) -> Self {
|
||||
Self::HttpFailure(NymNodeHttpError::ClientError { source: value })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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;
|
||||
@@ -24,4 +25,10 @@ pub enum NymNodeHttpError {
|
||||
#[from]
|
||||
source: nym_crypto::asymmetric::encryption::KeyRecoveryError,
|
||||
},
|
||||
|
||||
#[error("error building or using HTTP client: {source}")]
|
||||
ClientError {
|
||||
#[from]
|
||||
source: HttpClientError,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -814,9 +814,23 @@ impl NymNode {
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
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 };
|
||||
|
||||
// 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());
|
||||
|
||||
@@ -9,6 +9,7 @@ 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;
|
||||
@@ -167,6 +168,7 @@ impl NodesQuerier {
|
||||
) -> Result<NodesByAddressesResponse, ValidatorClientError> {
|
||||
let res = self
|
||||
.client
|
||||
.nym_api
|
||||
.nodes_by_addresses(ips)
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to obtain node information: {err}"));
|
||||
@@ -174,7 +176,7 @@ impl NodesQuerier {
|
||||
if res.is_err() {
|
||||
self.use_next_nym_api()
|
||||
}
|
||||
res
|
||||
Ok(res?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,9 +265,14 @@ 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::new_with_user_agent(nym_api_urls[0].clone(), user_agent),
|
||||
client: NymApiClient { nym_api },
|
||||
nym_api_urls,
|
||||
currently_used_api: 0,
|
||||
},
|
||||
|
||||
Generated
+42
-40
@@ -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#22cd0a16b674af1629110a2dc8b6cf6c73ea4cd9"
|
||||
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
"group",
|
||||
"pairing",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"serdect 0.3.0-pre.0",
|
||||
"serdect 0.3.0",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -540,11 +540,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.82"
|
||||
version = "1.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
|
||||
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1583,9 +1583,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.13.0"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
|
||||
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
@@ -3293,6 +3293,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
"tendermint 0.40.1",
|
||||
"tendermint-rpc",
|
||||
"thiserror 2.0.11",
|
||||
"time",
|
||||
"utoipa",
|
||||
@@ -3341,7 +3342,7 @@ dependencies = [
|
||||
"bls12_381",
|
||||
"bs58",
|
||||
"cfg-if",
|
||||
"digest 0.9.0",
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
"group",
|
||||
"itertools 0.14.0",
|
||||
@@ -3349,7 +3350,7 @@ dependencies = [
|
||||
"nym-pemstore",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"sha2 0.9.9",
|
||||
"sha2 0.10.8",
|
||||
"subtle",
|
||||
"thiserror 2.0.11",
|
||||
"zeroize",
|
||||
@@ -3557,6 +3558,7 @@ name = "nym-pemstore"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4759,16 +4761,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.3"
|
||||
version = "0.17.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e"
|
||||
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.10",
|
||||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4782,9 +4784,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rs_merkle"
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b241d2e59b74ef9e98d94c78c47623d04c8392abaf82014dfd372a16041128f"
|
||||
checksum = "bb09b49230ba22e8c676e7b75dfe2887dea8121f18b530ae0ba519ce442d2b21"
|
||||
dependencies = [
|
||||
"sha2 0.10.8",
|
||||
]
|
||||
@@ -5062,18 +5064,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -5089,18 +5091,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.16"
|
||||
version = "0.11.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "364fec0df39c49a083c9a8a18a23a6bcfd9af130fe9fe321d18520a0d113e09e"
|
||||
checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5196,9 +5198,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serdect"
|
||||
version = "0.3.0-pre.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "791ef964bfaba6be28a5c3f0c56836e17cb711ac009ca1074b9c735a3ebf240a"
|
||||
checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"serde",
|
||||
@@ -5269,6 +5271,12 @@ 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"
|
||||
@@ -5363,12 +5371,6 @@ 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"
|
||||
@@ -6055,9 +6057,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.37"
|
||||
version = "0.3.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa 1.0.9",
|
||||
@@ -6072,15 +6074,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.19"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -6113,9 +6115,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.43.0"
|
||||
version = "1.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
|
||||
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
||||
@@ -157,20 +157,6 @@ 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>
|
||||
|
||||
Generated
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO watcher_execution(start, end, error_message)\n VALUES (?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1aa7733ad4bbf3e6b8db909b8646bee247bc021b9534f1d4b0fcad32e2e56218"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT * FROM transactions\n WHERE height > ? \n ORDER BY height ASC, message_index ASC\n ",
|
||||
"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 ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -44,7 +44,7 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"name": "created_at: ::time::OffsetDateTime",
|
||||
"ordinal": 8,
|
||||
"type_info": "Datetime"
|
||||
}
|
||||
@@ -64,5 +64,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "7b9abf4ff422b8d7a942955dc4fba380e7d5f0127f4745705b8ac9af6c170d19"
|
||||
"hash": "f69907735e9b1e1572c4bf6fe8d44d4ea4e55c2a9c4d4f7e1c7e57bcb848ee08"
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nyx-chain-watcher"
|
||||
version = "0.1.11"
|
||||
version = "0.1.14"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
@@ -23,16 +23,11 @@ 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 }
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* 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
|
||||
)
|
||||
@@ -1,12 +1,14 @@
|
||||
use crate::config::PaymentWatcherConfig;
|
||||
use crate::config::PaymentWatchersConfig;
|
||||
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, NyxdScraper, ParsedTransactionResponse,
|
||||
PruningOptions, TxModule,
|
||||
error::ScraperError, storage::StorageTransaction, MsgModule, NyxdScraper,
|
||||
ParsedTransactionResponse, PruningOptions,
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
use std::fs;
|
||||
@@ -15,6 +17,7 @@ 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");
|
||||
|
||||
@@ -58,9 +61,10 @@ pub(crate) async fn run_chain_scraper(
|
||||
use_best_effort_start_height,
|
||||
},
|
||||
})
|
||||
.with_tx_module(EventScraperModule::new(
|
||||
.with_msg_module(BankScraperModule::new(
|
||||
db_pool,
|
||||
config.payment_watcher_config.clone().unwrap_or_default(),
|
||||
config.payment_watcher_config.clone(),
|
||||
shared_state,
|
||||
));
|
||||
|
||||
let instance = scraper.build_and_start().await?;
|
||||
@@ -71,16 +75,22 @@ pub(crate) async fn run_chain_scraper(
|
||||
Ok(instance)
|
||||
}
|
||||
|
||||
pub struct EventScraperModule {
|
||||
pub struct BankScraperModule {
|
||||
db_pool: SqlitePool,
|
||||
payment_config: PaymentWatcherConfig,
|
||||
payment_config: PaymentWatchersConfig,
|
||||
shared_state: BankScraperModuleState,
|
||||
}
|
||||
|
||||
impl EventScraperModule {
|
||||
pub fn new(db_pool: SqlitePool, payment_config: PaymentWatcherConfig) -> Self {
|
||||
impl BankScraperModule {
|
||||
pub fn new(
|
||||
db_pool: SqlitePool,
|
||||
payment_config: PaymentWatchersConfig,
|
||||
shared_state: BankScraperModuleState,
|
||||
) -> Self {
|
||||
Self {
|
||||
db_pool,
|
||||
payment_config,
|
||||
shared_state,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,23 +118,47 @@ impl EventScraperModule {
|
||||
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 TxModule for EventScraperModule {
|
||||
async fn handle_tx(
|
||||
impl MsgModule for BankScraperModule {
|
||||
fn type_url(&self) -> String {
|
||||
<MsgSend as Msg>::Proto::type_url()
|
||||
}
|
||||
|
||||
async fn handle_msg(
|
||||
&mut self,
|
||||
index: usize,
|
||||
msg: &Any,
|
||||
tx: &ParsedTransactionResponse,
|
||||
_: &mut StorageTransaction,
|
||||
_storage_tx: &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
|
||||
@@ -132,60 +166,53 @@ impl TxModule for EventScraperModule {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 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;
|
||||
let msg = self.recover_bank_msg(tx.hash, index, msg)?;
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check if any watcher is watching this recipient
|
||||
let is_watched = self
|
||||
.payment_config
|
||||
.is_being_watched(msg.to_address.as_ref());
|
||||
|
||||
// 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
|
||||
}
|
||||
});
|
||||
self.shared_state
|
||||
.new_bank_msg(tx, index, &msg, is_watched)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
use crate::cli::DEFAULT_NYX_CHAIN_WATCHER_ID;
|
||||
use crate::config::payments_watcher::HttpAuthenticationOptions::AuthorizationBearerToken;
|
||||
use crate::config::payments_watcher::PaymentWatcherEntry;
|
||||
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatcherConfig};
|
||||
use crate::config::payments_watcher::PaymentWatcherConfig;
|
||||
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatchersConfig};
|
||||
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(
|
||||
PaymentWatcherConfig {
|
||||
watchers: vec![PaymentWatcherEntry {
|
||||
PaymentWatchersConfig {
|
||||
watchers: vec![PaymentWatcherConfig {
|
||||
id: DEFAULT_NYX_CHAIN_WATCHER_ID.to_string(),
|
||||
webhook_url: "https://webhook.site".to_string(),
|
||||
watch_for_transfer_recipient_accounts: Some(vec![AccountId::from_str(
|
||||
watch_for_transfer_recipient_accounts: vec![AccountId::from_str(
|
||||
"n17g9a2pwwkg8m60wf59pq6mv0c2wusg9ukparkz",
|
||||
)
|
||||
.unwrap()]),
|
||||
.unwrap()],
|
||||
authentication: Some(AuthorizationBearerToken {
|
||||
token: "1234".to_string(),
|
||||
}),
|
||||
description: None,
|
||||
watch_for_chain_message_types: Some(vec![
|
||||
watch_for_chain_message_types: 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: Option<Vec<AccountId>>,
|
||||
pub watch_for_transfer_recipient_accounts: 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: Option<Vec<String>>,
|
||||
pub watch_for_chain_message_types: 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, PaymentWatcherEntry};
|
||||
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatcherConfig};
|
||||
use crate::config::payments_watcher::{HttpAuthenticationOptions, PaymentWatcherConfig};
|
||||
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatchersConfig};
|
||||
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_none()
|
||||
&& args.watch_for_transfer_recipient_accounts.is_none()
|
||||
if args.watch_for_transfer_recipient_accounts.is_empty()
|
||||
&& args.watch_for_transfer_recipient_accounts.is_empty()
|
||||
&& 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_none() {
|
||||
watch_for_chain_message_types = Some(vec!["/cosmos.bank.v1beta1.MsgSend".to_string()]);
|
||||
if watch_for_chain_message_types.is_empty() {
|
||||
watch_for_chain_message_types = vec!["/cosmos.bank.v1beta1.MsgSend".to_string()];
|
||||
}
|
||||
|
||||
// warn if no accounts set
|
||||
if watch_for_transfer_recipient_accounts.is_none() {
|
||||
if watch_for_transfer_recipient_accounts.is_empty() {
|
||||
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 = PaymentWatcherConfig {
|
||||
watchers: vec![PaymentWatcherEntry {
|
||||
let watcher_config = PaymentWatchersConfig {
|
||||
watchers: vec![PaymentWatcherConfig {
|
||||
id: DEFAULT_NYX_CHAIN_WATCHER_ID.to_string(),
|
||||
description: None,
|
||||
watch_for_transfer_recipient_accounts: watch_for_transfer_recipient_accounts
|
||||
|
||||
@@ -2,19 +2,115 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::NyxChainWatcherError;
|
||||
use tokio::join;
|
||||
use tracing::{error, info, trace};
|
||||
use anyhow::Context;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::task::{JoinHandle, JoinSet};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info};
|
||||
|
||||
mod args;
|
||||
mod config;
|
||||
|
||||
use crate::chain_scraper::run_chain_scraper;
|
||||
use crate::{db, http, payment_listener, price_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};
|
||||
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> {
|
||||
trace!("passed arguments: {args:#?}");
|
||||
let start = OffsetDateTime::now_utc();
|
||||
|
||||
info!("passed arguments: {args:#?}");
|
||||
|
||||
let config = config::get_run_config(args)?;
|
||||
|
||||
@@ -29,9 +125,7 @@ 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()
|
||||
.unwrap_or_default()
|
||||
std::path::Path::new(&config.chain_scraper_database_path()).canonicalize()
|
||||
);
|
||||
|
||||
// Ensure parent directory exists
|
||||
@@ -41,50 +135,115 @@ 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().await;
|
||||
let watcher_pool = storage.pool_owned();
|
||||
|
||||
// Spawn the chain scraper and get its storage
|
||||
let mut tasks = JoinSet::new();
|
||||
let cancellation_token = CancellationToken::new();
|
||||
|
||||
// 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();
|
||||
let price_scraper_pool = storage.pool_owned();
|
||||
let scraper_pool = storage.pool_owned();
|
||||
let shutdown_pool = storage.pool_owned();
|
||||
|
||||
// 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 {
|
||||
if let Err(e) =
|
||||
payment_listener::run_payment_listener(payment_watcher_config, price_scraper_pool)
|
||||
.await
|
||||
{
|
||||
error!("Payment listener error: {}", e);
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
// 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())
|
||||
}
|
||||
});
|
||||
|
||||
// Clone pool for each task that needs it
|
||||
//let background_pool = db_pool.clone();
|
||||
// 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
|
||||
});
|
||||
}
|
||||
|
||||
let price_scraper_handle = tokio::spawn(async move {
|
||||
price_scraper::run_price_scraper(&watcher_pool).await;
|
||||
});
|
||||
// 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 shutdown_handles = http::server::start_http_api(storage.pool_owned().await, http_port)
|
||||
.await
|
||||
.expect("Failed to start server");
|
||||
// 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
|
||||
});
|
||||
}
|
||||
|
||||
info!("Started HTTP server on port {}", http_port);
|
||||
// 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(())
|
||||
|
||||
// Wait for the short-lived tasks to complete
|
||||
let _ = join!(price_scraper_handle, payment_listener_handle);
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for a signal to terminate the long-running task
|
||||
wait_for_signal().await;
|
||||
|
||||
if let Err(err) = shutdown_handles.shutdown().await {
|
||||
error!("{err}");
|
||||
};
|
||||
wait_for_shutdown(shutdown_pool, start, cancellation_token, scraper_token, tasks).await
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use tracing::{debug, error};
|
||||
pub(crate) mod payments_watcher;
|
||||
mod template;
|
||||
|
||||
pub use crate::config::payments_watcher::PaymentWatcherConfig;
|
||||
pub use crate::config::payments_watcher::PaymentWatchersConfig;
|
||||
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<PaymentWatcherConfig>,
|
||||
pub payment_watcher_config: Option<PaymentWatchersConfig>,
|
||||
|
||||
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<PaymentWatcherConfig>,
|
||||
payment_watcher_config: impl Into<PaymentWatchersConfig>,
|
||||
) -> 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,
|
||||
payment_watcher_config: self.payment_watcher_config.unwrap_or_default(),
|
||||
data_dir: self.data_dir,
|
||||
db_path: self.db_path,
|
||||
chain_scraper_db_path: self.chain_scraper_db_path,
|
||||
@@ -116,7 +116,8 @@ pub struct Config {
|
||||
#[serde(skip)]
|
||||
chain_scraper_db_path: Option<String>,
|
||||
|
||||
pub payment_watcher_config: Option<PaymentWatcherConfig>,
|
||||
#[serde(default)]
|
||||
pub payment_watcher_config: PaymentWatchersConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettings,
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PaymentWatcherConfig {
|
||||
pub watchers: Vec<PaymentWatcherEntry>,
|
||||
#[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, Serialize, Deserialize)]
|
||||
pub struct PaymentWatcherEntry {
|
||||
pub struct PaymentWatcherConfig {
|
||||
pub id: String,
|
||||
pub description: Option<String>,
|
||||
pub webhook_url: String,
|
||||
pub watch_for_transfer_recipient_accounts: Option<Vec<AccountId>>,
|
||||
pub watch_for_chain_message_types: Option<Vec<String>>,
|
||||
pub watch_for_transfer_recipient_accounts: Vec<AccountId>,
|
||||
pub watch_for_chain_message_types: Vec<String>,
|
||||
pub authentication: Option<HttpAuthenticationOptions>,
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ impl Storage {
|
||||
}
|
||||
|
||||
/// Cloning pool is cheap, it's the same underlying set of connections
|
||||
pub async fn pool_owned(&self) -> DbPool {
|
||||
pub fn pool_owned(&self) -> DbPool {
|
||||
self.pool.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use anyhow::Context;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use time::OffsetDateTime;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Clone, Deserialize, Debug, ToSchema)]
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
|
||||
pub(crate) struct CurrencyPrices {
|
||||
pub(crate) chf: f32,
|
||||
pub(crate) usd: f32,
|
||||
@@ -11,7 +15,7 @@ pub(crate) struct CurrencyPrices {
|
||||
}
|
||||
|
||||
// Struct to hold Coingecko response
|
||||
#[derive(Clone, Deserialize, Debug, ToSchema)]
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
|
||||
pub(crate) struct CoingeckoPriceResponse {
|
||||
pub(crate) nym: CurrencyPrices,
|
||||
}
|
||||
@@ -41,3 +45,25 @@ 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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ 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 {
|
||||
@@ -25,8 +26,13 @@ impl RouterBuilder {
|
||||
"/",
|
||||
axum::routing::get(|| async { Redirect::permanent("/swagger") }),
|
||||
)
|
||||
.nest("/v1", Router::new().nest("/price", price::routes()))
|
||||
.nest("/v1", Router::new().nest("/watcher", watcher::routes()));
|
||||
.nest(
|
||||
"/v1",
|
||||
Router::new()
|
||||
.nest("/status", status::routes())
|
||||
.nest("/price", price::routes())
|
||||
.nest("/watcher", watcher::routes()),
|
||||
);
|
||||
|
||||
Self {
|
||||
unfinished_router: router,
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
// 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
Reference in New Issue
Block a user