Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 24fe43b6d5 |
@@ -21,7 +21,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ arc-ubuntu-22.04 ]
|
||||
platform: [ arc-ubuntu-20.04 ]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
|
||||
@@ -4,16 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2024.13-magura-drift] (2024-11-29)
|
||||
|
||||
- Optimised syncing bandwidth information to storage
|
||||
|
||||
## [2024.13-magura-patched] (2024-11-22)
|
||||
|
||||
- [experimental] allow clients to change between deterministic route selection based on packet headers and a pseudorandom distribution
|
||||
- Introduced a configurable limit on retransmission frequency of packets if ACKs are not received
|
||||
- Filtered out invalid IP addresses on nym-api
|
||||
|
||||
## [2024.13-magura] (2024-11-18)
|
||||
|
||||
- Limit race probability ([#5145])
|
||||
|
||||
Generated
+296
-40
@@ -26,9 +26,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.22.0"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
|
||||
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
@@ -607,9 +607,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.73"
|
||||
version = "0.3.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
|
||||
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
@@ -665,6 +665,16 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "better-panic"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"console",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "binascii"
|
||||
version = "0.1.4"
|
||||
@@ -943,6 +953,12 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
@@ -955,6 +971,15 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
|
||||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.18"
|
||||
@@ -1158,6 +1183,33 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"color-spantrace",
|
||||
"eyre",
|
||||
"indenter",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-spantrace"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-core",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.1"
|
||||
@@ -1193,7 +1245,22 @@ dependencies = [
|
||||
"crossterm 0.27.0",
|
||||
"strum 0.26.3",
|
||||
"strum_macros 0.26.4",
|
||||
"unicode-width",
|
||||
"unicode-width 0.1.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644"
|
||||
dependencies = [
|
||||
"castaway 0.2.3",
|
||||
"cfg-if",
|
||||
"itoa",
|
||||
"rustversion",
|
||||
"ryu",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1214,7 +1281,7 @@ dependencies = [
|
||||
"encode_unicode",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"unicode-width",
|
||||
"unicode-width 0.1.13",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -1634,6 +1701,23 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"crossterm_winapi",
|
||||
"futures-core",
|
||||
"mio 1.0.1",
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
@@ -1915,12 +1999,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.9"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
|
||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||
dependencies = [
|
||||
"darling_core 0.20.9",
|
||||
"darling_macro 0.20.9",
|
||||
"darling_core 0.20.10",
|
||||
"darling_macro 0.20.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1939,9 +2023,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.9"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120"
|
||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
@@ -1964,11 +2048,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.9"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
|
||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core 0.20.9",
|
||||
"darling_core 0.20.10",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
@@ -2428,7 +2512,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.43"
|
||||
version = "1.1.42"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.5.20",
|
||||
@@ -2634,6 +2718,12 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
@@ -2798,6 +2888,15 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gcc"
|
||||
version = "0.3.55"
|
||||
@@ -2875,9 +2974,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.29.0"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
@@ -3028,6 +3127,17 @@ dependencies = [
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.8.4"
|
||||
@@ -3512,9 +3622,15 @@ dependencies = [
|
||||
"instant",
|
||||
"number_prefix",
|
||||
"portable-atomic",
|
||||
"unicode-width",
|
||||
"unicode-width 0.1.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
|
||||
|
||||
[[package]]
|
||||
name = "inlinable_string"
|
||||
version = "0.1.15"
|
||||
@@ -3564,7 +3680,21 @@ dependencies = [
|
||||
"newline-converter",
|
||||
"thiserror",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"unicode-width 0.1.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e"
|
||||
dependencies = [
|
||||
"darling 0.20.10",
|
||||
"indoc",
|
||||
"pretty_assertions",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3642,7 +3772,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"castaway",
|
||||
"castaway 0.1.2",
|
||||
"crossbeam-utils",
|
||||
"curl",
|
||||
"curl-sys",
|
||||
@@ -3914,6 +4044,15 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macro_rules_attribute"
|
||||
version = "0.1.3"
|
||||
@@ -4099,6 +4238,7 @@ checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@@ -4451,7 +4591,7 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.47"
|
||||
version = "1.1.46"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4470,7 +4610,6 @@ dependencies = [
|
||||
"cw2",
|
||||
"cw3",
|
||||
"cw4",
|
||||
"dashmap",
|
||||
"dirs",
|
||||
"futures",
|
||||
"getset",
|
||||
@@ -4701,7 +4840,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.45"
|
||||
version = "1.1.44"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -4782,7 +4921,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.45"
|
||||
version = "1.1.43"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap 4.5.20",
|
||||
@@ -5093,7 +5232,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-credential-proxy"
|
||||
version = "0.1.6"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -5113,7 +5252,6 @@ dependencies = [
|
||||
"nym-credentials",
|
||||
"nym-credentials-interface",
|
||||
"nym-crypto",
|
||||
"nym-ecash-contract-common",
|
||||
"nym-http-api-common",
|
||||
"nym-network-defaults",
|
||||
"nym-validator-client",
|
||||
@@ -5621,15 +5759,12 @@ dependencies = [
|
||||
"axum-client-ip",
|
||||
"bytes",
|
||||
"colored",
|
||||
"futures",
|
||||
"mime",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tower 0.4.13",
|
||||
"tracing",
|
||||
"utoipa",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5903,7 +6038,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.46"
|
||||
version = "1.1.44"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -5954,7 +6089,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.1.12"
|
||||
version = "1.1.10"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
@@ -6310,7 +6445,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.45"
|
||||
version = "1.1.43"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap 4.5.20",
|
||||
@@ -6679,6 +6814,27 @@ dependencies = [
|
||||
"wasm-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-tui-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"better-panic",
|
||||
"color-eyre",
|
||||
"crossterm 0.28.1",
|
||||
"humantime-serde",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tui-logger",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-tun"
|
||||
version = "0.1.0"
|
||||
@@ -6890,7 +7046,6 @@ dependencies = [
|
||||
"nym-task",
|
||||
"nym-wireguard-types",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"x25519-dalek",
|
||||
@@ -6913,7 +7068,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.10"
|
||||
version = "0.1.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -6967,9 +7122,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.0"
|
||||
version = "0.32.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434"
|
||||
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -7151,6 +7306,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
||||
|
||||
[[package]]
|
||||
name = "pairing"
|
||||
version = "0.23.0"
|
||||
@@ -7810,6 +7971,28 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"cassowary",
|
||||
"compact_str",
|
||||
"crossterm 0.28.1",
|
||||
"indoc",
|
||||
"instability",
|
||||
"itertools 0.13.0",
|
||||
"lru",
|
||||
"paste",
|
||||
"serde",
|
||||
"strum 0.26.3",
|
||||
"unicode-segmentation",
|
||||
"unicode-truncate",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "11.2.0"
|
||||
@@ -8804,7 +8987,7 @@ version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
|
||||
dependencies = [
|
||||
"darling 0.20.9",
|
||||
"darling 0.20.10",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
@@ -8917,12 +9100,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio 0.8.11",
|
||||
"mio 1.0.1",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
@@ -9353,6 +9537,15 @@ dependencies = [
|
||||
"unicode-properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strip-ansi-escapes"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa"
|
||||
dependencies = [
|
||||
"vte",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
@@ -10180,6 +10373,16 @@ dependencies = [
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-error"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-futures"
|
||||
version = "0.2.5"
|
||||
@@ -10361,6 +10564,22 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tui-logger"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bdf8f5ebd2c83a5176c69b150ea7f2a855ec8dc2a2774e7f198d1b50feda5745"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fxhash",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"ratatui",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.20.1"
|
||||
@@ -10486,12 +10705,29 @@ version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-truncate"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
||||
dependencies = [
|
||||
"itertools 0.13.0",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.1.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
@@ -10823,6 +11059,26 @@ version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
"vte_generate_state_changes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte_generate_state_changes"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.2.0"
|
||||
|
||||
+10
-1
@@ -149,7 +149,7 @@ members = [
|
||||
"tools/internal/contract-state-importer/importer-cli",
|
||||
"tools/internal/contract-state-importer/importer-contract",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract", "common/nym-tui-common",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -412,6 +412,15 @@ wasm-bindgen-futures = "0.4.45"
|
||||
wasmtimer = "0.2.0"
|
||||
web-sys = "0.3.72"
|
||||
|
||||
# tui related
|
||||
better-panic = "0.3.0"
|
||||
color-eyre = "0.6.3"
|
||||
crossterm = "0.28.1"
|
||||
strip-ansi-escapes = "0.2.0"
|
||||
ratatui = "0.29.0"
|
||||
tui-logger = "0.14.0"
|
||||
|
||||
|
||||
# Profile settings for individual crates
|
||||
|
||||
# Compile-time verified queries do quite a bit of work at compile time. Incremental
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.45"
|
||||
version = "1.1.43"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.45"
|
||||
version = "1.1.43"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -17,7 +17,7 @@ use nym_validator_client::coconut::all_ecash_api_clients;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::EcashSigningClient;
|
||||
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, EcashQueryClient};
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ToSingletonContractData;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
|
||||
@@ -393,20 +393,13 @@ pub struct Traffic {
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Specify whether route selection should be determined by the packet header.
|
||||
pub deterministic_route_selection: bool,
|
||||
|
||||
/// Specify how many times particular packet can be retransmitted
|
||||
/// None - no limit
|
||||
pub maximum_number_of_retransmissions: Option<u32>,
|
||||
|
||||
/// Specifies the packet size used for sent messages.
|
||||
/// Do not override it unless you understand the consequences of that change.
|
||||
pub primary_packet_size: PacketSize,
|
||||
|
||||
/// Specifies the optional auxiliary packet size for optimizing message streams.
|
||||
/// Note that its use decreases overall anonymity.
|
||||
/// Do not set it unless you understand the consequences of that change.
|
||||
/// Do not set it it unless you understand the consequences of that change.
|
||||
pub secondary_packet_size: Option<PacketSize>,
|
||||
|
||||
pub packet_type: PacketType,
|
||||
@@ -431,8 +424,6 @@ impl Default for Traffic {
|
||||
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
|
||||
disable_main_poisson_packet_distribution: false,
|
||||
deterministic_route_selection: false,
|
||||
maximum_number_of_retransmissions: None,
|
||||
primary_packet_size: PacketSize::RegularPacket,
|
||||
secondary_packet_size: None,
|
||||
packet_type: PacketType::Mix,
|
||||
|
||||
@@ -111,7 +111,6 @@ impl From<ConfigV5> for Config {
|
||||
primary_packet_size: value.debug.traffic.primary_packet_size,
|
||||
secondary_packet_size: value.debug.traffic.secondary_packet_size,
|
||||
packet_type: value.debug.traffic.packet_type,
|
||||
..Default::default()
|
||||
},
|
||||
cover_traffic: CoverTraffic {
|
||||
loop_cover_traffic_average_delay: value
|
||||
|
||||
+9
-12
@@ -30,8 +30,7 @@ pub(crate) enum Action {
|
||||
InsertPending(Vec<PendingAcknowledgement>),
|
||||
|
||||
/// Removes given `PendingAcknowledgement` from the 'shared' state. Also cancels the retransmission timer.
|
||||
/// Initiated by `AcknowledgementListener` upon receiving the acknowledgement. Also by `RetransmissionRequestListener`
|
||||
/// upon deciding to abandon the data.
|
||||
/// Initiated by `AcknowledgementListener`
|
||||
RemovePending(FragmentIdentifier),
|
||||
|
||||
/// Starts the retransmission timer on given `PendingAcknowledgement` with the `Duration` based on
|
||||
@@ -42,7 +41,7 @@ pub(crate) enum Action {
|
||||
|
||||
/// Updates the expected delay of given `PendingAcknowledgement` with the new provided `SphinxDelay`.
|
||||
/// Initiated by `RetransmissionRequestListener`
|
||||
UpdatePendingAck(FragmentIdentifier, SphinxDelay),
|
||||
UpdateDelay(FragmentIdentifier, SphinxDelay),
|
||||
}
|
||||
|
||||
impl Action {
|
||||
@@ -58,8 +57,8 @@ impl Action {
|
||||
Action::StartTimer(frag_id)
|
||||
}
|
||||
|
||||
pub(crate) fn new_update_pending_ack(frag_id: FragmentIdentifier, delay: SphinxDelay) -> Self {
|
||||
Action::UpdatePendingAck(frag_id, delay)
|
||||
pub(crate) fn new_update_delay(frag_id: FragmentIdentifier, delay: SphinxDelay) -> Self {
|
||||
Action::UpdateDelay(frag_id, delay)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +135,7 @@ impl ActionController {
|
||||
}
|
||||
|
||||
fn handle_start_timer(&mut self, frag_id: FragmentIdentifier) {
|
||||
trace!("{frag_id} is starting its timer");
|
||||
trace!("{} is starting its timer", frag_id);
|
||||
|
||||
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.get_mut(&frag_id) {
|
||||
// the fact that this branch is now POSSIBLE is a sign of a need to refactor this whole
|
||||
@@ -194,7 +193,7 @@ impl ActionController {
|
||||
|
||||
// initiated basically as a first step of retransmission. At first data has its delay updated
|
||||
// (as new sphinx packet was created with new expected delivery time)
|
||||
fn handle_update_pending_ack(&mut self, frag_id: FragmentIdentifier, delay: SphinxDelay) {
|
||||
fn handle_update_delay(&mut self, frag_id: FragmentIdentifier, delay: SphinxDelay) {
|
||||
trace!("{} is updating its delay", frag_id);
|
||||
// TODO: is it possible to solve this without either locking or temporarily removing the value?
|
||||
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.remove(&frag_id) {
|
||||
@@ -203,7 +202,7 @@ impl ActionController {
|
||||
// reference to this Arc. HOWEVER, before the Action was pushed onto the queue, the reference
|
||||
// was dropped hence this unwrap is safe.
|
||||
let mut inner_data = Arc::try_unwrap(pending_ack_data).unwrap();
|
||||
inner_data.update_retransmitted(delay);
|
||||
inner_data.update_delay(delay);
|
||||
|
||||
self.pending_acks_data
|
||||
.insert(frag_id, (Arc::new(inner_data), queue_key));
|
||||
@@ -226,7 +225,7 @@ impl ActionController {
|
||||
// about it. Perhaps just reschedule it at later point?
|
||||
let frag_id = expired_ack.into_inner();
|
||||
|
||||
trace!("{frag_id} has expired");
|
||||
trace!("{} has expired", frag_id);
|
||||
|
||||
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.get_mut(&frag_id) {
|
||||
if queue_key.is_none() {
|
||||
@@ -259,9 +258,7 @@ impl ActionController {
|
||||
Action::InsertPending(pending_acks) => self.handle_insert(pending_acks),
|
||||
Action::RemovePending(frag_id) => self.handle_remove(frag_id),
|
||||
Action::StartTimer(frag_id) => self.handle_start_timer(frag_id),
|
||||
Action::UpdatePendingAck(frag_id, delay) => {
|
||||
self.handle_update_pending_ack(frag_id, delay)
|
||||
}
|
||||
Action::UpdateDelay(frag_id, delay) => self.handle_update_delay(frag_id, delay),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ pub(crate) struct PendingAcknowledgement {
|
||||
delay: SphinxDelay,
|
||||
destination: PacketDestination,
|
||||
mix_hops: Option<u8>,
|
||||
retransmissions: u32,
|
||||
}
|
||||
|
||||
impl PendingAcknowledgement {
|
||||
@@ -87,7 +86,6 @@ impl PendingAcknowledgement {
|
||||
delay,
|
||||
destination: PacketDestination::KnownRecipient(recipient.into()),
|
||||
mix_hops,
|
||||
retransmissions: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +105,6 @@ impl PendingAcknowledgement {
|
||||
// Messages sent using SURBs are using the number of mix hops set by the recipient when
|
||||
// they provided the SURBs, so it doesn't make sense to include it here.
|
||||
mix_hops: None,
|
||||
retransmissions: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,9 +116,8 @@ impl PendingAcknowledgement {
|
||||
self.message_chunk.clone()
|
||||
}
|
||||
|
||||
fn update_retransmitted(&mut self, new_delay: SphinxDelay) {
|
||||
fn update_delay(&mut self, new_delay: SphinxDelay) {
|
||||
self.delay = new_delay;
|
||||
self.retransmissions += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,9 +163,6 @@ impl AcknowledgementControllerConnectors {
|
||||
|
||||
/// Configurable parameters of the `AcknowledgementController`
|
||||
pub(super) struct Config {
|
||||
/// Specify how many times particular packet can be retransmitted
|
||||
maximum_retransmissions: Option<u32>,
|
||||
|
||||
/// Given ack timeout in the form a * BASE_DELAY + b, it specifies the additive part `b`
|
||||
ack_wait_addition: Duration,
|
||||
|
||||
@@ -181,13 +174,8 @@ pub(super) struct Config {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(super) fn new(
|
||||
maximum_retransmissions: Option<u32>,
|
||||
ack_wait_addition: Duration,
|
||||
ack_wait_multiplier: f64,
|
||||
) -> Self {
|
||||
pub(super) fn new(ack_wait_addition: Duration, ack_wait_multiplier: f64) -> Self {
|
||||
Config {
|
||||
maximum_retransmissions,
|
||||
ack_wait_addition,
|
||||
ack_wait_multiplier,
|
||||
packet_size: Default::default(),
|
||||
@@ -250,7 +238,6 @@ where
|
||||
|
||||
// will listen for any ack timeouts and trigger retransmission
|
||||
let retransmission_request_listener = RetransmissionRequestListener::new(
|
||||
config.maximum_retransmissions,
|
||||
connectors.ack_action_sender.clone(),
|
||||
message_handler,
|
||||
retransmission_rx,
|
||||
|
||||
+3
-16
@@ -20,7 +20,6 @@ use std::sync::{Arc, Weak};
|
||||
|
||||
// responsible for packet retransmission upon fired timer
|
||||
pub(super) struct RetransmissionRequestListener<R> {
|
||||
maximum_retransmissions: Option<u32>,
|
||||
action_sender: AckActionSender,
|
||||
message_handler: MessageHandler<R>,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
@@ -32,14 +31,12 @@ where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
pub(super) fn new(
|
||||
maximum_retransmissions: Option<u32>,
|
||||
action_sender: AckActionSender,
|
||||
message_handler: MessageHandler<R>,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
) -> Self {
|
||||
RetransmissionRequestListener {
|
||||
maximum_retransmissions,
|
||||
action_sender,
|
||||
message_handler,
|
||||
request_receiver,
|
||||
@@ -80,18 +77,6 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let frag_id = timed_out_ack.message_chunk.fragment_identifier();
|
||||
|
||||
if let Some(limit) = self.maximum_retransmissions {
|
||||
if timed_out_ack.retransmissions >= limit {
|
||||
warn!("reached maximum number of allowed retransmissions for the packet");
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_remove(frag_id))
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let maybe_prepared_fragment = match &timed_out_ack.destination {
|
||||
PacketDestination::Anonymous {
|
||||
recipient_tag,
|
||||
@@ -116,6 +101,8 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let frag_id = timed_out_ack.message_chunk.fragment_identifier();
|
||||
|
||||
let prepared_fragment = match maybe_prepared_fragment {
|
||||
Ok(prepared_fragment) => prepared_fragment,
|
||||
Err(err) => {
|
||||
@@ -149,7 +136,7 @@ where
|
||||
// with the additional poisson delay.
|
||||
// And since Actions are executed in order `UpdateTimer` will HAVE TO be executed before `StartTimer`
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_update_pending_ack(frag_id, new_delay))
|
||||
.unbounded_send(Action::new_update_delay(frag_id, new_delay))
|
||||
.unwrap();
|
||||
|
||||
// send to `OutQueueControl` to eventually send to the mix network
|
||||
|
||||
@@ -91,9 +91,6 @@ pub(crate) struct Config {
|
||||
/// and surb-based are going to be sent.
|
||||
sender_address: Recipient,
|
||||
|
||||
/// Specify whether route selection should be determined by the packet header.
|
||||
deterministic_route_selection: bool,
|
||||
|
||||
/// Average delay a data packet is going to get delay at a single mixnode.
|
||||
average_packet_delay: Duration,
|
||||
|
||||
@@ -117,12 +114,10 @@ impl Config {
|
||||
sender_address: Recipient,
|
||||
average_packet_delay: Duration,
|
||||
average_ack_delay: Duration,
|
||||
deterministic_route_selection: bool,
|
||||
) -> Self {
|
||||
Config {
|
||||
ack_key,
|
||||
sender_address,
|
||||
deterministic_route_selection,
|
||||
average_packet_delay,
|
||||
average_ack_delay,
|
||||
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
|
||||
@@ -181,7 +176,6 @@ where
|
||||
{
|
||||
let message_preparer = MessagePreparer::new(
|
||||
rng,
|
||||
config.deterministic_route_selection,
|
||||
config.sender_address,
|
||||
config.average_packet_delay,
|
||||
config.average_ack_delay,
|
||||
@@ -640,7 +634,7 @@ where
|
||||
|
||||
pub(crate) fn update_ack_delay(&self, id: FragmentIdentifier, new_delay: Delay) {
|
||||
self.action_sender
|
||||
.unbounded_send(Action::UpdatePendingAck(id, new_delay))
|
||||
.unbounded_send(Action::UpdateDelay(id, new_delay))
|
||||
.expect("action control task has died")
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ pub struct Config {
|
||||
impl<'a> From<&'a Config> for acknowledgement_control::Config {
|
||||
fn from(cfg: &'a Config) -> Self {
|
||||
acknowledgement_control::Config::new(
|
||||
cfg.traffic.maximum_number_of_retransmissions,
|
||||
cfg.acks.ack_wait_addition,
|
||||
cfg.acks.ack_wait_multiplier,
|
||||
)
|
||||
@@ -98,7 +97,6 @@ impl<'a> From<&'a Config> for message_handler::Config {
|
||||
cfg.self_recipient,
|
||||
cfg.traffic.average_packet_delay,
|
||||
cfg.acks.average_ack_delay,
|
||||
cfg.traffic.deterministic_route_selection,
|
||||
)
|
||||
.with_custom_primary_packet_size(cfg.traffic.primary_packet_size)
|
||||
.with_custom_secondary_packet_size(cfg.traffic.secondary_packet_size)
|
||||
|
||||
@@ -38,7 +38,7 @@ pub struct TopologyReadPermit<'a> {
|
||||
permit: RwLockReadGuard<'a, Option<NymTopology>>,
|
||||
}
|
||||
|
||||
impl Deref for TopologyReadPermit<'_> {
|
||||
impl<'a> Deref for TopologyReadPermit<'a> {
|
||||
type Target = Option<NymTopology>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
||||
@@ -32,7 +32,7 @@ impl Div<GasPrice> for Coin {
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<GasPrice> for &Coin {
|
||||
impl<'a> Div<GasPrice> for &'a Coin {
|
||||
type Output = Gas;
|
||||
|
||||
fn div(self, rhs: GasPrice) -> Self::Output {
|
||||
|
||||
@@ -13,44 +13,6 @@ use tracing::error;
|
||||
|
||||
pub use cosmrs::abci::MsgResponse;
|
||||
|
||||
pub fn parse_singleton_u32_from_contract_response(b: Vec<u8>) -> Result<u32, NyxdError> {
|
||||
if b.len() != 4 {
|
||||
return Err(NyxdError::MalformedResponseData {
|
||||
got: b.len(),
|
||||
expected: 4,
|
||||
});
|
||||
}
|
||||
Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
|
||||
}
|
||||
|
||||
pub fn parse_singleton_u64_from_contract_response(b: Vec<u8>) -> Result<u64, NyxdError> {
|
||||
if b.len() != 8 {
|
||||
return Err(NyxdError::MalformedResponseData {
|
||||
got: b.len(),
|
||||
expected: 8,
|
||||
});
|
||||
}
|
||||
Ok(u64::from_be_bytes([
|
||||
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
]))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParsedContractResponse {
|
||||
pub message_index: usize,
|
||||
pub response: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ParsedContractResponse {
|
||||
pub fn parse_singleton_u32_contract_data(self) -> Result<u32, NyxdError> {
|
||||
parse_singleton_u32_from_contract_response(self.response)
|
||||
}
|
||||
|
||||
pub fn parse_singleton_u64_contract_data(self) -> Result<u64, NyxdError> {
|
||||
parse_singleton_u64_from_contract_response(self.response)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_msg_responses(data: Bytes) -> Vec<MsgResponse> {
|
||||
// it seems that currently, on wasmd 0.43 + tendermint-rs 0.37 + cosmrs 0.17.0-pre
|
||||
// the data is left in undecoded base64 form, but I'd imagine this might change so if the decoding fails,
|
||||
@@ -72,25 +34,35 @@ pub fn parse_msg_responses(data: Bytes) -> Vec<MsgResponse> {
|
||||
}
|
||||
|
||||
// requires there's a single response message
|
||||
pub trait ContractResponseData: Sized {
|
||||
pub trait ToSingletonContractData: Sized {
|
||||
fn parse_singleton_u32_contract_data(&self) -> Result<u32, NyxdError> {
|
||||
let b = self.to_singleton_contract_data()?;
|
||||
parse_singleton_u32_from_contract_response(b)
|
||||
if b.len() != 4 {
|
||||
return Err(NyxdError::MalformedResponseData {
|
||||
got: b.len(),
|
||||
expected: 4,
|
||||
});
|
||||
}
|
||||
Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
|
||||
}
|
||||
|
||||
fn parse_singleton_u64_contract_data(&self) -> Result<u64, NyxdError> {
|
||||
let b = self.to_singleton_contract_data()?;
|
||||
parse_singleton_u64_from_contract_response(b)
|
||||
if b.len() != 8 {
|
||||
return Err(NyxdError::MalformedResponseData {
|
||||
got: b.len(),
|
||||
expected: 8,
|
||||
});
|
||||
}
|
||||
Ok(u64::from_be_bytes([
|
||||
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
]))
|
||||
}
|
||||
|
||||
fn to_singleton_contract_data(&self) -> Result<Vec<u8>, NyxdError>;
|
||||
|
||||
fn to_unchecked_contract_data(&self) -> Result<Vec<Vec<u8>>, NyxdError>;
|
||||
|
||||
fn to_contract_data(&self) -> Result<Vec<ParsedContractResponse>, NyxdError>;
|
||||
}
|
||||
|
||||
impl ContractResponseData for ExecuteResult {
|
||||
impl ToSingletonContractData for ExecuteResult {
|
||||
fn to_singleton_contract_data(&self) -> Result<Vec<u8>, NyxdError> {
|
||||
if self.msg_responses.len() != 1 {
|
||||
return Err(NyxdError::UnexpectedNumberOfMsgResponses {
|
||||
@@ -100,30 +72,6 @@ impl ContractResponseData for ExecuteResult {
|
||||
|
||||
self.msg_responses[0].to_contract_response_data()
|
||||
}
|
||||
|
||||
fn to_unchecked_contract_data(&self) -> Result<Vec<Vec<u8>>, NyxdError> {
|
||||
self.msg_responses
|
||||
.iter()
|
||||
.map(ToContractResponseData::to_contract_response_data)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn to_contract_data(&self) -> Result<Vec<ParsedContractResponse>, NyxdError> {
|
||||
let mut response = Vec::new();
|
||||
|
||||
for (message_index, msg) in self.msg_responses.iter().enumerate() {
|
||||
// unfortunately `Name` trait has not been derived for `MsgExecuteContractResponse`,
|
||||
// so we have to make an explicit string comparison instead
|
||||
if msg.type_url == "/cosmwasm.wasm.v1.MsgExecuteContractResponse" {
|
||||
response.push(ParsedContractResponse {
|
||||
message_index,
|
||||
response: msg.to_contract_response_data()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToContractResponseData: Sized {
|
||||
|
||||
@@ -23,7 +23,7 @@ use tendermint_rpc::endpoint::*;
|
||||
use tendermint_rpc::query::Query;
|
||||
use tendermint_rpc::{Error as TendermintRpcError, Order, Paging, SimpleRequest};
|
||||
|
||||
pub use helpers::{ContractResponseData, ToContractResponseData};
|
||||
pub use helpers::{ToContractResponseData, ToSingletonContractData};
|
||||
|
||||
#[cfg(feature = "http-client")]
|
||||
use crate::http_client;
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct GasPrice {
|
||||
pub denom: String,
|
||||
}
|
||||
|
||||
impl Mul<Gas> for &GasPrice {
|
||||
impl<'a> Mul<Gas> for &'a GasPrice {
|
||||
type Output = Coin;
|
||||
|
||||
fn mul(self, gas_limit: Gas) -> Self::Output {
|
||||
|
||||
@@ -32,7 +32,7 @@ pub(crate) mod string_rfc3339_offset_date_time {
|
||||
|
||||
struct Rfc3339OffsetDateTimeVisitor;
|
||||
|
||||
impl Visitor<'_> for Rfc3339OffsetDateTimeVisitor {
|
||||
impl<'de> Visitor<'de> for Rfc3339OffsetDateTimeVisitor {
|
||||
type Value = OffsetDateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
|
||||
@@ -111,7 +111,7 @@ impl<S: Storage + Clone + 'static> BandwidthStorageManager<S> {
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
pub async fn sync_storage_bandwidth(&mut self) -> Result<()> {
|
||||
async fn sync_storage_bandwidth(&mut self) -> Result<()> {
|
||||
trace!("syncing client bandwidth with the underlying storage");
|
||||
let updated = self
|
||||
.storage
|
||||
|
||||
@@ -8,8 +8,8 @@ use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_secs(5 * 60); // 5 minutes
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 5 * 1024 * 1024; // 5MB
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5);
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BandwidthFlushingBehaviourConfig {
|
||||
|
||||
@@ -18,7 +18,7 @@ use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
|
||||
};
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ContractResponseData;
|
||||
use nym_validator_client::nyxd::cosmwasm_client::ToSingletonContractData;
|
||||
use nym_validator_client::nyxd::cw3::Status;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
|
||||
@@ -26,8 +26,9 @@ const PARALLEL_RUNS: usize = 32;
|
||||
/// `lambda` ($\lambda$) in the DKG paper
|
||||
const SECURITY_PARAMETER: usize = 256;
|
||||
|
||||
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
|
||||
/// ceil(SECURITY_PARAMETER / PARALLEL_RUNS) in the paper
|
||||
const NUM_CHALLENGE_BITS: usize = SECURITY_PARAMETER.div_ceil(PARALLEL_RUNS);
|
||||
const NUM_CHALLENGE_BITS: usize = (SECURITY_PARAMETER + PARALLEL_RUNS - 1) / PARALLEL_RUNS;
|
||||
|
||||
// type alias for ease of use
|
||||
type FirstChallenge = Vec<Vec<Vec<u64>>>;
|
||||
|
||||
@@ -196,7 +196,7 @@ impl<'b> Add<&'b Polynomial> for Polynomial {
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Polynomial> for &Polynomial {
|
||||
impl<'a> Add<Polynomial> for &'a Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: Polynomial) -> Polynomial {
|
||||
@@ -212,10 +212,10 @@ impl Add<Polynomial> for Polynomial {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Add<&'a Polynomial> for &Polynomial {
|
||||
impl<'a, 'b> Add<&'b Polynomial> for &'a Polynomial {
|
||||
type Output = Polynomial;
|
||||
|
||||
fn add(self, rhs: &'a Polynomial) -> Self::Output {
|
||||
fn add(self, rhs: &'b Polynomial) -> Self::Output {
|
||||
let len = self.coefficients.len();
|
||||
let rhs_len = rhs.coefficients.len();
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ pub struct GatewayHandshake<'a> {
|
||||
handshake_future: BoxFuture<'a, Result<SharedGatewayKey, HandshakeError>>,
|
||||
}
|
||||
|
||||
impl Future for GatewayHandshake<'_> {
|
||||
impl<'a> Future for GatewayHandshake<'a> {
|
||||
type Output = Result<SharedGatewayKey, HandshakeError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
|
||||
@@ -15,15 +15,12 @@ axum-client-ip.workspace = true
|
||||
axum.workspace = true
|
||||
bytes = { workspace = true }
|
||||
colored.workspace = true
|
||||
futures = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
serde_yaml = { workspace = true }
|
||||
tower = { workspace = true }
|
||||
tracing.workspace = true
|
||||
utoipa = { workspace = true, optional = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
[features]
|
||||
utoipa = ["dep:utoipa"]
|
||||
|
||||
@@ -7,7 +7,7 @@ use axum::Json;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod middleware;
|
||||
pub mod logging;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use axum::extract::Request;
|
||||
use axum::http::header::{HOST, USER_AGENT};
|
||||
@@ -11,7 +11,6 @@ use colored::Colorize;
|
||||
use std::time::Instant;
|
||||
use tracing::info;
|
||||
|
||||
/// Simple logger for requests
|
||||
pub async fn logger(
|
||||
InsecureClientIp(addr): InsecureClientIp,
|
||||
request: Request,
|
||||
@@ -29,9 +29,6 @@ pub struct NodeTester<R> {
|
||||
|
||||
packet_size: PacketSize,
|
||||
|
||||
/// Specify whether route selection should be determined by the packet header.
|
||||
deterministic_route_selection: bool,
|
||||
|
||||
/// Average delay a data packet is going to get delay at a single mixnode.
|
||||
average_packet_delay: Duration,
|
||||
|
||||
@@ -51,13 +48,11 @@ impl<R> NodeTester<R>
|
||||
where
|
||||
R: Rng + CryptoRng,
|
||||
{
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
rng: R,
|
||||
base_topology: NymTopology,
|
||||
self_address: Option<Recipient>,
|
||||
packet_size: PacketSize,
|
||||
deterministic_route_selection: bool,
|
||||
average_packet_delay: Duration,
|
||||
average_ack_delay: Duration,
|
||||
ack_key: Arc<AckKey>,
|
||||
@@ -67,7 +62,6 @@ where
|
||||
base_topology,
|
||||
self_address,
|
||||
packet_size,
|
||||
deterministic_route_selection,
|
||||
average_packet_delay,
|
||||
average_ack_delay,
|
||||
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
|
||||
@@ -295,18 +289,10 @@ where
|
||||
impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
|
||||
type Rng = R;
|
||||
|
||||
fn deterministic_route_selection(&self) -> bool {
|
||||
self.deterministic_route_selection
|
||||
}
|
||||
|
||||
fn rng(&mut self) -> &mut Self::Rng {
|
||||
&mut self.rng
|
||||
}
|
||||
|
||||
fn nonce(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn num_mix_hops(&self) -> u8 {
|
||||
self.num_mix_hops
|
||||
}
|
||||
@@ -318,4 +304,8 @@ impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
|
||||
fn average_ack_delay(&self) -> Duration {
|
||||
self.average_ack_delay
|
||||
}
|
||||
|
||||
fn nonce(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
name = "nym-tui-common"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
humantime-serde = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync", "rt-multi-thread", "signal", "macros"] }
|
||||
tokio-stream = { workspace = true }
|
||||
tokio-util = { workspace = true, features = ["rt"] }
|
||||
tracing = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
# tui
|
||||
crossterm = { workspace = true, features = ["event-stream"] }
|
||||
ratatui = { workspace = true, features = ["serde", "macros", "crossterm"] }
|
||||
tui-logger = { workspace = true, optional = true, features = ["tracing-support"], default-features = false }
|
||||
|
||||
# panic handlers
|
||||
better-panic = { workspace = true }
|
||||
color-eyre = { workspace = true }
|
||||
strip-ansi-escapes = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
required-features = ["logger"]
|
||||
|
||||
|
||||
|
||||
|
||||
[features]
|
||||
logger = ["tui-logger"]
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use color_eyre::eyre;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use nym_tui_common::tui::config::keybindings::{KeyBinding, LoggerKeybindings};
|
||||
use nym_tui_common::{
|
||||
run_tui, Action, ActionDispatcher, ActionSender, AppAction, Component, DebugHistory, Logger,
|
||||
LoggerProps, State,
|
||||
};
|
||||
use ratatui::layout::{Layout, Rect};
|
||||
use ratatui::prelude::Constraint;
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::Frame;
|
||||
use tracing::log::LevelFilter;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
// useful pattern for tabs, etc:
|
||||
/*
|
||||
fn get_active_page_component_mut(&mut self) -> &mut dyn Component {
|
||||
match self.props.active_tab {
|
||||
ActiveTab::Tab1 => &mut self.tab1,
|
||||
ActiveTab::Tab2 => &mut self.tab2,
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
struct Props {
|
||||
custom_quit: KeyBinding,
|
||||
}
|
||||
|
||||
impl From<&HelloStore> for Props {
|
||||
fn from(store: &HelloStore) -> Self {
|
||||
Props {
|
||||
custom_quit: store.config.custom_quit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HelloRootApp {
|
||||
props: Props,
|
||||
|
||||
action_sender: ActionSender<HelloActions>,
|
||||
logger: Logger<HelloStore, HelloActions>,
|
||||
debug_history: DebugHistory<HelloStore, HelloActions>,
|
||||
}
|
||||
|
||||
impl Component for HelloRootApp {
|
||||
type State = HelloStore;
|
||||
type Actions = HelloActions;
|
||||
|
||||
fn new(state: &HelloStore, action_sender: ActionSender<HelloActions>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
HelloRootApp {
|
||||
props: Props::from(state),
|
||||
action_sender: action_sender.clone(),
|
||||
logger: Logger::new(state, action_sender.clone()),
|
||||
debug_history: DebugHistory::new(state, action_sender),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(self, state: &Self::State) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
HelloRootApp {
|
||||
logger: self.logger.update(state),
|
||||
debug_history: self.debug_history.update(state),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&mut self) -> bool {
|
||||
let logger_tick = self.logger.tick();
|
||||
let debug_history_tick = self.debug_history.tick();
|
||||
|
||||
logger_tick || debug_history_tick
|
||||
}
|
||||
|
||||
fn handle_key(&mut self, key: KeyEvent) -> eyre::Result<()> {
|
||||
let maybe_binding = KeyBinding::from(key);
|
||||
if maybe_binding == self.props.custom_quit {
|
||||
self.action_sender.send(Action::Quit);
|
||||
}
|
||||
|
||||
self.logger.handle_key(key)?;
|
||||
self.debug_history.handle_key(key)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn view(&mut self, frame: &mut Frame, rect: Rect) {
|
||||
let [logs, hello_rect] =
|
||||
Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]).areas(rect);
|
||||
|
||||
self.logger.view(frame, logs);
|
||||
|
||||
frame.render_widget(Paragraph::new("Hello world!").centered(), hello_rect);
|
||||
|
||||
self.debug_history.view(frame, rect);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HelloActions {}
|
||||
|
||||
impl AppAction for HelloActions {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HelloConfig {
|
||||
pub custom_quit: KeyBinding,
|
||||
pub logger_keybindings: LoggerKeybindings,
|
||||
}
|
||||
|
||||
impl Default for HelloConfig {
|
||||
fn default() -> Self {
|
||||
HelloConfig {
|
||||
custom_quit: KeyBinding::new(KeyCode::Char('x')),
|
||||
logger_keybindings: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct HelloStore {
|
||||
config: HelloConfig,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a HelloStore> for LoggerProps {
|
||||
fn from(store: &'a HelloStore) -> LoggerProps {
|
||||
LoggerProps {
|
||||
keybindings: store.config.logger_keybindings,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State for HelloStore {}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HelloDispatcher {}
|
||||
|
||||
#[async_trait]
|
||||
impl ActionDispatcher for HelloDispatcher {
|
||||
type Store = HelloStore;
|
||||
type Actions = HelloActions;
|
||||
|
||||
async fn handle_app_action(
|
||||
&mut self,
|
||||
action: Self::Actions,
|
||||
store: &mut Self::Store,
|
||||
) -> eyre::Result<()> {
|
||||
let _ = action;
|
||||
let _ = store;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> eyre::Result<()> {
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::Layer;
|
||||
|
||||
let filter: EnvFilter = "trace,mio=warn".parse()?;
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tui_logger::tracing_subscriber_layer().with_filter(filter))
|
||||
.init();
|
||||
tui_logger::init_logger(LevelFilter::Trace)?;
|
||||
|
||||
run_tui::<HelloRootApp, _>(Default::default(), HelloDispatcher {}, Default::default()).await
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::char::ParseCharError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NymTuiError {
|
||||
#[error("failed to abort tui processing task within specified duration")]
|
||||
TuiTaskAbortFailure,
|
||||
|
||||
#[error("{str} could not be parsed into a character: {source}")]
|
||||
InvalidCharacter {
|
||||
str: String,
|
||||
#[source]
|
||||
source: ParseCharError,
|
||||
},
|
||||
|
||||
#[error("could not process an unknown keybinding: '{value}'")]
|
||||
UnknownKeyBinding { value: String },
|
||||
|
||||
#[error("could not process an unknown key modifier: '{value}'")]
|
||||
UnknownKeyModifier { value: String },
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
#![warn(clippy::todo)]
|
||||
#![warn(clippy::dbg_macro)]
|
||||
#![warn(clippy::panic)]
|
||||
|
||||
use crate::tui::manager::TuiManager;
|
||||
use color_eyre::eyre;
|
||||
|
||||
pub mod error;
|
||||
pub mod tui;
|
||||
|
||||
pub use crate::tui::config::TuiConfig;
|
||||
pub use tui::action::{Action, AppAction};
|
||||
pub use tui::dispatcher::store::State;
|
||||
pub use tui::dispatcher::{ActionDispatcher, ActionSender};
|
||||
pub use tui::initialize_panic_handler;
|
||||
pub use tui::ui::components::Component;
|
||||
|
||||
// components:
|
||||
pub use tui::ui::components::common::DebugHistory;
|
||||
#[cfg(feature = "logger")]
|
||||
pub use tui::ui::components::common::{Logger, LoggerProps};
|
||||
|
||||
pub async fn run_tui<C, D>(
|
||||
config: TuiConfig,
|
||||
action_dispatcher: D,
|
||||
initial_state: D::Store,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
C: Component + Send + Sync + 'static,
|
||||
C::State: Send + Sync + 'static,
|
||||
C::Actions: Send + Sync + Clone + 'static,
|
||||
D: ActionDispatcher<Store = C::State, Actions = C::Actions> + Send + Sync + 'static,
|
||||
{
|
||||
initialize_panic_handler()?;
|
||||
|
||||
TuiManager::<C>::build_new(config, action_dispatcher, initial_state)?
|
||||
.wait_for_exit_or_signal()
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Action<T: AppAction> {
|
||||
Quit,
|
||||
|
||||
AppDefined(T),
|
||||
}
|
||||
|
||||
pub trait AppAction: Debug {}
|
||||
@@ -0,0 +1,271 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::error::NymTuiError;
|
||||
#[cfg(feature = "logger")]
|
||||
use tui_logger::TuiWidgetEvent;
|
||||
|
||||
static KEY_MODIFIERS: LazyLock<HashMap<&'static str, KeyModifiers>> = LazyLock::new(|| {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("shift", KeyModifiers::SHIFT);
|
||||
m.insert("ctrl", KeyModifiers::CONTROL);
|
||||
m.insert("alt", KeyModifiers::ALT);
|
||||
m.insert("super", KeyModifiers::SUPER);
|
||||
m.insert("hyper", KeyModifiers::HYPER);
|
||||
m.insert("meta", KeyModifiers::META);
|
||||
m
|
||||
});
|
||||
|
||||
static SPECIAL_KEYS: LazyLock<HashMap<&'static str, KeyCode>> = LazyLock::new(|| {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("backspace", KeyCode::Backspace);
|
||||
m.insert("enter", KeyCode::Enter);
|
||||
m.insert("left", KeyCode::Left);
|
||||
m.insert("right", KeyCode::Right);
|
||||
m.insert("up", KeyCode::Up);
|
||||
m.insert("down", KeyCode::Down);
|
||||
m.insert("home", KeyCode::Home);
|
||||
m.insert("end", KeyCode::End);
|
||||
m.insert("pageup", KeyCode::PageUp);
|
||||
m.insert("pagedown", KeyCode::PageDown);
|
||||
m.insert("tab", KeyCode::Tab);
|
||||
m.insert("backtab", KeyCode::BackTab);
|
||||
m.insert("delete", KeyCode::Delete);
|
||||
m.insert("insert", KeyCode::Insert);
|
||||
m.insert("null", KeyCode::Null);
|
||||
m.insert("esc", KeyCode::Esc);
|
||||
m.insert("space", KeyCode::Char(' '));
|
||||
m.insert("f1", KeyCode::F(1));
|
||||
m.insert("f2", KeyCode::F(2));
|
||||
m.insert("f3", KeyCode::F(3));
|
||||
m.insert("f4", KeyCode::F(4));
|
||||
m.insert("f5", KeyCode::F(5));
|
||||
m.insert("f6", KeyCode::F(6));
|
||||
m.insert("f7", KeyCode::F(7));
|
||||
m.insert("f8", KeyCode::F(8));
|
||||
m.insert("f9", KeyCode::F(9));
|
||||
m.insert("f10", KeyCode::F(10));
|
||||
m.insert("f11", KeyCode::F(11));
|
||||
m.insert("f12", KeyCode::F(12));
|
||||
m
|
||||
});
|
||||
|
||||
#[cfg(feature = "logger")]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields, default)]
|
||||
pub struct LoggerKeybindings {
|
||||
// TODO: give them better names
|
||||
tui_logger_space_key: KeyBinding,
|
||||
tui_logger_up_key: KeyBinding,
|
||||
tui_logger_down_key: KeyBinding,
|
||||
tui_logger_left_key: KeyBinding,
|
||||
tui_logger_right_key: KeyBinding,
|
||||
tui_logger_plus_key: KeyBinding,
|
||||
tui_logger_minus_key: KeyBinding,
|
||||
tui_logger_hide_key: KeyBinding,
|
||||
tui_logger_focus_key: KeyBinding,
|
||||
tui_logger_prev_page_key: KeyBinding,
|
||||
tui_logger_next_page_key: KeyBinding,
|
||||
tui_logger_escape_key: KeyBinding,
|
||||
}
|
||||
|
||||
#[cfg(feature = "logger")]
|
||||
impl Default for LoggerKeybindings {
|
||||
fn default() -> Self {
|
||||
LoggerKeybindings {
|
||||
tui_logger_space_key: KeyBinding::new(KeyCode::Char(' ')),
|
||||
tui_logger_up_key: KeyBinding::new(KeyCode::Up),
|
||||
tui_logger_down_key: KeyBinding::new(KeyCode::Down),
|
||||
tui_logger_left_key: KeyBinding::new(KeyCode::Left),
|
||||
tui_logger_right_key: KeyBinding::new(KeyCode::Right),
|
||||
tui_logger_plus_key: KeyBinding::new(KeyCode::Char('+')),
|
||||
tui_logger_minus_key: KeyBinding::new(KeyCode::Char('-')),
|
||||
tui_logger_hide_key: KeyBinding::new(KeyCode::Char('h')),
|
||||
tui_logger_focus_key: KeyBinding::new(KeyCode::Char('f')),
|
||||
tui_logger_prev_page_key: KeyBinding::new(KeyCode::PageUp),
|
||||
tui_logger_next_page_key: KeyBinding::new(KeyCode::PageDown),
|
||||
tui_logger_escape_key: KeyBinding::new(KeyCode::Esc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "logger")]
|
||||
impl LoggerKeybindings {
|
||||
pub fn tui_logger_event(&self, key: KeyBinding) -> Option<TuiWidgetEvent> {
|
||||
if key == self.tui_logger_space_key {
|
||||
Some(TuiWidgetEvent::SpaceKey)
|
||||
} else if key == self.tui_logger_up_key {
|
||||
Some(TuiWidgetEvent::UpKey)
|
||||
} else if key == self.tui_logger_down_key {
|
||||
Some(TuiWidgetEvent::DownKey)
|
||||
} else if key == self.tui_logger_left_key {
|
||||
Some(TuiWidgetEvent::LeftKey)
|
||||
} else if key == self.tui_logger_right_key {
|
||||
Some(TuiWidgetEvent::RightKey)
|
||||
} else if key == self.tui_logger_plus_key {
|
||||
Some(TuiWidgetEvent::PlusKey)
|
||||
} else if key == self.tui_logger_minus_key {
|
||||
Some(TuiWidgetEvent::MinusKey)
|
||||
} else if key == self.tui_logger_hide_key {
|
||||
Some(TuiWidgetEvent::HideKey)
|
||||
} else if key == self.tui_logger_focus_key {
|
||||
Some(TuiWidgetEvent::FocusKey)
|
||||
} else if key == self.tui_logger_prev_page_key {
|
||||
Some(TuiWidgetEvent::PrevPageKey)
|
||||
} else if key == self.tui_logger_next_page_key {
|
||||
Some(TuiWidgetEvent::NextPageKey)
|
||||
} else if key == self.tui_logger_escape_key {
|
||||
Some(TuiWidgetEvent::EscapeKey)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[serde(try_from = "String")]
|
||||
#[serde(into = "String")]
|
||||
pub struct KeyBinding {
|
||||
pub code: KeyCode,
|
||||
pub modifier: KeyModifiers,
|
||||
}
|
||||
|
||||
impl KeyBinding {
|
||||
pub fn new(code: KeyCode) -> Self {
|
||||
KeyBinding {
|
||||
code,
|
||||
modifier: KeyModifiers::NONE,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_modifier(code: KeyCode, modifier: KeyModifiers) -> Self {
|
||||
KeyBinding { code, modifier }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyEvent> for KeyBinding {
|
||||
fn from(value: KeyEvent) -> Self {
|
||||
KeyBinding {
|
||||
code: value.code,
|
||||
modifier: value.modifiers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for KeyBinding {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if self.modifier.contains(KeyModifiers::SHIFT) {
|
||||
write!(f, "shift+")?;
|
||||
}
|
||||
if self.modifier.contains(KeyModifiers::CONTROL) {
|
||||
write!(f, "ctrl+")?;
|
||||
}
|
||||
if self.modifier.contains(KeyModifiers::ALT) {
|
||||
write!(f, "alt+")?;
|
||||
}
|
||||
if self.modifier.contains(KeyModifiers::SUPER) {
|
||||
write!(f, "super+")?;
|
||||
}
|
||||
if self.modifier.contains(KeyModifiers::HYPER) {
|
||||
write!(f, "hyper+")?;
|
||||
}
|
||||
if self.modifier.contains(KeyModifiers::META) {
|
||||
write!(f, "meta+")?;
|
||||
}
|
||||
match self.code {
|
||||
KeyCode::Backspace => write!(f, "backspace"),
|
||||
KeyCode::Enter => write!(f, "enter"),
|
||||
KeyCode::Left => write!(f, "left"),
|
||||
KeyCode::Right => write!(f, "right"),
|
||||
KeyCode::Up => write!(f, "up"),
|
||||
KeyCode::Down => write!(f, "down"),
|
||||
KeyCode::Home => write!(f, "home"),
|
||||
KeyCode::End => write!(f, "end"),
|
||||
KeyCode::PageUp => write!(f, "pageup"),
|
||||
KeyCode::PageDown => write!(f, "pagedown"),
|
||||
KeyCode::Tab => write!(f, "tab"),
|
||||
KeyCode::BackTab => write!(f, "backtab"),
|
||||
KeyCode::Delete => write!(f, "delete"),
|
||||
KeyCode::Insert => write!(f, "insert"),
|
||||
KeyCode::Char(c) => write!(f, "{c}"),
|
||||
KeyCode::Null => write!(f, "null"),
|
||||
KeyCode::Esc => write!(f, "esc"),
|
||||
_ => write!(f, "unknown"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyBinding> for String {
|
||||
fn from(value: KeyBinding) -> Self {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for KeyBinding {
|
||||
type Error = NymTuiError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
value.parse()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for KeyBinding {
|
||||
type Err = NymTuiError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.rsplit_once('+') {
|
||||
Some((modifiers, value)) => Ok(Self {
|
||||
code: parse_keycode(value)?,
|
||||
modifier: parse_modifiers(modifiers)?,
|
||||
}),
|
||||
None => Ok(Self {
|
||||
code: parse_keycode(s)?,
|
||||
modifier: KeyModifiers::NONE,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_keycode(value: &str) -> Result<KeyCode, NymTuiError> {
|
||||
Ok(if value.len() == 1 {
|
||||
KeyCode::Char(
|
||||
char::from_str(value)
|
||||
.map_err(|source| NymTuiError::InvalidCharacter {
|
||||
str: value.to_string(),
|
||||
source,
|
||||
})?
|
||||
.to_ascii_lowercase(),
|
||||
)
|
||||
} else {
|
||||
SPECIAL_KEYS
|
||||
.get(value)
|
||||
.cloned()
|
||||
.ok_or_else(|| NymTuiError::UnknownKeyBinding {
|
||||
value: value.to_string(),
|
||||
})?
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_modifiers(modifiers: &str) -> Result<KeyModifiers, NymTuiError> {
|
||||
modifiers
|
||||
.split('+')
|
||||
.try_fold(KeyModifiers::NONE, |modifiers, token| {
|
||||
KEY_MODIFIERS
|
||||
.get(token)
|
||||
.map(|modifier| modifiers | *modifier)
|
||||
.ok_or_else(|| NymTuiError::UnknownKeyModifier {
|
||||
value: token.to_string(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn key_event_to_string(key_event: &KeyEvent) -> String {
|
||||
KeyBinding::from(*key_event).to_string()
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod keybindings;
|
||||
|
||||
pub const DEFAULT_TICK_RATE: Duration = Duration::from_millis(200);
|
||||
|
||||
const DEFAULT_SHUTDOWN_GRACE: Duration = Duration::from_millis(500);
|
||||
const DEFAULT_CANCEL_GRACE: Duration = Duration::from_millis(500);
|
||||
const DEFAULT_ABORT_GRACE: Duration = Duration::from_millis(200);
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct TuiConfig {
|
||||
pub tui: Tui,
|
||||
|
||||
// #[serde(default)]
|
||||
// pub key_bindings: KeyBindings,
|
||||
#[serde(default)]
|
||||
pub debug: Debug,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
pub struct Debug {
|
||||
pub debug_mode_enabled: bool,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub shutdown_grace: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub cancel_grace: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub abort_grace: Duration,
|
||||
}
|
||||
|
||||
impl Default for Debug {
|
||||
fn default() -> Self {
|
||||
Debug {
|
||||
debug_mode_enabled: true,
|
||||
shutdown_grace: DEFAULT_SHUTDOWN_GRACE,
|
||||
cancel_grace: DEFAULT_CANCEL_GRACE,
|
||||
abort_grace: DEFAULT_ABORT_GRACE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
pub struct Tui {
|
||||
#[serde(default, flatten)]
|
||||
pub debug: TuiDebug,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
pub struct TuiDebug {
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub tick_rate: Duration,
|
||||
}
|
||||
|
||||
impl Default for TuiDebug {
|
||||
fn default() -> Self {
|
||||
TuiDebug {
|
||||
tick_rate: DEFAULT_TICK_RATE,
|
||||
// frame_rate: DEFAULT_FRAME_RATE,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::tui::action::Action;
|
||||
use crate::tui::dispatcher::store::State;
|
||||
use crate::AppAction;
|
||||
use async_trait::async_trait;
|
||||
use color_eyre::eyre;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
pub mod store;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ActionSender<T: AppAction>(UnboundedSender<Action<T>>);
|
||||
|
||||
impl<T> From<UnboundedSender<Action<T>>> for ActionSender<T>
|
||||
where
|
||||
T: AppAction,
|
||||
{
|
||||
fn from(value: UnboundedSender<Action<T>>) -> Self {
|
||||
ActionSender(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ActionSender<T>
|
||||
where
|
||||
T: AppAction,
|
||||
{
|
||||
pub fn send(&self, action: impl Into<Action<T>>) {
|
||||
if let Err(unsent) = self.0.send(action.into()) {
|
||||
error!("failed to send {:?} action to the dispatcher", unsent.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ActionReceiver<T> = UnboundedReceiverStream<Action<T>>;
|
||||
|
||||
pub type StateUpdateSender<S> = UnboundedSender<S>;
|
||||
pub type StateUpdateReceiver<S> = UnboundedReceiverStream<S>;
|
||||
|
||||
#[async_trait]
|
||||
pub trait ActionDispatcher {
|
||||
type Store: State;
|
||||
type Actions: AppAction;
|
||||
|
||||
async fn handle_app_action(
|
||||
&mut self,
|
||||
action: Self::Actions,
|
||||
store: &mut Self::Store,
|
||||
) -> eyre::Result<()>;
|
||||
}
|
||||
|
||||
pub struct DispatcherLoop<D: ActionDispatcher> {
|
||||
dispatcher: D,
|
||||
store: D::Store,
|
||||
|
||||
// to be used with async actions
|
||||
#[allow(dead_code)]
|
||||
action_sender: ActionSender<D::Actions>,
|
||||
action_receiver: ActionReceiver<D::Actions>,
|
||||
state_update_sender: StateUpdateSender<D::Store>,
|
||||
cancellation_token: CancellationToken,
|
||||
}
|
||||
|
||||
impl<D> DispatcherLoop<D>
|
||||
where
|
||||
D: ActionDispatcher + Send + Sync + 'static,
|
||||
D::Store: Send + Sync + 'static,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
dispatcher: D,
|
||||
store: D::Store,
|
||||
action_sender: impl Into<ActionSender<D::Actions>>,
|
||||
action_receiver: impl Into<ActionReceiver<D::Actions>>,
|
||||
state_update_sender: StateUpdateSender<D::Store>,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> DispatcherLoop<D> {
|
||||
DispatcherLoop {
|
||||
dispatcher,
|
||||
store,
|
||||
action_sender: action_sender.into(),
|
||||
action_receiver: action_receiver.into(),
|
||||
state_update_sender,
|
||||
cancellation_token,
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_action(&mut self, action: Option<Action<D::Actions>>) -> eyre::Result<()> {
|
||||
let Some(action) = action else {
|
||||
warn!("the dispatcher channel has closed! we're probably already in shutdown!");
|
||||
// but if we're not, make sure to kick it off...
|
||||
self.cancellation_token.cancel();
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match action {
|
||||
Action::Quit => {
|
||||
debug!("attempting to handle the QUIT action");
|
||||
self.cancellation_token.cancel();
|
||||
// no need to send any state updates here
|
||||
return Ok(());
|
||||
}
|
||||
Action::AppDefined(action) => {
|
||||
debug!("attempting to handle the following action: {:?}", action);
|
||||
self.dispatcher
|
||||
.handle_app_action(action, &mut self.store)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
self.state_update_sender.send(self.store.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> eyre::Result<()> {
|
||||
info!("starting the dispatcher loop");
|
||||
|
||||
// issue initial state
|
||||
self.state_update_sender.send(self.store.clone())?;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.cancellation_token.cancelled() => {
|
||||
info!("received cancellation token");
|
||||
break;
|
||||
}
|
||||
maybe_action = self.action_receiver.next() => {
|
||||
self.handle_action(maybe_action).await?
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod bearer_auth;
|
||||
pub mod logging;
|
||||
pub trait State: Clone {
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use color_eyre::eyre;
|
||||
use crossterm::event::EventStream;
|
||||
use ratatui::backend::CrosstermBackend as Backend;
|
||||
use ratatui::crossterm::{
|
||||
self, cursor,
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio_stream::Stream;
|
||||
|
||||
pub struct TuiHandle {
|
||||
pub terminal: ratatui::Terminal<Backend<std::io::Stderr>>,
|
||||
pub crossterm_events: EventStream,
|
||||
}
|
||||
|
||||
impl TuiHandle {
|
||||
pub fn new() -> Result<TuiHandle, eyre::Error> {
|
||||
let terminal = ratatui::Terminal::new(Backend::new(std::io::stderr()))?;
|
||||
let crossterm_events = EventStream::new();
|
||||
|
||||
Ok(TuiHandle {
|
||||
terminal,
|
||||
crossterm_events,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn enter(&self) -> Result<(), eyre::Error> {
|
||||
crossterm::terminal::enable_raw_mode()?;
|
||||
crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exit(&mut self) -> eyre::Result<()> {
|
||||
if crossterm::terminal::is_raw_mode_enabled()? {
|
||||
self.terminal.flush()?;
|
||||
crossterm::execute!(std::io::stderr(), LeaveAlternateScreen, cursor::Show)?;
|
||||
crossterm::terminal::disable_raw_mode()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for TuiHandle {
|
||||
type Item = <EventStream as Stream>::Item;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
Pin::new(&mut self.crossterm_events).poll_next(cx)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.crossterm_events.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TuiHandle {
|
||||
type Target = ratatui::Terminal<Backend<std::io::Stderr>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.terminal
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for TuiHandle {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.terminal
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TuiHandle {
|
||||
fn drop(&mut self) {
|
||||
// well. at this point we can't do much, we'll just go straight into the panic handler
|
||||
#[allow(clippy::expect_used)]
|
||||
self.exit().expect("failed to teardown the terminal")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::tui::action::Action;
|
||||
use crate::tui::config::TuiConfig;
|
||||
use crate::tui::dispatcher::{ActionDispatcher, ActionSender, DispatcherLoop};
|
||||
use crate::tui::ui::UiEventLoop;
|
||||
use crate::Component;
|
||||
use color_eyre::eyre;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio::time::timeout;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::TaskTracker;
|
||||
use tracing::{error, info};
|
||||
|
||||
pub struct TuiManager<C: Component> {
|
||||
shutdown_grace: Duration,
|
||||
cancel_grace: Duration,
|
||||
abort_grace: Duration,
|
||||
|
||||
task_tracker: TaskTracker,
|
||||
cancellation_token: CancellationToken,
|
||||
|
||||
dispatcher_handle: JoinHandle<eyre::Result<()>>,
|
||||
ui_event_loop_handle: JoinHandle<eyre::Result<()>>,
|
||||
|
||||
action_sender: ActionSender<C::Actions>,
|
||||
}
|
||||
|
||||
impl<C> TuiManager<C>
|
||||
where
|
||||
C: Component + Send + Sync + 'static,
|
||||
C::State: Send + Sync + 'static,
|
||||
C::Actions: Send + Sync + Clone + 'static,
|
||||
{
|
||||
pub(crate) fn build_new<D>(
|
||||
config: TuiConfig,
|
||||
action_dispatcher: D,
|
||||
initial_state: D::Store,
|
||||
) -> eyre::Result<TuiManager<C>>
|
||||
where
|
||||
D: ActionDispatcher<Store = C::State, Actions = C::Actions> + Send + Sync + 'static,
|
||||
{
|
||||
let task_tracker = TaskTracker::new();
|
||||
let cancellation_token = CancellationToken::new();
|
||||
|
||||
let (action_tx, action_rx) = mpsc::unbounded_channel();
|
||||
let (state_tx, state_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let mut dispatcher_loop = DispatcherLoop::new(
|
||||
action_dispatcher,
|
||||
initial_state,
|
||||
action_tx.clone(),
|
||||
action_rx,
|
||||
state_tx,
|
||||
cancellation_token.clone(),
|
||||
);
|
||||
let mut ui_event_loop = UiEventLoop::<C>::new(
|
||||
config.tui.debug.tick_rate,
|
||||
cancellation_token.clone(),
|
||||
state_rx,
|
||||
action_tx.clone(),
|
||||
)?;
|
||||
|
||||
let dispatcher_handle = task_tracker.spawn(async move { dispatcher_loop.run().await });
|
||||
let ui_event_loop_handle = task_tracker.spawn(async move { ui_event_loop.run().await });
|
||||
|
||||
task_tracker.close();
|
||||
|
||||
Ok(TuiManager {
|
||||
shutdown_grace: config.debug.shutdown_grace,
|
||||
cancel_grace: config.debug.cancel_grace,
|
||||
abort_grace: config.debug.abort_grace,
|
||||
task_tracker,
|
||||
cancellation_token,
|
||||
dispatcher_handle,
|
||||
ui_event_loop_handle,
|
||||
action_sender: action_tx.into(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn graceful_shutdown(&self) {
|
||||
// 1. try to send quit action to handle it the most gracefully
|
||||
self.action_sender.send(Action::Quit);
|
||||
if timeout(self.shutdown_grace, self.task_tracker.wait())
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return;
|
||||
}
|
||||
error!("timed out while waiting for graceful shutdown");
|
||||
|
||||
// 2. if that doesn't work, issue cancellation token
|
||||
self.cancellation_token.cancel();
|
||||
if timeout(self.cancel_grace, self.task_tracker.wait())
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return;
|
||||
}
|
||||
error!("timed out while attempting to resolve cancellation token shutdown");
|
||||
|
||||
// 3. finally go with nuclear option and just abort the tasks
|
||||
self.dispatcher_handle.abort();
|
||||
self.ui_event_loop_handle.abort();
|
||||
|
||||
if timeout(self.abort_grace, self.task_tracker.wait())
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
error!("somehow we still failed to shutdown our tasks! we might end up in a dirty state... oh well")
|
||||
}
|
||||
|
||||
pub(crate) async fn wait_for_exit_or_signal(&self) {
|
||||
tokio::select! {
|
||||
_ = self.task_tracker.wait() => {
|
||||
// user decided to quit with 'normal' action
|
||||
}
|
||||
_ = wait_for_signal() => {
|
||||
self.graceful_shutdown().await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[allow(clippy::expect_used)]
|
||||
pub async fn wait_for_signal() {
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
let mut sigterm = signal(SignalKind::terminate()).expect("failed to setup SIGTERM channel");
|
||||
let mut sigquit = signal(SignalKind::quit()).expect("failed to setup SIGQUIT channel");
|
||||
|
||||
tokio::select! {
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
info!("Received SIGINT");
|
||||
},
|
||||
_ = sigterm.recv() => {
|
||||
info!("Received SIGTERM");
|
||||
}
|
||||
_ = sigquit.recv() => {
|
||||
info!("Received SIGQUIT");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub async fn wait_for_signal() {
|
||||
tokio::select! {
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
info!("Received SIGINT");
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::tui::handle::TuiHandle;
|
||||
use color_eyre::eyre;
|
||||
use tracing::error;
|
||||
|
||||
pub mod action;
|
||||
pub mod config;
|
||||
pub(crate) mod dispatcher;
|
||||
pub mod handle;
|
||||
pub(crate) mod manager;
|
||||
pub(crate) mod ui;
|
||||
|
||||
pub fn initialize_panic_handler() -> eyre::Result<()> {
|
||||
let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default()
|
||||
.display_location_section(true)
|
||||
.display_env_section(true)
|
||||
.into_hooks();
|
||||
eyre_hook.install()?;
|
||||
std::panic::set_hook(Box::new(move |panic_info| {
|
||||
if let Err(r) = TuiHandle::new().and_then(|mut h| h.exit()) {
|
||||
error!("Unable to exit Terminal: {:?}", r);
|
||||
}
|
||||
|
||||
let msg = format!("{}", panic_hook.panic_report(panic_info));
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
eprintln!("{}", msg); // prints color-eyre stack trace to stderr
|
||||
}
|
||||
error!("error: {}", strip_ansi_escapes::strip_str(msg));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// Better Panic stacktrace that is only enabled when debugging.
|
||||
better_panic::Settings::auto()
|
||||
.most_recent_first(false)
|
||||
.lineno_suffix(true)
|
||||
.verbosity(better_panic::Verbosity::Full)
|
||||
.create_panic_handler()(panic_info);
|
||||
}
|
||||
|
||||
std::process::exit(1);
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::tui::config::keybindings::key_event_to_string;
|
||||
use crate::tui::dispatcher::ActionSender;
|
||||
use crate::{AppAction, Component, State};
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::layout::{Alignment, Rect};
|
||||
use ratatui::prelude::{Line, Style, Stylize};
|
||||
use ratatui::widgets::Block;
|
||||
use ratatui::Frame;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct DebugHistory<S, A> {
|
||||
last_tick_key_events: Vec<KeyEvent>,
|
||||
|
||||
phantom_state: PhantomData<S>,
|
||||
phantom_action: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<S, A> Component for DebugHistory<S, A>
|
||||
where
|
||||
S: State,
|
||||
A: AppAction,
|
||||
{
|
||||
type State = S;
|
||||
type Actions = A;
|
||||
|
||||
fn new(_state: &Self::State, _action_sender: ActionSender<Self::Actions>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
DebugHistory {
|
||||
last_tick_key_events: vec![],
|
||||
phantom_state: PhantomData,
|
||||
phantom_action: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&mut self) -> bool {
|
||||
let was_empty = self.last_tick_key_events.is_empty();
|
||||
self.last_tick_key_events.drain(..);
|
||||
|
||||
!was_empty
|
||||
}
|
||||
|
||||
fn handle_key(&mut self, key: KeyEvent) -> color_eyre::Result<()> {
|
||||
self.last_tick_key_events.push(key);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn view(&mut self, frame: &mut Frame, rect: Rect) {
|
||||
frame.render_widget(
|
||||
Block::default()
|
||||
.title_top(
|
||||
Line::from(format!(
|
||||
"{:?}",
|
||||
&self
|
||||
.last_tick_key_events
|
||||
.iter()
|
||||
.map(key_event_to_string)
|
||||
.collect::<Vec<_>>()
|
||||
))
|
||||
.alignment(Alignment::Right),
|
||||
)
|
||||
.title_style(Style::default().bold()),
|
||||
rect,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::tui::config::keybindings::LoggerKeybindings;
|
||||
use crate::tui::dispatcher::store::State;
|
||||
use crate::tui::dispatcher::ActionSender;
|
||||
use crate::tui::ui::components::Component;
|
||||
use crate::AppAction;
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::widgets::Block;
|
||||
use ratatui::Frame;
|
||||
use std::marker::PhantomData;
|
||||
use tui_logger::{TuiLoggerLevelOutput, TuiLoggerSmartWidget, TuiWidgetState};
|
||||
|
||||
pub struct Props {
|
||||
pub keybindings: LoggerKeybindings,
|
||||
}
|
||||
|
||||
pub struct Logger<S, A> {
|
||||
props: Props,
|
||||
widget_state: TuiWidgetState,
|
||||
|
||||
phantom_state: PhantomData<S>,
|
||||
phantom_action: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<S, A> Component for Logger<S, A>
|
||||
where
|
||||
S: State,
|
||||
for<'a> Props: From<&'a S>,
|
||||
A: AppAction,
|
||||
{
|
||||
type State = S;
|
||||
type Actions = A;
|
||||
|
||||
fn new(state: &Self::State, _action_sender: ActionSender<Self::Actions>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Logger {
|
||||
props: Props::from(state),
|
||||
widget_state: TuiWidgetState::new(),
|
||||
phantom_state: PhantomData,
|
||||
phantom_action: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&mut self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn handle_key(&mut self, key: KeyEvent) -> color_eyre::Result<()> {
|
||||
if let Some(tui_event) = self.props.keybindings.tui_logger_event(key.into()) {
|
||||
self.widget_state.transition(tui_event)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn view(&mut self, frame: &mut Frame, rect: Rect) {
|
||||
let border = Block::bordered();
|
||||
let inner_area = border.inner(rect);
|
||||
frame.render_widget(border, rect);
|
||||
|
||||
let tui_sm = TuiLoggerSmartWidget::default()
|
||||
.style_error(Style::default().fg(Color::Red))
|
||||
.style_warn(Style::default().fg(Color::Yellow))
|
||||
.style_info(Style::default().fg(Color::Green))
|
||||
.style_debug(Style::default().fg(Color::Cyan))
|
||||
.style_trace(Style::default().fg(Color::Magenta))
|
||||
.output_separator(':')
|
||||
.output_timestamp(Some("%F %H:%M:%S%.3f".to_string()))
|
||||
.output_level(Some(TuiLoggerLevelOutput::Long))
|
||||
.output_target(true)
|
||||
.output_file(true)
|
||||
.output_line(true)
|
||||
.state(&self.widget_state);
|
||||
frame.render_widget(tui_sm, inner_area);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod debug_history;
|
||||
|
||||
#[cfg(feature = "logger")]
|
||||
pub mod logger;
|
||||
|
||||
pub use debug_history::DebugHistory;
|
||||
|
||||
#[cfg(feature = "logger")]
|
||||
pub use logger::{Logger, Props as LoggerProps};
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::tui::dispatcher::ActionSender;
|
||||
use crate::{AppAction, State};
|
||||
use color_eyre::eyre;
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::prelude::Rect;
|
||||
use ratatui::Frame;
|
||||
|
||||
pub mod common;
|
||||
|
||||
// pub trait Props<'a>: From<&'a Self::State> {
|
||||
// type State: State;
|
||||
// }
|
||||
|
||||
pub trait Component {
|
||||
type State: State;
|
||||
type Actions: AppAction;
|
||||
|
||||
fn new(state: &Self::State, action_sender: ActionSender<Self::Actions>) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn update(self, state: &Self::State) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let _ = state;
|
||||
self
|
||||
}
|
||||
|
||||
// returns boolean indicating whether a rerender is needed
|
||||
// fn tick(&mut self) -> bool;
|
||||
fn tick(&mut self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn handle_key(&mut self, key: KeyEvent) -> eyre::Result<()> {
|
||||
let _ = key;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn view(&mut self, frame: &mut Frame, rect: Rect);
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::tui::dispatcher::{ActionSender, StateUpdateReceiver};
|
||||
use crate::tui::handle::TuiHandle;
|
||||
use crate::tui::ui::components::Component;
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::eyre::eyre;
|
||||
use crossterm::event::Event;
|
||||
use humantime_serde::re::humantime;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Duration;
|
||||
use tokio::time::{timeout, Instant};
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{info, trace, warn};
|
||||
|
||||
pub mod components;
|
||||
|
||||
pub struct UiEventLoop<C: Component> {
|
||||
tick_rate: Duration,
|
||||
cancellation_token: CancellationToken,
|
||||
state_receiver: StateUpdateReceiver<C::State>,
|
||||
|
||||
// only to be used to construct root 'App' instance
|
||||
action_sender: ActionSender<C::Actions>,
|
||||
tui_handle: TuiHandle,
|
||||
|
||||
root_component: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<C> UiEventLoop<C>
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
pub fn new(
|
||||
tick_rate: Duration,
|
||||
cancellation_token: CancellationToken,
|
||||
state_receiver: impl Into<StateUpdateReceiver<C::State>>,
|
||||
action_sender: impl Into<ActionSender<C::Actions>>,
|
||||
) -> eyre::Result<Self> {
|
||||
Ok(UiEventLoop {
|
||||
tick_rate,
|
||||
cancellation_token,
|
||||
state_receiver: state_receiver.into(),
|
||||
action_sender: action_sender.into(),
|
||||
tui_handle: TuiHandle::new()?,
|
||||
root_component: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_ui_event(
|
||||
&mut self,
|
||||
event: Option<io::Result<Event>>,
|
||||
root_app: &mut C,
|
||||
) -> eyre::Result<()> {
|
||||
let Some(event) = event else {
|
||||
warn!("the crossterm event channel has closed! we're probably already in shutdown!");
|
||||
// but if we're not, make sure to kick it off...
|
||||
self.cancellation_token.cancel();
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match event? {
|
||||
Event::FocusGained => {}
|
||||
Event::FocusLost => {}
|
||||
Event::Key(key_event) => root_app.handle_key(key_event)?,
|
||||
Event::Mouse(_) => {}
|
||||
Event::Paste(_) => {}
|
||||
Event::Resize(_, _) => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_updated_state(&mut self, state: Option<C::State>, root_app: C) -> C
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
let Some(updated_state) = state else {
|
||||
warn!("the state update channel has closed! we're probably already in shutdown!");
|
||||
// but if we're not, make sure to kick it off...
|
||||
self.cancellation_token.cancel();
|
||||
return root_app;
|
||||
};
|
||||
|
||||
root_app.update(&updated_state)
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) -> eyre::Result<()>
|
||||
where
|
||||
// this clone shouldn't really be needed...
|
||||
C::Actions: Clone,
|
||||
{
|
||||
info!("starting the ui loop");
|
||||
|
||||
// wait for initial state...
|
||||
let initial_state = timeout(Duration::from_secs(1), self.state_receiver.next())
|
||||
.await?
|
||||
.ok_or_else(|| eyre!("did not receive initial state!"))?;
|
||||
|
||||
let mut root_app = C::new(&initial_state, self.action_sender.clone());
|
||||
|
||||
let mut tick_rate = self.tick_rate;
|
||||
let mut tick_interval = tokio::time::interval(tick_rate);
|
||||
self.tui_handle.enter()?;
|
||||
|
||||
let mut draw = true;
|
||||
|
||||
loop {
|
||||
if draw {
|
||||
let draw_start = Instant::now();
|
||||
|
||||
trace!("redrawing the UI");
|
||||
self.tui_handle
|
||||
.draw(|frame| root_app.view(frame, frame.area()))?;
|
||||
|
||||
let taken = humantime::format_duration(draw_start.elapsed()).to_string();
|
||||
trace!(time_taken = taken, "UI drawing");
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.cancellation_token.cancelled() => {
|
||||
info!("received cancellation token");
|
||||
break;
|
||||
}
|
||||
maybe_ui_event = self.tui_handle.next() => {
|
||||
self.handle_ui_event(maybe_ui_event, &mut root_app).await?;
|
||||
draw = true;
|
||||
}
|
||||
state_update = self.state_receiver.next() => {
|
||||
root_app = self.handle_updated_state(state_update, root_app).await;
|
||||
// the tick rate has changed
|
||||
if self.tick_rate != tick_rate {
|
||||
tick_rate = self.tick_rate;
|
||||
tick_interval = tokio::time::interval(tick_rate);
|
||||
}
|
||||
draw = true;
|
||||
}
|
||||
_ = tick_interval.tick() => {
|
||||
let tick_start = Instant::now();
|
||||
draw = root_app.tick();
|
||||
|
||||
let taken = humantime::format_duration(tick_start.elapsed()).to_string();
|
||||
trace!(time_taken = taken, will_redraw = draw, "app tick");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -324,6 +324,18 @@ pub fn unchecked_aggregate_indices_signatures(
|
||||
_aggregate_indices_signatures(params, vk, signatures_shares, false)
|
||||
}
|
||||
|
||||
/// Generates parameters for the scheme setup.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `total_coins` - it is the number of coins in a freshly generated wallet. It is the public parameter of the scheme.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Parameters` struct containing group parameters, public key, the number of signatures (`total_coins`),
|
||||
/// and a map of signatures for each index `l`.
|
||||
///
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -264,7 +264,7 @@ impl<'b> Add<&'b VerificationKeyAuth> for VerificationKeyAuth {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Scalar> for &VerificationKeyAuth {
|
||||
impl<'a> Mul<Scalar> for &'a VerificationKeyAuth {
|
||||
type Output = VerificationKeyAuth;
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -984,7 +984,7 @@ pub struct SerialNumberRef<'a> {
|
||||
pub(crate) inner: &'a [G1Projective],
|
||||
}
|
||||
|
||||
impl SerialNumberRef<'_> {
|
||||
impl<'a> SerialNumberRef<'a> {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let ss_len = self.inner.len();
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(ss_len * 48);
|
||||
|
||||
@@ -206,10 +206,10 @@ impl Deref for PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul<&'a Scalar> for &PublicKey {
|
||||
impl<'a, 'b> Mul<&'b Scalar> for &'a PublicKey {
|
||||
type Output = G1Projective;
|
||||
|
||||
fn mul(self, rhs: &'a Scalar) -> Self::Output {
|
||||
fn mul(self, rhs: &'b Scalar) -> Self::Output {
|
||||
self.0 * rhs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ impl<'b> Add<&'b VerificationKey> for VerificationKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Scalar> for &VerificationKey {
|
||||
impl<'a> Mul<Scalar> for &'a VerificationKey {
|
||||
type Output = VerificationKey;
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -64,7 +64,7 @@ impl<'de> Deserialize<'de> for Recipient {
|
||||
{
|
||||
struct RecipientVisitor;
|
||||
|
||||
impl Visitor<'_> for RecipientVisitor {
|
||||
impl<'de> Visitor<'de> for RecipientVisitor {
|
||||
type Value = Recipient;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Encodoing and decoding node routing information.
|
||||
//!
|
||||
//! This module is responsible for encoding and decoding node routing information, so that
|
||||
//! they could be later put into an appropriate field in a sphinx header.
|
||||
//! Currently, that routing information is an IP address, but in principle it can be anything
|
||||
//! for as long as it's going to fit in the field.
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx_types::{NodeAddressBytes, NODE_ADDRESS_LENGTH};
|
||||
|
||||
@@ -18,6 +12,13 @@ use thiserror::Error;
|
||||
pub type NodeIdentity = identity::PublicKey;
|
||||
pub const NODE_IDENTITY_SIZE: usize = identity::PUBLIC_KEY_LENGTH;
|
||||
|
||||
/// Encodoing and decoding node routing information.
|
||||
///
|
||||
/// This module is responsible for encoding and decoding node routing information, so that
|
||||
/// they could be later put into an appropriate field in a sphinx header.
|
||||
/// Currently, that routing information is an IP address, but in principle it can be anything
|
||||
/// for as long as it's going to fit in the field.
|
||||
|
||||
/// MAX_UNPADDED_LEN represents maximum length an unpadded address could have.
|
||||
/// In this case it's an ipv6 socket address (with version prefix)
|
||||
pub const MAX_NODE_ADDRESS_UNPADDED_LEN: usize = 19;
|
||||
|
||||
@@ -56,7 +56,7 @@ impl<'de> Deserialize<'de> for ReplySurb {
|
||||
{
|
||||
struct ReplySurbVisitor;
|
||||
|
||||
impl Visitor<'_> for ReplySurbVisitor {
|
||||
impl<'de> Visitor<'de> for ReplySurbVisitor {
|
||||
type Value = ReplySurb;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||
|
||||
@@ -18,7 +18,7 @@ use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX
|
||||
use nym_sphinx_types::{Delay, NymPacket};
|
||||
use nym_topology::{NymTopology, NymTopologyError};
|
||||
use rand::{CryptoRng, Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
|
||||
use nym_sphinx_chunking::monitoring;
|
||||
use std::time::Duration;
|
||||
@@ -51,7 +51,6 @@ impl From<PreparedFragment> for MixPacket {
|
||||
pub trait FragmentPreparer {
|
||||
type Rng: CryptoRng + Rng;
|
||||
|
||||
fn deterministic_route_selection(&self) -> bool;
|
||||
fn rng(&mut self) -> &mut Self::Rng;
|
||||
fn nonce(&self) -> i32;
|
||||
fn num_mix_hops(&self) -> u8;
|
||||
@@ -202,7 +201,9 @@ pub trait FragmentPreparer {
|
||||
// could perform diffie-hellman with its own keys followed by a kdf to re-derive
|
||||
// the packet encryption key
|
||||
|
||||
let fragment_header = fragment.header();
|
||||
let seed = fragment.seed().wrapping_mul(self.nonce());
|
||||
let mut rng = ChaCha20Rng::seed_from_u64(seed as u64);
|
||||
|
||||
let destination = packet_recipient.gateway();
|
||||
let hops = mix_hops.unwrap_or(self.num_mix_hops());
|
||||
monitoring::fragment_sent(&fragment, self.nonce(), *destination, hops);
|
||||
@@ -240,18 +241,8 @@ pub trait FragmentPreparer {
|
||||
};
|
||||
|
||||
// generate pseudorandom route for the packet
|
||||
log::trace!("Preparing chunk for sending with {hops} mix hops");
|
||||
let route = if self.deterministic_route_selection() {
|
||||
log::trace!("using deterministic route selection");
|
||||
let seed = fragment_header.seed().wrapping_mul(self.nonce());
|
||||
let mut rng = ChaCha8Rng::seed_from_u64(seed as u64);
|
||||
topology.random_route_to_gateway(&mut rng, hops, destination)?
|
||||
} else {
|
||||
log::trace!("using pseudorandom route selection");
|
||||
let mut rng = self.rng();
|
||||
topology.random_route_to_gateway(&mut rng, hops, destination)?
|
||||
};
|
||||
|
||||
log::trace!("Preparing chunk for sending with {} mix hops", hops);
|
||||
let route = topology.random_route_to_gateway(&mut rng, hops, destination)?;
|
||||
let destination = packet_recipient.as_sphinx_destination();
|
||||
|
||||
// including set of delays
|
||||
@@ -322,9 +313,6 @@ pub struct MessagePreparer<R> {
|
||||
/// Instance of a cryptographically secure random number generator.
|
||||
rng: R,
|
||||
|
||||
/// Specify whether route selection should be determined by the packet header.
|
||||
deterministic_route_selection: bool,
|
||||
|
||||
/// Address of this client which also represent an address to which all acknowledgements
|
||||
/// and surb-based are going to be sent.
|
||||
sender_address: Recipient,
|
||||
@@ -348,7 +336,6 @@ where
|
||||
{
|
||||
pub fn new(
|
||||
rng: R,
|
||||
deterministic_route_selection: bool,
|
||||
sender_address: Recipient,
|
||||
average_packet_delay: Duration,
|
||||
average_ack_delay: Duration,
|
||||
@@ -357,7 +344,6 @@ where
|
||||
let nonce = rng.gen();
|
||||
MessagePreparer {
|
||||
rng,
|
||||
deterministic_route_selection,
|
||||
sender_address,
|
||||
average_packet_delay,
|
||||
average_ack_delay,
|
||||
@@ -471,18 +457,10 @@ where
|
||||
impl<R: CryptoRng + Rng> FragmentPreparer for MessagePreparer<R> {
|
||||
type Rng = R;
|
||||
|
||||
fn deterministic_route_selection(&self) -> bool {
|
||||
self.deterministic_route_selection
|
||||
}
|
||||
|
||||
fn rng(&mut self) -> &mut Self::Rng {
|
||||
&mut self.rng
|
||||
}
|
||||
|
||||
fn nonce(&self) -> i32 {
|
||||
self.nonce
|
||||
}
|
||||
|
||||
fn num_mix_hops(&self) -> u8 {
|
||||
self.num_mix_hops
|
||||
}
|
||||
@@ -494,6 +472,10 @@ impl<R: CryptoRng + Rng> FragmentPreparer for MessagePreparer<R> {
|
||||
fn average_ack_delay(&self) -> Duration {
|
||||
self.average_ack_delay
|
||||
}
|
||||
|
||||
fn nonce(&self) -> i32 {
|
||||
self.nonce
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -253,15 +253,25 @@ impl Socks5RequestContent {
|
||||
/// Deserialize the request type, connection id, destination address and port,
|
||||
/// and the request body from bytes.
|
||||
///
|
||||
// TODO: this was already inaccurate
|
||||
// /// Serialized bytes looks like this:
|
||||
// ///
|
||||
// /// --------------------------------------------------------------------------------------
|
||||
// /// request_flag | connection_id | address_length | remote_address_bytes | request_data |
|
||||
// /// 1 | 8 | 2 | address_length | ... |
|
||||
// /// --------------------------------------------------------------------------------------
|
||||
///
|
||||
/// The request_flag tells us whether this is a new connection request (`new_connect`),
|
||||
/// an already-established connection we should send up (`new_send`), or
|
||||
/// a request to close an established connection (`new_close`).
|
||||
|
||||
// connect:
|
||||
// RequestFlag::Connect || CONN_ID || ADDR_LEN || ADDR || <RETURN_ADDR>
|
||||
//
|
||||
// send:
|
||||
// RequestFlag::Send || CONN_ID || LOCAL_CLOSED || DATA
|
||||
// where DATA: SEQ || TRUE_DATA
|
||||
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<Socks5RequestContent, RequestDeserializationError> {
|
||||
// each request needs to at least contain flag and ConnectionId
|
||||
if b.is_empty() {
|
||||
|
||||
@@ -162,13 +162,6 @@ pub struct TrafficWasm {
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
pub message_sending_average_delay_ms: u32,
|
||||
|
||||
/// Specify how many times particular packet can be retransmitted
|
||||
/// None - no limit
|
||||
pub maximum_number_of_retransmissions: Option<u32>,
|
||||
|
||||
/// Specify whether route selection should be determined by the packet header.
|
||||
pub deterministic_route_selection: bool,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
@@ -203,8 +196,6 @@ impl From<TrafficWasm> for ConfigTraffic {
|
||||
message_sending_average_delay: Duration::from_millis(
|
||||
traffic.message_sending_average_delay_ms as u64,
|
||||
),
|
||||
deterministic_route_selection: traffic.deterministic_route_selection,
|
||||
maximum_number_of_retransmissions: traffic.maximum_number_of_retransmissions,
|
||||
disable_main_poisson_packet_distribution: traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
primary_packet_size: PacketSize::RegularPacket,
|
||||
@@ -220,8 +211,6 @@ impl From<ConfigTraffic> for TrafficWasm {
|
||||
average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u32,
|
||||
message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis()
|
||||
as u32,
|
||||
deterministic_route_selection: traffic.deterministic_route_selection,
|
||||
maximum_number_of_retransmissions: traffic.maximum_number_of_retransmissions,
|
||||
disable_main_poisson_packet_distribution: traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size: traffic.secondary_packet_size.is_some(),
|
||||
|
||||
@@ -88,14 +88,6 @@ pub struct TrafficWasmOverride {
|
||||
#[tsify(optional)]
|
||||
pub message_sending_average_delay_ms: Option<u32>,
|
||||
|
||||
/// Specify how many times particular packet can be retransmitted
|
||||
#[tsify(optional)]
|
||||
pub maximum_number_of_retransmissions: Option<u32>,
|
||||
|
||||
/// Specify whether route selection should be determined by the packet header.
|
||||
#[tsify(optional)]
|
||||
pub deterministic_route_selection: Option<bool>,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
#[tsify(optional)]
|
||||
@@ -121,10 +113,6 @@ impl From<TrafficWasmOverride> for TrafficWasm {
|
||||
message_sending_average_delay_ms: value
|
||||
.message_sending_average_delay_ms
|
||||
.unwrap_or(def.message_sending_average_delay_ms),
|
||||
maximum_number_of_retransmissions: value.maximum_number_of_retransmissions,
|
||||
deterministic_route_selection: value
|
||||
.deterministic_route_selection
|
||||
.unwrap_or(def.deterministic_route_selection),
|
||||
disable_main_poisson_packet_distribution: value
|
||||
.disable_main_poisson_packet_distribution
|
||||
.unwrap_or(def.disable_main_poisson_packet_distribution),
|
||||
|
||||
@@ -26,7 +26,6 @@ log.workspace = true
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
|
||||
tokio-stream = { workspace = true }
|
||||
time = { workspace = true }
|
||||
|
||||
nym-authenticator-requests = { path = "../authenticator-requests" }
|
||||
nym-credential-verification = { path = "../credential-verification" }
|
||||
|
||||
@@ -20,7 +20,6 @@ use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
pub(crate) mod error;
|
||||
pub mod peer_controller;
|
||||
pub mod peer_handle;
|
||||
pub mod peer_storage_manager;
|
||||
|
||||
pub struct WgApiWrapper {
|
||||
inner: WGApi,
|
||||
@@ -119,7 +118,7 @@ pub async fn start_wireguard<St: nym_gateway_storage::Storage + Clone + 'static>
|
||||
storage
|
||||
.insert_wireguard_peer(peer, bandwidth_manager.is_some())
|
||||
.await?;
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), (bandwidth_manager, peer.clone()));
|
||||
peer_bandwidth_managers.insert(peer.public_key.clone(), bandwidth_manager);
|
||||
}
|
||||
wg_api.create_interface()?;
|
||||
let interface_config = InterfaceConfiguration {
|
||||
|
||||
@@ -20,9 +20,9 @@ use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tokio_stream::{wrappers::IntervalStream, StreamExt};
|
||||
|
||||
use crate::peer_handle::PeerHandle;
|
||||
use crate::WgApiWrapper;
|
||||
use crate::{error::Error, peer_handle::SharedBandwidthStorageManager};
|
||||
use crate::{peer_handle::PeerHandle, peer_storage_manager::PeerStorageManager};
|
||||
|
||||
pub enum PeerControlRequest {
|
||||
AddPeer {
|
||||
@@ -79,7 +79,7 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
storage: St,
|
||||
wg_api: Arc<WgApiWrapper>,
|
||||
initial_host_information: Host,
|
||||
bw_storage_managers: HashMap<Key, (Option<SharedBandwidthStorageManager<St>>, Peer)>,
|
||||
bw_storage_managers: HashMap<Key, Option<SharedBandwidthStorageManager<St>>>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
request_rx: mpsc::Receiver<PeerControlRequest>,
|
||||
task_client: nym_task::TaskClient,
|
||||
@@ -88,16 +88,11 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK),
|
||||
);
|
||||
let host_information = Arc::new(RwLock::new(initial_host_information));
|
||||
for (public_key, (bandwidth_storage_manager, peer)) in bw_storage_managers.iter() {
|
||||
let peer_storage_manager = PeerStorageManager::new(
|
||||
storage.clone(),
|
||||
peer.clone(),
|
||||
bandwidth_storage_manager.is_some(),
|
||||
);
|
||||
for (public_key, bandwidth_storage_manager) in bw_storage_managers.iter() {
|
||||
let mut handle = PeerHandle::new(
|
||||
storage.clone(),
|
||||
public_key.clone(),
|
||||
host_information.clone(),
|
||||
peer_storage_manager,
|
||||
bandwidth_storage_manager.clone(),
|
||||
request_tx.clone(),
|
||||
&task_client,
|
||||
@@ -108,10 +103,6 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
}
|
||||
});
|
||||
}
|
||||
let bw_storage_managers = bw_storage_managers
|
||||
.into_iter()
|
||||
.map(|(k, (m, _))| (k, m))
|
||||
.collect();
|
||||
|
||||
PeerController {
|
||||
storage,
|
||||
@@ -193,15 +184,10 @@ impl<St: Storage + Clone + 'static> PeerController<St> {
|
||||
Self::generate_bandwidth_manager(self.storage.clone(), &peer.public_key)
|
||||
.await?
|
||||
.map(|bw_m| Arc::new(RwLock::new(bw_m)));
|
||||
let peer_storage_manager = PeerStorageManager::new(
|
||||
self.storage.clone(),
|
||||
peer.clone(),
|
||||
bandwidth_storage_manager.is_some(),
|
||||
);
|
||||
let mut handle = PeerHandle::new(
|
||||
self.storage.clone(),
|
||||
peer.public_key.clone(),
|
||||
self.host_information.clone(),
|
||||
peer_storage_manager,
|
||||
bandwidth_storage_manager.clone(),
|
||||
self.request_tx.clone(),
|
||||
&self.task_client,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::peer_controller::PeerControlRequest;
|
||||
use crate::peer_storage_manager::PeerStorageManager;
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use defguard_wireguard_rs::{host::Host, key::Key};
|
||||
use futures::channel::oneshot;
|
||||
@@ -22,9 +21,9 @@ pub(crate) type SharedBandwidthStorageManager<St> = Arc<RwLock<BandwidthStorageM
|
||||
const AUTO_REMOVE_AFTER: Duration = Duration::from_secs(60 * 60 * 24 * 30); // 30 days
|
||||
|
||||
pub struct PeerHandle<St> {
|
||||
storage: St,
|
||||
public_key: Key,
|
||||
host_information: Arc<RwLock<Host>>,
|
||||
peer_storage_manager: PeerStorageManager<St>,
|
||||
bandwidth_storage_manager: Option<SharedBandwidthStorageManager<St>>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
timeout_check_interval: IntervalStream,
|
||||
@@ -34,9 +33,9 @@ pub struct PeerHandle<St> {
|
||||
|
||||
impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
pub fn new(
|
||||
storage: St,
|
||||
public_key: Key,
|
||||
host_information: Arc<RwLock<Host>>,
|
||||
peer_storage_manager: PeerStorageManager<St>,
|
||||
bandwidth_storage_manager: Option<SharedBandwidthStorageManager<St>>,
|
||||
request_tx: mpsc::Sender<PeerControlRequest>,
|
||||
task_client: &TaskClient,
|
||||
@@ -47,9 +46,9 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
let mut task_client = task_client.fork(format!("peer-{public_key}"));
|
||||
task_client.disarm();
|
||||
PeerHandle {
|
||||
storage,
|
||||
public_key,
|
||||
host_information,
|
||||
peer_storage_manager,
|
||||
bandwidth_storage_manager,
|
||||
request_tx,
|
||||
timeout_check_interval,
|
||||
@@ -85,19 +84,16 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
.ok_or(Error::InconsistentConsumedBytes)?
|
||||
.try_into()
|
||||
.map_err(|_| Error::InconsistentConsumedBytes)?;
|
||||
if spent_bandwidth > 0 {
|
||||
self.peer_storage_manager.update_trx(kernel_peer);
|
||||
if bandwidth_manager
|
||||
if spent_bandwidth > 0
|
||||
&& bandwidth_manager
|
||||
.write()
|
||||
.await
|
||||
.try_use_bandwidth(spent_bandwidth)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
let success = self.remove_peer().await?;
|
||||
self.peer_storage_manager.remove_peer();
|
||||
return Ok(!success);
|
||||
}
|
||||
{
|
||||
let success = self.remove_peer().await?;
|
||||
return Ok(!success);
|
||||
}
|
||||
} else {
|
||||
if SystemTime::now().duration_since(self.startup_timestamp)? >= AUTO_REMOVE_AFTER {
|
||||
@@ -136,7 +132,7 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
// the host information hasn't beed updated yet
|
||||
continue;
|
||||
};
|
||||
let Some(storage_peer) = self.peer_storage_manager.get_peer() else {
|
||||
let Some(storage_peer) = self.storage.get_wireguard_peer(&self.public_key.to_string()).await? else {
|
||||
log::debug!("Peer {:?} not in storage anymore, shutting down handle", self.public_key);
|
||||
return Ok(());
|
||||
};
|
||||
@@ -145,18 +141,12 @@ impl<St: Storage + Clone + 'static> PeerHandle<St> {
|
||||
return Ok(());
|
||||
} else {
|
||||
// Update storage values
|
||||
self.peer_storage_manager.sync_storage_peer().await?;
|
||||
self.storage.insert_wireguard_peer(&kernel_peer, self.bandwidth_storage_manager.is_some()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
_ = self.task_client.recv() => {
|
||||
log::trace!("PeerHandle: Received shutdown");
|
||||
if let Some(bandwidth_manager) = &self.bandwidth_storage_manager {
|
||||
if let Err(e) = bandwidth_manager.write().await.sync_storage_bandwidth().await {
|
||||
log::error!("Storage sync failed - {e}, unaccounted bandwidth might have been consumed");
|
||||
}
|
||||
}
|
||||
log::trace!("PeerHandle: Finished shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::Error;
|
||||
use defguard_wireguard_rs::host::Peer;
|
||||
use nym_gateway_storage::models::WireguardPeer;
|
||||
use nym_gateway_storage::Storage;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
const DEFAULT_PEER_MAX_FLUSHING_RATE: Duration = Duration::from_secs(60 * 60 * 24); // 24h
|
||||
const DEFAULT_PEER_MAX_DELTA_FLUSHING_AMOUNT: u64 = 512 * 1024 * 1024; // 512MB
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PeerFlushingBehaviourConfig {
|
||||
/// Defines maximum delay between peer information being flushed to the persistent storage.
|
||||
pub peer_max_flushing_rate: Duration,
|
||||
|
||||
/// Defines a maximum change in peer before it gets flushed to the persistent storage.
|
||||
pub peer_max_delta_flushing_amount: u64,
|
||||
}
|
||||
|
||||
impl Default for PeerFlushingBehaviourConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
peer_max_flushing_rate: DEFAULT_PEER_MAX_FLUSHING_RATE,
|
||||
peer_max_delta_flushing_amount: DEFAULT_PEER_MAX_DELTA_FLUSHING_AMOUNT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PeerStorageManager<S> {
|
||||
pub(crate) storage: S,
|
||||
pub(crate) peer_information: Option<PeerInformation>,
|
||||
pub(crate) cfg: PeerFlushingBehaviourConfig,
|
||||
pub(crate) with_client_id: bool,
|
||||
}
|
||||
|
||||
impl<S: Storage + Clone + 'static> PeerStorageManager<S> {
|
||||
pub(crate) fn new(storage: S, peer: Peer, with_client_id: bool) -> Self {
|
||||
let peer_information = Some(PeerInformation::new(peer));
|
||||
Self {
|
||||
storage,
|
||||
peer_information,
|
||||
cfg: PeerFlushingBehaviourConfig::default(),
|
||||
with_client_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_peer(&self) -> Option<WireguardPeer> {
|
||||
self.peer_information
|
||||
.as_ref()
|
||||
.map(|p| p.peer.clone().into())
|
||||
}
|
||||
|
||||
pub(crate) fn remove_peer(&mut self) {
|
||||
self.peer_information = None;
|
||||
}
|
||||
|
||||
pub(crate) fn update_trx(&mut self, kernel_peer: &Peer) {
|
||||
if let Some(peer_information) = self.peer_information.as_mut() {
|
||||
peer_information.update_trx_bytes(kernel_peer.tx_bytes, kernel_peer.rx_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn sync_storage_peer(&mut self) -> Result<(), Error> {
|
||||
let Some(peer_information) = self.peer_information.as_mut() else {
|
||||
return Ok(());
|
||||
};
|
||||
if !peer_information.should_sync(self.cfg) {
|
||||
return Ok(());
|
||||
}
|
||||
if self
|
||||
.storage
|
||||
.get_wireguard_peer(&peer_information.peer().public_key.to_string())
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
self.peer_information = None;
|
||||
return Ok(());
|
||||
}
|
||||
self.storage
|
||||
.insert_wireguard_peer(peer_information.peer(), self.with_client_id)
|
||||
.await?;
|
||||
|
||||
peer_information.resync_peer_with_storage();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct PeerInformation {
|
||||
pub(crate) peer: Peer,
|
||||
pub(crate) last_synced: OffsetDateTime,
|
||||
|
||||
pub(crate) bytes_delta_since_sync: u64,
|
||||
}
|
||||
|
||||
impl PeerInformation {
|
||||
pub fn new(peer: Peer) -> PeerInformation {
|
||||
PeerInformation {
|
||||
peer,
|
||||
last_synced: OffsetDateTime::now_utc(),
|
||||
bytes_delta_since_sync: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn should_sync(&self, cfg: PeerFlushingBehaviourConfig) -> bool {
|
||||
if self.bytes_delta_since_sync >= cfg.peer_max_delta_flushing_amount {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.last_synced + cfg.peer_max_flushing_rate < OffsetDateTime::now_utc()
|
||||
&& self.bytes_delta_since_sync != 0
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn peer(&self) -> &Peer {
|
||||
&self.peer
|
||||
}
|
||||
|
||||
pub(crate) fn update_trx_bytes(&mut self, tx_bytes: u64, rx_bytes: u64) {
|
||||
self.bytes_delta_since_sync += tx_bytes.saturating_sub(self.peer.tx_bytes)
|
||||
+ rx_bytes.saturating_sub(self.peer.rx_bytes);
|
||||
self.peer.tx_bytes = tx_bytes;
|
||||
self.peer.rx_bytes = rx_bytes;
|
||||
}
|
||||
|
||||
pub(crate) fn resync_peer_with_storage(&mut self) {
|
||||
self.bytes_delta_since_sync = 0;
|
||||
self.last_synced = OffsetDateTime::now_utc();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.43"
|
||||
version = "1.1.42"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ impl GeoIp {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&City<'_>> for Location {
|
||||
impl<'a> TryFrom<&City<'a>> for Location {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(city: &City) -> Result<Self, Self::Error> {
|
||||
|
||||
@@ -65,7 +65,7 @@ impl<'r> FromRequest<'r> for Location {
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenApiFromRequest<'_> for Location {
|
||||
impl<'a> OpenApiFromRequest<'a> for Location {
|
||||
fn from_request_input(
|
||||
_gen: &mut OpenApiGenerator,
|
||||
_name: String,
|
||||
|
||||
+12
-46
@@ -5,8 +5,6 @@ use async_trait::async_trait;
|
||||
use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent};
|
||||
use nym_topology::{gateway, NymTopology, TopologyProvider};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::debug;
|
||||
use url::Url;
|
||||
@@ -19,7 +17,6 @@ pub struct GatewayTopologyProvider {
|
||||
impl GatewayTopologyProvider {
|
||||
pub fn new(
|
||||
gateway_node: gateway::LegacyNode,
|
||||
cache_ttl: Duration,
|
||||
user_agent: UserAgent,
|
||||
nym_api_url: Vec<Url>,
|
||||
) -> GatewayTopologyProvider {
|
||||
@@ -34,9 +31,6 @@ impl GatewayTopologyProvider {
|
||||
env!("CARGO_PKG_VERSION").to_string(),
|
||||
Some(user_agent),
|
||||
),
|
||||
cache_ttl,
|
||||
cached_at: OffsetDateTime::UNIX_EPOCH,
|
||||
cached: None,
|
||||
gateway_node,
|
||||
})),
|
||||
}
|
||||
@@ -45,53 +39,25 @@ impl GatewayTopologyProvider {
|
||||
|
||||
struct GatewayTopologyProviderInner {
|
||||
inner: NymApiTopologyProvider,
|
||||
cache_ttl: Duration,
|
||||
cached_at: OffsetDateTime,
|
||||
cached: Option<NymTopology>,
|
||||
gateway_node: gateway::LegacyNode,
|
||||
}
|
||||
|
||||
impl GatewayTopologyProviderInner {
|
||||
fn cached_topology(&self) -> Option<NymTopology> {
|
||||
if let Some(cached_topology) = &self.cached {
|
||||
if self.cached_at + self.cache_ttl > OffsetDateTime::now_utc() {
|
||||
return Some(cached_topology.clone());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
async fn update_cache(&mut self) -> Option<NymTopology> {
|
||||
let updated_cache = match self.inner.get_new_topology().await {
|
||||
None => None,
|
||||
Some(mut base) => {
|
||||
if !base.gateway_exists(&self.gateway_node.identity_key) {
|
||||
debug!(
|
||||
"{} didn't exist in topology. inserting it.",
|
||||
self.gateway_node.identity_key
|
||||
);
|
||||
base.insert_gateway(self.gateway_node.clone());
|
||||
}
|
||||
Some(base)
|
||||
}
|
||||
};
|
||||
|
||||
self.cached_at = OffsetDateTime::now_utc();
|
||||
self.cached = updated_cache.clone();
|
||||
|
||||
updated_cache
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TopologyProvider for GatewayTopologyProvider {
|
||||
async fn get_new_topology(&mut self) -> Option<NymTopology> {
|
||||
let mut guard = self.inner.lock().await;
|
||||
// check the cache
|
||||
if let Some(cached) = guard.cached_topology() {
|
||||
return Some(cached);
|
||||
match guard.inner.get_new_topology().await {
|
||||
None => None,
|
||||
Some(mut base) => {
|
||||
if !base.gateway_exists(&guard.gateway_node.identity_key) {
|
||||
debug!(
|
||||
"{} didn't exist in topology. inserting it.",
|
||||
guard.gateway_node.identity_key
|
||||
);
|
||||
base.insert_gateway(guard.gateway_node.clone());
|
||||
}
|
||||
Some(base)
|
||||
}
|
||||
}
|
||||
guard.update_cache().await
|
||||
}
|
||||
}
|
||||
|
||||
+16
-26
@@ -30,7 +30,6 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tracing::*;
|
||||
|
||||
pub(crate) mod client_handling;
|
||||
@@ -149,11 +148,8 @@ impl<St> Gateway<St> {
|
||||
}
|
||||
|
||||
fn gateway_topology_provider(&self) -> GatewayTopologyProvider {
|
||||
// TODO: make topology ttl configurable
|
||||
// (to be done in reeses with the final smooshing)
|
||||
GatewayTopologyProvider::new(
|
||||
self.as_topology_node(),
|
||||
Duration::from_secs(5 * 60),
|
||||
self.user_agent.clone(),
|
||||
self.config.gateway.nym_api_urls.clone(),
|
||||
)
|
||||
@@ -235,28 +231,22 @@ impl<St> Gateway<St> {
|
||||
forwarding_channel,
|
||||
router_tx,
|
||||
);
|
||||
let mut used_private_network_ips = vec![];
|
||||
let mut all_peers = vec![];
|
||||
for wireguard_peer in self
|
||||
.client_storage
|
||||
.get_all_wireguard_peers()
|
||||
.await?
|
||||
.into_iter()
|
||||
{
|
||||
let mut peer = defguard_wireguard_rs::host::Peer::try_from(wireguard_peer.clone())?;
|
||||
let Some(peer) = peer.allowed_ips.pop() else {
|
||||
tracing::warn!(
|
||||
"Peer {} has empty allowed ips. It will be removed",
|
||||
peer.public_key
|
||||
);
|
||||
self.client_storage
|
||||
.remove_wireguard_peer(&peer.public_key.to_string())
|
||||
.await?;
|
||||
continue;
|
||||
};
|
||||
used_private_network_ips.push(peer.ip);
|
||||
all_peers.push(wireguard_peer);
|
||||
}
|
||||
let all_peers = self.client_storage.get_all_wireguard_peers().await?;
|
||||
let used_private_network_ips = all_peers
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|wireguard_peer| {
|
||||
defguard_wireguard_rs::host::Peer::try_from(wireguard_peer).map(|mut peer| {
|
||||
peer.allowed_ips
|
||||
.pop()
|
||||
.ok_or(Box::new(GatewayError::InternalWireguardError(format!(
|
||||
"no private IP set for peer {}",
|
||||
peer.public_key
|
||||
))))
|
||||
.map(|p| p.ip)
|
||||
})
|
||||
})
|
||||
.collect::<Result<Result<Vec<_>, _>, _>>()??;
|
||||
|
||||
if let Some(wireguard_data) = self.wireguard_data.take() {
|
||||
let (on_start_tx, on_start_rx) = oneshot::channel();
|
||||
|
||||
+2
-3
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.47"
|
||||
version = "1.1.46"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
@@ -20,7 +20,6 @@ bloomfilter = { workspace = true }
|
||||
cfg-if = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "derive", "env"] }
|
||||
console-subscriber = { workspace = true, optional = true } # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable"
|
||||
dashmap = { workspace = true }
|
||||
dirs = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
@@ -121,7 +120,7 @@ nym-types = { path = "../common/types" }
|
||||
nym-http-api-common = { path = "../common/http-api-common", features = ["utoipa"] }
|
||||
nym-serde-helpers = { path = "../common/serde-helpers", features = ["date"] }
|
||||
nym-ticketbooks-merkle = { path = "../common/ticketbooks-merkle" }
|
||||
nym-statistics-common = { path = "../common/statistics" }
|
||||
nym-statistics-common = {path ="../common/statistics" }
|
||||
|
||||
[features]
|
||||
no-reward = []
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
CREATE INDEX IF NOT EXISTS monitor_run_id on monitor_run(id);
|
||||
CREATE INDEX IF NOT EXISTS monitor_run_timestamp on monitor_run(timestamp);
|
||||
CREATE INDEX IF NOT EXISTS testing_route_monitor_run_id on testing_route(monitor_run_id);
|
||||
@@ -64,7 +64,7 @@ pub(crate) mod overengineered_offset_date_time_serde {
|
||||
])),
|
||||
];
|
||||
|
||||
impl Visitor<'_> for OffsetDateTimeVisitor {
|
||||
impl<'de> Visitor<'de> for OffsetDateTimeVisitor {
|
||||
type Value = OffsetDateTime;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::ecash::error::EcashError;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::extract::Path;
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -21,19 +21,28 @@ use tracing::trace;
|
||||
use utoipa::IntoParams;
|
||||
|
||||
/// routes with globally aggregated keys, signatures, etc.
|
||||
pub(crate) fn aggregation_routes() -> Router<AppState> {
|
||||
pub(crate) fn aggregation_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/master-verification-key",
|
||||
axum::routing::get(master_verification_key),
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|epoch_id| master_verification_key(epoch_id, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/aggregated-expiration-date-signatures",
|
||||
axum::routing::get(expiration_date_signatures),
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|expiration_date| expiration_date_signatures(expiration_date, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/aggregated-coin-indices-signatures",
|
||||
axum::routing::get(coin_indices_signatures),
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|epoch_id| coin_indices_signatures(epoch_id, ecash_state)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -49,8 +58,8 @@ pub(crate) fn aggregation_routes() -> Router<AppState> {
|
||||
)
|
||||
)]
|
||||
async fn master_verification_key(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>,
|
||||
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<VerificationKeyResponse>> {
|
||||
trace!("aggregated_verification_key request");
|
||||
|
||||
@@ -63,6 +72,7 @@ async fn master_verification_key(
|
||||
}
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
#[into_params(parameter_in = Path)]
|
||||
struct ExpirationDateParam {
|
||||
expiration_date: Option<String>,
|
||||
}
|
||||
@@ -79,8 +89,8 @@ struct ExpirationDateParam {
|
||||
)
|
||||
)]
|
||||
async fn expiration_date_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(ExpirationDateParam { expiration_date }): Query<ExpirationDateParam>,
|
||||
Path(ExpirationDateParam { expiration_date }): Path<ExpirationDateParam>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<AggregatedExpirationDateSignatureResponse>> {
|
||||
trace!("aggregated_expiration_date_signatures request");
|
||||
|
||||
@@ -116,8 +126,8 @@ async fn expiration_date_signatures(
|
||||
)
|
||||
)]
|
||||
async fn coin_indices_signatures(
|
||||
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>,
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Path(EpochIdParam { epoch_id }): Path<EpochIdParam>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<AggregatedCoinIndicesSignatureResponse>> {
|
||||
trace!("aggregated_coin_indices_signatures request");
|
||||
|
||||
|
||||
@@ -5,13 +5,15 @@ use crate::ecash::api_routes::aggregation::aggregation_routes;
|
||||
use crate::ecash::api_routes::issued::issued_routes;
|
||||
use crate::ecash::api_routes::partial_signing::partial_signing_routes;
|
||||
use crate::ecash::api_routes::spending::spending_routes;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::Router;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) fn ecash_routes() -> Router<AppState> {
|
||||
pub(crate) fn ecash_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
Router::new()
|
||||
.merge(aggregation_routes())
|
||||
.merge(issued_routes())
|
||||
.merge(partial_signing_routes())
|
||||
.merge(spending_routes())
|
||||
.merge(aggregation_routes(Arc::clone(&ecash_state)))
|
||||
.merge(issued_routes(Arc::clone(&ecash_state)))
|
||||
.merge(partial_signing_routes(Arc::clone(&ecash_state)))
|
||||
.merge(spending_routes(Arc::clone(&ecash_state)))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#[derive(serde::Deserialize, utoipa::IntoParams)]
|
||||
#[into_params(parameter_in = Path)]
|
||||
pub(super) struct EpochIdParam {
|
||||
pub(super) epoch_id: Option<u64>,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::extract::Path;
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::ecash::models::{
|
||||
IssuedTicketbooksChallengeRequest, IssuedTicketbooksChallengeResponse,
|
||||
@@ -17,15 +17,21 @@ use time::Date;
|
||||
use tracing::trace;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
pub(crate) fn issued_routes() -> Router<AppState> {
|
||||
pub(crate) fn issued_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/issued-ticketbooks-for/:expiration_date",
|
||||
axum::routing::get(issued_ticketbooks_for),
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|expiration_date| issued_ticketbooks_for(expiration_date, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/issued-ticketbooks-challenge",
|
||||
axum::routing::post(issued_ticketbooks_challenge),
|
||||
axum::routing::post({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|body| issued_ticketbooks_challenge(body, ecash_state)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -52,8 +58,8 @@ pub(crate) struct ExpirationDatePathParam {
|
||||
)
|
||||
)]
|
||||
async fn issued_ticketbooks_for(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Path(ExpirationDatePathParam { expiration_date }): Path<ExpirationDatePathParam>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<IssuedTicketbooksForResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -77,8 +83,8 @@ async fn issued_ticketbooks_for(
|
||||
)
|
||||
)]
|
||||
async fn issued_ticketbooks_challenge(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Json(challenge): Json<IssuedTicketbooksChallengeRequest>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<IssuedTicketbooksChallengeResponse>> {
|
||||
trace!("replying to ticketbooks challenge: {:?}", challenge);
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::ecash::helpers::blind_sign;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::{Query, State};
|
||||
use axum::extract::Query;
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::ecash::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
|
||||
@@ -22,16 +22,28 @@ use time::Date;
|
||||
use tracing::{debug, trace};
|
||||
use utoipa::IntoParams;
|
||||
|
||||
pub(crate) fn partial_signing_routes() -> Router<AppState> {
|
||||
pub(crate) fn partial_signing_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/blind-sign", axum::routing::post(post_blind_sign))
|
||||
.route(
|
||||
"/blind-sign",
|
||||
axum::routing::post({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|body| post_blind_sign(body, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/partial-expiration-date-signatures",
|
||||
axum::routing::get(partial_expiration_date_signatures),
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|expiration_date| partial_expiration_date_signatures(expiration_date, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/partial-coin-indices-signatures",
|
||||
axum::routing::get(partial_coin_indices_signatures),
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|epoch_id| partial_coin_indices_signatures(epoch_id, ecash_state)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,8 +59,8 @@ pub(crate) fn partial_signing_routes() -> Router<AppState> {
|
||||
)
|
||||
)]
|
||||
async fn post_blind_sign(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Json(blind_sign_request_body): Json<BlindSignRequestBody>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<BlindedSignatureResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -122,8 +134,8 @@ struct ExpirationDateParam {
|
||||
)
|
||||
)]
|
||||
async fn partial_expiration_date_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(ExpirationDateParam { expiration_date }): Query<ExpirationDateParam>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<PartialExpirationDateSignatureResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -160,8 +172,8 @@ async fn partial_expiration_date_signatures(
|
||||
)
|
||||
)]
|
||||
async fn partial_coin_indices_signatures(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
Query(EpochIdParam { epoch_id }): Query<EpochIdParam>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<PartialCoinIndicesSignatureResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::ecash::error::EcashError;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::constants::MIN_BATCH_REDEMPTION_DELAY;
|
||||
use nym_api_requests::ecash::models::{
|
||||
@@ -22,16 +21,28 @@ use time::{OffsetDateTime, Time};
|
||||
use tracing::{error, warn};
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn spending_routes() -> Router<AppState> {
|
||||
pub(crate) fn spending_routes(ecash_state: Arc<EcashState>) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/verify-ecash-ticket", axum::routing::post(verify_ticket))
|
||||
.route(
|
||||
"/verify-ecash-ticket",
|
||||
axum::routing::post({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|body| verify_ticket(body, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/batch-redeem-ecash-tickets",
|
||||
axum::routing::post(batch_redeem_tickets),
|
||||
axum::routing::post({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|body| batch_redeem_tickets(body, ecash_state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/double-spending-filter-v1",
|
||||
axum::routing::get(double_spending_filter_v1),
|
||||
axum::routing::get({
|
||||
let ecash_state = Arc::clone(&ecash_state);
|
||||
|| double_spending_filter_v1(ecash_state)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,9 +67,9 @@ fn reject_ticket(
|
||||
)
|
||||
)]
|
||||
async fn verify_ticket(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
// TODO in the future: make it send binary data rather than json
|
||||
Json(verify_ticket_body): Json<VerifyEcashTicketBody>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<EcashTicketVerificationResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -159,9 +170,9 @@ async fn verify_ticket(
|
||||
)
|
||||
)]
|
||||
async fn batch_redeem_tickets(
|
||||
State(state): State<Arc<EcashState>>,
|
||||
// TODO in the future: make it send binary data rather than json
|
||||
Json(batch_redeem_credentials_body): Json<BatchRedeemTicketsBody>,
|
||||
state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<EcashBatchTicketRedemptionResponse>> {
|
||||
state.ensure_signer().await?;
|
||||
|
||||
@@ -233,6 +244,8 @@ async fn batch_redeem_tickets(
|
||||
)
|
||||
)]
|
||||
#[deprecated]
|
||||
async fn double_spending_filter_v1() -> AxumResult<Json<SpentCredentialsResponse>> {
|
||||
async fn double_spending_filter_v1(
|
||||
_state: Arc<EcashState>,
|
||||
) -> AxumResult<Json<SpentCredentialsResponse>> {
|
||||
AxumResult::Err(AxumErrorResponse::internal_msg("permanently restricted"))
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ use nym_validator_client::EcashApiClient;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Client {
|
||||
async fn address(&self) -> Result<AccountId>;
|
||||
async fn address(&self) -> AccountId;
|
||||
|
||||
async fn dkg_contract_address(&self) -> Result<AccountId>;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ impl DkgClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_address(&self) -> Result<AccountId, EcashError> {
|
||||
pub(crate) async fn get_address(&self) -> AccountId {
|
||||
self.inner.address().await
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ impl DkgClient {
|
||||
|
||||
pub(crate) async fn group_member(&self) -> Result<MemberResponse, EcashError> {
|
||||
self.inner
|
||||
.group_member(self.get_address().await?.to_string())
|
||||
.group_member(self.get_address().await.to_string())
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ impl DkgClient {
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> Result<Option<ContractVKShare>, EcashError> {
|
||||
let address = self.inner.address().await?;
|
||||
let address = self.inner.address().await;
|
||||
self.get_verification_key_share(epoch_id, address).await
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ impl DkgClient {
|
||||
}
|
||||
|
||||
pub(crate) async fn get_vote(&self, proposal_id: u64) -> Result<VoteResponse, EcashError> {
|
||||
let address = self.get_address().await?.to_string();
|
||||
let address = self.get_address().await.to_string();
|
||||
self.inner.get_vote(proposal_id, address).await
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
resharing: bool,
|
||||
) -> Result<(), DealingGenerationError> {
|
||||
let dealing_state = self.state.dealing_exchange_state(epoch_id)?;
|
||||
let address = self.dkg_client.get_address().await?.to_string();
|
||||
let address = self.dkg_client.get_address().await.to_string();
|
||||
|
||||
let status = self
|
||||
.dkg_client
|
||||
@@ -259,7 +259,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
.checked_sub(1)
|
||||
.expect("resharing epoch invariant has been broken");
|
||||
|
||||
let address = self.dkg_client.get_address().await?;
|
||||
let address = self.dkg_client.get_address().await;
|
||||
Ok(self
|
||||
.dkg_client
|
||||
.dealer_in_epoch(previous_epoch_id, address.to_string())
|
||||
|
||||
@@ -494,7 +494,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
// submitted proposals and find the one with our address
|
||||
self.get_validation_proposals()
|
||||
.await?
|
||||
.get(self.dkg_client.get_address().await?.as_ref())
|
||||
.get(self.dkg_client.get_address().await.as_ref())
|
||||
.copied()
|
||||
.ok_or(KeyDerivationError::UnrecoverableProposalId)
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ impl<R: RngCore + CryptoRng> DkgController<R> {
|
||||
};
|
||||
|
||||
// if this is our share, obviously vote for yes without spending time on verification
|
||||
if owner.as_ref() == self.dkg_client.get_address().await?.as_ref() {
|
||||
if owner.as_ref() == self.dkg_client.get_address().await.as_ref() {
|
||||
votes.insert(*proposal_id, true);
|
||||
continue;
|
||||
}
|
||||
@@ -313,7 +313,7 @@ mod tests {
|
||||
exchange_dealings(&mut controllers, false).await;
|
||||
derive_keypairs(&mut controllers, false).await;
|
||||
|
||||
let first_dealer = controllers[0].dkg_client.get_address().await?;
|
||||
let first_dealer = controllers[0].dkg_client.get_address().await;
|
||||
|
||||
{
|
||||
let mut guard = chain.lock().unwrap();
|
||||
@@ -365,8 +365,8 @@ mod tests {
|
||||
exchange_dealings(&mut controllers, false).await;
|
||||
derive_keypairs(&mut controllers, false).await;
|
||||
|
||||
let first_dealer = controllers[0].dkg_client.get_address().await?;
|
||||
let second_dealer = controllers[1].dkg_client.get_address().await?;
|
||||
let first_dealer = controllers[0].dkg_client.get_address().await;
|
||||
let second_dealer = controllers[1].dkg_client.get_address().await;
|
||||
|
||||
{
|
||||
let mut guard = chain.lock().unwrap();
|
||||
|
||||
@@ -25,9 +25,6 @@ pub enum EcashError {
|
||||
#[error(transparent)]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error("this instance is running without on-chain signing capabilities so no transactions can be sent")]
|
||||
ChainSignerNotEnabled,
|
||||
|
||||
#[error("this operation couldn't be completed as this nym-api is not an active ecash signer")]
|
||||
NotASigner,
|
||||
|
||||
|
||||
@@ -139,9 +139,7 @@ impl EcashState {
|
||||
.local
|
||||
.active_signer
|
||||
.get_or_init(epoch_id, || async {
|
||||
let Ok(address) = self.aux.client.address().await else {
|
||||
return Ok(false);
|
||||
};
|
||||
let address = self.aux.client.address().await;
|
||||
let ecash_signers = self.aux.comm_channel.ecash_clients(epoch_id).await?;
|
||||
|
||||
// check if any ecash signers for this epoch has the same cosmos address as this api
|
||||
@@ -248,7 +246,7 @@ impl EcashState {
|
||||
let threshold = self.aux.comm_channel.ecash_threshold(epoch_id).await?;
|
||||
|
||||
// let mut shares = Mutex::new(Vec::with_capacity(all_apis.len()));
|
||||
let cosmos_address = self.aux.client.address().await.ok();
|
||||
let cosmos_address = self.aux.client.address().await;
|
||||
|
||||
let get_partial_signatures = |api: EcashApiClient| async {
|
||||
// move the api into the closure
|
||||
@@ -258,7 +256,7 @@ impl EcashState {
|
||||
|
||||
// check if we're attempting to query ourselves, in that case just get local signature
|
||||
// rather than making the http query
|
||||
let partial = if Some(api.cosmos_address) == cosmos_address {
|
||||
let partial = if api.cosmos_address == cosmos_address {
|
||||
self.partial_coin_index_signatures(Some(epoch_id))
|
||||
.await?
|
||||
.signatures
|
||||
@@ -382,7 +380,7 @@ impl EcashState {
|
||||
let all_apis = self.aux.comm_channel.ecash_clients(epoch_id).await?;
|
||||
let threshold = self.aux.comm_channel.ecash_threshold(epoch_id).await?;
|
||||
|
||||
let cosmos_address = self.aux.client.address().await.ok();
|
||||
let cosmos_address = self.aux.client.address().await;
|
||||
|
||||
let get_partial_signatures = |api: EcashApiClient| async {
|
||||
// move the api into the closure
|
||||
@@ -392,7 +390,7 @@ impl EcashState {
|
||||
|
||||
// check if we're attempting to query ourselves, in that case just get local signature
|
||||
// rather than making the http query
|
||||
let partial = if Some(api.cosmos_address) == cosmos_address {
|
||||
let partial = if api.cosmos_address == cosmos_address {
|
||||
self.partial_expiration_date_signatures(expiration_date)
|
||||
.await?
|
||||
.signatures
|
||||
|
||||
@@ -263,7 +263,7 @@ pub(crate) struct TestingDkgController {
|
||||
|
||||
impl TestingDkgController {
|
||||
pub async fn address(&self) -> AccountId {
|
||||
self.dkg_client.get_address().await.unwrap()
|
||||
self.dkg_client.get_address().await
|
||||
}
|
||||
|
||||
pub async fn cw_address(&self) -> Addr {
|
||||
|
||||
@@ -51,7 +51,7 @@ pub(crate) async fn initialise_dkg(controllers: &mut [TestingDkgController], res
|
||||
|
||||
// add every dealer to group contract
|
||||
for controller in controllers.iter() {
|
||||
let address = controller.dkg_client.get_address().await.unwrap();
|
||||
let address = controller.dkg_client.get_address().await;
|
||||
let mut chain = controllers[0].chain_state.lock().unwrap();
|
||||
chain.add_member(address.as_ref(), 10);
|
||||
}
|
||||
@@ -76,7 +76,7 @@ pub(crate) async fn submit_public_keys(controllers: &mut [TestingDkgController],
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let threshold = (2 * controllers.len() as u64).div_ceil(3);
|
||||
let threshold = (2 * controllers.len() as u64 + 3 - 1) / 3;
|
||||
|
||||
let mut guard = controllers[0].chain_state.lock().unwrap();
|
||||
guard.dkg_contract.epoch.state = EpochState::DealingExchange { resharing };
|
||||
|
||||
@@ -11,10 +11,9 @@ use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::handlers::unstable;
|
||||
use crate::node_status_api::NodeStatusCache;
|
||||
use crate::nym_contract_cache::cache::NymContractCache;
|
||||
use crate::status::ApiStatusState;
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::config;
|
||||
use crate::support::http::state::{AppState, ForcedRefresh};
|
||||
use crate::support::http::state::AppState;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
use async_trait::async_trait;
|
||||
use axum::Router;
|
||||
@@ -525,8 +524,8 @@ impl DummyClient {
|
||||
|
||||
#[async_trait]
|
||||
impl super::client::Client for DummyClient {
|
||||
async fn address(&self) -> Result<AccountId> {
|
||||
Ok(self.validator_address.clone())
|
||||
async fn address(&self) -> AccountId {
|
||||
self.validator_address.clone()
|
||||
}
|
||||
|
||||
async fn dkg_contract_address(&self) -> Result<AccountId> {
|
||||
@@ -1263,9 +1262,9 @@ struct TestFixture {
|
||||
}
|
||||
|
||||
impl TestFixture {
|
||||
fn build_app_state(storage: NymApiStorage, ecash_state: EcashState) -> AppState {
|
||||
fn build_app_state(storage: NymApiStorage) -> AppState {
|
||||
AppState {
|
||||
forced_refresh: ForcedRefresh::new(true),
|
||||
forced_refresh: Default::default(),
|
||||
nym_contract_cache: NymContractCache::new(),
|
||||
node_status_cache: NodeStatusCache::new(),
|
||||
circulating_supply_cache: CirculatingSupplyCache::new("unym".to_owned()),
|
||||
@@ -1276,8 +1275,6 @@ impl TestFixture {
|
||||
NymNetworkDetails::new_empty(),
|
||||
),
|
||||
node_info_cache: unstable::NodeInfoCache::default(),
|
||||
api_status: ApiStatusState::new(None),
|
||||
ecash_state: Arc::new(ecash_state),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1340,8 +1337,8 @@ impl TestFixture {
|
||||
TestFixture {
|
||||
axum: TestServer::new(
|
||||
Router::new()
|
||||
.nest("/v1/ecash", ecash_routes())
|
||||
.with_state(Self::build_app_state(storage.clone(), ecash_state)),
|
||||
.nest("/v1/ecash", ecash_routes(Arc::new(ecash_state)))
|
||||
.with_state(Self::build_app_state(storage.clone())),
|
||||
)
|
||||
.unwrap(),
|
||||
storage,
|
||||
|
||||
@@ -11,9 +11,6 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RewardingError {
|
||||
#[error("this instance is running without on-chain signing capabilities so no transactions can be sent")]
|
||||
ChainSignerNotEnabled,
|
||||
|
||||
#[error("Our account ({our_address}) is not permitted to update rewarded set and perform rewarding. The allowed address is {allowed_address}")]
|
||||
Unauthorised {
|
||||
our_address: AccountId,
|
||||
|
||||
@@ -134,12 +134,9 @@ impl EpochAdvancer {
|
||||
|
||||
let epoch_status = self.nyxd_client.get_current_epoch_status().await?;
|
||||
if !epoch_status.is_in_progress() {
|
||||
// SAFETY: before `EpochAdvancer` is started, `ensure_rewarding_permission` is called
|
||||
// which is not allowed to progress if this instance is not using a signing client
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let address = self.nyxd_client.client_address().await.unwrap();
|
||||
|
||||
if epoch_status.being_advanced_by.as_str() != address.as_ref() {
|
||||
if epoch_status.being_advanced_by.as_str()
|
||||
!= self.nyxd_client.client_address().await.as_ref()
|
||||
{
|
||||
// another nym-api is already handling
|
||||
error!("another nym-api ({}) is already advancing the epoch... but we shouldn't have other nym-apis yet!", epoch_status.being_advanced_by);
|
||||
return Ok(());
|
||||
@@ -321,10 +318,8 @@ impl EpochAdvancer {
|
||||
pub(crate) async fn ensure_rewarding_permission(
|
||||
nyxd_client: &Client,
|
||||
) -> Result<(), RewardingError> {
|
||||
let Some(our_address) = nyxd_client.client_address().await else {
|
||||
return Err(RewardingError::ChainSignerNotEnabled);
|
||||
};
|
||||
let allowed_address = nyxd_client.get_rewarding_validator_address().await?;
|
||||
let our_address = nyxd_client.client_address().await;
|
||||
if allowed_address != our_address {
|
||||
Err(RewardingError::Unauthorised {
|
||||
our_address,
|
||||
|
||||
@@ -59,7 +59,7 @@ impl GatewayClientHandle {
|
||||
}
|
||||
}
|
||||
|
||||
impl UnlockedGatewayClientHandle<'_> {
|
||||
impl<'a> UnlockedGatewayClientHandle<'a> {
|
||||
pub(crate) fn get_mut_unchecked(
|
||||
&mut self,
|
||||
) -> &mut GatewayClient<nyxd::Client, PersistentStorage> {
|
||||
|
||||
@@ -14,6 +14,7 @@ use nym_sphinx::params::PacketType;
|
||||
use nym_sphinx::receiver::MessageReceiver;
|
||||
use nym_task::TaskClient;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::process;
|
||||
use tokio::time::{sleep, Duration, Instant};
|
||||
use tracing::{debug, error, info, trace};
|
||||
|
||||
@@ -94,7 +95,10 @@ impl<R: MessageReceiver + Send> Monitor<R> {
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Failed to submit monitor run information to the database: {err}",);
|
||||
error!("Failed to submit monitor run information to the database - {err}",);
|
||||
|
||||
// TODO: slightly more graceful shutdown here
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,6 @@ impl PacketPreparer {
|
||||
test_route.topology().clone(),
|
||||
self_address,
|
||||
PacketSize::RegularPacket,
|
||||
false,
|
||||
DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
DEFAULT_AVERAGE_ACK_DELAY,
|
||||
self.ack_key.clone(),
|
||||
|
||||
@@ -57,9 +57,6 @@ pub enum NodeDescribeCacheError {
|
||||
// TODO: perhaps include more details here like whether key/signature/payload was malformed
|
||||
#[error("could not verify signed host information for node {node_id}")]
|
||||
MissignedHostInformation { node_id: NodeId },
|
||||
|
||||
#[error("node {node_id} is announcing an illegal ip address")]
|
||||
IllegalIpAddress { node_id: NodeId },
|
||||
}
|
||||
|
||||
// this exists because I've been moving things around quite a lot and now the place that holds the type
|
||||
@@ -202,18 +199,13 @@ impl DescribedNodes {
|
||||
pub struct NodeDescriptionProvider {
|
||||
contract_cache: NymContractCache,
|
||||
|
||||
allow_all_ips: bool,
|
||||
batch_size: usize,
|
||||
}
|
||||
|
||||
impl NodeDescriptionProvider {
|
||||
pub(crate) fn new(
|
||||
contract_cache: NymContractCache,
|
||||
allow_all_ips: bool,
|
||||
) -> NodeDescriptionProvider {
|
||||
pub(crate) fn new(contract_cache: NymContractCache) -> NodeDescriptionProvider {
|
||||
NodeDescriptionProvider {
|
||||
contract_cache,
|
||||
allow_all_ips,
|
||||
batch_size: DEFAULT_NODE_DESCRIBE_BATCH_SIZE,
|
||||
}
|
||||
}
|
||||
@@ -278,7 +270,6 @@ async fn try_get_client(
|
||||
|
||||
async fn try_get_description(
|
||||
data: RefreshData,
|
||||
allow_all_ips: bool,
|
||||
) -> Result<NymNodeDescription, NodeDescribeCacheError> {
|
||||
let client = try_get_client(&data.host, data.node_id, data.port).await?;
|
||||
|
||||
@@ -295,12 +286,6 @@ async fn try_get_description(
|
||||
});
|
||||
}
|
||||
|
||||
if !allow_all_ips && !host_info.data.check_ips() {
|
||||
return Err(NodeDescribeCacheError::IllegalIpAddress {
|
||||
node_id: data.node_id,
|
||||
});
|
||||
}
|
||||
|
||||
let node_info = query_for_described_data(&client, data.node_id).await?;
|
||||
let description = node_info.into_node_description(host_info.data);
|
||||
|
||||
@@ -372,8 +357,8 @@ impl RefreshData {
|
||||
self.node_id
|
||||
}
|
||||
|
||||
pub(crate) async fn try_refresh(self, allow_all_ips: bool) -> Option<NymNodeDescription> {
|
||||
match try_get_description(self, allow_all_ips).await {
|
||||
pub(crate) async fn try_refresh(self) -> Option<NymNodeDescription> {
|
||||
match try_get_description(self).await {
|
||||
Ok(description) => Some(description),
|
||||
Err(err) => {
|
||||
debug!("failed to obtain node self-described data: {err}");
|
||||
@@ -427,15 +412,11 @@ impl CacheItemProvider for NodeDescriptionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
let nodes = stream::iter(
|
||||
nodes_to_query
|
||||
.into_iter()
|
||||
.map(|n| n.try_refresh(self.allow_all_ips)),
|
||||
)
|
||||
.buffer_unordered(self.batch_size)
|
||||
.filter_map(|x| async move { x.map(|d| (d.node_id, d)) })
|
||||
.collect::<HashMap<_, _>>()
|
||||
.await;
|
||||
let nodes = stream::iter(nodes_to_query.into_iter().map(|n| n.try_refresh()))
|
||||
.buffer_unordered(self.batch_size)
|
||||
.filter_map(|x| async move { x.map(|d| (d.node_id, d)) })
|
||||
.collect::<HashMap<_, _>>()
|
||||
.await;
|
||||
|
||||
info!("refreshed self described data for {} nodes", nodes.len());
|
||||
|
||||
@@ -451,11 +432,8 @@ pub(crate) fn new_refresher(
|
||||
) -> CacheRefresher<DescribedNodes, NodeDescribeCacheError> {
|
||||
CacheRefresher::new(
|
||||
Box::new(
|
||||
NodeDescriptionProvider::new(
|
||||
contract_cache,
|
||||
config.debug.node_describe_allow_illegal_ips,
|
||||
)
|
||||
.with_batch_size(config.debug.node_describe_batch_size),
|
||||
NodeDescriptionProvider::new(contract_cache)
|
||||
.with_batch_size(config.debug.node_describe_batch_size),
|
||||
),
|
||||
config.debug.node_describe_caching_interval,
|
||||
)
|
||||
@@ -468,11 +446,8 @@ pub(crate) fn new_refresher_with_initial_value(
|
||||
) -> CacheRefresher<DescribedNodes, NodeDescribeCacheError> {
|
||||
CacheRefresher::new_with_initial_value(
|
||||
Box::new(
|
||||
NodeDescriptionProvider::new(
|
||||
contract_cache,
|
||||
config.debug.node_describe_allow_illegal_ips,
|
||||
)
|
||||
.with_batch_size(config.debug.node_describe_batch_size),
|
||||
NodeDescriptionProvider::new(contract_cache)
|
||||
.with_batch_size(config.debug.node_describe_batch_size),
|
||||
),
|
||||
config.debug.node_describe_caching_interval,
|
||||
initial,
|
||||
|
||||
@@ -88,6 +88,7 @@ pub(crate) async fn submit_gateway_monitoring_results(
|
||||
|
||||
match state
|
||||
.storage
|
||||
.manager
|
||||
.submit_gateway_statuses_v2(message.results())
|
||||
.await
|
||||
{
|
||||
@@ -132,6 +133,7 @@ pub(crate) async fn submit_node_monitoring_results(
|
||||
|
||||
match state
|
||||
.storage
|
||||
.manager
|
||||
.submit_mixnode_statuses_v2(message.results())
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -86,9 +86,8 @@ async fn refresh_described(
|
||||
}
|
||||
// to make sure you can't ddos the endpoint while a request is in progress
|
||||
state.forced_refresh.set_last_refreshed(node_id).await;
|
||||
let allow_all_ips = state.forced_refresh.allow_all_ip_addresses;
|
||||
|
||||
if let Some(updated_data) = refresh_data.try_refresh(allow_all_ips).await {
|
||||
if let Some(updated_data) = refresh_data.try_refresh().await {
|
||||
let Ok(mut describe_cache) = state.described_nodes_cache.write().await else {
|
||||
return Err(AxumErrorResponse::service_unavailable());
|
||||
};
|
||||
|
||||
@@ -4,20 +4,37 @@
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::status::ApiStatusState;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::Json;
|
||||
use axum::Router;
|
||||
use nym_api_requests::models::{ApiHealthResponse, SignerInformationResponse};
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
use nym_compact_ecash::Base58;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) fn api_status_routes() -> Router<AppState> {
|
||||
let api_status_state = Arc::new(ApiStatusState::new());
|
||||
|
||||
Router::new()
|
||||
.route("/health", axum::routing::get(health))
|
||||
.route("/build-information", axum::routing::get(build_information))
|
||||
.route(
|
||||
"/health",
|
||||
axum::routing::get({
|
||||
let state = Arc::clone(&api_status_state);
|
||||
|| health(state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/build-information",
|
||||
axum::routing::get({
|
||||
let state = Arc::clone(&api_status_state);
|
||||
|| build_information(state)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/signer-information",
|
||||
axum::routing::get(signer_information),
|
||||
axum::routing::get({
|
||||
let state = Arc::clone(&api_status_state);
|
||||
|| signer_information(state)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,7 +46,7 @@ pub(crate) fn api_status_routes() -> Router<AppState> {
|
||||
(status = 200, body = ApiHealthResponse)
|
||||
)
|
||||
)]
|
||||
async fn health(State(state): State<ApiStatusState>) -> Json<ApiHealthResponse> {
|
||||
async fn health(state: Arc<ApiStatusState>) -> Json<ApiHealthResponse> {
|
||||
let uptime = state.startup_time.elapsed();
|
||||
let health = ApiHealthResponse::new_healthy(uptime);
|
||||
Json(health)
|
||||
@@ -43,9 +60,7 @@ async fn health(State(state): State<ApiStatusState>) -> Json<ApiHealthResponse>
|
||||
(status = 200, body = BinaryBuildInformationOwned)
|
||||
)
|
||||
)]
|
||||
async fn build_information(
|
||||
State(state): State<ApiStatusState>,
|
||||
) -> Json<BinaryBuildInformationOwned> {
|
||||
async fn build_information(state: Arc<ApiStatusState>) -> Json<BinaryBuildInformationOwned> {
|
||||
Json(state.build_information.to_owned())
|
||||
}
|
||||
|
||||
@@ -58,7 +73,7 @@ async fn build_information(
|
||||
)
|
||||
)]
|
||||
async fn signer_information(
|
||||
State(state): State<ApiStatusState>,
|
||||
state: Arc<ApiStatusState>,
|
||||
) -> AxumResult<Json<SignerInformationResponse>> {
|
||||
let signer_state = state.signer_information.as_ref().ok_or_else(|| {
|
||||
AxumErrorResponse::internal_msg("this api does not expose zk-nym signing functionalities")
|
||||
|
||||
@@ -4,25 +4,12 @@
|
||||
use crate::ecash;
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_bin_common::build_information::BinaryBuildInformation;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::time::Instant;
|
||||
|
||||
pub(crate) mod handlers;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ApiStatusState {
|
||||
inner: Arc<ApiStatusStateInner>,
|
||||
}
|
||||
|
||||
impl Deref for ApiStatusState {
|
||||
type Target = ApiStatusStateInner;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ApiStatusStateInner {
|
||||
startup_time: Instant,
|
||||
build_information: BinaryBuildInformation,
|
||||
signer_information: Option<SignerState>,
|
||||
@@ -40,13 +27,15 @@ pub(crate) struct SignerState {
|
||||
}
|
||||
|
||||
impl ApiStatusState {
|
||||
pub fn new(signer_information: Option<SignerState>) -> Self {
|
||||
pub fn new() -> Self {
|
||||
ApiStatusState {
|
||||
inner: Arc::new(ApiStatusStateInner {
|
||||
startup_time: Instant::now(),
|
||||
build_information: bin_info!(),
|
||||
signer_information,
|
||||
}),
|
||||
startup_time: Instant::now(),
|
||||
build_information: bin_info!(),
|
||||
signer_information: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_zk_nym_signer(&mut self, signer_information: SignerState) {
|
||||
self.signer_information = Some(signer_information)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
|
||||
use crate::ecash::api_routes::handlers::ecash_routes;
|
||||
use crate::ecash::client::Client;
|
||||
use crate::ecash::comm::QueryCommunicationChannel;
|
||||
use crate::ecash::dkg::controller::keys::{
|
||||
@@ -20,9 +21,7 @@ use crate::status::{ApiStatusState, SignerState};
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::config::helpers::try_load_current_config;
|
||||
use crate::support::config::Config;
|
||||
use crate::support::http::state::{
|
||||
AppState, ForcedRefresh, ShutdownHandles, TASK_MANAGER_TIMEOUT_S,
|
||||
};
|
||||
use crate::support::http::state::{AppState, ShutdownHandles, TASK_MANAGER_TIMEOUT_S};
|
||||
use crate::support::http::RouterBuilder;
|
||||
use crate::support::nyxd;
|
||||
use crate::support::storage::runtime_migrations::m001_directory_services_v2_1::migrate_to_directory_services_v2_1;
|
||||
@@ -137,6 +136,8 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
|
||||
let described_nodes_cache = SharedCache::<DescribedNodes>::new();
|
||||
let node_info_cache = unstable::NodeInfoCache::default();
|
||||
|
||||
let mut status_state = ApiStatusState::new();
|
||||
|
||||
let ecash_contract = nyxd_client
|
||||
.get_ecash_contract_address()
|
||||
.await
|
||||
@@ -158,8 +159,8 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
|
||||
|
||||
// if ecash signer is enabled, there are additional constraints on the nym-api,
|
||||
// such as having sufficient token balance
|
||||
let signer_information = if config.ecash_signer.enabled {
|
||||
let cosmos_address = nyxd_client.address().await?;
|
||||
let router = if config.ecash_signer.enabled {
|
||||
let cosmos_address = nyxd_client.address().await;
|
||||
|
||||
// make sure we have some tokens to cover multisig fees
|
||||
let balance = nyxd_client.balance(&mix_denom).await?;
|
||||
@@ -174,20 +175,20 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
|
||||
.clone()
|
||||
.map(|u| u.to_string())
|
||||
.unwrap_or_default();
|
||||
Some(SignerState {
|
||||
status_state.add_zk_nym_signer(SignerState {
|
||||
cosmos_address: cosmos_address.to_string(),
|
||||
identity: encoded_identity,
|
||||
announce_address,
|
||||
ecash_keypair: ecash_keypair_wrapper.clone(),
|
||||
})
|
||||
});
|
||||
|
||||
router.nest("/v1/ecash", ecash_routes(Arc::new(ecash_state)))
|
||||
} else {
|
||||
None
|
||||
router
|
||||
};
|
||||
|
||||
let router = router.with_state(AppState {
|
||||
forced_refresh: ForcedRefresh::new(
|
||||
config.topology_cacher.debug.node_describe_allow_illegal_ips,
|
||||
),
|
||||
forced_refresh: Default::default(),
|
||||
nym_contract_cache: nym_contract_cache_state.clone(),
|
||||
node_status_cache: node_status_cache_state.clone(),
|
||||
circulating_supply_cache: circulating_supply_cache.clone(),
|
||||
@@ -195,8 +196,6 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
|
||||
described_nodes_cache: described_nodes_cache.clone(),
|
||||
network_details,
|
||||
node_info_cache,
|
||||
api_status: ApiStatusState::new(signer_information),
|
||||
ecash_state: Arc::new(ecash_state),
|
||||
});
|
||||
|
||||
let task_manager = TaskManager::new(TASK_MANAGER_TIMEOUT_S);
|
||||
|
||||
@@ -439,8 +439,6 @@ pub struct TopologyCacherDebug {
|
||||
pub node_describe_caching_interval: Duration,
|
||||
|
||||
pub node_describe_batch_size: usize,
|
||||
|
||||
pub node_describe_allow_illegal_ips: bool,
|
||||
}
|
||||
|
||||
impl Default for TopologyCacherDebug {
|
||||
@@ -449,7 +447,6 @@ impl Default for TopologyCacherDebug {
|
||||
caching_interval: DEFAULT_TOPOLOGY_CACHE_INTERVAL,
|
||||
node_describe_caching_interval: DEFAULT_NODE_DESCRIBE_CACHE_INTERVAL,
|
||||
node_describe_batch_size: DEFAULT_NODE_DESCRIBE_BATCH_SIZE,
|
||||
node_describe_allow_illegal_ips: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user