Compare commits

..

11 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacşu 2659293383 Extend raw ws fd for gateway client 2024-12-05 11:34:34 +00:00
benedetta davico 0041937ed3 Merge pull request #5170 from nymtech/merge/release/2024.13-magura-patched
merge patched magura into develop
2024-11-25 09:06:32 +01:00
Jędrzej Stuczyński 0e5bd966dd Merge branch 'release/2024.13-magura-patched' into develop 2024-11-22 15:53:59 +00:00
Jędrzej Stuczyński 6acd936368 bugfix: additional improvements to nym-api db performance (#5168)
* added statement logging to nym-api db

* adding additional pool options

* dont blow up upon failing to submit network monitor results

* store in-memory cache of node database ids
2024-11-22 15:52:26 +00:00
benedettadavico 01c7b2819e update changelog 2024-11-22 10:50:59 +01:00
Simon Wicky 042a8a58aa start session collection for exit gateways (#5148) (#5161) 2024-11-22 09:12:01 +01:00
benedettadavico d8ab2a8f15 update version for clients and nym-node 2024-11-22 07:25:54 +01:00
Jędrzej Stuczyński d6d2239685 implement optional cap for number of ack retransmissions (#5158)
* implement optional cap for number of ack retransmissions

* Wasm
2024-11-21 18:57:24 +00:00
Jędrzej Stuczyński 3d704fbbf1 change: make nym-api optionally ignore nodes with illegal ip addresses, like loopback (#5159) 2024-11-21 18:54:14 +00:00
Jędrzej Stuczyński 119c36b0bb added 'deterministic_route_selection' for sphinx packet route selection (#5157)
* added 'deterministic_route_selection' for sphinx packet route selection

* clippy + wasm

* Switch to ChaCha8Rng for deterministic route generation

---------

Co-authored-by: durch <durch@users.noreply.github.com>
2024-11-21 18:21:01 +00:00
Jędrzej Stuczyński 6b5b97199b fix: tracing logger to output to stderr 2024-11-19 17:47:13 +00:00
50 changed files with 426 additions and 1919 deletions
+6
View File
@@ -4,6 +4,12 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [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
+30 -291
View File
@@ -26,9 +26,9 @@ dependencies = [
[[package]]
name = "addr2line"
version = "0.21.0"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
dependencies = [
"gimli",
]
@@ -607,9 +607,9 @@ dependencies = [
[[package]]
name = "backtrace"
version = "0.3.71"
version = "0.3.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
dependencies = [
"addr2line",
"cc",
@@ -665,16 +665,6 @@ 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"
@@ -953,12 +943,6 @@ 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"
@@ -971,15 +955,6 @@ 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"
@@ -1183,33 +1158,6 @@ 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"
@@ -1245,22 +1193,7 @@ dependencies = [
"crossterm 0.27.0",
"strum 0.26.3",
"strum_macros 0.26.4",
"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",
"unicode-width",
]
[[package]]
@@ -1281,7 +1214,7 @@ dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"unicode-width 0.1.13",
"unicode-width",
"windows-sys 0.52.0",
]
@@ -1701,23 +1634,6 @@ 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"
@@ -1999,12 +1915,12 @@ dependencies = [
[[package]]
name = "darling"
version = "0.20.10"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
dependencies = [
"darling_core 0.20.10",
"darling_macro 0.20.10",
"darling_core 0.20.9",
"darling_macro 0.20.9",
]
[[package]]
@@ -2023,9 +1939,9 @@ dependencies = [
[[package]]
name = "darling_core"
version = "0.20.10"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120"
dependencies = [
"fnv",
"ident_case",
@@ -2048,11 +1964,11 @@ dependencies = [
[[package]]
name = "darling_macro"
version = "0.20.10"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
dependencies = [
"darling_core 0.20.10",
"darling_core 0.20.9",
"quote",
"syn 2.0.87",
]
@@ -2718,12 +2634,6 @@ 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"
@@ -2888,15 +2798,6 @@ 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"
@@ -2974,9 +2875,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.28.1"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "glob"
@@ -3127,17 +3028,6 @@ 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"
@@ -3622,15 +3512,9 @@ dependencies = [
"instant",
"number_prefix",
"portable-atomic",
"unicode-width 0.1.13",
"unicode-width",
]
[[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"
@@ -3680,21 +3564,7 @@ dependencies = [
"newline-converter",
"thiserror",
"unicode-segmentation",
"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",
"unicode-width",
]
[[package]]
@@ -3772,7 +3642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9"
dependencies = [
"async-channel",
"castaway 0.1.2",
"castaway",
"crossbeam-utils",
"curl",
"curl-sys",
@@ -4044,15 +3914,6 @@ 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"
@@ -4238,7 +4099,6 @@ checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
dependencies = [
"hermit-abi 0.3.9",
"libc",
"log",
"wasi",
"windows-sys 0.52.0",
]
@@ -4610,6 +4470,7 @@ dependencies = [
"cw2",
"cw3",
"cw4",
"dashmap",
"dirs",
"futures",
"getset",
@@ -4921,7 +4782,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.43"
version = "1.1.44"
dependencies = [
"bs58",
"clap 4.5.20",
@@ -6038,7 +5899,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.44"
version = "1.1.45"
dependencies = [
"addr",
"anyhow",
@@ -6089,7 +5950,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.1.10"
version = "1.1.11"
dependencies = [
"anyhow",
"bip39",
@@ -6445,7 +6306,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.43"
version = "1.1.44"
dependencies = [
"bs58",
"clap 4.5.20",
@@ -6814,27 +6675,6 @@ 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"
@@ -7122,9 +6962,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.32.2"
version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434"
dependencies = [
"memchr",
]
@@ -7306,12 +7146,6 @@ 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"
@@ -7971,28 +7805,6 @@ 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"
@@ -8987,7 +8799,7 @@ version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350"
dependencies = [
"darling 0.20.10",
"darling 0.20.9",
"proc-macro2",
"quote",
"syn 2.0.87",
@@ -9100,13 +8912,12 @@ dependencies = [
[[package]]
name = "signal-hook-mio"
version = "0.2.4"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio 0.8.11",
"mio 1.0.1",
"signal-hook",
]
@@ -9537,15 +9348,6 @@ 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"
@@ -10373,16 +10175,6 @@ 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"
@@ -10564,22 +10356,6 @@ 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"
@@ -10705,29 +10481,12 @@ 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"
@@ -11059,26 +10818,6 @@ 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"
+1 -10
View File
@@ -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", "common/nym-tui-common",
"tools/internal/testnet-manager/dkg-bypass-contract",
]
default-members = [
@@ -412,15 +412,6 @@ 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 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.43"
version = "1.1.44"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.43"
version = "1.1.44"
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"
+10 -1
View File
@@ -393,13 +393,20 @@ 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 it unless you understand the consequences of that change.
/// Do not set it unless you understand the consequences of that change.
pub secondary_packet_size: Option<PacketSize>,
pub packet_type: PacketType,
@@ -424,6 +431,8 @@ 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,6 +111,7 @@ 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
@@ -30,7 +30,8 @@ pub(crate) enum Action {
InsertPending(Vec<PendingAcknowledgement>),
/// Removes given `PendingAcknowledgement` from the 'shared' state. Also cancels the retransmission timer.
/// Initiated by `AcknowledgementListener`
/// Initiated by `AcknowledgementListener` upon receiving the acknowledgement. Also by `RetransmissionRequestListener`
/// upon deciding to abandon the data.
RemovePending(FragmentIdentifier),
/// Starts the retransmission timer on given `PendingAcknowledgement` with the `Duration` based on
@@ -41,7 +42,7 @@ pub(crate) enum Action {
/// Updates the expected delay of given `PendingAcknowledgement` with the new provided `SphinxDelay`.
/// Initiated by `RetransmissionRequestListener`
UpdateDelay(FragmentIdentifier, SphinxDelay),
UpdatePendingAck(FragmentIdentifier, SphinxDelay),
}
impl Action {
@@ -57,8 +58,8 @@ impl Action {
Action::StartTimer(frag_id)
}
pub(crate) fn new_update_delay(frag_id: FragmentIdentifier, delay: SphinxDelay) -> Self {
Action::UpdateDelay(frag_id, delay)
pub(crate) fn new_update_pending_ack(frag_id: FragmentIdentifier, delay: SphinxDelay) -> Self {
Action::UpdatePendingAck(frag_id, delay)
}
}
@@ -135,7 +136,7 @@ impl ActionController {
}
fn handle_start_timer(&mut self, frag_id: FragmentIdentifier) {
trace!("{} is starting its timer", frag_id);
trace!("{frag_id} is starting its timer");
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
@@ -193,7 +194,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_delay(&mut self, frag_id: FragmentIdentifier, delay: SphinxDelay) {
fn handle_update_pending_ack(&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) {
@@ -202,7 +203,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_delay(delay);
inner_data.update_retransmitted(delay);
self.pending_acks_data
.insert(frag_id, (Arc::new(inner_data), queue_key));
@@ -225,7 +226,7 @@ impl ActionController {
// about it. Perhaps just reschedule it at later point?
let frag_id = expired_ack.into_inner();
trace!("{} has expired", frag_id);
trace!("{frag_id} has expired");
if let Some((pending_ack_data, queue_key)) = self.pending_acks_data.get_mut(&frag_id) {
if queue_key.is_none() {
@@ -258,7 +259,9 @@ 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::UpdateDelay(frag_id, delay) => self.handle_update_delay(frag_id, delay),
Action::UpdatePendingAck(frag_id, delay) => {
self.handle_update_pending_ack(frag_id, delay)
}
}
}
@@ -71,6 +71,7 @@ pub(crate) struct PendingAcknowledgement {
delay: SphinxDelay,
destination: PacketDestination,
mix_hops: Option<u8>,
retransmissions: u32,
}
impl PendingAcknowledgement {
@@ -86,6 +87,7 @@ impl PendingAcknowledgement {
delay,
destination: PacketDestination::KnownRecipient(recipient.into()),
mix_hops,
retransmissions: 0,
}
}
@@ -105,6 +107,7 @@ 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,
}
}
@@ -116,8 +119,9 @@ impl PendingAcknowledgement {
self.message_chunk.clone()
}
fn update_delay(&mut self, new_delay: SphinxDelay) {
fn update_retransmitted(&mut self, new_delay: SphinxDelay) {
self.delay = new_delay;
self.retransmissions += 1;
}
}
@@ -163,6 +167,9 @@ 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,
@@ -174,8 +181,13 @@ pub(super) struct Config {
}
impl Config {
pub(super) fn new(ack_wait_addition: Duration, ack_wait_multiplier: f64) -> Self {
pub(super) fn new(
maximum_retransmissions: Option<u32>,
ack_wait_addition: Duration,
ack_wait_multiplier: f64,
) -> Self {
Config {
maximum_retransmissions,
ack_wait_addition,
ack_wait_multiplier,
packet_size: Default::default(),
@@ -238,6 +250,7 @@ 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,
@@ -20,6 +20,7 @@ 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,
@@ -31,12 +32,14 @@ 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,
@@ -77,6 +80,18 @@ 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,
@@ -101,8 +116,6 @@ 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) => {
@@ -136,7 +149,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_delay(frag_id, new_delay))
.unbounded_send(Action::new_update_pending_ack(frag_id, new_delay))
.unwrap();
// send to `OutQueueControl` to eventually send to the mix network
@@ -91,6 +91,9 @@ 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,
@@ -114,10 +117,12 @@ 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,
@@ -176,6 +181,7 @@ where
{
let message_preparer = MessagePreparer::new(
rng,
config.deterministic_route_selection,
config.sender_address,
config.average_packet_delay,
config.average_ack_delay,
@@ -634,7 +640,7 @@ where
pub(crate) fn update_ack_delay(&self, id: FragmentIdentifier, new_delay: Delay) {
self.action_sender
.unbounded_send(Action::UpdateDelay(id, new_delay))
.unbounded_send(Action::UpdatePendingAck(id, new_delay))
.expect("action control task has died")
}
@@ -65,6 +65,7 @@ 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,
)
@@ -97,6 +98,7 @@ 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)
@@ -46,7 +46,8 @@ pub(crate) fn ws_fd(_conn: &WsConn) -> Option<RawFd> {
#[cfg(unix)]
match _conn.get_ref() {
MaybeTlsStream::Plain(stream) => Some(stream.as_raw_fd()),
&_ => None,
MaybeTlsStream::Rustls(tls_stream) => Some(tls_stream.as_raw_fd()),
_ => None,
}
#[cfg(not(unix))]
None
+14 -4
View File
@@ -29,6 +29,9 @@ 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,
@@ -48,11 +51,13 @@ 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>,
@@ -62,6 +67,7 @@ where
base_topology,
self_address,
packet_size,
deterministic_route_selection,
average_packet_delay,
average_ack_delay,
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
@@ -289,10 +295,18 @@ 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
}
@@ -304,8 +318,4 @@ impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
fn average_ack_delay(&self) -> Duration {
self.average_ack_delay
}
fn nonce(&self) -> i32 {
1
}
}
-45
View File
@@ -1,45 +0,0 @@
[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"]
@@ -1,175 +0,0 @@
// 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
}
-24
View File
@@ -1,24 +0,0 @@
// 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 },
}
-46
View File
@@ -1,46 +0,0 @@
// 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(())
}
-13
View File
@@ -1,13 +0,0 @@
// 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 {}
@@ -1,271 +0,0 @@
// 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()
}
@@ -1,69 +0,0 @@
// 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,
}
}
}
@@ -1,139 +0,0 @@
// 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(())
}
}
@@ -1,6 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub trait State: Clone {
//
}
-80
View File
@@ -1,80 +0,0 @@
// 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")
}
}
-158
View File
@@ -1,158 +0,0 @@
// 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");
},
}
}
-46
View File
@@ -1,46 +0,0 @@
// 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(())
}
@@ -1,70 +0,0 @@
// 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,
);
}
}
@@ -1,81 +0,0 @@
// 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);
}
}
@@ -1,12 +0,0 @@
// 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};
@@ -1,45 +0,0 @@
// 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);
}
-154
View File
@@ -1,154 +0,0 @@
// 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(())
}
}
+28 -10
View File
@@ -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::ChaCha20Rng;
use rand_chacha::ChaCha8Rng;
use nym_sphinx_chunking::monitoring;
use std::time::Duration;
@@ -51,6 +51,7 @@ 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;
@@ -201,9 +202,7 @@ pub trait FragmentPreparer {
// could perform diffie-hellman with its own keys followed by a kdf to re-derive
// the packet encryption key
let seed = fragment.seed().wrapping_mul(self.nonce());
let mut rng = ChaCha20Rng::seed_from_u64(seed as u64);
let fragment_header = fragment.header();
let destination = packet_recipient.gateway();
let hops = mix_hops.unwrap_or(self.num_mix_hops());
monitoring::fragment_sent(&fragment, self.nonce(), *destination, hops);
@@ -241,8 +240,18 @@ pub trait FragmentPreparer {
};
// generate pseudorandom route for the packet
log::trace!("Preparing chunk for sending with {} mix hops", hops);
let route = topology.random_route_to_gateway(&mut rng, hops, destination)?;
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)?
};
let destination = packet_recipient.as_sphinx_destination();
// including set of delays
@@ -313,6 +322,9 @@ 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,
@@ -336,6 +348,7 @@ where
{
pub fn new(
rng: R,
deterministic_route_selection: bool,
sender_address: Recipient,
average_packet_delay: Duration,
average_ack_delay: Duration,
@@ -344,6 +357,7 @@ where
let nonce = rng.gen();
MessagePreparer {
rng,
deterministic_route_selection,
sender_address,
average_packet_delay,
average_ack_delay,
@@ -457,10 +471,18 @@ 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
}
@@ -472,10 +494,6 @@ impl<R: CryptoRng + Rng> FragmentPreparer for MessagePreparer<R> {
fn average_ack_delay(&self) -> Duration {
self.average_ack_delay
}
fn nonce(&self) -> i32 {
self.nonce
}
}
/*
+11
View File
@@ -162,6 +162,13 @@ 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,
@@ -196,6 +203,8 @@ 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,
@@ -211,6 +220,8 @@ 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,6 +88,14 @@ 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)]
@@ -113,6 +121,10 @@ 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),
+1
View File
@@ -20,6 +20,7 @@ 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 }
+2 -2
View File
@@ -13,7 +13,7 @@ use crate::node_status_api::NodeStatusCache;
use crate::nym_contract_cache::cache::NymContractCache;
use crate::support::caching::cache::SharedCache;
use crate::support::config;
use crate::support::http::state::AppState;
use crate::support::http::state::{AppState, ForcedRefresh};
use crate::support::storage::NymApiStorage;
use async_trait::async_trait;
use axum::Router;
@@ -1264,7 +1264,7 @@ struct TestFixture {
impl TestFixture {
fn build_app_state(storage: NymApiStorage) -> AppState {
AppState {
forced_refresh: Default::default(),
forced_refresh: ForcedRefresh::new(true),
nym_contract_cache: NymContractCache::new(),
node_status_cache: NodeStatusCache::new(),
circulating_supply_cache: CirculatingSupplyCache::new("unym".to_owned()),
+1 -5
View File
@@ -14,7 +14,6 @@ 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};
@@ -95,10 +94,7 @@ impl<R: MessageReceiver + Send> Monitor<R> {
)
.await
{
error!("Failed to submit monitor run information to the database - {err}",);
// TODO: slightly more graceful shutdown here
process::exit(1);
error!("Failed to submit monitor run information to the database: {err}",);
}
}
@@ -121,6 +121,7 @@ impl PacketPreparer {
test_route.topology().clone(),
self_address,
PacketSize::RegularPacket,
false,
DEFAULT_AVERAGE_PACKET_DELAY,
DEFAULT_AVERAGE_ACK_DELAY,
self.ack_key.clone(),
+37 -12
View File
@@ -57,6 +57,9 @@ 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
@@ -199,13 +202,18 @@ impl DescribedNodes {
pub struct NodeDescriptionProvider {
contract_cache: NymContractCache,
allow_all_ips: bool,
batch_size: usize,
}
impl NodeDescriptionProvider {
pub(crate) fn new(contract_cache: NymContractCache) -> NodeDescriptionProvider {
pub(crate) fn new(
contract_cache: NymContractCache,
allow_all_ips: bool,
) -> NodeDescriptionProvider {
NodeDescriptionProvider {
contract_cache,
allow_all_ips,
batch_size: DEFAULT_NODE_DESCRIBE_BATCH_SIZE,
}
}
@@ -270,6 +278,7 @@ 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?;
@@ -286,6 +295,12 @@ 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);
@@ -357,8 +372,8 @@ impl RefreshData {
self.node_id
}
pub(crate) async fn try_refresh(self) -> Option<NymNodeDescription> {
match try_get_description(self).await {
pub(crate) async fn try_refresh(self, allow_all_ips: bool) -> Option<NymNodeDescription> {
match try_get_description(self, allow_all_ips).await {
Ok(description) => Some(description),
Err(err) => {
debug!("failed to obtain node self-described data: {err}");
@@ -412,11 +427,15 @@ impl CacheItemProvider for NodeDescriptionProvider {
}
}
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;
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;
info!("refreshed self described data for {} nodes", nodes.len());
@@ -432,8 +451,11 @@ pub(crate) fn new_refresher(
) -> CacheRefresher<DescribedNodes, NodeDescribeCacheError> {
CacheRefresher::new(
Box::new(
NodeDescriptionProvider::new(contract_cache)
.with_batch_size(config.debug.node_describe_batch_size),
NodeDescriptionProvider::new(
contract_cache,
config.debug.node_describe_allow_illegal_ips,
)
.with_batch_size(config.debug.node_describe_batch_size),
),
config.debug.node_describe_caching_interval,
)
@@ -446,8 +468,11 @@ pub(crate) fn new_refresher_with_initial_value(
) -> CacheRefresher<DescribedNodes, NodeDescribeCacheError> {
CacheRefresher::new_with_initial_value(
Box::new(
NodeDescriptionProvider::new(contract_cache)
.with_batch_size(config.debug.node_describe_batch_size),
NodeDescriptionProvider::new(
contract_cache,
config.debug.node_describe_allow_illegal_ips,
)
.with_batch_size(config.debug.node_describe_batch_size),
),
config.debug.node_describe_caching_interval,
initial,
@@ -88,7 +88,6 @@ pub(crate) async fn submit_gateway_monitoring_results(
match state
.storage
.manager
.submit_gateway_statuses_v2(message.results())
.await
{
@@ -133,7 +132,6 @@ pub(crate) async fn submit_node_monitoring_results(
match state
.storage
.manager
.submit_mixnode_statuses_v2(message.results())
.await
{
+2 -1
View File
@@ -86,8 +86,9 @@ 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().await {
if let Some(updated_data) = refresh_data.try_refresh(allow_all_ips).await {
let Ok(mut describe_cache) = state.described_nodes_cache.write().await else {
return Err(AxumErrorResponse::service_unavailable());
};
+6 -2
View File
@@ -21,7 +21,9 @@ 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, ShutdownHandles, TASK_MANAGER_TIMEOUT_S};
use crate::support::http::state::{
AppState, ForcedRefresh, 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;
@@ -188,7 +190,9 @@ async fn start_nym_api_tasks_axum(config: &Config) -> anyhow::Result<ShutdownHan
};
let router = router.with_state(AppState {
forced_refresh: Default::default(),
forced_refresh: ForcedRefresh::new(
config.topology_cacher.debug.node_describe_allow_illegal_ips,
),
nym_contract_cache: nym_contract_cache_state.clone(),
node_status_cache: node_status_cache_state.clone(),
circulating_supply_cache: circulating_supply_cache.clone(),
+3
View File
@@ -439,6 +439,8 @@ 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 {
@@ -447,6 +449,7 @@ 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,
}
}
}
+9 -1
View File
@@ -82,12 +82,20 @@ pub(crate) struct AppState {
pub(crate) node_info_cache: unstable::NodeInfoCache,
}
#[derive(Clone, Default)]
#[derive(Clone)]
pub(crate) struct ForcedRefresh {
pub(crate) allow_all_ip_addresses: bool,
pub(crate) refreshes: Arc<RwLock<HashMap<NodeId, OffsetDateTime>>>,
}
impl ForcedRefresh {
pub(crate) fn new(allow_all_ip_addresses: bool) -> ForcedRefresh {
ForcedRefresh {
allow_all_ip_addresses,
refreshes: Arc::new(Default::default()),
}
}
pub(crate) async fn last_refreshed(&self, node_id: NodeId) -> Option<OffsetDateTime> {
self.refreshes.read().await.get(&node_id).copied()
}
+96 -96
View File
@@ -7,6 +7,7 @@ use crate::support::storage::models::{
ActiveGateway, ActiveMixnode, GatewayDetails, HistoricalUptime, MixnodeDetails, NodeStatus,
RewardingReport, TestedGatewayStatus, TestedMixnodeStatus, TestingRoute,
};
use crate::support::storage::DbIdCache;
use nym_mixnet_contract_common::{EpochId, IdentityKey, NodeId};
use nym_types::monitoring::NodeResult;
use sqlx::FromRow;
@@ -51,24 +52,7 @@ impl AvgGatewayReliability {
// all SQL goes here
impl StorageManager {
pub(crate) async fn get_mixnode_mix_ids_by_identity(
&self,
identity: &str,
) -> Result<Vec<NodeId>, sqlx::Error> {
let ids = sqlx::query!(
r#"SELECT mix_id as "mix_id: NodeId" FROM mixnode_details WHERE identity_key = ?"#,
identity
)
.fetch_all(&self.connection_pool)
.await?
.into_iter()
.map(|row| row.mix_id)
.collect();
Ok(ids)
}
pub(crate) async fn get_all_avg_mix_reliability_in_last_24hr(
pub(super) async fn get_all_avg_mix_reliability_in_last_24hr(
&self,
end_ts_secs: i64,
) -> Result<Vec<AvgMixnodeReliability>, sqlx::Error> {
@@ -77,7 +61,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_all_avg_gateway_reliability_in_last_24hr(
pub(super) async fn get_all_avg_gateway_reliability_in_last_24hr(
&self,
end_ts_secs: i64,
) -> Result<Vec<AvgGatewayReliability>, sqlx::Error> {
@@ -86,7 +70,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_all_avg_mix_reliability_in_time_interval(
pub(super) async fn get_all_avg_mix_reliability_in_time_interval(
&self,
start_ts_secs: i64,
end_ts_secs: i64,
@@ -114,7 +98,7 @@ impl StorageManager {
Ok(result)
}
pub(crate) async fn get_all_avg_gateway_reliability_in_interval(
pub(super) async fn get_all_avg_gateway_reliability_in_interval(
&self,
start_ts_secs: i64,
end_ts_secs: i64,
@@ -147,7 +131,7 @@ impl StorageManager {
/// # Arguments
///
/// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode.
pub(crate) async fn get_mixnode_database_id(
pub(super) async fn get_mixnode_database_id(
&self,
mix_id: NodeId,
) -> Result<Option<i64>, sqlx::Error> {
@@ -159,7 +143,7 @@ impl StorageManager {
Ok(id)
}
pub(crate) async fn get_gateway_database_id(
pub(super) async fn get_gateway_database_id(
&self,
node_id: NodeId,
) -> Result<Option<i64>, sqlx::Error> {
@@ -172,7 +156,7 @@ impl StorageManager {
}
/// Tries to obtain row id of given gateway given its identity
pub(crate) async fn get_gateway_database_id_by_identity(
pub(super) async fn get_gateway_database_id_by_identity(
&self,
identity: &str,
) -> Result<Option<i64>, sqlx::Error> {
@@ -187,7 +171,7 @@ impl StorageManager {
Ok(id)
}
pub(crate) async fn get_gateway_node_id_from_identity_key(
pub(super) async fn get_gateway_node_id_from_identity_key(
&self,
identity: &str,
) -> Result<Option<NodeId>, sqlx::Error> {
@@ -202,7 +186,7 @@ impl StorageManager {
Ok(node_id)
}
pub(crate) async fn get_gateway_identity_key(
pub(super) async fn get_gateway_identity_key(
&self,
node_id: NodeId,
) -> Result<Option<IdentityKey>, sqlx::Error> {
@@ -222,7 +206,7 @@ impl StorageManager {
/// # Arguments
///
/// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode.
pub(crate) async fn get_mixnode_identity_key(
pub(super) async fn get_mixnode_identity_key(
&self,
mix_id: NodeId,
) -> Result<Option<IdentityKey>, sqlx::Error> {
@@ -244,7 +228,7 @@ impl StorageManager {
///
/// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode.
/// * `timestamp`: unix timestamp of the lower bound of the selection.
pub(crate) async fn get_mixnode_statuses_since(
pub(super) async fn get_mixnode_statuses_since(
&self,
mix_id: NodeId,
timestamp: i64,
@@ -272,7 +256,7 @@ impl StorageManager {
///
/// * `identity`: identity (base58-encoded public key) of the gateway.
/// * `timestamp`: unix timestamp of the lower bound of the selection.
pub(crate) async fn get_gateway_statuses_since(
pub(super) async fn get_gateway_statuses_since(
&self,
node_id: NodeId,
timestamp: i64,
@@ -298,7 +282,7 @@ impl StorageManager {
/// # Arguments
///
/// * `mix_id`: mix-id (as assigned by the smart contract) of the mixnode.
pub(crate) async fn get_mixnode_historical_uptimes(
pub(super) async fn get_mixnode_historical_uptimes(
&self,
mix_id: NodeId,
) -> Result<Vec<ApiHistoricalUptime>, sqlx::Error> {
@@ -336,7 +320,7 @@ impl StorageManager {
/// # Arguments
///
/// * `identity`: identity (base58-encoded public key) of the gateway.
pub(crate) async fn get_gateway_historical_uptimes(
pub(super) async fn get_gateway_historical_uptimes(
&self,
node_id: NodeId,
) -> Result<Vec<ApiHistoricalUptime>, sqlx::Error> {
@@ -369,7 +353,7 @@ impl StorageManager {
Ok(uptimes)
}
pub(crate) async fn get_historical_mix_uptime_on(
pub(super) async fn get_historical_mix_uptime_on(
&self,
contract_node_id: i64,
date: Date,
@@ -393,7 +377,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_historical_gateway_uptime_on(
pub(super) async fn get_historical_gateway_uptime_on(
&self,
contract_node_id: i64,
date: Date,
@@ -424,7 +408,7 @@ impl StorageManager {
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_mixnode_statuses_by_database_id(
pub(super) async fn get_mixnode_statuses_by_database_id(
&self,
id: i64,
since: i64,
@@ -445,7 +429,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_mixnode_average_reliability_in_interval(
pub(super) async fn get_mixnode_average_reliability_in_interval(
&self,
id: i64,
start: i64,
@@ -507,7 +491,7 @@ impl StorageManager {
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_gateway_statuses_by_database_id(
pub(super) async fn get_gateway_statuses_by_database_id(
&self,
id: i64,
since: i64,
@@ -534,27 +518,36 @@ impl StorageManager {
///
/// * `timestamp`: unix timestamp indicating when the measurements took place.
/// * `mixnode_results`: reliability results of each node that got tested.
pub(crate) async fn submit_mixnode_statuses(
pub(super) async fn submit_mixnode_statuses(
&self,
timestamp: i64,
mixnode_results: Vec<NodeResult>,
id_cache: &DbIdCache,
) -> Result<(), sqlx::Error> {
// insert it all in a transaction to make sure all nodes are updated at the same time
// (plus it's a nice guard against new nodes)
let mut tx = self.connection_pool.begin().await?;
for mixnode_result in mixnode_results {
let mixnode_id = sqlx::query!(
r#"
INSERT OR IGNORE INTO mixnode_details(mix_id, identity_key) VALUES (?, ?);
SELECT id FROM mixnode_details WHERE mix_id = ?;
"#,
mixnode_result.node_id,
mixnode_result.identity,
mixnode_result.node_id,
)
.fetch_one(&mut *tx)
.await?
.id;
let mixnode_id = match id_cache.mixnode_db_id(mixnode_result.node_id) {
Some(id) => id,
None => {
let mixnode_id = sqlx::query!(
r#"
INSERT OR IGNORE INTO mixnode_details(mix_id, identity_key) VALUES (?, ?);
SELECT id FROM mixnode_details WHERE mix_id = ?;
"#,
mixnode_result.node_id,
mixnode_result.identity,
mixnode_result.node_id,
)
.fetch_one(&mut *tx)
.await?
.id;
id_cache.set_mixnode_db_id(mixnode_result.node_id, mixnode_id);
mixnode_id
}
};
// insert the actual status
sqlx::query!(
@@ -573,7 +566,7 @@ impl StorageManager {
tx.commit().await
}
pub(crate) async fn submit_mixnode_statuses_v2(
pub(super) async fn submit_mixnode_statuses_v2(
&self,
mixnode_results: &[NodeResult],
) -> Result<(), sqlx::Error> {
@@ -620,10 +613,11 @@ impl StorageManager {
///
/// * `timestamp`: unix timestamp indicating when the measurements took place.
/// * `gateway_results`: reliability results of each node that got tested.
pub(crate) async fn submit_gateway_statuses(
pub(super) async fn submit_gateway_statuses(
&self,
timestamp: i64,
gateway_results: Vec<NodeResult>,
id_cache: &DbIdCache,
) -> Result<(), sqlx::Error> {
// insert it all in a transaction to make sure all nodes are updated at the same time
// (plus it's a nice guard against new nodes)
@@ -631,39 +625,45 @@ impl StorageManager {
for gateway_result in gateway_results {
// if gateway info doesn't exist, insert it and get its id
// same ID "problem" as described for mixnode insertion
let gateway_id = sqlx::query!(
r#"
INSERT OR IGNORE INTO gateway_details(node_id, identity) VALUES (?, ?);
SELECT id FROM gateway_details WHERE identity = ?;
"#,
gateway_result.node_id,
gateway_result.identity,
gateway_result.identity,
)
.fetch_one(&mut *tx)
.await?
.id;
let gateway_id = match id_cache.gateway_db_id(gateway_result.node_id) {
Some(id) => id,
None => {
let gateway_id = sqlx::query!(
r#"
INSERT OR IGNORE INTO gateway_details(node_id, identity) VALUES (?, ?);
SELECT id FROM gateway_details WHERE identity = ?;
"#,
gateway_result.node_id,
gateway_result.identity,
gateway_result.identity,
)
.fetch_one(&mut *tx)
.await?
.id;
id_cache.set_gateway_db_id(gateway_result.node_id, gateway_id);
gateway_id
}
};
// insert the actual status
sqlx::query!(
r#"
INSERT INTO gateway_status (gateway_details_id, reliability, timestamp) VALUES (?, ?, ?);
"#,
gateway_id,
gateway_result.reliability,
timestamp
)
.execute(&mut *tx)
.await?;
r#"
INSERT INTO gateway_status (gateway_details_id, reliability, timestamp) VALUES (?, ?, ?);
"#,
gateway_id,
gateway_result.reliability,
timestamp
)
.execute(&mut *tx)
.await?;
}
// finally commit the transaction
tx.commit().await
}
pub(crate) async fn submit_gateway_statuses_v2(
pub(super) async fn submit_gateway_statuses_v2(
&self,
gateway_results: &[NodeResult],
) -> Result<(), sqlx::Error> {
@@ -714,7 +714,7 @@ impl StorageManager {
/// # Arguments
///
/// * `testing_route`: test route used for this particular network monitor run.
pub(crate) async fn submit_testing_route_used(
pub(super) async fn submit_testing_route_used(
&self,
testing_route: TestingRoute,
) -> Result<(), sqlx::Error> {
@@ -742,7 +742,7 @@ impl StorageManager {
///
/// * `db_mixnode_id`: id (as saved in the database) of the mixnode.
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
pub(crate) async fn get_mixnode_testing_route_presence_count_since(
pub(super) async fn get_mixnode_testing_route_presence_count_since(
&self,
db_mixnode_id: i64,
since: i64,
@@ -781,7 +781,7 @@ impl StorageManager {
///
/// * `gateway_id`: id (as saved in the database) of the gateway.
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
pub(crate) async fn get_gateway_testing_route_presence_count_since(
pub(super) async fn get_gateway_testing_route_presence_count_since(
&self,
gateway_id: i64,
since: i64,
@@ -813,7 +813,7 @@ impl StorageManager {
}
/// Checks whether there are already any historical uptimes with this particular date.
pub(crate) async fn check_for_historical_uptime_existence(
pub(super) async fn check_for_historical_uptime_existence(
&self,
today_iso_8601: &str,
) -> Result<bool, sqlx::Error> {
@@ -833,7 +833,7 @@ impl StorageManager {
/// * `node_id`: id of the mixnode (as inserted in `mixnode_details_id` table).
/// * `date`: date associated with the uptime represented in ISO 8601, i.e. YYYY-MM-DD.
/// * `uptime`: the actual uptime of the node during the specified day.
pub(crate) async fn insert_mixnode_historical_uptime(
pub(super) async fn insert_mixnode_historical_uptime(
&self,
mix_id: i64,
date: &str,
@@ -855,7 +855,7 @@ impl StorageManager {
/// * `node_id`: id of the gateway (as inserted in `gateway_details_id` table).
/// * `date`: date associated with the uptime represented in ISO 8601, i.e. YYYY-MM-DD.
/// * `uptime`: the actual uptime of the node during the specified day.
pub(crate) async fn insert_gateway_historical_uptime(
pub(super) async fn insert_gateway_historical_uptime(
&self,
db_id: i64,
date: &str,
@@ -876,7 +876,7 @@ impl StorageManager {
/// # Arguments
///
/// * `timestamp`: unix timestamp at which the monitor test run has occurred
pub(crate) async fn insert_monitor_run(&self, timestamp: i64) -> Result<i64, sqlx::Error> {
pub(super) async fn insert_monitor_run(&self, timestamp: i64) -> Result<i64, sqlx::Error> {
let res = sqlx::query!("INSERT INTO monitor_run(timestamp) VALUES (?)", timestamp)
.execute(&self.connection_pool)
.await?;
@@ -889,7 +889,7 @@ impl StorageManager {
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_monitor_runs_count(
pub(super) async fn get_monitor_runs_count(
&self,
since: i64,
until: i64,
@@ -911,7 +911,7 @@ impl StorageManager {
/// # Arguments
///
/// * `until`: timestamp specifying the purge cutoff.
pub(crate) async fn purge_old_mixnode_statuses(
pub(super) async fn purge_old_mixnode_statuses(
&self,
timestamp: i64,
) -> Result<(), sqlx::Error> {
@@ -927,7 +927,7 @@ impl StorageManager {
/// # Arguments
///
/// * `until`: timestamp specifying the purge cutoff.
pub(crate) async fn purge_old_gateway_statuses(
pub(super) async fn purge_old_gateway_statuses(
&self,
timestamp: i64,
) -> Result<(), sqlx::Error> {
@@ -944,7 +944,7 @@ impl StorageManager {
///
/// * `since`: indicates the lower bound timestamp for deciding whether given mixnode is active
/// * `until`: indicates the upper bound timestamp for deciding whether given mixnode is active
pub(crate) async fn get_all_active_mixnodes_in_interval(
pub(super) async fn get_all_active_mixnodes_in_interval(
&self,
since: i64,
until: i64,
@@ -978,7 +978,7 @@ impl StorageManager {
///
/// * `since`: indicates the lower bound timestamp for deciding whether given gateway is active
/// * `until`: indicates the upper bound timestamp for deciding whether given gateway is active
pub(crate) async fn get_all_active_gateways_in_interval(
pub(super) async fn get_all_active_gateways_in_interval(
&self,
since: i64,
until: i64,
@@ -1025,7 +1025,7 @@ impl StorageManager {
///
/// * `report`: report to insert into the database
#[allow(unused)]
pub(crate) async fn insert_rewarding_report(
pub(super) async fn insert_rewarding_report(
&self,
report: RewardingReport,
) -> Result<(), sqlx::Error> {
@@ -1044,7 +1044,7 @@ impl StorageManager {
}
#[allow(unused)]
pub(crate) async fn get_rewarding_report(
pub(super) async fn get_rewarding_report(
&self,
absolute_epoch_id: EpochId,
) -> Result<Option<RewardingReport>, sqlx::Error> {
@@ -1069,7 +1069,7 @@ impl StorageManager {
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_all_active_mixnodes_statuses_in_interval(
pub(super) async fn get_all_active_mixnodes_statuses_in_interval(
&self,
since: i64,
until: i64,
@@ -1102,7 +1102,7 @@ impl StorageManager {
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_all_active_gateways_statuses_in_interval(
pub(super) async fn get_all_active_gateways_statuses_in_interval(
&self,
since: i64,
until: i64,
@@ -1129,7 +1129,7 @@ impl StorageManager {
Ok(active_day_statuses)
}
pub(crate) async fn get_mixnode_details_by_db_id(
pub(super) async fn get_mixnode_details_by_db_id(
&self,
id: i64,
) -> Result<Option<MixnodeDetails>, sqlx::Error> {
@@ -1142,7 +1142,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_gateway_details_by_db_id(
pub(super) async fn get_gateway_details_by_db_id(
&self,
id: i64,
) -> Result<Option<GatewayDetails>, sqlx::Error> {
@@ -1154,7 +1154,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_mixnode_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
pub(super) async fn get_mixnode_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
sqlx::query!(
r#"
SELECT COUNT(*) as count
@@ -1170,7 +1170,7 @@ impl StorageManager {
.map(|record| record.count)
}
pub(crate) async fn get_mixnode_statuses(
pub(super) async fn get_mixnode_statuses(
&self,
mix_id: NodeId,
limit: u32,
@@ -1206,7 +1206,7 @@ impl StorageManager {
.await
}
pub(crate) async fn get_gateway_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
pub(super) async fn get_gateway_statuses_count(&self, db_id: i64) -> Result<i32, sqlx::Error> {
sqlx::query!(
r#"
SELECT COUNT(*) as count
@@ -1222,7 +1222,7 @@ impl StorageManager {
.map(|record| record.count)
}
pub(crate) async fn get_gateway_statuses(
pub(super) async fn get_gateway_statuses(
&self,
gateway_identity: &str,
limit: u32,
+85 -28
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use self::manager::{AvgGatewayReliability, AvgMixnodeReliability};
use crate::network_monitor::test_route::TestRoute;
use crate::node_status_api::models::{
GatewayStatusReport, GatewayUptimeHistory, HistoricalUptime as ApiHistoricalUptime,
@@ -12,37 +13,71 @@ use crate::storage::models::{NodeStatus, TestingRoute};
use crate::support::storage::models::{
GatewayDetails, HistoricalUptime, MixnodeDetails, TestedGatewayStatus, TestedMixnodeStatus,
};
use dashmap::DashMap;
use nym_mixnet_contract_common::NodeId;
use nym_types::monitoring::NodeResult;
use sqlx::ConnectOptions;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use time::{Date, OffsetDateTime};
use tracing::log::LevelFilter;
use tracing::{error, info, warn};
use self::manager::{AvgGatewayReliability, AvgMixnodeReliability};
pub(crate) mod manager;
pub(crate) mod models;
pub(crate) mod runtime_migrations;
#[derive(Default)]
pub(crate) struct DbIdCache {
pub mixnodes_v1: DashMap<NodeId, i64>,
pub gateways_v1: DashMap<NodeId, i64>,
}
impl DbIdCache {
pub(crate) fn mixnode_db_id(&self, node_id: NodeId) -> Option<i64> {
self.mixnodes_v1.get(&node_id).map(|v| *v)
}
pub(crate) fn gateway_db_id(&self, node_id: NodeId) -> Option<i64> {
self.gateways_v1.get(&node_id).map(|v| *v)
}
pub(crate) fn set_mixnode_db_id(&self, node_id: NodeId, db_id: i64) {
self.mixnodes_v1.insert(node_id, db_id);
}
pub(crate) fn set_gateway_db_id(&self, node_id: NodeId, db_id: i64) {
self.gateways_v1.insert(node_id, db_id);
}
}
// note that clone here is fine as upon cloning the same underlying pool will be used
#[derive(Clone)]
pub(crate) struct NymApiStorage {
pub manager: StorageManager,
pub db_id_cache: Arc<DbIdCache>,
}
impl NymApiStorage {
pub async fn init<P: AsRef<Path>>(database_path: P) -> Result<Self, NymApiStorageError> {
// TODO: we can inject here more stuff based on our nym-api global config
// struct. Maybe different pool size or timeout intervals?
let opts = sqlx::sqlite::SqliteConnectOptions::new()
let connect_opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(true)
.disable_statement_logging();
.log_statements(LevelFilter::Trace)
.log_slow_statements(LevelFilter::Warn, Duration::from_millis(250));
// TODO: do we want auto_vacuum ?
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
let pool_opts = sqlx::sqlite::SqlitePoolOptions::new()
.min_connections(5)
.max_connections(25)
.acquire_timeout(Duration::from_secs(60));
let connection_pool = match pool_opts.connect_with(connect_opts).await {
Ok(db) => db,
Err(err) => {
error!("Failed to connect to SQLx database: {err}");
@@ -59,32 +94,38 @@ impl NymApiStorage {
let storage = NymApiStorage {
manager: StorageManager { connection_pool },
db_id_cache: Arc::new(Default::default()),
};
Ok(storage)
}
#[allow(unused)]
pub(crate) async fn mix_identity_to_mix_ids(
pub(crate) async fn get_mixnode_database_id(
&self,
identity: &str,
) -> Result<Vec<NodeId>, NymApiStorageError> {
Ok(self
.manager
.get_mixnode_mix_ids_by_identity(identity)
.await?)
node_id: NodeId,
) -> Result<Option<i64>, NymApiStorageError> {
if let Some(cached) = self.db_id_cache.mixnode_db_id(node_id) {
return Ok(Some(cached));
}
if let Some(retrieved) = self.manager.get_mixnode_database_id(node_id).await? {
self.db_id_cache.set_mixnode_db_id(node_id, retrieved);
return Ok(Some(retrieved));
}
Ok(None)
}
#[allow(unused)]
pub(crate) async fn mix_identity_to_latest_mix_id(
pub(crate) async fn get_gateway_database_id(
&self,
identity: &str,
) -> Result<Option<NodeId>, NymApiStorageError> {
Ok(self
.mix_identity_to_mix_ids(identity)
.await?
.into_iter()
.max())
node_id: NodeId,
) -> Result<Option<i64>, NymApiStorageError> {
if let Some(cached) = self.db_id_cache.gateway_db_id(node_id) {
return Ok(Some(cached));
}
if let Some(retrieved) = self.manager.get_gateway_database_id(node_id).await? {
self.db_id_cache.set_gateway_db_id(node_id, retrieved);
return Ok(Some(retrieved));
}
Ok(None)
}
pub(crate) async fn get_all_avg_gateway_reliability_in_last_24hr(
@@ -576,7 +617,6 @@ impl NymApiStorage {
// we MUST have those entries in the database, otherwise the route wouldn't have been chosen
// in the first place
let layer1_mix_db_id = self
.manager
.get_mixnode_database_id(test_route.layer_one_mix().mix_id)
.await?
.ok_or_else(|| NymApiStorageError::DatabaseInconsistency {
@@ -584,7 +624,6 @@ impl NymApiStorage {
})?;
let layer2_mix_db_id = self
.manager
.get_mixnode_database_id(test_route.layer_two_mix().mix_id)
.await?
.ok_or_else(|| NymApiStorageError::DatabaseInconsistency {
@@ -592,7 +631,6 @@ impl NymApiStorage {
})?;
let layer3_mix_db_id = self
.manager
.get_mixnode_database_id(test_route.layer_three_mix().mix_id)
.await?
.ok_or_else(|| NymApiStorageError::DatabaseInconsistency {
@@ -600,7 +638,6 @@ impl NymApiStorage {
})?;
let gateway_db_id = self
.manager
.get_gateway_database_id(test_route.gateway().node_id)
.await?
.ok_or_else(|| NymApiStorageError::DatabaseInconsistency {
@@ -701,11 +738,11 @@ impl NymApiStorage {
let monitor_run_id = self.manager.insert_monitor_run(now).await?;
self.manager
.submit_mixnode_statuses(now, mixnode_results)
.submit_mixnode_statuses(now, mixnode_results, &self.db_id_cache)
.await?;
self.manager
.submit_gateway_statuses(now, gateway_results)
.submit_gateway_statuses(now, gateway_results, &self.db_id_cache)
.await?;
for test_route in test_routes {
@@ -715,6 +752,26 @@ impl NymApiStorage {
Ok(())
}
pub(crate) async fn submit_mixnode_statuses_v2(
&self,
mixnode_results: &[NodeResult],
) -> Result<(), NymApiStorageError> {
self.manager
.submit_mixnode_statuses_v2(mixnode_results)
.await?;
Ok(())
}
pub(crate) async fn submit_gateway_statuses_v2(
&self,
gateway_results: &[NodeResult],
) -> Result<(), NymApiStorageError> {
self.manager
.submit_gateway_statuses_v2(gateway_results)
.await?;
Ok(())
}
/// Obtains number of network monitor test runs that have occurred within the specified interval.
///
/// # Arguments
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node"
version = "1.1.10"
version = "1.1.11"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -59,6 +59,17 @@ pub struct HostInformation {
pub keys: HostKeys,
}
impl HostInformation {
pub fn check_ips(&self) -> bool {
for ip in &self.ip_address {
if ip.is_unspecified() || ip.is_loopback() || ip.is_multicast() {
return false;
}
}
true
}
}
#[derive(Serialize)]
pub struct LegacyHostInformationV2 {
pub ip_address: Vec<IpAddr>,
@@ -4,7 +4,7 @@
[package]
name = "nym-network-requester"
license = "GPL-3.0"
version = "1.1.44"
version = "1.1.45"
authors.workspace = true
edition.workspace = true
rust-version = "1.70"
+1
View File
@@ -231,6 +231,7 @@ impl NymNodeTesterBuilder {
self.base_topology,
Some(address(&managed_keys, gateway_identity)),
PacketSize::default(),
false,
Duration::from_millis(5),
Duration::from_millis(5),
managed_keys.ack_key(),