Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f75cd90092 | |||
| 1164f66d7a | |||
| 8ddadf2aa5 | |||
| b0234d0140 | |||
| 6c2b917706 | |||
| 0b8a5d5d86 | |||
| 74623fa2b8 | |||
| 454712b520 | |||
| 5fe5541d02 | |||
| c25c4548b2 | |||
| d6e996e8e9 | |||
| cf88364dda | |||
| 24d32a64e0 | |||
| 28d1d9d989 | |||
| 74a4197b77 | |||
| 3bc7301d94 | |||
| cc342458f9 |
@@ -6,7 +6,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [2025.4-dorina-patched] (2025-03-06)
|
||||
|
||||
- use legacy crypto for constructing SURB headers ([#5579])
|
||||
- bugfix: make sure to correctly decode response content when putting it into error message ([#5571])
|
||||
- Tweak surb management to be more conservative ([#5570])
|
||||
- Deserialize v5 authenticator requests ([#5568])
|
||||
@@ -16,7 +15,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- feature: v2 authentication request (#5537) ([#5563])
|
||||
- Create authenticator v5 request/response types ([#5561])
|
||||
|
||||
[#5579]: https://github.com/nymtech/nym/pull/5579
|
||||
[#5571]: https://github.com/nymtech/nym/pull/5571
|
||||
[#5570]: https://github.com/nymtech/nym/pull/5570
|
||||
[#5568]: https://github.com/nymtech/nym/pull/5568
|
||||
|
||||
Generated
+47
-67
@@ -902,9 +902,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -941,7 +941,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver 1.0.26",
|
||||
"semver 1.0.25",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
@@ -955,7 +955,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver 1.0.26",
|
||||
"semver 1.0.25",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
@@ -1587,7 +1587,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"crossterm_winapi",
|
||||
"parking_lot",
|
||||
"rustix 0.38.44",
|
||||
"rustix",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -1799,7 +1799,7 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw2",
|
||||
"schemars",
|
||||
"semver 1.0.26",
|
||||
"semver 1.0.25",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
@@ -1814,7 +1814,7 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"schemars",
|
||||
"semver 1.0.26",
|
||||
"semver 1.0.25",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
@@ -2527,9 +2527,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.13.1"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
|
||||
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"subtle 2.6.1",
|
||||
@@ -4144,12 +4144,6 @@ version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
|
||||
|
||||
[[package]]
|
||||
name = "lioness"
|
||||
version = "0.1.2"
|
||||
@@ -4847,7 +4841,7 @@ dependencies = [
|
||||
"rand_chacha 0.3.1",
|
||||
"reqwest 0.12.4",
|
||||
"schemars",
|
||||
"semver 1.0.26",
|
||||
"semver 1.0.25",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.9.9",
|
||||
@@ -5960,7 +5954,6 @@ dependencies = [
|
||||
"bytes",
|
||||
"nym-bin-common",
|
||||
"nym-crypto",
|
||||
"nym-sdk",
|
||||
"nym-service-provider-requests-common",
|
||||
"nym-sphinx",
|
||||
"rand 0.8.5",
|
||||
@@ -5969,7 +5962,6 @@ dependencies = [
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6063,7 +6055,7 @@ dependencies = [
|
||||
"nym-contracts-common",
|
||||
"rand_chacha 0.3.1",
|
||||
"schemars",
|
||||
"semver 1.0.26",
|
||||
"semver 1.0.25",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"serde_repr",
|
||||
@@ -6215,7 +6207,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.6.2"
|
||||
version = "1.6.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -6264,7 +6256,7 @@ dependencies = [
|
||||
"nym-wireguard",
|
||||
"nym-wireguard-types",
|
||||
"rand 0.8.5",
|
||||
"semver 1.0.26",
|
||||
"semver 1.0.25",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"si-scale",
|
||||
@@ -7235,7 +7227,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nyx-chain-watcher"
|
||||
version = "0.1.13"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -7250,6 +7242,7 @@ dependencies = [
|
||||
"nym-validator-client",
|
||||
"nyxd-scraper",
|
||||
"reqwest 0.12.4",
|
||||
"rocket",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -8417,9 +8410,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.13"
|
||||
version = "0.17.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
|
||||
checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
@@ -8606,9 +8599,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rs_merkle"
|
||||
version = "1.5.0"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb09b49230ba22e8c676e7b75dfe2887dea8121f18b530ae0ba519ce442d2b21"
|
||||
checksum = "3b241d2e59b74ef9e98d94c78c47623d04c8392abaf82014dfd372a16041128f"
|
||||
dependencies = [
|
||||
"sha2 0.10.8",
|
||||
]
|
||||
@@ -8710,7 +8703,7 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver 1.0.26",
|
||||
"semver 1.0.25",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8722,20 +8715,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.2",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -9011,9 +8991,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -9026,9 +9006,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -9066,18 +9046,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.17"
|
||||
version = "0.11.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96"
|
||||
checksum = "364fec0df39c49a083c9a8a18a23a6bcfd9af130fe9fe321d18520a0d113e09e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9180,9 +9160,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.20"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
|
||||
checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9453,9 +9433,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-packet"
|
||||
version = "0.3.2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c23047e0cf36ff6904603f499fd13153425cdf5ba47bfbaedbc999da0bd92f4e"
|
||||
checksum = "535f2c430778bf59c22249fcc1ed6d384129eb2f0f694706015d636c688f9ac6"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arrayref",
|
||||
@@ -9974,15 +9954,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.18.0"
|
||||
version = "3.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567"
|
||||
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand 2.3.0",
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix 1.0.1",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -10110,7 +10090,7 @@ dependencies = [
|
||||
"pin-project",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.11.27",
|
||||
"semver 1.0.26",
|
||||
"semver 1.0.25",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
@@ -10270,9 +10250,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.39"
|
||||
version = "0.3.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -10288,15 +10268,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.3"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.20"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -10339,9 +10319,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.44.0"
|
||||
version = "1.43.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a"
|
||||
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@@ -12066,8 +12046,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"rustix 0.38.44",
|
||||
"linux-raw-sys",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+10
-10
@@ -209,7 +209,7 @@ blake3 = "1.6.1"
|
||||
bloomfilter = "1.0.14"
|
||||
bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
bytes = "1.10.1"
|
||||
bytes = "1.7.2"
|
||||
cargo_metadata = "0.18.1"
|
||||
celes = "2.5.0"
|
||||
cfg-if = "1.0.0"
|
||||
@@ -308,12 +308,12 @@ reqwest = { version = "0.12.4", default-features = false }
|
||||
rocket = "0.5.0"
|
||||
rocket_cors = "0.6.0"
|
||||
rocket_okapi = "0.8.0"
|
||||
rs_merkle = "1.5.0"
|
||||
rs_merkle = "1.4.2"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.22"
|
||||
semver = "1.0.26"
|
||||
serde = "1.0.219"
|
||||
serde_bytes = "0.11.17"
|
||||
semver = "1.0.25"
|
||||
serde = "1.0.217"
|
||||
serde_bytes = "0.11.16"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0.140"
|
||||
serde_json_path = "0.7.2"
|
||||
@@ -322,7 +322,7 @@ serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
sha2 = "0.10.8"
|
||||
si-scale = "0.2.3"
|
||||
sphinx-packet = "=0.3.2"
|
||||
sphinx-packet = "0.3.1"
|
||||
sqlx = "0.7.4"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
@@ -331,10 +331,10 @@ syn = "1"
|
||||
sysinfo = "0.33.0"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.44"
|
||||
tempfile = "3.18"
|
||||
tempfile = "3.15"
|
||||
thiserror = "2.0"
|
||||
time = "0.3.39"
|
||||
tokio = "1.44"
|
||||
time = "0.3.37"
|
||||
tokio = "1.43"
|
||||
tokio-postgres = "0.7"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-test = "0.4.4"
|
||||
@@ -372,7 +372,7 @@ prometheus = { version = "0.13.0" }
|
||||
# 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" }
|
||||
group = { version = "0.13.0", default-features = false }
|
||||
ff = { version = "0.13.1", default-features = false }
|
||||
ff = { version = "0.13.0", default-features = false }
|
||||
subtle = "2.5.0"
|
||||
|
||||
# cosmwasm-related
|
||||
|
||||
@@ -117,7 +117,7 @@ impl Drop for Handler {
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
async fn get_lane_queue_length(&mut self, connection_id: ConnectionId) -> Option<ServerResponse> {
|
||||
async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> Option<ServerResponse> {
|
||||
let req_start = Instant::now();
|
||||
|
||||
// get the base queue length
|
||||
@@ -282,7 +282,7 @@ impl Handler {
|
||||
None
|
||||
}
|
||||
|
||||
async fn handle_get_lane_queue_length(&mut self, connection_id: u64) -> Option<ServerResponse> {
|
||||
async fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option<ServerResponse> {
|
||||
self.get_lane_queue_length(connection_id).await
|
||||
}
|
||||
|
||||
|
||||
@@ -658,9 +658,6 @@ pub struct ReplySurbs {
|
||||
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
|
||||
/// the default value is used.
|
||||
pub surb_mix_hops: Option<u8>,
|
||||
|
||||
/// Specifies if we should reset all the sender tags on startup
|
||||
pub fresh_sender_tags: bool,
|
||||
}
|
||||
|
||||
impl Default for ReplySurbs {
|
||||
@@ -678,7 +675,6 @@ impl Default for ReplySurbs {
|
||||
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
|
||||
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
|
||||
surb_mix_hops: None,
|
||||
fresh_sender_tags: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
let db_path = db_path.as_ref();
|
||||
if db_path.exists() {
|
||||
info!("loading existing surb database");
|
||||
match fs_backend::Backend::try_load(db_path, surb_config.fresh_sender_tags).await {
|
||||
match fs_backend::Backend::try_load(db_path).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
|
||||
@@ -18,7 +18,7 @@ pub type ClientRequestSender = tokio::sync::mpsc::Sender<ClientRequest>;
|
||||
pub mod transceiver;
|
||||
|
||||
// We remind ourselves that 32 x 32kb = 1024kb, a reasonable size for a network buffer.
|
||||
pub const MIX_MESSAGE_RECEIVER_BUFFER_SIZE: usize = 5;
|
||||
pub const MIX_MESSAGE_RECEIVER_BUFFER_SIZE: usize = 32;
|
||||
const MAX_FAILURE_COUNT: usize = 100;
|
||||
|
||||
// that's also disgusting.
|
||||
@@ -96,7 +96,6 @@ impl MixTrafficController {
|
||||
mut mix_packets: Vec<MixPacket>,
|
||||
) -> Result<(), ErasedGatewayError> {
|
||||
debug_assert!(!mix_packets.is_empty());
|
||||
info!("on_messages: received {} mix_packets", mix_packets.len());
|
||||
|
||||
let result = if mix_packets.len() == 1 {
|
||||
let mix_packet = mix_packets.pop().unwrap();
|
||||
|
||||
+2
-17
@@ -69,7 +69,7 @@ where
|
||||
// offload reply handling to the dedicated task
|
||||
if let Err(err) = self
|
||||
.reply_controller_sender
|
||||
.send_reply(recipient_tag, data, lane).await
|
||||
.send_reply(recipient_tag, data, lane)
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("failed to send a reply - {err}");
|
||||
@@ -111,17 +111,12 @@ where
|
||||
}
|
||||
|
||||
async fn on_input_message(&mut self, msg: InputMessage) {
|
||||
log::info!(
|
||||
"input_message_listener: received a message: {:?}",
|
||||
msg.lane()
|
||||
);
|
||||
match msg {
|
||||
InputMessage::Regular {
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
} => {
|
||||
log::info!("input_message_listener: regular");
|
||||
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
|
||||
.await
|
||||
}
|
||||
@@ -131,7 +126,6 @@ where
|
||||
reply_surbs,
|
||||
lane,
|
||||
} => {
|
||||
log::info!("input_message_listener: anonymous");
|
||||
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix)
|
||||
.await
|
||||
}
|
||||
@@ -140,13 +134,9 @@ where
|
||||
data,
|
||||
lane,
|
||||
} => {
|
||||
log::info!("input_message_listener: reply");
|
||||
self.handle_reply(recipient_tag, data, lane).await;
|
||||
}
|
||||
InputMessage::Premade { msgs, lane } => {
|
||||
log::info!("input_message_listener: premade");
|
||||
self.handle_premade_packets(msgs, lane).await
|
||||
}
|
||||
InputMessage::Premade { msgs, lane } => self.handle_premade_packets(msgs, lane).await,
|
||||
InputMessage::MessageWrapper {
|
||||
message,
|
||||
packet_type,
|
||||
@@ -156,8 +146,6 @@ where
|
||||
data,
|
||||
lane,
|
||||
} => {
|
||||
log::info!("input_message_listener: wrapper regular");
|
||||
|
||||
self.handle_plain_message(recipient, data, lane, packet_type)
|
||||
.await
|
||||
}
|
||||
@@ -167,7 +155,6 @@ where
|
||||
reply_surbs,
|
||||
lane,
|
||||
} => {
|
||||
log::info!("input_message_listener: wrapper anonymous");
|
||||
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
|
||||
.await
|
||||
}
|
||||
@@ -176,11 +163,9 @@ where
|
||||
data,
|
||||
lane,
|
||||
} => {
|
||||
log::info!("input_message_listener: wrapper reply");
|
||||
self.handle_reply(recipient_tag, data, lane).await;
|
||||
}
|
||||
InputMessage::Premade { msgs, lane } => {
|
||||
log::info!("input_message_listener: wrapper premade");
|
||||
self.handle_premade_packets(msgs, lane).await
|
||||
}
|
||||
// MessageWrappers can't be nested
|
||||
|
||||
+5
-9
@@ -99,15 +99,11 @@ where
|
||||
} => {
|
||||
// if this is retransmission for reply, offload it to the dedicated task
|
||||
// that deals with all the surbs
|
||||
if let Err(err) = self
|
||||
.reply_controller_sender
|
||||
.send_retransmission_data(
|
||||
*recipient_tag,
|
||||
weak_timed_out_ack,
|
||||
*extra_surb_request,
|
||||
)
|
||||
.await
|
||||
{
|
||||
if let Err(err) = self.reply_controller_sender.send_retransmission_data(
|
||||
*recipient_tag,
|
||||
weak_timed_out_ack,
|
||||
*extra_surb_request,
|
||||
) {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send retransmission data to the reply controller: {err}");
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ where
|
||||
let (next_message, fragment_id, packet_size) = match next_message {
|
||||
StreamMessage::Cover => {
|
||||
let cover_traffic_packet_size = self.loop_cover_message_size();
|
||||
info!("the next loop cover message will be put in a {cover_traffic_packet_size} packet");
|
||||
trace!("the next loop cover message will be put in a {cover_traffic_packet_size} packet");
|
||||
|
||||
// TODO for way down the line: in very rare cases (during topology update) we might have
|
||||
// to wait a really tiny bit before actually obtaining the permit hence messing with our
|
||||
@@ -277,8 +277,6 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let mix_tx_capacity = self.mix_tx.capacity();
|
||||
log::info!("mix_tx_capacity: {mix_tx_capacity}");
|
||||
if let Err(err) = self.mix_tx.send(vec![next_message]).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
log::error!("Failed to send: {err}");
|
||||
@@ -538,13 +536,13 @@ where
|
||||
format!("Packet backlog: {lane_status}, avg delay: {delay}ms ({mult})")
|
||||
};
|
||||
|
||||
// if packets > 1000 {
|
||||
// log::warn!("{status_str}");
|
||||
// } else if packets > 0 {
|
||||
if packets > 1000 {
|
||||
log::warn!("{status_str}");
|
||||
} else if packets > 0 {
|
||||
log::info!("{status_str}");
|
||||
// } else {
|
||||
// log::debug!("{status_str}");
|
||||
// }
|
||||
} else {
|
||||
log::debug!("{status_str}");
|
||||
}
|
||||
|
||||
// Send status message to whoever is listening (possibly UI)
|
||||
if mult == self.sending_delay_controller.max_multiplier() {
|
||||
@@ -561,7 +559,7 @@ where
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let mut status_timer = tokio::time::interval(Duration::from_secs(1));
|
||||
let mut status_timer = tokio::time::interval(Duration::from_secs(5));
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
@@ -574,7 +572,6 @@ where
|
||||
self.log_status(&mut shutdown);
|
||||
}
|
||||
next_message = self.next() => if let Some(next_message) = next_message {
|
||||
self.log_status(&mut shutdown);
|
||||
self.on_message(next_message).await;
|
||||
} else {
|
||||
log::trace!("OutQueueControl: Stopping since channel closed");
|
||||
|
||||
@@ -224,7 +224,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
msgs.into_iter().map(Into::into).collect()
|
||||
}
|
||||
|
||||
async fn handle_reconstructed_repliable_messages(
|
||||
fn handle_reconstructed_repliable_messages(
|
||||
&mut self,
|
||||
msgs: Vec<RepliableMessage>,
|
||||
) -> Vec<ReconstructedMessage> {
|
||||
@@ -261,11 +261,11 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = self
|
||||
.reply_controller_sender
|
||||
.send_additional_surbs(msg.sender_tag, reply_surbs, from_surb_request)
|
||||
.await
|
||||
{
|
||||
if let Err(err) = self.reply_controller_sender.send_additional_surbs(
|
||||
msg.sender_tag,
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
) {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("{err}");
|
||||
}
|
||||
@@ -274,7 +274,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
reconstructed
|
||||
}
|
||||
|
||||
async fn handle_reconstructed_reply_messages(
|
||||
fn handle_reconstructed_reply_messages(
|
||||
&mut self,
|
||||
msgs: Vec<ReplyMessage>,
|
||||
) -> Vec<ReconstructedMessage> {
|
||||
@@ -287,7 +287,6 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
if let Err(err) = self
|
||||
.reply_controller_sender
|
||||
.send_additional_surbs_request(*recipient, amount)
|
||||
.await
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("{err}");
|
||||
@@ -317,16 +316,10 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
}
|
||||
|
||||
let mut reconstructed_messages = self.handle_reconstructed_plain_messages(plain_messages);
|
||||
reconstructed_messages.append(
|
||||
&mut self
|
||||
.handle_reconstructed_repliable_messages(repliable_messages)
|
||||
.await,
|
||||
);
|
||||
reconstructed_messages.append(
|
||||
&mut self
|
||||
.handle_reconstructed_reply_messages(reply_messages)
|
||||
.await,
|
||||
);
|
||||
reconstructed_messages
|
||||
.append(&mut self.handle_reconstructed_repliable_messages(repliable_messages));
|
||||
reconstructed_messages
|
||||
.append(&mut self.handle_reconstructed_reply_messages(reply_messages));
|
||||
|
||||
let mut inner_guard = self.inner.lock().await;
|
||||
debug!(
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
SinkExt,
|
||||
};
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use log::error;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
@@ -14,128 +11,117 @@ use nym_task::connections::{ConnectionId, TransmissionLane};
|
||||
use std::sync::Weak;
|
||||
|
||||
pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) {
|
||||
let (tx, rx) = mpsc::channel(8);
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
(tx.into(), rx)
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ReplyControllerSenderError {
|
||||
#[error("failed to send retransmission data to reply controller")]
|
||||
// SendRetransmissionData(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
SendRetransmissionData,
|
||||
SendRetransmissionData(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
|
||||
#[error("failed to send reply to reply controller")]
|
||||
// SendReply(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
SendReply,
|
||||
SendReply(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
|
||||
#[error("failed to send additional surbs to reply controller")]
|
||||
// AdditionalSurbs(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
AdditionalSurbs,
|
||||
AdditionalSurbs(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
|
||||
#[error("failed to send additional surbs request to reply controller")]
|
||||
// AdditionalSurbsRequest(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
AdditionalSurbsRequest,
|
||||
AdditionalSurbsRequest(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
|
||||
#[error("failed to request lane queue length from reply controller")]
|
||||
// LaneQueueLength(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
LaneQueueLength,
|
||||
LaneQueueLength(#[source] mpsc::TrySendError<ReplyControllerMessage>),
|
||||
|
||||
#[error("response channel was dropped before we could receive the response")]
|
||||
// ResponseChannelDropped(#[source] oneshot::Canceled),
|
||||
ResponseChannelDropped,
|
||||
ResponseChannelDropped(#[source] oneshot::Canceled),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReplyControllerSender(mpsc::Sender<ReplyControllerMessage>);
|
||||
pub struct ReplyControllerSender(mpsc::UnboundedSender<ReplyControllerMessage>);
|
||||
|
||||
impl From<mpsc::Sender<ReplyControllerMessage>> for ReplyControllerSender {
|
||||
fn from(inner: mpsc::Sender<ReplyControllerMessage>) -> Self {
|
||||
impl From<mpsc::UnboundedSender<ReplyControllerMessage>> for ReplyControllerSender {
|
||||
fn from(inner: mpsc::UnboundedSender<ReplyControllerMessage>) -> Self {
|
||||
ReplyControllerSender(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplyControllerSender {
|
||||
pub(crate) async fn send_retransmission_data(
|
||||
&mut self,
|
||||
pub(crate) fn send_retransmission_data(
|
||||
&self,
|
||||
recipient: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
extra_surb_request: bool,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
self.0
|
||||
.send(ReplyControllerMessage::RetransmitReply {
|
||||
.unbounded_send(ReplyControllerMessage::RetransmitReply {
|
||||
recipient,
|
||||
timed_out_ack,
|
||||
extra_surb_request,
|
||||
})
|
||||
.await
|
||||
.map_err(|_| ReplyControllerSenderError::SendRetransmissionData)
|
||||
.map_err(ReplyControllerSenderError::SendRetransmissionData)
|
||||
}
|
||||
|
||||
pub(crate) async fn send_reply(
|
||||
&mut self,
|
||||
pub(crate) fn send_reply(
|
||||
&self,
|
||||
recipient: AnonymousSenderTag,
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
self.0
|
||||
.send(ReplyControllerMessage::SendReply {
|
||||
.unbounded_send(ReplyControllerMessage::SendReply {
|
||||
recipient,
|
||||
message,
|
||||
lane,
|
||||
})
|
||||
.await
|
||||
.map_err(|_| ReplyControllerSenderError::SendReply)
|
||||
.map_err(ReplyControllerSenderError::SendReply)
|
||||
}
|
||||
|
||||
pub(crate) async fn send_additional_surbs(
|
||||
&mut self,
|
||||
pub(crate) fn send_additional_surbs(
|
||||
&self,
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
from_surb_request: bool,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
self.0
|
||||
.send(ReplyControllerMessage::AdditionalSurbs {
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbs {
|
||||
sender_tag,
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
})
|
||||
.await
|
||||
.map_err(|_| ReplyControllerSenderError::AdditionalSurbs)
|
||||
.map_err(ReplyControllerSenderError::AdditionalSurbs)
|
||||
}
|
||||
|
||||
pub(crate) async fn send_additional_surbs_request(
|
||||
&mut self,
|
||||
pub(crate) fn send_additional_surbs_request(
|
||||
&self,
|
||||
recipient: Recipient,
|
||||
amount: u32,
|
||||
) -> Result<(), ReplyControllerSenderError> {
|
||||
self.0
|
||||
.send(ReplyControllerMessage::AdditionalSurbsRequest {
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest {
|
||||
recipient: Box::new(recipient),
|
||||
amount,
|
||||
})
|
||||
.await
|
||||
.map_err(|_| ReplyControllerSenderError::AdditionalSurbsRequest)
|
||||
.map_err(ReplyControllerSenderError::AdditionalSurbsRequest)
|
||||
}
|
||||
|
||||
pub async fn get_lane_queue_length(
|
||||
&mut self,
|
||||
&self,
|
||||
connection_id: ConnectionId,
|
||||
) -> Result<usize, ReplyControllerSenderError> {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
if let Err(_err) = self
|
||||
if let Err(err) = self
|
||||
.0
|
||||
.send(ReplyControllerMessage::LaneQueueLength {
|
||||
.unbounded_send(ReplyControllerMessage::LaneQueueLength {
|
||||
connection_id,
|
||||
response_channel: response_tx,
|
||||
})
|
||||
.await
|
||||
{
|
||||
return Err(ReplyControllerSenderError::LaneQueueLength);
|
||||
return Err(ReplyControllerSenderError::LaneQueueLength(err));
|
||||
}
|
||||
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| ReplyControllerSenderError::ResponseChannelDropped)
|
||||
.map_err(ReplyControllerSenderError::ResponseChannelDropped)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +137,7 @@ impl ReplyQueueLengths {
|
||||
}
|
||||
|
||||
pub async fn get_lane_queue_length(
|
||||
&mut self,
|
||||
&self,
|
||||
connection_id: ConnectionId,
|
||||
) -> Result<usize, ReplyControllerSenderError> {
|
||||
self.reply_controller_sender
|
||||
@@ -160,7 +146,7 @@ impl ReplyQueueLengths {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type ReplyControllerReceiver = mpsc::Receiver<ReplyControllerMessage>;
|
||||
pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver<ReplyControllerMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReplyControllerMessage {
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use log::{debug, error, info, warn};
|
||||
use log::{error, info, warn};
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -52,10 +52,7 @@ impl Backend {
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
pub async fn try_load<P: AsRef<Path>>(
|
||||
database_path: P,
|
||||
fresh_sender_tags: bool,
|
||||
) -> Result<Self, StorageError> {
|
||||
pub async fn try_load<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
|
||||
let owned_path: PathBuf = database_path.as_ref().into();
|
||||
if owned_path.file_name().is_none() {
|
||||
return Err(StorageError::DatabasePathWithoutFilename {
|
||||
@@ -121,9 +118,6 @@ impl Backend {
|
||||
if days > 2 {
|
||||
info!("it's been over {days} days and {hours} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.");
|
||||
manager.delete_all_tags().await?;
|
||||
} else if fresh_sender_tags {
|
||||
debug!("starting with fresh sender tags");
|
||||
manager.delete_all_tags().await?;
|
||||
}
|
||||
|
||||
Ok(Backend {
|
||||
|
||||
@@ -21,5 +21,3 @@ thiserror = { workspace = true }
|
||||
time = { workspace = true }
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
nym-sdk = { path = "../../sdk/rust/nym-sdk" }
|
||||
tracing.workspace = true
|
||||
|
||||
@@ -74,39 +74,6 @@ impl MultiIpPacketCodec {
|
||||
Some(packets)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn buffer_timeout2(
|
||||
&mut self,
|
||||
lane_queue_lengths: nym_sdk::mixnet::LaneQueueLengths,
|
||||
) -> Option<Bytes> {
|
||||
// Wait for buffer_timeout to tick
|
||||
let _ = self.buffer_timeout.tick().await;
|
||||
|
||||
// wait for lane_queue_lenghts to go to zero
|
||||
{
|
||||
let now = std::time::Instant::now();
|
||||
loop {
|
||||
let lane_queue_lengths = lane_queue_lengths.lock().unwrap().total();
|
||||
if lane_queue_lengths < 10 {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
let elapsed = now.elapsed();
|
||||
tracing::error!(
|
||||
"Waited for lane_queue_lengths to go to zero for {:?}",
|
||||
elapsed
|
||||
);
|
||||
}
|
||||
|
||||
// Flush the buffer and return it
|
||||
let packets = self.flush_current_buffer();
|
||||
if packets.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(packets)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encoder<Bytes> for MultiIpPacketCodec {
|
||||
|
||||
@@ -7,7 +7,7 @@ use nym_sphinx_addressing::clients::Recipient;
|
||||
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
|
||||
use nym_sphinx_params::packet_sizes::PacketSize;
|
||||
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm};
|
||||
use nym_sphinx_types::{NymPacket, SURBMaterial, SphinxError, SURB, UPDATED_LEGACY_VERSION};
|
||||
use nym_sphinx_types::{NymPacket, SURBMaterial, SphinxError, SURB};
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use serde::de::{Error as SerdeError, Visitor};
|
||||
@@ -101,8 +101,7 @@ impl ReplySurb {
|
||||
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
|
||||
let destination = recipient.as_sphinx_destination();
|
||||
|
||||
let surb_material =
|
||||
SURBMaterial::new(route, delays, destination).with_version(UPDATED_LEGACY_VERSION);
|
||||
let surb_material = SURBMaterial::new(route, delays, destination);
|
||||
|
||||
// this can't fail as we know we have a valid route to gateway and have correct number of delays
|
||||
Ok(ReplySurb {
|
||||
|
||||
@@ -103,8 +103,4 @@ impl LaneQueueLengthsInner {
|
||||
{
|
||||
self.map.entry(*lane).and_modify(f);
|
||||
}
|
||||
|
||||
pub fn total(&self) -> usize {
|
||||
self.map.values().sum()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,9 +494,6 @@ pub struct ReplySurbsWasm {
|
||||
/// Defines how many mix nodes the reply surb should go through.
|
||||
/// If not set, the default value is going to be used.
|
||||
pub surb_mix_hops: Option<u8>,
|
||||
|
||||
/// Specifies if we should reset all the sender tags on startup
|
||||
pub fresh_sender_tags: bool,
|
||||
}
|
||||
|
||||
impl Default for ReplySurbsWasm {
|
||||
@@ -528,7 +525,6 @@ impl From<ReplySurbsWasm> for ConfigReplySurbs {
|
||||
reply_surbs.maximum_reply_key_age_ms as u64,
|
||||
),
|
||||
surb_mix_hops: reply_surbs.surb_mix_hops,
|
||||
fresh_sender_tags: reply_surbs.fresh_sender_tags,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,7 +548,6 @@ impl From<ConfigReplySurbs> for ReplySurbsWasm {
|
||||
maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u32,
|
||||
maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u32,
|
||||
surb_mix_hops: reply_surbs.surb_mix_hops,
|
||||
fresh_sender_tags: reply_surbs.fresh_sender_tags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,9 +378,6 @@ pub struct ReplySurbsWasmOverride {
|
||||
|
||||
#[tsify(optional)]
|
||||
pub surb_mix_hops: Option<u8>,
|
||||
|
||||
/// Specifies if we should reset all the sender tags on startup
|
||||
pub fresh_sender_tags: bool,
|
||||
}
|
||||
|
||||
impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
|
||||
@@ -419,7 +416,6 @@ impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
|
||||
.maximum_reply_key_age_ms
|
||||
.unwrap_or(def.maximum_reply_key_age_ms),
|
||||
surb_mix_hops: value.surb_mix_hops,
|
||||
fresh_sender_tags: value.fresh_sender_tags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+12
-12
@@ -1522,18 +1522,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -1558,9 +1558,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1777,9 +1777,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.39"
|
||||
version = "0.3.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -1794,15 +1794,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.3"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.20"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
|
||||
@@ -7,6 +7,4 @@ package-lock.json
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env
|
||||
|
||||
scratch.md
|
||||
.env
|
||||
@@ -1,10 +0,0 @@
|
||||
- Run synchronization against [pool.ntp.org](https://www.ntppool.org/en/):
|
||||
```bash
|
||||
ntpdate -q pool.ntp.org
|
||||
```
|
||||
- Enable `ntp` service, start and review the status:
|
||||
```bash
|
||||
systemctl enable --now ntp
|
||||
service ntp start
|
||||
service ntp status
|
||||
```
|
||||
+1
-1
@@ -1 +1 @@
|
||||
808_623_916
|
||||
807_251_217
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.68%
|
||||
0.64%
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
42.157
|
||||
44.811
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1_028_488
|
||||
1_025_628
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
404_311_958
|
||||
403_625_608
|
||||
|
||||
+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 | 191_376_083 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 192_748_782 |
|
||||
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 808_623_916 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_028_488 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 807_251_217 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_025_628 |
|
||||
|
||||
@@ -1 +1 @@
|
||||
Tuesday, March 11th 2025, 11:04:18 UTC
|
||||
Wednesday, February 26th 2025, 16:02:47 UTC
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
{
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
{
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# Node Status API
|
||||
The Node Status API serves information about individual `nym-nodes` in the Mixnet, such as which role they are operating in, statistics about them, services such as Network Requesters, as well as summaries of the state of the Mixnet.
|
||||
|
||||
The Node Status API contains information about the network, its topology, and the routing scores of all nodes within it. It offers broadly similar information to the experimental [Habourmaster frontend](https://harbourmaster.nymtech.net/) but is stable where the Harbourmaster is subject to sudden changes as we modify and experiment with how we scrape and present data about the Mixnet infrastructure.
|
||||
|
||||
<Callout type="info">
|
||||
We recommend that developers building applications such as explorers or analytics interfaces about the Mixnet run their own instance of the API, in order to promote a robust network of downstream services, and spread the load of API calls amongst as many endpoints as possible.
|
||||
People building applications or dashboards which requires information about nodes, their uptime, and their delegations should use this instead of Habourmaster.
|
||||
</Callout>
|
||||
|
||||
The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/nym-node-status-api). In the future we will encourage developers to run their own instance of this API in order to distribute endpoints and query load.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"ns-api-run-deploy":"Run Instance",
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import { RedocStandalone } from 'redoc';
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://mainnet-node-status-api.nymtech.cc/swagger/](https://mainnet-node-status-api.nymtech.cc/swagger/).
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
<RedocStandalone
|
||||
specUrl="https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json"
|
||||
options={{
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx'
|
||||
|
||||
# NS API: Deployment Guide
|
||||
|
||||
## Components
|
||||
The Node Status API is made up of 3 components:
|
||||
- `nym-node-status-api`
|
||||
- `nym-node-status-client`
|
||||
- `nym-node-status-agent`
|
||||
|
||||
The API stores its data in a local SQLite database. It periodically gets most of its data from a [NymAPI](./nym-api) instance, and for instances also running with the Gateway Probe, uses the stored topology to conduct performance probe tests.
|
||||
|
||||
## Gateway Probe
|
||||
You can run the `nym-node-status-api` alone, but if you want to run probes for `nym-node`s running as Gateways, you have to also run the `nym-node-status-agent` (which consumes the `nym-node-status-client` lib). The probe will run a set of tests per Gateway node, and return the sort of results seen [here](https://harbourmaster.nymtech.net/gateway/23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb).
|
||||
|
||||
## UI
|
||||
The API exposes a [Swagger](https://swagger.io/docs/) documentation UI by default on port `8000` (see an example [here](https://mainnet-node-status-api.nymtech.cc/swagger/)).
|
||||
|
||||
Currently we are not shipping a custom UI component for the Node Status API. The [Harbourmaster](https://harbourmaster.nymtech.net/) frontend consumes this API, amongst other things, but this is an internal repo we use to experiment with new APIs and data on, so it is not public yet.
|
||||
|
||||
<Callout type="info">
|
||||
We invite developers to roll their own UI for their Node Status API instance.
|
||||
</Callout>
|
||||
|
||||
## Docker Images
|
||||
We will ship Docker images for both the `agent` and `api` in the future. There are Docker images for both in root of each corresponding crate ([`agent`](https://github.com/nymtech/nym/blob/09ea406c02e9a3beebc062f525e4ea1b4222dcbb/nym-node-status-api/nym-node-status-agent/Dockerfile), [`api`](https://github.com/nymtech/nym/blob/develop/nym-node-status-api/nym-node-status-api/Dockerfile)) which are used internally, which could be a starting point for developers to dockerize their instance for the moment.
|
||||
|
||||
## Build
|
||||
### Prerequisites
|
||||
- Rust
|
||||
- SQLite
|
||||
- Get an `ipinfo` key following instructions [here](https://github.com/ipinfo/rust?tab=readme-ov-file#getting-started).
|
||||
|
||||
### Compilation
|
||||
```shell
|
||||
cargo build --release --package nym-node-status-api --package nym-node-status-agent --package nym-node-status-client
|
||||
```
|
||||
|
||||
## Run
|
||||
Since the Node Status API depends on both flags and environmental variables, it might be easier to run the binary via a script like the one below - this this script essentially just `source`-s the defined `.env` file after exporting certain binary-specific variables, and then runs the binary. You can find the `.env` files [here](https://github.com/nymtech/nym/tree/master/envs).
|
||||
|
||||
<Callout type="info">
|
||||
All CLI flags are configurable as environmental variables and vice versa, so take the following scripts / setups as guides that you can change however best suits your setup. You can see all definitions [here](https://github.com/nymtech/nym/blob/develop/nym-node-status-api/nym-node-status-api/src/cli/mod.rs#L14).
|
||||
</Callout>
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"} # see nym/envs/ for all possible environments
|
||||
export NYM_API_CLIENT_TIMEOUT=60
|
||||
export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=120
|
||||
export IPINFO_API_TOKEN=<YOUR_IPINFO_API_KEY>
|
||||
|
||||
monorepo_root=<PATH/TO/NYM/>
|
||||
set -a
|
||||
source "${monorepo_root}/envs/${ENVIRONMENT}.env"
|
||||
echo ${monorepo_root}/envs/${ENVIRONMENT}.env
|
||||
set +a
|
||||
export RUST_LOG=${RUST_LOG:-debug} # debug is useful to check everything is working initially, but quite verbose
|
||||
|
||||
echo "Verifying environment variables were properly sourced:"
|
||||
echo "RUST_LOG=${RUST_LOG}"
|
||||
echo "BECH32_PREFIX=${BECH32_PREFIX}"
|
||||
echo "NETWORK_NAME=${NETWORK_NAME}"
|
||||
|
||||
<PATH/TO/>nym-node-status-api -- --ipinfo-api-token $IPINFO_API_TOKEN
|
||||
```
|
||||
|
||||
### Functionality Without Gateway Probe
|
||||
Data will be restricted to information that doesn't involve Probe results; `routing` and `config` scores will be `0` and `last_probe` results `null`, as you can see in this snipped output of the `gateways` endpoint:
|
||||
|
||||
<AccordionTemplate name="Output">
|
||||
```shell
|
||||
{
|
||||
"gateway_identity_key": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
|
||||
"bonded": true,
|
||||
"performance": 99,
|
||||
"self_described": {
|
||||
"authenticator": {
|
||||
"address": "6Gdtw13Fa46AvkqkHELZZCMKWASDodoJeK9APRNpjjdj.7ji8DDkpjA2AdgwK7wbZm8yi4xZGogGJeypBQt4hAw3P@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb"
|
||||
},
|
||||
"auxiliary_details": {
|
||||
"accepted_operator_terms_and_conditions": true,
|
||||
"announce_ports": {
|
||||
"mix_port": null,
|
||||
"verloc_port": null
|
||||
},
|
||||
"location": null
|
||||
},
|
||||
"build_information": {
|
||||
"binary_name": "nym-node",
|
||||
"build_timestamp": "2025-02-13T11:49:34.670488195Z",
|
||||
"build_version": "1.5.0",
|
||||
"cargo_profile": "release",
|
||||
"cargo_triple": "x86_64-unknown-linux-gnu",
|
||||
"commit_branch": "HEAD",
|
||||
"commit_sha": "a3e19b4563843055b305ea9a397eb1ad84b5c378",
|
||||
"commit_timestamp": "2025-02-10T18:14:47.000000000+01:00",
|
||||
"rustc_channel": "stable",
|
||||
"rustc_version": "1.84.1"
|
||||
},
|
||||
"declared_role": {
|
||||
"entry": true,
|
||||
"exit_ipr": true,
|
||||
"exit_nr": true,
|
||||
"mixnode": false
|
||||
},
|
||||
"host_information": {
|
||||
"hostname": "bwng1.bwnym.xyz",
|
||||
"ip_address": [
|
||||
"95.164.2.86"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
|
||||
"x25519": "H6pFjqtdSVxkxEQ3wFnuSoobDAUqHx1bYMVJzPZdRByn",
|
||||
"x25519_noise": null
|
||||
}
|
||||
},
|
||||
"ip_packet_router": {
|
||||
"address": "7ms2D2uYiTuhX6MKeVL5rz5usgehEoxAAovwYm9nJyBF.3siMjk3wTU7ykaXLNi9c7LpX8yonYKPCA4BQoMwhsfTV@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb"
|
||||
},
|
||||
"last_polled": "2025-03-03 09:48:03.635274187 +00:00:00",
|
||||
"mixnet_websockets": {
|
||||
"ws_port": 9000,
|
||||
"wss_port": 9001
|
||||
},
|
||||
"network_requester": {
|
||||
"address": "HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
|
||||
"uses_exit_policy": true
|
||||
},
|
||||
"wireguard": {
|
||||
"port": 51822,
|
||||
"public_key": "6o8x9GitFjcrkjrJnivWaQCPnxXykQPYLneNr2FEB8Vq"
|
||||
}
|
||||
},
|
||||
"explorer_pretty_bond": {
|
||||
"identity_key": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
|
||||
"location": {
|
||||
"latitude": 52.5083,
|
||||
"longitude": 5.475,
|
||||
"two_letter_iso_country_code": "NL"
|
||||
},
|
||||
"owner": "n1cp5gq0apat6c7qmenqp5zjprn2vwvc7jl29j8r",
|
||||
"pledge_amount": {
|
||||
"amount": "100000000",
|
||||
"denom": "unym"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"moniker": "bwn_g1",
|
||||
"website": "https://bwnym.xyz",
|
||||
"security_contact": "bwnym@proton.me",
|
||||
"details": "This gateway is part of the NYM project, which is dedicated to create outstanding privacy software that is legally compliant without sacrificing integrity or having any backdoors."
|
||||
},
|
||||
"last_probe_result": null,
|
||||
"last_probe_log": null,
|
||||
"last_testrun_utc": null,
|
||||
"last_updated_utc": "2025-03-03T10:45:48+00:00",
|
||||
"routing_score": 0.0,
|
||||
"config_score": 0
|
||||
},
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
If you have already run the API before, make sure to add the following to your script `--database-url "sqlite://node-status-api.sqlite?mode=rwc` so it will continue using the same DB.
|
||||
|
||||
### Functionality with Gateway Probe
|
||||
If you want to enable Gateway node probes and have the NS API store that data, you need to periodically run the `nym-node-status-agent`, authenticated with your `nym-node-status-api` instance. Authentication is to make sure that only the `-agent` you are operating (or others you trust) are submitting data to your API instance.
|
||||
|
||||
#### Compile Gateway Probe
|
||||
The `nym-node-status-agent` is a thin wrapper around the Gateway Probe binary which currently is in the NymVPN repo. `git checkout` to the most recent [release](https://github.com/nymtech/nym-vpn-client/releases), and compile the probe by following the [readme instructions](https://github.com/nymtech/nym-vpn-client/tree/develop/nym-vpn-core/crates/nym-gateway-probe). You will point the `-agent` at this binary when doing Probe testruns.
|
||||
|
||||
#### Generate Keypair
|
||||
```shell
|
||||
<PATH/TO/>nym-node-status-agent generate-keypair --path <PATH/TO/KEY/FILE/TO/GENERATE>/<KEY_NAME>
|
||||
# e.g.
|
||||
# nym-node-status-agent generate-keypair --path ~/.ssh/ns-agent-key
|
||||
```
|
||||
|
||||
You will then want to export the generated `public-key` so its accessible to the `nym-node-status-api` however you are setting your environmental variables, as `NODE_STATUS_API_AGENT_KEY_LIST`:
|
||||
|
||||
```bash
|
||||
export NODE_STATUS_API_AGENT_KEY_LIST=<YOUR_KEY> # e.g. "H4z8kx5Kkf5JNQHfxaE1MwRndjDCD1C7HsVhHTFfBZ4J"
|
||||
```
|
||||
|
||||
In this situation, you are probably only using one key. However, it is possible to set multiple keys as a comma seperated list, in case you wish to whitelist multiple `-agent`s to be able to submit to a single `-api` instance.
|
||||
|
||||
#### Run the Node Status Agent
|
||||
```shell
|
||||
<PATH/TO/>nym-node-status-agent run-probe --server-address http://127.0.0.1 --server-port 8000 --ns-api-auth-key "<NS_AGENT_PRIVATE_KEY>" --probe-path <PATH/TO/> nym-gateway-probe
|
||||
```
|
||||
|
||||
You will see a lot of output like so:
|
||||
|
||||
<AccordionTemplate name="Output">
|
||||
```shell
|
||||
listen_port=48586
|
||||
public_key=3f95caf771b8a63b9bdae6c186f7aba2eae93483f1c82c0243b5e00c85b4ec26
|
||||
preshared_key=0000000000000000000000000000000000000000000000000000000000000000
|
||||
protocol_version=1
|
||||
endpoint=185.186.78.251:51822
|
||||
last_handshake_time_sec=0
|
||||
last_handshake_time_nsec=0
|
||||
tx_bytes=0
|
||||
rx_bytes=0
|
||||
persistent_keepalive_interval=0
|
||||
allowed_ip=0.0.0.0/0
|
||||
2025/03/03 18:58:28 Pinging nymtech.net seq=0
|
||||
2025/03/03 18:58:29 Ping latency: 44.503483ms
|
||||
2025/03/03 18:58:29 Pinging nymtech.net seq=1
|
||||
2025/03/03 18:58:29 Ping latency: 42.852414ms
|
||||
2025/03/03 18:58:29 Pinging nymtech.net seq=2
|
||||
2025/03/03 18:58:29 Ping latency: 43.627256ms
|
||||
2025/03/03 18:58:29 Pinging nymtech.net seq=3
|
||||
2025/03/03 18:58:29 Ping latency: 43.638839ms
|
||||
2025/03/03 18:58:29 Pinging nymtech.net seq=4
|
||||
2025/03/03 18:58:29 Ping latency: 43.345357ms
|
||||
2025/03/03 18:58:29 Pinging 1.1.1.1 seq=0
|
||||
2025/03/03 18:58:29 Ping latency: 46.327233ms
|
||||
2025/03/03 18:58:34 Pinging 1.1.1.1 seq=1
|
||||
2025/03/03 18:58:34 Ping latency: 46.273726ms
|
||||
2025/03/03 18:58:39 Pinging 1.1.1.1 seq=2
|
||||
2025/03/03 18:58:39 Ping latency: 46.542774ms
|
||||
2025/03/03 18:58:44 Pinging 1.1.1.1 seq=3
|
||||
2025/03/03 18:58:44 Ping latency: 45.663545ms
|
||||
2025/03/03 18:58:49 Pinging 1.1.1.1 seq=4
|
||||
2025/03/03 18:58:49 Ping latency: 43.803063ms
|
||||
2025/03/03 18:58:56 Downloaded file content length: 1.00 MB
|
||||
2025/03/03 18:58:56 Download duration: 1.308072386s
|
||||
2025/03/03 18:58:56 private_key=1083749e43f4f8fb008f3f7deef9107ef86a68969670ddbb9f07bf85f94fb564
|
||||
listen_port=39129
|
||||
public_key=3f95caf771b8a63b9bdae6c186f7aba2eae93483f1c82c0243b5e00c85b4ec26
|
||||
preshared_key=0000000000000000000000000000000000000000000000000000000000000000
|
||||
protocol_version=1
|
||||
endpoint=185.186.78.251:51822
|
||||
last_handshake_time_sec=0
|
||||
last_handshake_time_nsec=0
|
||||
tx_bytes=0
|
||||
rx_bytes=0
|
||||
persistent_keepalive_interval=0
|
||||
allowed_ip=::/0
|
||||
2025/03/03 18:58:56 Pinging ipv6.google.com seq=0
|
||||
2025/03/03 18:58:56 Ping latency: 42.839528ms
|
||||
2025/03/03 18:58:56 Pinging ipv6.google.com seq=1
|
||||
2025/03/03 18:58:56 Ping latency: 54.844651ms
|
||||
2025/03/03 18:58:56 Pinging ipv6.google.com seq=2
|
||||
2025/03/03 18:58:56 Ping latency: 51.23104ms
|
||||
2025/03/03 18:58:56 Pinging ipv6.google.com seq=3
|
||||
2025/03/03 18:58:56 Ping latency: 43.320409ms
|
||||
2025/03/03 18:58:56 Pinging ipv6.google.com seq=4
|
||||
2025/03/03 18:58:56 Ping latency: 63.517358ms
|
||||
2025/03/03 18:58:56 Pinging 2001:4860:4860::8888 seq=0
|
||||
2025/03/03 18:58:56 Ping latency: 54.682534ms
|
||||
2025/03/03 18:59:01 Pinging 2001:4860:4860::8888 seq=1
|
||||
2025/03/03 18:59:01 Ping latency: 55.56235ms
|
||||
2025/03/03 18:59:06 Pinging 2001:4860:4860::8888 seq=2
|
||||
2025/03/03 18:59:06 Ping latency: 55.970418ms
|
||||
2025/03/03 18:59:11 Pinging 2001:4860:4860::8888 seq=3
|
||||
2025/03/03 18:59:14 Failed to send ping: i/o timeout
|
||||
2025/03/03 18:59:19 Pinging 2001:4860:4860::8888 seq=4
|
||||
2025/03/03 18:59:22 Failed to send ping: i/o timeout
|
||||
2025/03/03 18:59:27 Pinging 2606:4700:4700::1111 seq=0
|
||||
2025/03/03 18:59:27 Ping latency: 45.072616ms
|
||||
2025/03/03 18:59:32 Pinging 2606:4700:4700::1111 seq=1
|
||||
2025/03/03 18:59:32 Ping latency: 44.357306ms
|
||||
2025/03/03 18:59:37 Pinging 2606:4700:4700::1111 seq=2
|
||||
2025/03/03 18:59:37 Ping latency: 44.013562ms
|
||||
2025/03/03 18:59:42 Pinging 2606:4700:4700::1111 seq=3
|
||||
2025/03/03 18:59:42 Ping latency: 46.94342ms
|
||||
2025/03/03 18:59:47 Pinging 2606:4700:4700::1111 seq=4
|
||||
2025/03/03 18:59:48 Ping latency: 43.372288ms
|
||||
2025/03/03 18:59:53 Pinging 2620:fe::fe seq=0
|
||||
2025/03/03 18:59:53 Ping latency: 42.164952ms
|
||||
2025/03/03 18:59:58 Pinging 2620:fe::fe seq=1
|
||||
2025/03/03 18:59:58 Ping latency: 42.295812ms
|
||||
2025/03/03 19:00:03 Pinging 2620:fe::fe seq=2
|
||||
2025/03/03 19:00:03 Ping latency: 43.117534ms
|
||||
2025/03/03 19:00:08 Pinging 2620:fe::fe seq=3
|
||||
2025/03/03 19:00:08 Ping latency: 44.26068ms
|
||||
2025/03/03 19:00:13 Pinging 2620:fe::fe seq=4
|
||||
2025/03/03 19:00:13 Ping latency: 45.29956ms
|
||||
2025/03/03 19:00:21 Downloaded file content length: 10.00 MB
|
||||
2025/03/03 19:00:21 Download duration: 3.39529252s
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
Whilst you can run the `-agent` directly, it might be easier to run multiple instances in parallel with a script like so:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"}
|
||||
|
||||
probe_git_ref="nym-vpn-core-v1.4.0" # check for the most recent release
|
||||
monorepo_root=<PATH/TO/NYM/>
|
||||
|
||||
set -a
|
||||
source "${monorepo_root}/envs/${ENVIRONMENT}.env"
|
||||
set +a
|
||||
|
||||
export RUST_LOG="info"
|
||||
export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1"
|
||||
export NODE_STATUS_AGENT_SERVER_PORT="8000"
|
||||
export NODE_STATUS_AGENT_AUTH_KEY=<NS_AGENT_PRIVATE_KEY>
|
||||
export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1"
|
||||
|
||||
workers=${1:-1}
|
||||
echo "Running $workers workers in parallel"
|
||||
|
||||
function swarm() {
|
||||
local workers=$1
|
||||
|
||||
for ((i = 1; i <= workers; i++)); do
|
||||
${monorepo_root}/target/release/nym-node-status-agent run-probe --probe-path ~/<PATH/TO>/nym-vpn-client/nym-vpn-core/target/debug/nym-gateway-probe &
|
||||
done
|
||||
|
||||
wait
|
||||
|
||||
echo "All agents completed"
|
||||
}
|
||||
|
||||
swarm $workers
|
||||
```
|
||||
|
||||
And run specifying the number of workers with `./<SCRIPT_NAME>.sh <NUMBER_OF_WORKERS>`.
|
||||
|
||||
<Callout type="info">
|
||||
When running the probe, use logging level `RUST_LOG=info`. The Node Status API relies on that granularity for parsing probe results, and the logs grow incessantly if a lower level (e.g. `DEBUG`) is used.
|
||||
</Callout>
|
||||
|
||||
### Ports
|
||||
By default the API listens on `8000`, so you will need to configure this post to be reachable on your remote server. You can modify this with the `--http_port` flag.
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"mainnet":"Mainnet Endpoints"
|
||||
}
|
||||
@@ -52,22 +52,21 @@ This page displays a full list of all the changes during our release cycle from
|
||||
Patched version of `dorina` with a few fixes and tweaks to the release. We would like to ask `nym-node` operators to upgrade to this version as quickly as possible to implement the fixes across the network and improve general quality before NymVPN launch.
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.4-dorina-patched)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.6.2`
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.6.1`
|
||||
|
||||
```shell
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-03-06T20:32:36.922212778Z
|
||||
Build Version: 1.6.2
|
||||
Commit SHA: 247ebb7c4339de0a298a7fcb2574122a8306c3b8
|
||||
Commit Date: 2025-03-06T21:26:16.000000000+01:00
|
||||
Build Timestamp: 2025-03-06T14:28:21.408539599Z
|
||||
Build Version: 1.6.1
|
||||
Commit SHA: 1fb2ebad7ad1bad455d5b896ea14204211727417
|
||||
Commit Date: 2025-03-06T15:26:18.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.85.0
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
- Use legacy crypto for constructing SURB headers ([#5579])
|
||||
- Bugfix: make sure to correctly decode response content when putting it into error message ([#5571])
|
||||
- Tweak surb management to be more conservative ([#5570])
|
||||
- Deserialize v5 authenticator requests ([#5568])
|
||||
@@ -77,7 +76,6 @@ cargo Profile: release
|
||||
- Feature: v2 authentication request (#5537) ([#5563])
|
||||
- Create authenticator v5 request/response types ([#5561])
|
||||
|
||||
[#5579]: https://github.com/nymtech/nym/pull/5579
|
||||
[#5571]: https://github.com/nymtech/nym/pull/5571
|
||||
[#5570]: https://github.com/nymtech/nym/pull/5570
|
||||
[#5568]: https://github.com/nymtech/nym/pull/5568
|
||||
|
||||
@@ -20,10 +20,10 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-03-06T20:32:36.922212778Z
|
||||
Build Version: 1.6.2
|
||||
Commit SHA: 247ebb7c4339de0a298a7fcb2574122a8306c3b8
|
||||
Commit Date: 2025-03-06T21:26:16.000000000+01:00
|
||||
Build Timestamp: 2025-03-06T14:28:21.408539599Z
|
||||
Build Version: 1.6.1
|
||||
Commit SHA: 1fb2ebad7ad1bad455d5b896ea14204211727417
|
||||
Commit Date: 2025-03-06T15:26:18.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.85.0
|
||||
rustc Channel: stable
|
||||
|
||||
@@ -3,9 +3,8 @@ import { VarInfo } from 'components/variable-info.tsx';
|
||||
import { Steps } from 'nextra/components';import { Tabs } from 'nextra/components';
|
||||
import { MyTab } from 'components/generic-tabs.tsx';
|
||||
import PortsNymNode from 'components/operators/snippets/ports-nym-node.mdx';
|
||||
import PortsValidator from 'components/operators/snippets/ports-validator.mdx';
|
||||
import NymNodeSpecs from 'components/operators/snippets/nym-node-specs.mdx';
|
||||
import NTPSync from 'components/operators/snippets/ntp-time-sync.mdx'
|
||||
import PortsValidator from 'components/operators/snippets/ports-validator.mdx'
|
||||
import NymNodeSpecs from 'components/operators/snippets/nym-node-specs.mdx'
|
||||
|
||||
# VPS Setup & Configuration
|
||||
|
||||
@@ -43,18 +42,14 @@ apt update -y && apt --fix-broken install
|
||||
```
|
||||
- Install dependencies
|
||||
```sh
|
||||
apt -y install ca-certificates jq curl wget ufw jq tmux pkg-config build-essential libssl-dev git ntp ntpdate
|
||||
apt -y install ca-certificates jq curl wget ufw jq tmux pkg-config build-essential libssl-dev git
|
||||
```
|
||||
- Double check ufw is installed correctly
|
||||
```sh
|
||||
apt install ufw --fix-missing
|
||||
```
|
||||
|
||||
###### 2. Synchronize time of your server
|
||||
|
||||
<NTPSync />
|
||||
|
||||
###### 3. Configure your firewall using Uncomplicated Firewall (UFW)
|
||||
###### 2. Configure your firewall using Uncomplicated Firewall (UFW)
|
||||
|
||||
For a `nym-node` or Nyx validator to recieve traffic, you need to open ports on the server. The following commands will allow you to set up a firewall using `ufw`.
|
||||
|
||||
@@ -75,7 +70,7 @@ ufw enable
|
||||
ufw status
|
||||
```
|
||||
|
||||
###### 4. Open all needed ports to have your firewall for `nym-node` working correctly
|
||||
###### 3. Open all needed ports to have your firewall for `nym-node` working correctly
|
||||
|
||||
<div>
|
||||
<Tabs items={[
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# Nym Node Status API
|
||||
The Node Status API serves information about individual `nym-nodes` in the Mixnet, such as which role they are operating in, statistics about them, services such as Network Requesters, as well as summaries of the state of the Mixnet.
|
||||
|
||||
We recommend that developers building applications such as explorers or analytics interfaces about the Mixnet run their own instance of the API, in order to promote a robust network of downstream services, and spread the load of API calls amongst as many endpoints as possible.
|
||||
|
||||
You can find build and operation instructions in the [docs](https://nym.com/docs/apis/ns-api).
|
||||
@@ -10,8 +10,7 @@ pub(crate) fn generate_key_pair(path: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||
let mut private_key_file = File::create(priv_key_path)?;
|
||||
private_key_file.write_all(keypair.private_key().to_base58_string().as_bytes())?;
|
||||
|
||||
let pub_key_path = priv_key_path.with_file_name("public-key");
|
||||
|
||||
let pub_key_path = priv_key_path.with_extension("public");
|
||||
let mut public_key_file = File::create(&pub_key_path)?;
|
||||
public_key_file.write_all(keypair.public_key().to_base58_string().as_bytes())?;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use tracing::{debug, error, info};
|
||||
use tracing::error;
|
||||
|
||||
pub(crate) struct GwProbe {
|
||||
path: String,
|
||||
@@ -10,62 +10,21 @@ impl GwProbe {
|
||||
}
|
||||
|
||||
pub(crate) async fn version(&self) -> String {
|
||||
debug!("Attempting to execute binary at: {}", &self.path);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
match tokio::fs::metadata(&self.path).await {
|
||||
Ok(metadata) => {
|
||||
let perms = metadata.permissions();
|
||||
let mode = perms.mode();
|
||||
if mode & 0o111 == 0 {
|
||||
error!(
|
||||
"Binary is not executable: {} (mode: {:o})",
|
||||
&self.path, mode
|
||||
);
|
||||
return "Binary is not executable".to_string();
|
||||
}
|
||||
debug!("Binary exists with permissions: {:o}", mode);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to stat binary at {}: {}", &self.path, e);
|
||||
return format!("Failed to access binary: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut command = tokio::process::Command::new(&self.path);
|
||||
command.stdout(std::process::Stdio::piped());
|
||||
command.arg("--version");
|
||||
|
||||
info!("Executing command: {:?} --version", &self.path);
|
||||
|
||||
match command.spawn() {
|
||||
Ok(child) => match child.wait_with_output().await {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
String::from_utf8(output.stdout)
|
||||
.unwrap_or_else(|_| "Unable to parse version output".to_string())
|
||||
} else {
|
||||
let stderr = String::from_utf8(output.stderr)
|
||||
.unwrap_or_else(|_| "Unable to parse error output".to_string());
|
||||
error!(
|
||||
"Command failed with exit code {}: {}",
|
||||
output.status.code().unwrap_or(-1),
|
||||
stderr
|
||||
);
|
||||
format!("Command failed: {}", stderr)
|
||||
}
|
||||
Ok(child) => {
|
||||
if let Ok(output) = child.wait_with_output().await {
|
||||
return String::from_utf8(output.stdout)
|
||||
.unwrap_or("Unable to get log from test run".to_string());
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get command output: {}", e);
|
||||
format!("Failed to get command output: {}", e)
|
||||
}
|
||||
},
|
||||
"Unable to get probe version".to_string()
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to spawn process: {}", e);
|
||||
format!("Failed to spawn process: {}", e)
|
||||
error!("Failed to get probe version: {}", e);
|
||||
"Failed to get probe version".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,14 +296,9 @@ impl Monitor {
|
||||
Some(location) => return location,
|
||||
None => {
|
||||
for ip in node.description.host_information.ip_address.iter() {
|
||||
match self.ipinfo.locate_ip(ip.to_string()).await {
|
||||
Ok(location) => {
|
||||
self.geocache.insert(node_id, location.clone()).await;
|
||||
return location;
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("Couldn't locate IP {} due to: {}", ip, err)
|
||||
}
|
||||
if let Ok(location) = self.ipinfo.locate_ip(ip.to_string()).await {
|
||||
self.geocache.insert(node_id, location.clone()).await;
|
||||
return location;
|
||||
}
|
||||
}
|
||||
// if no data could be retrieved
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node"
|
||||
version = "1.6.2"
|
||||
version = "1.6.1"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -50,10 +50,6 @@ pub struct Debug {
|
||||
/// The maximum number of client connections the gateway will keep open at once.
|
||||
pub maximum_open_connections: usize,
|
||||
|
||||
/// Specifies the minimum performance of mixnodes in the network that are to be used in internal topologies
|
||||
/// of the services providers
|
||||
pub minimum_mix_performance: u8,
|
||||
|
||||
/// Defines the maximum age of a signed authentication request before it's deemed too stale to process.
|
||||
pub maximum_auth_request_age: Duration,
|
||||
|
||||
@@ -65,10 +61,9 @@ pub struct Debug {
|
||||
}
|
||||
|
||||
impl Debug {
|
||||
pub const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
pub const DEFAULT_MINIMUM_MIX_PERFORMANCE: u8 = 50;
|
||||
pub const DEFAULT_MAXIMUM_AUTH_REQUEST_AGE: Duration = Duration::from_secs(30);
|
||||
pub const DEFAULT_MAXIMUM_OPEN_CONNECTIONS: usize = 8192;
|
||||
pub const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
pub const DEFAULT_MAXIMUM_AUTH_REQUEST_AGE: Duration = Duration::from_secs(30);
|
||||
}
|
||||
|
||||
impl Default for Debug {
|
||||
@@ -77,7 +72,6 @@ impl Default for 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,
|
||||
minimum_mix_performance: Self::DEFAULT_MINIMUM_MIX_PERFORMANCE,
|
||||
stale_messages: Default::default(),
|
||||
client_bandwidth: Default::default(),
|
||||
zk_nym_tickets: Default::default(),
|
||||
|
||||
Generated
+16
-17
@@ -1583,9 +1583,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.13.1"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
|
||||
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"subtle",
|
||||
@@ -3557,7 +3557,6 @@ name = "nym-pemstore"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5063,18 +5062,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -5090,18 +5089,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.17"
|
||||
version = "0.11.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96"
|
||||
checksum = "364fec0df39c49a083c9a8a18a23a6bcfd9af130fe9fe321d18520a0d113e09e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6056,9 +6055,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.39"
|
||||
version = "0.3.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa 1.0.9",
|
||||
@@ -6073,15 +6072,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.3"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.20"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
|
||||
Generated
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO watcher_execution(start, end, error_message)\n VALUES (?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1aa7733ad4bbf3e6b8db909b8646bee247bc021b9534f1d4b0fcad32e2e56218"
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT id, tx_hash, height, message_index, sender, recipient, amount, memo, created_at as \"created_at: ::time::OffsetDateTime\"\n FROM transactions\n WHERE height > ?\n ORDER BY height ASC, message_index ASC\n ",
|
||||
"query": "\n SELECT * FROM transactions\n WHERE height > ? \n ORDER BY height ASC, message_index ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -44,7 +44,7 @@
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at: ::time::OffsetDateTime",
|
||||
"name": "created_at",
|
||||
"ordinal": 8,
|
||||
"type_info": "Datetime"
|
||||
}
|
||||
@@ -64,5 +64,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "f69907735e9b1e1572c4bf6fe8d44d4ea4e55c2a9c4d4f7e1c7e57bcb848ee08"
|
||||
"hash": "7b9abf4ff422b8d7a942955dc4fba380e7d5f0127f4745705b8ac9af6c170d19"
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nyx-chain-watcher"
|
||||
version = "0.1.13"
|
||||
version = "0.1.11"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
@@ -29,6 +29,7 @@ nym-node-requests = { path = "../nym-node/nym-node-requests", features = [
|
||||
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 }
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
CREATE TABLE watcher_execution
|
||||
(
|
||||
start TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
end TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
error_message TEXT
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::config::PaymentWatchersConfig;
|
||||
use crate::config::PaymentWatcherConfig;
|
||||
use crate::env::vars::{
|
||||
NYXD_SCRAPER_START_HEIGHT, NYXD_SCRAPER_UNSAFE_NUKE_DB,
|
||||
NYXD_SCRAPER_USE_BEST_EFFORT_START_HEIGHT,
|
||||
@@ -10,7 +10,7 @@ use nyxd_scraper::{
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
use std::fs;
|
||||
use tracing::{error, info, warn};
|
||||
use tracing::{info, warn};
|
||||
|
||||
pub(crate) async fn run_chain_scraper(
|
||||
config: &crate::config::Config,
|
||||
@@ -60,7 +60,7 @@ pub(crate) async fn run_chain_scraper(
|
||||
})
|
||||
.with_tx_module(EventScraperModule::new(
|
||||
db_pool,
|
||||
config.payment_watcher_config.clone(),
|
||||
config.payment_watcher_config.clone().unwrap_or_default(),
|
||||
));
|
||||
|
||||
let instance = scraper.build_and_start().await?;
|
||||
@@ -73,11 +73,11 @@ pub(crate) async fn run_chain_scraper(
|
||||
|
||||
pub struct EventScraperModule {
|
||||
db_pool: SqlitePool,
|
||||
payment_config: PaymentWatchersConfig,
|
||||
payment_config: PaymentWatcherConfig,
|
||||
}
|
||||
|
||||
impl EventScraperModule {
|
||||
pub fn new(db_pool: SqlitePool, payment_config: PaymentWatchersConfig) -> Self {
|
||||
pub fn new(db_pool: SqlitePool, payment_config: PaymentWatcherConfig) -> Self {
|
||||
Self {
|
||||
db_pool,
|
||||
payment_config,
|
||||
@@ -132,12 +132,6 @@ impl TxModule for EventScraperModule {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if tx.tx.body.messages.len() > 1 {
|
||||
error!(
|
||||
"this transaction has more than 1 message in it - payment information will be lost"
|
||||
);
|
||||
}
|
||||
|
||||
// Process each event
|
||||
for event in events {
|
||||
// Only process transfer events
|
||||
@@ -163,7 +157,17 @@ impl TxModule for EventScraperModule {
|
||||
// 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.is_being_watched(&recipient);
|
||||
let is_watched = self.payment_config.watchers.iter().any(|watcher| {
|
||||
if let Some(watched_accounts) =
|
||||
&watcher.watch_for_transfer_recipient_accounts
|
||||
{
|
||||
watched_accounts
|
||||
.iter()
|
||||
.any(|account| account.to_string() == recipient)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if is_watched {
|
||||
if let Err(e) = self
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
use crate::cli::DEFAULT_NYX_CHAIN_WATCHER_ID;
|
||||
use crate::config::payments_watcher::HttpAuthenticationOptions::AuthorizationBearerToken;
|
||||
use crate::config::payments_watcher::PaymentWatcherConfig;
|
||||
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatchersConfig};
|
||||
use crate::config::payments_watcher::PaymentWatcherEntry;
|
||||
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatcherConfig};
|
||||
use crate::error::NyxChainWatcherError;
|
||||
use nym_config::save_unformatted_config_to_file;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
@@ -18,22 +18,22 @@ pub(crate) async fn execute(_args: Args) -> Result<(), NyxChainWatcherError> {
|
||||
let data_dir = Config::default_data_directory(&config_path)?;
|
||||
|
||||
let builder = ConfigBuilder::new(config_path.clone(), data_dir).with_payment_watcher_config(
|
||||
PaymentWatchersConfig {
|
||||
watchers: vec![PaymentWatcherConfig {
|
||||
PaymentWatcherConfig {
|
||||
watchers: vec![PaymentWatcherEntry {
|
||||
id: DEFAULT_NYX_CHAIN_WATCHER_ID.to_string(),
|
||||
webhook_url: "https://webhook.site".to_string(),
|
||||
watch_for_transfer_recipient_accounts: vec![AccountId::from_str(
|
||||
watch_for_transfer_recipient_accounts: Some(vec![AccountId::from_str(
|
||||
"n17g9a2pwwkg8m60wf59pq6mv0c2wusg9ukparkz",
|
||||
)
|
||||
.unwrap()],
|
||||
.unwrap()]),
|
||||
authentication: Some(AuthorizationBearerToken {
|
||||
token: "1234".to_string(),
|
||||
}),
|
||||
description: None,
|
||||
watch_for_chain_message_types: vec![
|
||||
watch_for_chain_message_types: Some(vec![
|
||||
"/cosmos.bank.v1beta1.MsgSend".to_string(),
|
||||
"/ibc.applications.transfer.v1.MsgTransfer".to_string(),
|
||||
],
|
||||
]),
|
||||
}],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ pub(crate) struct Args {
|
||||
value_delimiter = ',',
|
||||
env = NYX_CHAIN_WATCHER_WATCH_ACCOUNTS
|
||||
)]
|
||||
pub watch_for_transfer_recipient_accounts: Vec<AccountId>,
|
||||
pub watch_for_transfer_recipient_accounts: Option<Vec<AccountId>>,
|
||||
|
||||
/// (Override) Watch for chain messages of these types
|
||||
#[clap(
|
||||
@@ -28,7 +28,7 @@ pub(crate) struct Args {
|
||||
value_delimiter = ',',
|
||||
env = NYX_CHAIN_WATCHER_WATCH_CHAIN_MESSAGE_TYPES
|
||||
)]
|
||||
pub watch_for_chain_message_types: Vec<String>,
|
||||
pub watch_for_chain_message_types: Option<Vec<String>>,
|
||||
|
||||
/// (Override) The webhook to call when we find something
|
||||
#[clap(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::cli::commands::run::args::Args;
|
||||
use crate::cli::DEFAULT_NYX_CHAIN_WATCHER_ID;
|
||||
use crate::config::payments_watcher::{HttpAuthenticationOptions, PaymentWatcherConfig};
|
||||
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatchersConfig};
|
||||
use crate::config::payments_watcher::{HttpAuthenticationOptions, PaymentWatcherEntry};
|
||||
use crate::config::{default_config_filepath, Config, ConfigBuilder, PaymentWatcherConfig};
|
||||
use crate::error::NyxChainWatcherError;
|
||||
use tracing::{info, warn};
|
||||
|
||||
@@ -18,8 +18,8 @@ pub(crate) fn get_run_config(args: Args) -> Result<Config, NyxChainWatcherError>
|
||||
} = args;
|
||||
|
||||
// if there are no args set, then try load the config
|
||||
if args.watch_for_transfer_recipient_accounts.is_empty()
|
||||
&& args.watch_for_transfer_recipient_accounts.is_empty()
|
||||
if args.watch_for_transfer_recipient_accounts.is_none()
|
||||
&& args.watch_for_transfer_recipient_accounts.is_none()
|
||||
&& args.chain_watcher_db_path.is_none()
|
||||
{
|
||||
info!("Loading default config file...");
|
||||
@@ -27,12 +27,12 @@ pub(crate) fn get_run_config(args: Args) -> Result<Config, NyxChainWatcherError>
|
||||
}
|
||||
|
||||
// set default messages
|
||||
if watch_for_chain_message_types.is_empty() {
|
||||
watch_for_chain_message_types = vec!["/cosmos.bank.v1beta1.MsgSend".to_string()];
|
||||
if watch_for_chain_message_types.is_none() {
|
||||
watch_for_chain_message_types = Some(vec!["/cosmos.bank.v1beta1.MsgSend".to_string()]);
|
||||
}
|
||||
|
||||
// warn if no accounts set
|
||||
if watch_for_transfer_recipient_accounts.is_empty() {
|
||||
if watch_for_transfer_recipient_accounts.is_none() {
|
||||
warn!(
|
||||
"You did not specify any accounts to watch in {}. Only chain data will be stored.",
|
||||
crate::env::vars::NYX_CHAIN_WATCHER_WATCH_ACCOUNTS
|
||||
@@ -58,8 +58,8 @@ pub(crate) fn get_run_config(args: Args) -> Result<Config, NyxChainWatcherError>
|
||||
let authentication =
|
||||
webhook_auth.map(|token| HttpAuthenticationOptions::AuthorizationBearerToken { token });
|
||||
|
||||
let watcher_config = PaymentWatchersConfig {
|
||||
watchers: vec![PaymentWatcherConfig {
|
||||
let watcher_config = PaymentWatcherConfig {
|
||||
watchers: vec![PaymentWatcherEntry {
|
||||
id: DEFAULT_NYX_CHAIN_WATCHER_ID.to_string(),
|
||||
description: None,
|
||||
watch_for_transfer_recipient_accounts: watch_for_transfer_recipient_accounts
|
||||
|
||||
@@ -2,114 +2,19 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::NyxChainWatcherError;
|
||||
use anyhow::Context;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::task::{JoinHandle, JoinSet};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info};
|
||||
use tokio::join;
|
||||
use tracing::{error, info, trace};
|
||||
|
||||
mod args;
|
||||
mod config;
|
||||
|
||||
use crate::chain_scraper::run_chain_scraper;
|
||||
use crate::db::DbPool;
|
||||
use crate::http::state::PaymentListenerState;
|
||||
use crate::payment_listener::PaymentListener;
|
||||
use crate::{db, http, price_scraper};
|
||||
use crate::{db, http, payment_listener, price_scraper};
|
||||
pub(crate) use args::Args;
|
||||
use nym_task::signal::wait_for_signal;
|
||||
|
||||
async fn try_insert_watcher_execution_information(
|
||||
db_pool: DbPool,
|
||||
start: OffsetDateTime,
|
||||
end: OffsetDateTime,
|
||||
error_message: Option<String>,
|
||||
) {
|
||||
let _ = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO watcher_execution(start, end, error_message)
|
||||
VALUES (?, ?, ?)
|
||||
"#,
|
||||
start,
|
||||
end,
|
||||
error_message
|
||||
)
|
||||
.execute(&db_pool)
|
||||
.await
|
||||
.inspect_err(|err| error!("failed to insert run information: {err}"));
|
||||
}
|
||||
|
||||
async fn wait_for_shutdown(
|
||||
db_pool: DbPool,
|
||||
start: OffsetDateTime,
|
||||
main_cancellation_token: CancellationToken,
|
||||
scraper_cancellation_token: CancellationToken,
|
||||
mut tasks: JoinSet<Option<anyhow::Result<()>>>,
|
||||
) {
|
||||
async fn finalize_shutdown(
|
||||
db_pool: DbPool,
|
||||
start: OffsetDateTime,
|
||||
main_cancellation_token: CancellationToken,
|
||||
scraper_cancellation_token: CancellationToken,
|
||||
mut tasks: JoinSet<Option<anyhow::Result<()>>>,
|
||||
error_message: Option<String>,
|
||||
) {
|
||||
// cancel all tasks
|
||||
main_cancellation_token.cancel();
|
||||
scraper_cancellation_token.cancel();
|
||||
|
||||
// stupid nasty and hacky workaround to make sure all relevant tasks have finished before hard aborting them
|
||||
// nasty stupid and hacky workaround
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
tasks.abort_all();
|
||||
|
||||
// insert execution result into the db
|
||||
try_insert_watcher_execution_information(
|
||||
db_pool,
|
||||
start,
|
||||
OffsetDateTime::now_utc(),
|
||||
error_message,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
// graceful shutdown
|
||||
_ = wait_for_signal() => {
|
||||
info!("received shutdown signal");
|
||||
finalize_shutdown(db_pool, start, main_cancellation_token, scraper_cancellation_token, tasks, None).await;
|
||||
}
|
||||
_ = scraper_cancellation_token.cancelled() => {
|
||||
info!("the scraper has issued cancellation");
|
||||
finalize_shutdown(db_pool, start, main_cancellation_token, scraper_cancellation_token, tasks, Some("unexpected scraper task cancellation".into())).await;
|
||||
}
|
||||
_ = main_cancellation_token.cancelled() => {
|
||||
info!("one of the tasks has cancelled the token");
|
||||
finalize_shutdown(db_pool, start, main_cancellation_token, scraper_cancellation_token, tasks, Some("unexpected main task cancellation".into())).await;
|
||||
}
|
||||
task_result = tasks.join_next() => {
|
||||
// the first unwrap is fine => join set was not empty
|
||||
let error_message = match task_result.unwrap() {
|
||||
Err(_join_err) => Some("unexpected join error".to_string()),
|
||||
Ok(Some(Ok(_))) => None,
|
||||
Ok(Some(Err(err))) => Some(err.to_string()),
|
||||
Ok(None) => {
|
||||
Some("unexpected task cancellation".to_string())
|
||||
}
|
||||
};
|
||||
|
||||
error!("unexpected task termination: {error_message:?}");
|
||||
finalize_shutdown(db_pool, start, main_cancellation_token, scraper_cancellation_token, tasks, error_message).await;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args, http_port: u16) -> Result<(), NyxChainWatcherError> {
|
||||
let start = OffsetDateTime::now_utc();
|
||||
|
||||
info!("passed arguments: {args:#?}");
|
||||
trace!("passed arguments: {args:#?}");
|
||||
|
||||
let config = config::get_run_config(args)?;
|
||||
|
||||
@@ -124,7 +29,9 @@ pub(crate) async fn execute(args: Args, http_port: u16) -> Result<(), NyxChainWa
|
||||
);
|
||||
info!(
|
||||
"Chain History Database path is {:?}",
|
||||
std::path::Path::new(&config.chain_scraper_database_path()).canonicalize()
|
||||
std::path::Path::new(&config.chain_scraper_database_path())
|
||||
.canonicalize()
|
||||
.unwrap_or_default()
|
||||
);
|
||||
|
||||
// Ensure parent directory exists
|
||||
@@ -134,109 +41,50 @@ pub(crate) async fn execute(args: Args, http_port: u16) -> Result<(), NyxChainWa
|
||||
|
||||
let connection_url = format!("sqlite://{}?mode=rwc", db_path);
|
||||
let storage = db::Storage::init(connection_url).await?;
|
||||
let watcher_pool = storage.pool_owned();
|
||||
let watcher_pool = storage.pool_owned().await;
|
||||
|
||||
let mut tasks = JoinSet::new();
|
||||
let cancellation_token = CancellationToken::new();
|
||||
// Spawn the chain scraper and get its storage
|
||||
|
||||
let price_scraper_pool = storage.pool_owned();
|
||||
let scraper_pool = storage.pool_owned();
|
||||
let shutdown_pool = storage.pool_owned();
|
||||
// Spawn the payment listener task
|
||||
let payment_listener_handle = tokio::spawn({
|
||||
let price_scraper_pool = storage.pool_owned().await;
|
||||
let scraper_pool = storage.pool_owned().await;
|
||||
run_chain_scraper(&config, scraper_pool).await?;
|
||||
let payment_watcher_config = config.payment_watcher_config.unwrap_or_default();
|
||||
|
||||
// construct shared state
|
||||
let payment_listener_shared_state = PaymentListenerState::new();
|
||||
|
||||
// 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();
|
||||
async move {
|
||||
// this only blocks until startup sync is done; it then runs on its own set of tasks
|
||||
let scraper = run_chain_scraper(&config, scraper_pool).await?;
|
||||
Ok(scraper.cancel_token())
|
||||
if let Err(e) =
|
||||
payment_listener::run_payment_listener(payment_watcher_config, price_scraper_pool)
|
||||
.await
|
||||
{
|
||||
error!("Payment listener error: {}", e);
|
||||
}
|
||||
Ok::<_, anyhow::Error>(())
|
||||
}
|
||||
});
|
||||
|
||||
// 2. payment listener
|
||||
let token = cancellation_token.clone();
|
||||
let payment_watcher_config = config.payment_watcher_config.clone();
|
||||
let payment_listener = PaymentListener::new(
|
||||
price_scraper_pool,
|
||||
payment_watcher_config,
|
||||
payment_listener_shared_state.clone(),
|
||||
)?;
|
||||
{
|
||||
tasks.spawn(async move {
|
||||
token
|
||||
.run_until_cancelled(async move {
|
||||
payment_listener.run().await;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
});
|
||||
}
|
||||
// Clone pool for each task that needs it
|
||||
//let background_pool = db_pool.clone();
|
||||
|
||||
// 3. price scraper (note, this task never terminates on its own)
|
||||
{
|
||||
let token = cancellation_token.clone();
|
||||
tasks.spawn(async move {
|
||||
token
|
||||
.run_until_cancelled(async move {
|
||||
price_scraper::run_price_scraper(&watcher_pool).await;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
});
|
||||
}
|
||||
let price_scraper_handle = tokio::spawn(async move {
|
||||
price_scraper::run_price_scraper(&watcher_pool).await;
|
||||
});
|
||||
|
||||
// 4. http api
|
||||
let http_server = http::server::build_http_api(
|
||||
storage.pool_owned(),
|
||||
&config,
|
||||
http_port,
|
||||
payment_listener_shared_state,
|
||||
)
|
||||
.await?;
|
||||
{
|
||||
let token = cancellation_token.clone();
|
||||
tasks.spawn(async move {
|
||||
info!("Starting HTTP server on port {http_port}",);
|
||||
async move {
|
||||
Some(
|
||||
http_server
|
||||
.run(token.cancelled_owned())
|
||||
.await
|
||||
.context("http server failure"),
|
||||
)
|
||||
}
|
||||
.await
|
||||
});
|
||||
}
|
||||
let shutdown_handles = http::server::start_http_api(storage.pool_owned().await, http_port)
|
||||
.await
|
||||
.expect("Failed to start server");
|
||||
|
||||
// 1. wait for either shutdown or scraper having finished startup
|
||||
tokio::select! {
|
||||
_ = wait_for_signal() => {
|
||||
info!("received shutdown signal while waiting for scraper to finish its startup");
|
||||
return Ok(())
|
||||
}
|
||||
scraper_token = scraper_token_handle => {
|
||||
let scraper_token = match scraper_token {
|
||||
Ok(Ok(token)) => token,
|
||||
Ok(Err(startup_err)) => {
|
||||
error!("failed to startup the chain scraper: {startup_err}");
|
||||
return Err(startup_err.into());
|
||||
}
|
||||
Err(runtime_err) => {
|
||||
error!("failed to finish the scraper startup task: {runtime_err}");
|
||||
return Ok(())
|
||||
info!("Started HTTP server on port {}", http_port);
|
||||
|
||||
}
|
||||
};
|
||||
// Wait for the short-lived tasks to complete
|
||||
let _ = join!(price_scraper_handle, payment_listener_handle);
|
||||
|
||||
wait_for_shutdown(shutdown_pool, start, cancellation_token, scraper_token, tasks).await
|
||||
}
|
||||
}
|
||||
// Wait for a signal to terminate the long-running task
|
||||
wait_for_signal().await;
|
||||
|
||||
if let Err(err) = shutdown_handles.shutdown().await {
|
||||
error!("{err}");
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use tracing::{debug, error};
|
||||
pub(crate) mod payments_watcher;
|
||||
mod template;
|
||||
|
||||
pub use crate::config::payments_watcher::PaymentWatchersConfig;
|
||||
pub use crate::config::payments_watcher::PaymentWatcherConfig;
|
||||
use crate::error::NyxChainWatcherError;
|
||||
|
||||
const DEFAULT_NYM_CHAIN_WATCHER_DIR: &str = "nym-chain-watcher";
|
||||
@@ -46,7 +46,7 @@ pub struct ConfigBuilder {
|
||||
|
||||
pub chain_scraper_db_path: Option<String>,
|
||||
|
||||
pub payment_watcher_config: Option<PaymentWatchersConfig>,
|
||||
pub payment_watcher_config: Option<PaymentWatcherConfig>,
|
||||
|
||||
pub logging: Option<LoggingSettings>,
|
||||
}
|
||||
@@ -76,7 +76,7 @@ impl ConfigBuilder {
|
||||
#[allow(dead_code)]
|
||||
pub fn with_payment_watcher_config(
|
||||
mut self,
|
||||
payment_watcher_config: impl Into<PaymentWatchersConfig>,
|
||||
payment_watcher_config: impl Into<PaymentWatcherConfig>,
|
||||
) -> Self {
|
||||
self.payment_watcher_config = Some(payment_watcher_config.into());
|
||||
self
|
||||
@@ -92,7 +92,7 @@ impl ConfigBuilder {
|
||||
Config {
|
||||
logging: self.logging.unwrap_or_default(),
|
||||
save_path: Some(self.config_path),
|
||||
payment_watcher_config: self.payment_watcher_config.unwrap_or_default(),
|
||||
payment_watcher_config: self.payment_watcher_config,
|
||||
data_dir: self.data_dir,
|
||||
db_path: self.db_path,
|
||||
chain_scraper_db_path: self.chain_scraper_db_path,
|
||||
@@ -116,8 +116,7 @@ pub struct Config {
|
||||
#[serde(skip)]
|
||||
chain_scraper_db_path: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub payment_watcher_config: PaymentWatchersConfig,
|
||||
pub payment_watcher_config: Option<PaymentWatcherConfig>,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettings,
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct PaymentWatchersConfig {
|
||||
pub watchers: Vec<PaymentWatcherConfig>,
|
||||
}
|
||||
|
||||
impl PaymentWatchersConfig {
|
||||
pub fn is_being_watched(&self, account: &str) -> bool {
|
||||
self.watchers.iter().any(|watcher| {
|
||||
watcher
|
||||
.watch_for_transfer_recipient_accounts
|
||||
.iter()
|
||||
.any(|acc| acc.as_ref() == account)
|
||||
})
|
||||
}
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PaymentWatcherConfig {
|
||||
pub watchers: Vec<PaymentWatcherEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PaymentWatcherConfig {
|
||||
pub struct PaymentWatcherEntry {
|
||||
pub id: String,
|
||||
pub description: Option<String>,
|
||||
pub webhook_url: String,
|
||||
pub watch_for_transfer_recipient_accounts: Vec<AccountId>,
|
||||
pub watch_for_chain_message_types: Vec<String>,
|
||||
pub watch_for_transfer_recipient_accounts: Option<Vec<AccountId>>,
|
||||
pub watch_for_chain_message_types: Option<Vec<String>>,
|
||||
pub authentication: Option<HttpAuthenticationOptions>,
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ impl Storage {
|
||||
}
|
||||
|
||||
/// Cloning pool is cheap, it's the same underlying set of connections
|
||||
pub fn pool_owned(&self) -> DbPool {
|
||||
pub async fn pool_owned(&self) -> DbPool {
|
||||
self.pool.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
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)]
|
||||
@@ -45,25 +41,3 @@ pub(crate) struct PaymentRecord {
|
||||
pub(crate) timestamp: i64,
|
||||
pub(crate) height: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, FromRow)]
|
||||
pub(crate) struct Transaction {
|
||||
pub(crate) id: i64,
|
||||
pub(crate) tx_hash: String,
|
||||
pub(crate) height: i64,
|
||||
pub(crate) message_index: i64,
|
||||
pub(crate) sender: String,
|
||||
pub(crate) recipient: String,
|
||||
pub(crate) amount: String,
|
||||
pub(crate) memo: Option<String>,
|
||||
pub(crate) created_at: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
pub(crate) fn funds(&self) -> anyhow::Result<Coin> {
|
||||
self.amount
|
||||
.as_str()
|
||||
.parse()
|
||||
.context("failed to parse transaction amount")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::vec_deque::{IntoIter, Iter};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct RingBuffer<T> {
|
||||
#[serde(flatten)]
|
||||
inner: VecDeque<T>,
|
||||
}
|
||||
|
||||
impl<T> RingBuffer<T> {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
inner: VecDeque::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, item: T) {
|
||||
if self.inner.len() == self.inner.capacity() {
|
||||
self.inner.pop_front();
|
||||
self.inner.push_back(item);
|
||||
debug_assert!(self.inner.len() == self.inner.capacity());
|
||||
} else {
|
||||
self.inner.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter<'_, T> {
|
||||
self.inner.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<RingBuffer<T>> for VecDeque<T> {
|
||||
fn from(value: RingBuffer<T>) -> Self {
|
||||
value.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<RingBuffer<T>> for Vec<T> {
|
||||
fn from(value: RingBuffer<T>) -> Self {
|
||||
value.inner.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoIterator for RingBuffer<T> {
|
||||
type Item = T;
|
||||
type IntoIter = IntoIter<T>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.inner.into_iter()
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ use utoipa_swagger_ui::SwaggerUi;
|
||||
use crate::http::{api_docs, server::HttpServer, state::AppState};
|
||||
|
||||
pub(crate) mod price;
|
||||
pub(crate) mod status;
|
||||
pub(crate) mod watcher;
|
||||
|
||||
pub(crate) struct RouterBuilder {
|
||||
@@ -26,13 +25,8 @@ impl RouterBuilder {
|
||||
"/",
|
||||
axum::routing::get(|| async { Redirect::permanent("/swagger") }),
|
||||
)
|
||||
.nest(
|
||||
"/v1",
|
||||
Router::new()
|
||||
.nest("/status", status::routes())
|
||||
.nest("/price", price::routes())
|
||||
.nest("/watcher", watcher::routes()),
|
||||
);
|
||||
.nest("/v1", Router::new().nest("/price", price::routes()))
|
||||
.nest("/v1", Router::new().nest("/watcher", watcher::routes()));
|
||||
|
||||
Self {
|
||||
unfinished_router: router,
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::http::models::status::{
|
||||
ActivePaymentWatchersResponse, PaymentListenerFailureDetails, PaymentListenerStatusResponse,
|
||||
ProcessedPayment, WatcherFailureDetails, WatcherState,
|
||||
};
|
||||
use crate::http::state::{AppState, PaymentListenerState};
|
||||
use axum::extract::State;
|
||||
use axum::routing::get;
|
||||
use axum::{Json, Router};
|
||||
use std::ops::Deref;
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/active-payment-watchers", get(active_payment_watchers))
|
||||
.route("/payment-listener", get(payment_listener_status))
|
||||
}
|
||||
|
||||
#[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(),
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::config::Config;
|
||||
use crate::env;
|
||||
use crate::http::error::HttpResult;
|
||||
use crate::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::{Json, Router};
|
||||
use std::env::var;
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new().route("/addresses", axum::routing::get(get_addresses))
|
||||
@@ -17,6 +19,30 @@ pub(crate) fn routes() -> Router<AppState> {
|
||||
)]
|
||||
|
||||
/// Fetch the addresses being watched by the chain watcher
|
||||
async fn get_addresses(State(state): State<AppState>) -> HttpResult<Json<Vec<String>>> {
|
||||
Ok(Json(state.watched_accounts()))
|
||||
async fn get_addresses() -> HttpResult<Json<Vec<String>>> {
|
||||
let addresses = match Config::read_from_toml_file_in_default_location() {
|
||||
Ok(config) => config
|
||||
.payment_watcher_config
|
||||
.as_ref()
|
||||
.and_then(|config| {
|
||||
config.watchers.iter().find_map(|watcher| {
|
||||
watcher
|
||||
.watch_for_transfer_recipient_accounts
|
||||
.as_ref()
|
||||
.map(|accounts| {
|
||||
accounts
|
||||
.iter()
|
||||
.map(|account| account.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
// If the config file doesn't exist, fall back to env variable
|
||||
Err(_) => var(env::vars::NYX_CHAIN_WATCHER_WATCH_ACCOUNTS)
|
||||
.map(|accounts| accounts.split(',').map(String::from).collect())
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
|
||||
Ok(Json(addresses))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
pub(crate) mod api;
|
||||
pub(crate) mod api_docs;
|
||||
pub(crate) mod error;
|
||||
pub(crate) mod models;
|
||||
pub(crate) mod server;
|
||||
pub(crate) mod state;
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// if we ever create some sort of chain watcher client, those would need to be extracted
|
||||
|
||||
pub mod status {
|
||||
use crate::config::payments_watcher::PaymentWatcherConfig;
|
||||
use crate::models::openapi_schema;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use time::OffsetDateTime;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ActivePaymentWatchersResponse {
|
||||
pub watchers: Vec<PaymentWatcher>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct PaymentWatcher {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
pub webhook_url: String,
|
||||
pub watched_accounts: Vec<String>,
|
||||
pub watched_message_types: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<&PaymentWatcherConfig> for PaymentWatcher {
|
||||
fn from(value: &PaymentWatcherConfig) -> Self {
|
||||
PaymentWatcher {
|
||||
id: value.id.clone(),
|
||||
description: value.description.clone().unwrap_or_default(),
|
||||
webhook_url: value.webhook_url.clone(),
|
||||
watched_accounts: value
|
||||
.watch_for_transfer_recipient_accounts
|
||||
.iter()
|
||||
.map(|a| a.to_string())
|
||||
.collect(),
|
||||
watched_message_types: value.watch_for_chain_message_types.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct PaymentListenerStatusResponse {
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub last_checked: OffsetDateTime,
|
||||
|
||||
pub processed_payments_since_startup: u64,
|
||||
pub watcher_errors_since_startup: u64,
|
||||
pub payment_listener_errors_since_startup: u64,
|
||||
|
||||
pub last_processed_payment: Option<ProcessedPayment>,
|
||||
|
||||
pub latest_failures: Vec<PaymentListenerFailureDetails>,
|
||||
pub watchers: HashMap<String, WatcherState>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub(crate) struct ProcessedPayment {
|
||||
pub processed_at: OffsetDateTime,
|
||||
|
||||
pub tx_hash: String,
|
||||
pub message_index: u64,
|
||||
pub height: u64,
|
||||
pub sender: String,
|
||||
pub receiver: String,
|
||||
|
||||
#[schema(value_type = openapi_schema::Coin)]
|
||||
pub funds: Coin,
|
||||
|
||||
pub memo: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub(crate) struct PaymentListenerFailureDetails {
|
||||
pub(crate) timestamp: OffsetDateTime,
|
||||
pub(crate) error: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub(crate) struct WatcherState {
|
||||
pub(crate) latest_failures: Vec<WatcherFailureDetails>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub(crate) struct WatcherFailureDetails {
|
||||
pub(crate) timestamp: OffsetDateTime,
|
||||
pub(crate) error: String,
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,70 @@
|
||||
use axum::Router;
|
||||
use core::net::SocketAddr;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_util::sync::WaitForCancellationFutureOwned;
|
||||
use tokio::{net::TcpListener, task::JoinHandle};
|
||||
use tokio_util::sync::{CancellationToken, WaitForCancellationFutureOwned};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::http::state::PaymentListenerState;
|
||||
use crate::{
|
||||
db::DbPool,
|
||||
http::{api::RouterBuilder, state::AppState},
|
||||
};
|
||||
|
||||
pub(crate) async fn build_http_api(
|
||||
/// Return handles that allow for graceful shutdown of server + awaiting its
|
||||
/// background tokio task
|
||||
pub(crate) async fn start_http_api(
|
||||
db_pool: DbPool,
|
||||
config: &Config,
|
||||
http_port: u16,
|
||||
payment_listener_state: PaymentListenerState,
|
||||
) -> anyhow::Result<HttpServer> {
|
||||
) -> anyhow::Result<ShutdownHandles> {
|
||||
let router_builder = RouterBuilder::with_default_routes();
|
||||
|
||||
let state = AppState::new(
|
||||
db_pool,
|
||||
config
|
||||
.payment_watcher_config
|
||||
.watchers
|
||||
.iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
payment_listener_state,
|
||||
);
|
||||
let state = AppState::new(db_pool);
|
||||
let router = router_builder.with_state(state);
|
||||
|
||||
let bind_addr = format!("0.0.0.0:{}", http_port);
|
||||
let server = router.build_server(bind_addr).await?;
|
||||
Ok(server)
|
||||
|
||||
Ok(start_server(server))
|
||||
}
|
||||
|
||||
fn start_server(server: HttpServer) -> ShutdownHandles {
|
||||
// one copy is stored to trigger a graceful shutdown later
|
||||
let shutdown_button = CancellationToken::new();
|
||||
// other copy is given to server to listen for a shutdown
|
||||
let shutdown_receiver = shutdown_button.clone();
|
||||
let shutdown_receiver = shutdown_receiver.cancelled_owned();
|
||||
|
||||
let server_handle = tokio::spawn(async move { server.run(shutdown_receiver).await });
|
||||
|
||||
ShutdownHandles {
|
||||
server_handle,
|
||||
shutdown_button,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ShutdownHandles {
|
||||
server_handle: JoinHandle<std::io::Result<()>>,
|
||||
shutdown_button: CancellationToken,
|
||||
}
|
||||
|
||||
impl ShutdownHandles {
|
||||
/// Send graceful shutdown signal to server and wait for server task to complete
|
||||
pub(crate) async fn shutdown(self) -> anyhow::Result<()> {
|
||||
self.shutdown_button.cancel();
|
||||
|
||||
match self.server_handle.await {
|
||||
Ok(Ok(_)) => {
|
||||
tracing::info!("HTTP server shut down without errors");
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
tracing::error!("HTTP server terminated with: {err}");
|
||||
anyhow::bail!(err)
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Server task panicked: {err}");
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct HttpServer {
|
||||
|
||||
@@ -1,183 +1,16 @@
|
||||
use crate::db::DbPool;
|
||||
use crate::helpers::RingBuffer;
|
||||
use crate::http::models::status::PaymentWatcher;
|
||||
use crate::models::WebhookPayload;
|
||||
use axum::extract::FromRef;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct AppState {
|
||||
db_pool: DbPool,
|
||||
pub(crate) registered_payment_watchers: Arc<Vec<PaymentWatcher>>,
|
||||
pub(crate) payment_listener_state: PaymentListenerState,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub(crate) fn new(
|
||||
db_pool: DbPool,
|
||||
registered_payment_watchers: Vec<PaymentWatcher>,
|
||||
payment_listener_state: PaymentListenerState,
|
||||
) -> Self {
|
||||
Self {
|
||||
db_pool,
|
||||
registered_payment_watchers: Arc::new(registered_payment_watchers),
|
||||
payment_listener_state,
|
||||
}
|
||||
pub(crate) fn new(db_pool: DbPool) -> Self {
|
||||
Self { db_pool }
|
||||
}
|
||||
|
||||
pub(crate) fn db_pool(&self) -> &DbPool {
|
||||
&self.db_pool
|
||||
}
|
||||
|
||||
pub(crate) fn watched_accounts(&self) -> Vec<String> {
|
||||
self.registered_payment_watchers
|
||||
.iter()
|
||||
.flat_map(|w| w.watched_accounts.iter())
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PaymentListenerState {
|
||||
pub(crate) inner: Arc<RwLock<PaymentListenerStateInner>>,
|
||||
}
|
||||
|
||||
impl PaymentListenerState {
|
||||
// TODO: make those configurable
|
||||
const MAX_WATCHER_FAILURES: usize = 20;
|
||||
const MAX_PAYMENT_LISTENER_FAILURES: usize = 50;
|
||||
|
||||
pub(crate) fn new() -> Self {
|
||||
PaymentListenerState {
|
||||
inner: Arc::new(RwLock::new(PaymentListenerStateInner {
|
||||
last_checked: OffsetDateTime::UNIX_EPOCH,
|
||||
processed_payments_since_startup: 0,
|
||||
watcher_errors_since_startup: 0,
|
||||
payment_listener_errors_since_startup: 0,
|
||||
last_processed_payment: None,
|
||||
latest_failures: RingBuffer::new(Self::MAX_PAYMENT_LISTENER_FAILURES),
|
||||
watchers: Default::default(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_listener_failure(&self, failure: PaymentListenerFailureDetails) {
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
guard.payment_listener_errors_since_startup += 1;
|
||||
guard.latest_failures.push(failure);
|
||||
}
|
||||
|
||||
pub(crate) async fn insert_watcher_failure(&self, id: &str, failure: WatcherFailureDetails) {
|
||||
self.inner
|
||||
.write()
|
||||
.await
|
||||
.watchers
|
||||
.entry(id.to_string())
|
||||
.or_insert(WatcherState {
|
||||
latest_failures: RingBuffer::new(Self::MAX_WATCHER_FAILURES),
|
||||
})
|
||||
.latest_failures
|
||||
.push(failure);
|
||||
}
|
||||
|
||||
pub(crate) async fn processed_payment_transaction(&self, payment: ProcessedPayment) {
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
guard.processed_payments_since_startup += 1;
|
||||
guard.last_processed_payment = Some(payment)
|
||||
}
|
||||
|
||||
pub(crate) async fn update_last_checked(&self) {
|
||||
self.inner.write().await.last_checked = OffsetDateTime::now_utc();
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for PaymentListenerState {
|
||||
fn from_ref(input: &AppState) -> Self {
|
||||
input.payment_listener_state.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PaymentListenerStateInner {
|
||||
pub(crate) last_checked: OffsetDateTime,
|
||||
|
||||
pub(crate) processed_payments_since_startup: u64,
|
||||
pub(crate) watcher_errors_since_startup: u64,
|
||||
pub(crate) payment_listener_errors_since_startup: u64,
|
||||
|
||||
pub(crate) last_processed_payment: Option<ProcessedPayment>,
|
||||
|
||||
pub(crate) latest_failures: RingBuffer<PaymentListenerFailureDetails>,
|
||||
pub(crate) watchers: HashMap<String, WatcherState>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct ProcessedPayment {
|
||||
pub processed_at: OffsetDateTime,
|
||||
|
||||
pub tx_hash: String,
|
||||
pub message_index: u64,
|
||||
pub height: u64,
|
||||
pub sender: String,
|
||||
pub receiver: String,
|
||||
pub funds: Coin,
|
||||
pub memo: String,
|
||||
}
|
||||
|
||||
impl From<WebhookPayload> for ProcessedPayment {
|
||||
fn from(payload: WebhookPayload) -> Self {
|
||||
ProcessedPayment {
|
||||
processed_at: OffsetDateTime::now_utc(),
|
||||
tx_hash: payload.transaction_hash,
|
||||
message_index: payload.message_index,
|
||||
height: payload.height as u64,
|
||||
sender: payload.sender_address,
|
||||
receiver: payload.receiver_address,
|
||||
funds: payload.funds.into(),
|
||||
memo: payload.memo.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PaymentListenerFailureDetails {
|
||||
pub(crate) timestamp: OffsetDateTime,
|
||||
pub(crate) error: String,
|
||||
}
|
||||
|
||||
impl PaymentListenerFailureDetails {
|
||||
pub(crate) fn new(error: String) -> Self {
|
||||
PaymentListenerFailureDetails {
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WatcherState {
|
||||
pub(crate) latest_failures: RingBuffer<WatcherFailureDetails>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WatcherFailureDetails {
|
||||
pub(crate) timestamp: OffsetDateTime,
|
||||
pub(crate) error: String,
|
||||
}
|
||||
|
||||
impl WatcherFailureDetails {
|
||||
pub(crate) fn new(error: String) -> Self {
|
||||
WatcherFailureDetails {
|
||||
timestamp: OffsetDateTime::now_utc(),
|
||||
error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ mod config;
|
||||
mod db;
|
||||
mod env;
|
||||
mod error;
|
||||
pub(crate) mod helpers;
|
||||
mod http;
|
||||
mod logging;
|
||||
pub mod models;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use nym_validator_client::nyxd::CosmWasmCoin;
|
||||
use rocket::serde::{Deserialize, Serialize};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, JsonSchema, ToSchema)]
|
||||
|
||||
@@ -1,152 +1,108 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::PaymentWatchersConfig;
|
||||
use crate::db::models::Transaction;
|
||||
use crate::config::payments_watcher::HttpAuthenticationOptions;
|
||||
use crate::config::PaymentWatcherConfig;
|
||||
use crate::db::queries;
|
||||
use crate::http::state::{
|
||||
PaymentListenerFailureDetails, PaymentListenerState, ProcessedPayment, WatcherFailureDetails,
|
||||
};
|
||||
use crate::models::WebhookPayload;
|
||||
use crate::payment_listener::watcher::PaymentWatcher;
|
||||
use anyhow::Context;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use reqwest::Client;
|
||||
use sqlx::SqlitePool;
|
||||
use std::str::FromStr;
|
||||
use tokio::time::{self, Duration};
|
||||
use tracing::{debug, error, info};
|
||||
use tracing::{error, info};
|
||||
|
||||
pub(crate) mod watcher;
|
||||
pub(crate) async fn run_payment_listener(
|
||||
payment_watcher_config: PaymentWatcherConfig,
|
||||
watcher_pool: SqlitePool,
|
||||
) -> anyhow::Result<()> {
|
||||
let client = Client::new();
|
||||
|
||||
pub(crate) struct PaymentListener {
|
||||
db_pool: SqlitePool,
|
||||
payment_watchers: Vec<PaymentWatcher>,
|
||||
shared_state: PaymentListenerState,
|
||||
}
|
||||
loop {
|
||||
// 1. get the last height this watcher ran at
|
||||
let last_checked_height = queries::payments::get_last_checked_height(&watcher_pool).await?;
|
||||
info!("Last checked height: {}", last_checked_height);
|
||||
|
||||
impl PaymentListener {
|
||||
pub(crate) fn new(
|
||||
db_pool: SqlitePool,
|
||||
config: PaymentWatchersConfig,
|
||||
shared_state: PaymentListenerState,
|
||||
) -> anyhow::Result<Self> {
|
||||
Ok(PaymentListener {
|
||||
db_pool,
|
||||
payment_watchers: config
|
||||
.watchers
|
||||
.iter()
|
||||
.map(|watcher_cfg| PaymentWatcher::new(watcher_cfg.clone()))
|
||||
.collect::<anyhow::Result<Vec<_>>>()?,
|
||||
shared_state,
|
||||
})
|
||||
}
|
||||
// 2. iterate through watchers
|
||||
for watcher in &payment_watcher_config.watchers {
|
||||
if watcher.watch_for_transfer_recipient_accounts.is_some() {
|
||||
// 3. Query new transactions for this watcher's recipient accounts
|
||||
let transactions = sqlx::query!(
|
||||
r#"
|
||||
SELECT * FROM transactions
|
||||
WHERE height > ?
|
||||
ORDER BY height ASC, message_index ASC
|
||||
"#,
|
||||
last_checked_height
|
||||
)
|
||||
.fetch_all(&watcher_pool)
|
||||
.await?;
|
||||
|
||||
async fn retrieve_unprocessed_transactions(&self) -> anyhow::Result<Vec<Transaction>> {
|
||||
let last_checked_height = queries::payments::get_last_checked_height(&self.db_pool).await?;
|
||||
let txs = sqlx::query_as!(
|
||||
Transaction,
|
||||
r#"
|
||||
SELECT id, tx_hash, height, message_index, sender, recipient, amount, memo, created_at as "created_at: ::time::OffsetDateTime"
|
||||
FROM transactions
|
||||
WHERE height > ?
|
||||
ORDER BY height ASC, message_index ASC
|
||||
"#,
|
||||
last_checked_height
|
||||
)
|
||||
.fetch_all(&self.db_pool)
|
||||
.await?;
|
||||
if !transactions.is_empty() {
|
||||
info!(
|
||||
"[watcher = {}] Processing {} transactions",
|
||||
watcher.id,
|
||||
transactions.len()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(txs)
|
||||
}
|
||||
for tx in transactions {
|
||||
let funds = Coin::from_str(&tx.amount)?;
|
||||
let amount: f64 = funds.amount as f64 / 1e6f64; // convert to major value, there will be precision loss
|
||||
|
||||
async fn process_transaction(&self, tx: Transaction) -> anyhow::Result<()> {
|
||||
// 3.1 process any payments
|
||||
let funds = tx.funds()?;
|
||||
let amount: f64 = funds.amount as f64 / 1e6f64; // convert to major value, there will be precision loss
|
||||
// Store transaction hash for later use
|
||||
let tx_hash = tx.tx_hash.clone();
|
||||
let message_index = tx.message_index;
|
||||
|
||||
// TODO: FIXME: it may happen that we insert a payment but fail to invoke all webhooks
|
||||
|
||||
queries::payments::insert_payment(
|
||||
&self.db_pool,
|
||||
tx.tx_hash.clone(),
|
||||
tx.sender.clone(),
|
||||
tx.recipient.clone(),
|
||||
amount,
|
||||
tx.height,
|
||||
tx.memo.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 3.1. invoke all relevant webhooks for all registered watchers
|
||||
let webhook_data = WebhookPayload {
|
||||
transaction_hash: tx.tx_hash,
|
||||
message_index: tx.message_index as u64,
|
||||
sender_address: tx.sender,
|
||||
receiver_address: tx.recipient,
|
||||
funds: funds.into(),
|
||||
height: tx.height as u128,
|
||||
memo: tx.memo,
|
||||
};
|
||||
|
||||
for watcher in &self.payment_watchers {
|
||||
if let Err(err) = watcher.invoke_webhook(&webhook_data).await {
|
||||
error!("watcher {} failure: {err:#}", watcher.id());
|
||||
self.shared_state
|
||||
.insert_watcher_failure(
|
||||
watcher.id(),
|
||||
WatcherFailureDetails::new(err.to_string()),
|
||||
queries::payments::insert_payment(
|
||||
&watcher_pool,
|
||||
tx.tx_hash,
|
||||
tx.sender.clone(),
|
||||
tx.recipient.clone(),
|
||||
amount,
|
||||
tx.height,
|
||||
tx.memo.clone(),
|
||||
)
|
||||
.await
|
||||
.await?;
|
||||
|
||||
let webhook_data = WebhookPayload {
|
||||
transaction_hash: tx_hash.clone(),
|
||||
message_index: message_index as u64,
|
||||
sender_address: tx.sender,
|
||||
receiver_address: tx.recipient,
|
||||
funds: funds.into(),
|
||||
height: tx.height as u128,
|
||||
memo: tx.memo,
|
||||
};
|
||||
|
||||
let mut request_builder = client.post(&watcher.webhook_url).json(&webhook_data);
|
||||
|
||||
if let Some(auth) = &watcher.authentication {
|
||||
match auth {
|
||||
HttpAuthenticationOptions::AuthorizationBearerToken { token } => {
|
||||
request_builder = request_builder.bearer_auth(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match request_builder.send().await {
|
||||
Ok(res) => info!(
|
||||
"[watcher = {}] ✅ Webhook {} {} - tx {}, index {}",
|
||||
watcher.id,
|
||||
res.status(),
|
||||
res.url(),
|
||||
tx_hash,
|
||||
message_index,
|
||||
),
|
||||
Err(e) => error!(
|
||||
"[watcher = {}] ❌ Webhook {:?} {:?} error = {}",
|
||||
watcher.id,
|
||||
e.status(),
|
||||
e.url(),
|
||||
e,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.shared_state
|
||||
.processed_payment_transaction(ProcessedPayment::from(webhook_data))
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_for_unprocessed_payments(&self) -> anyhow::Result<()> {
|
||||
// 1. retrieve any unprocessed transactions
|
||||
let unprocessed_transactions = self
|
||||
.retrieve_unprocessed_transactions()
|
||||
.await
|
||||
.context("failed to retrieve unprocessed transactions")?;
|
||||
|
||||
if unprocessed_transactions.is_empty() {
|
||||
debug!("no payment transactions to process.");
|
||||
return Ok(());
|
||||
} else {
|
||||
info!(
|
||||
"processing {} payment transactions",
|
||||
unprocessed_transactions.len()
|
||||
);
|
||||
}
|
||||
|
||||
// 2. attempt to process them
|
||||
for tx in unprocessed_transactions {
|
||||
let hash = tx.tx_hash.clone();
|
||||
let height = tx.height;
|
||||
self.process_transaction(tx).await.with_context(|| {
|
||||
format!("failed to process transaction {hash} at height {height}")
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&self) {
|
||||
loop {
|
||||
time::sleep(Duration::from_secs(10)).await;
|
||||
|
||||
if let Err(err) = self.check_for_unprocessed_payments().await {
|
||||
error!("failed to fully process payments: {err:#}");
|
||||
self.shared_state
|
||||
.insert_listener_failure(PaymentListenerFailureDetails::new(err.to_string()))
|
||||
.await;
|
||||
continue;
|
||||
}
|
||||
|
||||
self.shared_state.update_last_checked().await;
|
||||
}
|
||||
time::sleep(Duration::from_secs(10)).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::payments_watcher::{HttpAuthenticationOptions, PaymentWatcherConfig};
|
||||
use crate::models::WebhookPayload;
|
||||
use anyhow::Context;
|
||||
use reqwest::{Client, Url};
|
||||
use tracing::{error, info};
|
||||
|
||||
pub(crate) struct PaymentWatcher {
|
||||
webhook_url: Url,
|
||||
config: PaymentWatcherConfig,
|
||||
}
|
||||
|
||||
impl PaymentWatcher {
|
||||
pub(crate) fn new(config: PaymentWatcherConfig) -> anyhow::Result<Self> {
|
||||
Ok(PaymentWatcher {
|
||||
webhook_url: config
|
||||
.webhook_url
|
||||
.as_str()
|
||||
.parse()
|
||||
.context("couldn't create payment watcher: provided webhook URL is malformed")?,
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn id(&self) -> &str {
|
||||
&self.config.id
|
||||
}
|
||||
|
||||
pub(crate) async fn invoke_webhook(&self, payload: &WebhookPayload) -> anyhow::Result<()> {
|
||||
let client = Client::builder()
|
||||
.user_agent(format!(
|
||||
"nyx-chain-watcher/{}/watcher-{}",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
self.config.id
|
||||
))
|
||||
.build()
|
||||
.context("failed to build reqwest client")?;
|
||||
|
||||
let mut request_builder = client.post(self.webhook_url.clone()).json(payload);
|
||||
|
||||
if let Some(auth) = &self.config.authentication {
|
||||
match auth {
|
||||
HttpAuthenticationOptions::AuthorizationBearerToken { token } => {
|
||||
request_builder = request_builder.bearer_auth(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match request_builder.send().await {
|
||||
Ok(res) => info!(
|
||||
"[watcher = {}] ✅ Webhook {} {} - tx {}, index {}",
|
||||
self.config.id,
|
||||
res.status(),
|
||||
res.url(),
|
||||
payload.transaction_hash,
|
||||
payload.message_index,
|
||||
),
|
||||
Err(err) => {
|
||||
error!(
|
||||
"[watcher = {}] ❌ Webhook {:?} {:?} error = {err}",
|
||||
self.config.id,
|
||||
err.status(),
|
||||
err.url(),
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use crate::db::{
|
||||
queries::price::insert_nym_prices,
|
||||
};
|
||||
use core::str;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::Duration;
|
||||
|
||||
use crate::db::DbPool;
|
||||
@@ -12,7 +13,7 @@ const FAILURE_RETRY_DELAY: Duration = Duration::from_secs(60 * 2);
|
||||
const COINGECKO_API_URL: &str =
|
||||
"https://api.coingecko.com/api/v3/simple/price?ids=nym&vs_currencies=chf,usd,eur,gbp,btc";
|
||||
|
||||
pub(crate) async fn run_price_scraper(db_pool: &DbPool) {
|
||||
pub(crate) async fn run_price_scraper(db_pool: &DbPool) -> JoinHandle<()> {
|
||||
loop {
|
||||
tracing::info!("Running in a loop 🏃");
|
||||
if let Err(e) = get_coingecko_prices(db_pool).await {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use nym_sdk::mixnet::{
|
||||
AnonymousSenderTag, MixnetClientBuilder, MixnetMessageSender, ReconstructedMessage,
|
||||
self, AnonymousSenderTag, MixnetClientBuilder, MixnetMessageSender, ReconstructedMessage,
|
||||
StoragePaths,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
@@ -29,8 +29,16 @@ async fn main() {
|
||||
println!("\nOur client nym address is: {our_address}");
|
||||
|
||||
// Send a message through the mixnet to ourselves using our nym address
|
||||
// client
|
||||
// .send_plain_message(*our_address, "hello there")
|
||||
// .await
|
||||
// .unwrap();
|
||||
client
|
||||
.send_plain_message(*our_address, "hello there")
|
||||
.send_message(
|
||||
*our_address,
|
||||
"hello there",
|
||||
mixnet::IncludedSurbs::Amount(40),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ pub use nym_sphinx::{
|
||||
pub use nym_statistics_common::clients::{
|
||||
connection::ConnectionStatsEvent, ClientStatsEvents, ClientStatsSender,
|
||||
};
|
||||
pub use nym_task::connections::{LaneQueueLengths, TransmissionLane};
|
||||
pub use nym_task::connections::TransmissionLane;
|
||||
pub use nym_topology::{provider_trait::TopologyProvider, NymTopology};
|
||||
pub use paths::StoragePaths;
|
||||
pub use socks5_client::Socks5MixnetClient;
|
||||
|
||||
Reference in New Issue
Block a user