Compare commits

..

1 Commits

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

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