Compare commits
199 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a98306d340 | |||
| b68e13f0f2 | |||
| 46fe1bc819 | |||
| 37ae72d8ec | |||
| 0b58b6f728 | |||
| e709e30e43 | |||
| 29b405f813 | |||
| f5d22a66f6 | |||
| bf7cd15428 | |||
| 757da6a456 | |||
| 62c581a9ae | |||
| 32e06e19e7 | |||
| fd1b524037 | |||
| 36d0adfe92 | |||
| fcf782674c | |||
| 4b35c36299 | |||
| ca7cbac320 | |||
| e410aecf40 | |||
| ae76335c31 | |||
| dac7f1f83c | |||
| 31ff3645c5 | |||
| 6dd3b78a74 | |||
| 0ab14f7041 | |||
| 9c6310264e | |||
| aa37bfb7ff | |||
| 66c2454775 | |||
| 670f383faa | |||
| 9714351fd8 | |||
| 7352499328 | |||
| 06717037e5 | |||
| 1a7aa2ce90 | |||
| c594361cd1 | |||
| df52f10f52 | |||
| 6a96d8205b | |||
| 22793bc45e | |||
| 6eb8f29235 | |||
| c9ef46c51d | |||
| f1024bc976 | |||
| 1aec4c2f8e | |||
| 1b79107726 | |||
| a69b473ba1 | |||
| fe01c922c0 | |||
| 4b0fbc663a | |||
| 4b292ca142 | |||
| 9262e24892 | |||
| e0c74c5eb0 | |||
| a293d6da7d | |||
| f12a554e85 | |||
| 8c3a797750 | |||
| 00d0ae0b5b | |||
| de0ae687ef | |||
| 42c051dfa3 | |||
| 26f4dd8f39 | |||
| 2d37c33a3d | |||
| f1be6ae788 | |||
| c13b4aa745 | |||
| 68eae18b8b | |||
| 28dc7cae4d | |||
| 37f3ef58a3 | |||
| 52f98de73b | |||
| 6d63ba1f4c | |||
| 6170ca2a14 | |||
| 2cc59aadc5 | |||
| 89dc865ec6 | |||
| 6d1d9d58a5 | |||
| 3057721845 | |||
| 54c7a01482 | |||
| 76993a9b94 | |||
| b00e1f2fff | |||
| ef7974fde9 | |||
| a488a1b489 | |||
| bb5b43492a | |||
| f9e2311574 | |||
| 50d768976f | |||
| 3825d5f173 | |||
| 9b076197b1 | |||
| 7b96adf7a8 | |||
| 18d271f481 | |||
| 752c7915b3 | |||
| 6c01c9fceb | |||
| 4e7b4715b0 | |||
| 4fdbcb051a | |||
| 47c6006bb7 | |||
| dcfd0f77ad | |||
| 78fb779010 | |||
| b4544c2b48 | |||
| 90e07d9980 | |||
| b8479e1cde | |||
| 37e3a101b1 | |||
| 45a1074377 | |||
| 1b9af19e20 | |||
| 7a339d4c4d | |||
| 568268d39b | |||
| 9c5847dc67 | |||
| 22db132c09 | |||
| 5496cce5c9 | |||
| 8de37eb6c9 | |||
| 40a7a87c61 | |||
| 95ee3a7c7d | |||
| 4736f1eb52 | |||
| c23e139eaa | |||
| bfcb4c79bb | |||
| 2933732225 | |||
| 2e059865a1 | |||
| 06c0c36c67 | |||
| bdc0f5022d | |||
| b742ace7e0 | |||
| 82a9563ca0 | |||
| ef25480c20 | |||
| bcce854a8b | |||
| f960bfa91b | |||
| 4f991061dd | |||
| e5aef76256 | |||
| 6acc54d2bc | |||
| ab6e08dd13 | |||
| e09066858c | |||
| 842ce93a60 | |||
| ce26105986 | |||
| cc95358385 | |||
| cc04a09ed7 | |||
| ae47d53f0c | |||
| e0ff09f323 | |||
| 10707fd2c5 | |||
| 9bdd2af14c | |||
| 228ef8b158 | |||
| d820131d2c | |||
| 054715a600 | |||
| 3f560180b7 | |||
| f62dbbdae0 | |||
| edecc4ba01 | |||
| 1a4d64a0e5 | |||
| 58c0e289c2 | |||
| 6d8edc4bc7 | |||
| a44cdf1c7c | |||
| 6b8a6283a4 | |||
| 94151965bb | |||
| e8ca490db1 | |||
| fe7470ea44 | |||
| 21d52244cb | |||
| c0c58026a8 | |||
| 0fe863c889 | |||
| 4e5d88f64c | |||
| 1559f6a912 | |||
| 766024be27 | |||
| 5ba181b118 | |||
| 76fc9f4a90 | |||
| 8ca6af7c86 | |||
| 45e14a7fb1 | |||
| a38917cb9b | |||
| cf8a399089 | |||
| ba01820586 | |||
| 8c799b2976 | |||
| de4fb6291c | |||
| 81fd37e5c0 | |||
| 219f3af967 | |||
| aea7442525 | |||
| 1525aed657 | |||
| 943b5fa8bc | |||
| 71e0c025c6 | |||
| 239c6c701b | |||
| 91d0b7bdad | |||
| 99b28b2b6f | |||
| 5627ada57e | |||
| 4e1228fff0 | |||
| 04be5624fa | |||
| a6fe1b1de7 | |||
| c5971d0e9d | |||
| 06dd74ba34 | |||
| c6a0256b03 | |||
| d04b61a88b | |||
| 70a119ac58 | |||
| e2fe3a60a6 | |||
| 71301ee0cc | |||
| aba6c9d4ac | |||
| c617bbb240 | |||
| 1f8144eb11 | |||
| 694135c81b | |||
| e815f08505 | |||
| f402da8e60 | |||
| 34a500d0a2 | |||
| ef52f25564 | |||
| 010b013094 | |||
| c503a5f0e8 | |||
| 781afd3522 | |||
| 58083df0b0 | |||
| 4e8d29d0c5 | |||
| 66797efa80 | |||
| 5a26fa262e | |||
| 73a34935ae | |||
| a8086675d9 | |||
| 0453345d65 | |||
| b56d9505e6 | |||
| bdacc72003 | |||
| 9eca9efd82 | |||
| cc74d218fc | |||
| dea8a287e6 | |||
| fc5d310935 | |||
| de27778192 | |||
| 37c4a3aa36 |
@@ -16,7 +16,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -10,13 +10,13 @@ env:
|
||||
|
||||
jobs:
|
||||
check-if-tag-exists:
|
||||
runs-on: arc-ubuntu-22.04-dind
|
||||
runs-on: arc-linux-latest-dind
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.24.6"
|
||||
|
||||
|
||||
@@ -14,6 +14,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: SonarQube Scan
|
||||
uses: SonarSource/sonarqube-scan-action@v5
|
||||
uses: SonarSource/sonarqube-scan-action@v6
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
# Upload entire repository
|
||||
path: './ppa'
|
||||
|
||||
@@ -8,6 +8,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/first-interaction@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: 'Thank you for raising this issue'
|
||||
pr-message: 'Thank you for making this first PR'
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue_message: 'Thank you for raising this issue'
|
||||
pr_message: 'Thank you for making this first PR'
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-api/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --global user.name "Lawrence Stalder"
|
||||
|
||||
- name: Get version from cargo.toml
|
||||
uses: mikefarah/yq@v4.47.1
|
||||
uses: mikefarah/yq@v4.48.1
|
||||
id: get_version
|
||||
with:
|
||||
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
|
||||
|
||||
@@ -4,6 +4,84 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2026.1-niolo] (2026-01-13)
|
||||
|
||||
- bugfix: mozzarella -> niolo config migration ([#6259])
|
||||
- chore: remove run DKG migration ([#6253])
|
||||
- bugfix: reexposed 'derive_extended_private_key' ([#6247])
|
||||
- Bump js-yaml from 3.14.1 to 3.14.2 in /sdk/typescript/codegen/contract-clients ([#6231])
|
||||
- Statistics API v2 ([#6227])
|
||||
- Bump golang.org/x/crypto from 0.39.0 to 0.45.0 in /nym-gateway-probe/netstack_ping ([#6220])
|
||||
- Update chain registry link ([#6219])
|
||||
- Bump glob from 10.3.4 to 10.5.0 in /documentation/scripts/post-process ([#6216])
|
||||
- Bump js-yaml from 4.1.0 to 4.1.1 in /sdk/typescript/tests/integration-tests/mix-fetch ([#6215])
|
||||
- gateway-probe fixes for run-local ([#6212])
|
||||
- chore: updated default endpoint for retrieving attestation.json ([#6207])
|
||||
- chore: remove support for legacy mixnode within the performance contract ([#6205])
|
||||
- feat: upgrade mode: VPN adjustments ([#6189])
|
||||
- Bump min-document from 2.19.0 to 2.19.1 ([#6181])
|
||||
- Bump next from 15.4.1 to 15.4.7 in /nym-node-status-api/nym-node-status-ui ([#6180])
|
||||
- feat: merge intermediate upgrade mode changes ([#6174])
|
||||
- Add weighted scoring to NS API ([#6144])
|
||||
- build(deps): bump mikefarah/yq from 4.47.1 to 4.48.1 ([#6107])
|
||||
- build(deps): bump SonarSource/sonarqube-scan-action from 5 to 6 in /.github/workflows ([#6068])
|
||||
- build(deps): bump tar-fs from 3.0.9 to 3.1.1 in /sdk/typescript/tests/integration-tests/mix-fetch ([#6063])
|
||||
- build(deps): bump ammonia from 4.1.1 to 4.1.2 ([#6057])
|
||||
- build(deps): bump tower-http from 0.5.2 to 0.6.6 ([#6030])
|
||||
- build(deps): bump actions/setup-go from 5 to 6 ([#6013])
|
||||
- build(deps): bump next from 14.2.28 to 14.2.32 ([#5996])
|
||||
- build(deps): bump tracing-subscriber from 0.3.19 to 0.3.20 ([#5993])
|
||||
- build(deps): bump actions/upload-pages-artifact from 3 to 4 ([#5992])
|
||||
|
||||
[#6259]: https://github.com/nymtech/nym/pull/6259
|
||||
[#6253]: https://github.com/nymtech/nym/pull/6253
|
||||
[#6247]: https://github.com/nymtech/nym/pull/6247
|
||||
[#6231]: https://github.com/nymtech/nym/pull/6231
|
||||
[#6227]: https://github.com/nymtech/nym/pull/6227
|
||||
[#6220]: https://github.com/nymtech/nym/pull/6220
|
||||
[#6219]: https://github.com/nymtech/nym/pull/6219
|
||||
[#6216]: https://github.com/nymtech/nym/pull/6216
|
||||
[#6215]: https://github.com/nymtech/nym/pull/6215
|
||||
[#6212]: https://github.com/nymtech/nym/pull/6212
|
||||
[#6207]: https://github.com/nymtech/nym/pull/6207
|
||||
[#6205]: https://github.com/nymtech/nym/pull/6205
|
||||
[#6189]: https://github.com/nymtech/nym/pull/6189
|
||||
[#6181]: https://github.com/nymtech/nym/pull/6181
|
||||
[#6180]: https://github.com/nymtech/nym/pull/6180
|
||||
[#6174]: https://github.com/nymtech/nym/pull/6174
|
||||
[#6144]: https://github.com/nymtech/nym/pull/6144
|
||||
[#6107]: https://github.com/nymtech/nym/pull/6107
|
||||
[#6068]: https://github.com/nymtech/nym/pull/6068
|
||||
[#6063]: https://github.com/nymtech/nym/pull/6063
|
||||
[#6057]: https://github.com/nymtech/nym/pull/6057
|
||||
[#6030]: https://github.com/nymtech/nym/pull/6030
|
||||
[#6013]: https://github.com/nymtech/nym/pull/6013
|
||||
[#5996]: https://github.com/nymtech/nym/pull/5996
|
||||
[#5993]: https://github.com/nymtech/nym/pull/5993
|
||||
[#5992]: https://github.com/nymtech/nym/pull/5992
|
||||
|
||||
## [2025.21-mozzarella] (2025-11-25)
|
||||
|
||||
- [bugfix] Tunnel not waiting on MixnetClient to shut down cleanly ([#6225])
|
||||
- bugfix: fix credential proxy upgrade mode attestation url arg ([#6202])
|
||||
- HTTP API resilience enable & domain rotation conditions ([#6200])
|
||||
- Remove debug feature from http-macro spec in gateway probe ([#6195])
|
||||
- DNS relibility and troubleshooting ([#6179])
|
||||
- [bugfix] Distinguish authenticator errors by credential spent ([#6176])
|
||||
- Typescript SDK 1.4.1 ([#6146])
|
||||
- Enable URL rotation and retries for mixnet gateway init ([#6126])
|
||||
- Feature/credential proxy jwt ([#5957])
|
||||
|
||||
[#6225]: https://github.com/nymtech/nym/pull/6225
|
||||
[#6202]: https://github.com/nymtech/nym/pull/6202
|
||||
[#6200]: https://github.com/nymtech/nym/pull/6200
|
||||
[#6195]: https://github.com/nymtech/nym/pull/6195
|
||||
[#6179]: https://github.com/nymtech/nym/pull/6179
|
||||
[#6176]: https://github.com/nymtech/nym/pull/6176
|
||||
[#6146]: https://github.com/nymtech/nym/pull/6146
|
||||
[#6126]: https://github.com/nymtech/nym/pull/6126
|
||||
[#5957]: https://github.com/nymtech/nym/pull/5957
|
||||
|
||||
## [2025.20-leerdammer] (2025-11-12)
|
||||
|
||||
- Max/tweak ts sdk actions ([#6185])
|
||||
|
||||
Generated
+49
-72
@@ -1283,7 +1283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3965,7 +3965,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4374,11 +4374,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4741,6 +4741,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
@@ -4825,7 +4834,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.69"
|
||||
version = "1.1.71"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -4896,7 +4905,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tower-http 0.5.2",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"ts-rs",
|
||||
"url",
|
||||
@@ -5048,7 +5057,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.66"
|
||||
version = "1.1.68"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -5131,7 +5140,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.66"
|
||||
version = "1.1.68"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -5478,7 +5487,7 @@ dependencies = [
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-http 0.5.2",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"url",
|
||||
"utoipa",
|
||||
@@ -6358,7 +6367,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.67"
|
||||
version = "1.1.69"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -6408,7 +6417,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.21.0"
|
||||
version = "1.23.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -6432,7 +6441,6 @@ dependencies = [
|
||||
"futures",
|
||||
"hkdf",
|
||||
"human-repr",
|
||||
"humantime",
|
||||
"humantime-serde",
|
||||
"indicatif",
|
||||
"ipnetwork",
|
||||
@@ -6483,7 +6491,7 @@ dependencies = [
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"toml 0.8.23",
|
||||
"tower-http 0.5.2",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-indicatif",
|
||||
"tracing-subscriber",
|
||||
@@ -6553,7 +6561,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node-status-api"
|
||||
version = "4.0.11-rc1"
|
||||
version = "4.0.12"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
@@ -6601,7 +6609,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tower-http 0.5.2",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-log 0.2.0",
|
||||
"tracing-subscriber",
|
||||
@@ -6938,7 +6946,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.66"
|
||||
version = "1.1.68"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -7198,7 +7206,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-statistics-api"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -7212,13 +7220,12 @@ dependencies = [
|
||||
"nym-statistics-common",
|
||||
"nym-task",
|
||||
"nym-validator-client",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-http 0.5.2",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
@@ -7399,6 +7406,7 @@ dependencies = [
|
||||
name = "nym-validator-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bip32",
|
||||
@@ -7616,7 +7624,7 @@ dependencies = [
|
||||
"nym-wireguard-private-metadata-shared",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-http 0.5.2",
|
||||
"tower-http",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
]
|
||||
@@ -7654,7 +7662,7 @@ dependencies = [
|
||||
"time",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"tower-http 0.5.2",
|
||||
"tower-http",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
@@ -7672,7 +7680,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.31"
|
||||
version = "0.1.33"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7723,7 +7731,7 @@ dependencies = [
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-http 0.5.2",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"utoipa",
|
||||
@@ -8651,7 +8659,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8812,17 +8820,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8833,15 +8832,9 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
@@ -8921,7 +8914,7 @@ dependencies = [
|
||||
"tokio-rustls 0.26.2",
|
||||
"tokio-util",
|
||||
"tower 0.5.2",
|
||||
"tower-http 0.6.6",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
@@ -9121,7 +9114,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10437,7 +10430,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.0.8",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11056,9 +11049,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.5.2"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"bitflags 2.9.1",
|
||||
@@ -11070,33 +11063,17 @@ dependencies = [
|
||||
"http-body-util",
|
||||
"http-range-header",
|
||||
"httpdate",
|
||||
"iri-string",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body 1.0.1",
|
||||
"iri-string",
|
||||
"pin-project-lite",
|
||||
"tower 0.5.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11204,14 +11181,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"nu-ansi-term 0.50.1",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
@@ -11247,7 +11224,7 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ec6adcab41b1391b08a308cc6302b79f8095d1673f6947c2dc65ffb028b0b2d"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-ansi-term 0.46.0",
|
||||
"tracing-core",
|
||||
"tracing-log 0.1.4",
|
||||
"tracing-subscriber",
|
||||
@@ -12249,7 +12226,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+2
-2
@@ -347,11 +347,11 @@ tokio-tungstenite = { version = "0.20.1" }
|
||||
tokio-util = "0.7.15"
|
||||
toml = "0.8.22"
|
||||
tower = "0.5.2"
|
||||
tower-http = "0.5.2"
|
||||
tower-http = "0.6.6"
|
||||
tracing = "0.1.41"
|
||||
tracing-log = "0.2"
|
||||
tracing-opentelemetry = "0.19.0"
|
||||
tracing-subscriber = "0.3.19"
|
||||
tracing-subscriber = "0.3.20"
|
||||
tracing-tree = "0.2.2"
|
||||
tracing-indicatif = "0.3.9"
|
||||
tracing-test = "0.2.5"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.66"
|
||||
version = "1.1.68"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.66"
|
||||
version = "1.1.68"
|
||||
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"
|
||||
|
||||
@@ -75,6 +75,7 @@ workspace = true
|
||||
features = ["json", "rustls-tls"]
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
bip39 = { workspace = true }
|
||||
cosmrs = { workspace = true, features = ["bip32"] }
|
||||
ts-rs = { workspace = true }
|
||||
|
||||
@@ -7,6 +7,7 @@ use cosmrs::{tx, AccountId, Coin, Denom};
|
||||
use nym_validator_client::http_client;
|
||||
use nym_validator_client::nyxd::CosmWasmClient;
|
||||
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use nym_validator_client::signing::signer::OfflineSigner;
|
||||
use nym_validator_client::signing::tx_signer::TxSigner;
|
||||
use nym_validator_client::signing::SignerData;
|
||||
|
||||
@@ -19,8 +20,8 @@ async fn main() {
|
||||
let validator = "https://rpc.sandbox.nymtech.net";
|
||||
let to_address: AccountId = "n1pefc2utwpy5w78p2kqdsfmpjxfwmn9d39k5mqa".parse().unwrap();
|
||||
|
||||
let signer = DirectSecp256k1HdWallet::from_mnemonic(prefix, signer_mnemonic);
|
||||
let signer_address = signer.try_derive_accounts().unwrap()[0].address().clone();
|
||||
let signer = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, signer_mnemonic).unwrap();
|
||||
let signer_address = signer.signer_addresses()[0].clone();
|
||||
|
||||
// local 'client' ONLY signing messages
|
||||
let tx_signer = signer;
|
||||
@@ -57,9 +58,15 @@ async fn main() {
|
||||
100000u32,
|
||||
);
|
||||
|
||||
let tx_raw = tx_signer
|
||||
.sign_direct(&signer_address, vec![send_msg], fee, memo, signer_data)
|
||||
.unwrap();
|
||||
let tx_raw = TxSigner::sign_direct(
|
||||
&tx_signer,
|
||||
&signer_address,
|
||||
vec![send_msg],
|
||||
fee,
|
||||
memo,
|
||||
signer_data,
|
||||
)
|
||||
.unwrap();
|
||||
let tx_bytes = tx_raw.to_bytes().unwrap();
|
||||
|
||||
// compare balances from before and after the tx
|
||||
|
||||
@@ -5,8 +5,7 @@ use crate::nyxd::{self, NyxdClient};
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use crate::signing::signer::{NoSigner, OfflineSigner};
|
||||
use crate::{
|
||||
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient,
|
||||
ValidatorClientError,
|
||||
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ValidatorClientError,
|
||||
};
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -164,7 +163,7 @@ impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
|
||||
) -> Result<DirectSigningHttpRpcValidatorClient, ValidatorClientError> {
|
||||
let rpc_client = http_client(config.nyxd_url.as_str())?;
|
||||
let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)?;
|
||||
|
||||
Ok(Self::new_signing_with_rpc_client(
|
||||
config, rpc_client, wallet,
|
||||
@@ -177,12 +176,13 @@ impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Client<ReqwestRpcClient, DirectSecp256k1HdWallet> {
|
||||
#[allow(deprecated)]
|
||||
impl Client<crate::ReqwestRpcClient, DirectSecp256k1HdWallet> {
|
||||
pub fn new_reqwest_signing(
|
||||
config: Config,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> DirectSigningReqwestRpcValidatorClient {
|
||||
let rpc_client = ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
|
||||
@@ -203,9 +203,10 @@ impl Client<HttpRpcClient> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Client<ReqwestRpcClient> {
|
||||
#[allow(deprecated)]
|
||||
impl Client<crate::ReqwestRpcClient> {
|
||||
pub fn new_reqwest_query(config: Config) -> QueryReqwestRpcValidatorClient {
|
||||
let rpc_client = ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
|
||||
Self::new_with_rpc_client(config, rpc_client)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nym_api;
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWalletError;
|
||||
pub use tendermint_rpc::error::Error as TendermintRpcError;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -26,6 +27,12 @@ pub enum ValidatorClientError {
|
||||
|
||||
#[error("No validator API url has been provided")]
|
||||
NoAPIUrlAvailable,
|
||||
|
||||
#[error("failed to derive signing accounts: {source}")]
|
||||
AccountDerivationFailure {
|
||||
#[from]
|
||||
source: DirectSecp256k1HdWalletError,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<nym_api::error::NymAPIError> for ValidatorClientError {
|
||||
|
||||
@@ -12,6 +12,7 @@ pub mod rpc;
|
||||
pub mod signing;
|
||||
|
||||
pub use crate::error::ValidatorClientError;
|
||||
#[allow(deprecated)]
|
||||
pub use crate::rpc::reqwest::ReqwestRpcClient;
|
||||
pub use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
pub use client::{Client, Config, EcashApiClient};
|
||||
@@ -38,9 +39,13 @@ pub type DirectSigningHttpRpcValidatorClient = Client<HttpRpcClient, DirectSecp2
|
||||
#[cfg(feature = "http-client")]
|
||||
pub type DirectSigningHttpRpcNyxdClient = nyxd::NyxdClient<HttpRpcClient, DirectSecp256k1HdWallet>;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub type QueryReqwestRpcValidatorClient = Client<ReqwestRpcClient>;
|
||||
#[allow(deprecated)]
|
||||
pub type QueryReqwestRpcNyxdClient = nyxd::NyxdClient<ReqwestRpcClient>;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub type DirectSigningReqwestRpcValidatorClient = Client<ReqwestRpcClient, DirectSecp256k1HdWallet>;
|
||||
#[allow(deprecated)]
|
||||
pub type DirectSigningReqwestRpcNyxdClient =
|
||||
nyxd::NyxdClient<ReqwestRpcClient, DirectSecp256k1HdWallet>;
|
||||
|
||||
@@ -178,7 +178,7 @@ where
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("dkg contract"))?;
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
|
||||
self.execute(signer_address, dkg_contract_address, &msg, fee, memo, funds)
|
||||
.await
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ where
|
||||
.ok_or_else(|| NyxdError::unavailable_contract_address("coconut bandwidth contract"))?;
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
|
||||
self.execute(
|
||||
signer_address,
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ where
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
group_contract_address,
|
||||
|
||||
+1
-1
@@ -667,7 +667,7 @@ where
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let memo = msg.default_memo();
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
mixnet_contract_address,
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ where
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
multisig_contract_address,
|
||||
|
||||
+1
-1
@@ -165,7 +165,7 @@ where
|
||||
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
performance_contract_address,
|
||||
|
||||
+1
-1
@@ -375,7 +375,7 @@ where
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier())));
|
||||
let memo = msg.name().to_string();
|
||||
|
||||
let signer_address = &self.signer_addresses()?[0];
|
||||
let signer_address = &self.signer_addresses()[0];
|
||||
self.execute(
|
||||
signer_address,
|
||||
vesting_contract_address,
|
||||
|
||||
@@ -324,7 +324,7 @@ where
|
||||
{
|
||||
type Error = S::Error;
|
||||
|
||||
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
|
||||
fn get_accounts(&self) -> &[AccountData] {
|
||||
self.signer.get_accounts()
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::signing::signer::NoSigner;
|
||||
use crate::signing::signer::OfflineSigner;
|
||||
use crate::signing::tx_signer::TxSigner;
|
||||
use crate::signing::AccountData;
|
||||
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient};
|
||||
use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient};
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::tendermint::{abci, evidence::Evidence, Genesis};
|
||||
use cosmrs::tx::{Raw, SignDoc};
|
||||
@@ -158,12 +158,13 @@ impl NyxdClient<HttpClient> {
|
||||
}
|
||||
}
|
||||
|
||||
impl NyxdClient<ReqwestRpcClient> {
|
||||
#[allow(deprecated)]
|
||||
impl NyxdClient<crate::ReqwestRpcClient> {
|
||||
pub fn connect_reqwest(
|
||||
config: Config,
|
||||
endpoint: Url,
|
||||
) -> Result<QueryReqwestRpcNyxdClient, NyxdError> {
|
||||
let client = ReqwestRpcClient::new(endpoint);
|
||||
let client = crate::ReqwestRpcClient::new(endpoint);
|
||||
|
||||
Ok(NyxdClient {
|
||||
client: MaybeSigningClient::new(client, (&config).into()),
|
||||
@@ -195,18 +196,19 @@ impl NyxdClient<HttpClient, DirectSecp256k1HdWallet> {
|
||||
let client = http_client(endpoint)?;
|
||||
|
||||
let prefix = &config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)?;
|
||||
Ok(Self::connect_with_signer(config, client, wallet))
|
||||
}
|
||||
}
|
||||
|
||||
impl NyxdClient<ReqwestRpcClient, DirectSecp256k1HdWallet> {
|
||||
#[allow(deprecated)]
|
||||
impl NyxdClient<crate::ReqwestRpcClient, DirectSecp256k1HdWallet> {
|
||||
pub fn connect_reqwest_with_mnemonic(
|
||||
config: Config,
|
||||
endpoint: Url,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> DirectSigningReqwestRpcNyxdClient {
|
||||
let client = ReqwestRpcClient::new(endpoint);
|
||||
let client = crate::ReqwestRpcClient::new(endpoint);
|
||||
|
||||
let prefix = &config.chain_details.bech32_account_prefix;
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
@@ -391,17 +393,12 @@ where
|
||||
S: OfflineSigner + Send + Sync,
|
||||
NyxdError: From<<S as OfflineSigner>::Error>,
|
||||
{
|
||||
pub fn signing_account(&self) -> Result<AccountData, NyxdError> {
|
||||
pub fn signing_account(&self) -> Result<&AccountData, NyxdError> {
|
||||
Ok(self.find_account(&self.address())?)
|
||||
}
|
||||
|
||||
pub fn address(&self) -> AccountId {
|
||||
match self.client.signer_addresses() {
|
||||
Ok(addresses) => addresses[0].clone(),
|
||||
Err(_) => {
|
||||
panic!("key derivation failure")
|
||||
}
|
||||
}
|
||||
self.client.signer_addresses()[0].clone()
|
||||
}
|
||||
|
||||
pub fn mix_coin(&self, amount: u128) -> Coin {
|
||||
@@ -867,7 +864,7 @@ where
|
||||
{
|
||||
type Error = S::Error;
|
||||
|
||||
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
|
||||
fn get_accounts(&self) -> &[AccountData] {
|
||||
self.client.get_accounts()
|
||||
}
|
||||
|
||||
|
||||
@@ -42,12 +42,15 @@ macro_rules! perform_with_compat {
|
||||
}};
|
||||
}
|
||||
|
||||
// the separate implementation is now completely redundant
|
||||
#[deprecated(note = "use HttpClient directly instead")]
|
||||
pub struct ReqwestRpcClient {
|
||||
compat: CompatMode,
|
||||
inner: reqwest::Client,
|
||||
url: Url,
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl ReqwestRpcClient {
|
||||
pub fn new(url: Url) -> Self {
|
||||
ReqwestRpcClient {
|
||||
@@ -131,6 +134,7 @@ impl TendermintRpcErrorMap for reqwest::Error {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl TendermintRpcClient for ReqwestRpcClient {
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::signing::signer::{OfflineSigner, SigningError};
|
||||
use crate::signing::{AccountData, Secp256k1Derivation};
|
||||
use cosmrs::bip32::{DerivationPath, XPrv};
|
||||
use cosmrs::crypto::secp256k1::SigningKey;
|
||||
use cosmrs::crypto::PublicKey;
|
||||
use crate::signing::{
|
||||
derive_extended_private_key, derive_keypair, AccountData, Secp256k1Derivation, Secp256k1Keypair,
|
||||
};
|
||||
use bip32::XPrv;
|
||||
use cosmrs::bip32::DerivationPath;
|
||||
use cosmrs::tx;
|
||||
use cosmrs::tx::SignDoc;
|
||||
use nym_config::defaults;
|
||||
use std::borrow::Cow;
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
|
||||
type Secp256k1Keypair = (SigningKey, PublicKey);
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum DirectSecp256k1HdWalletError {
|
||||
#[error(transparent)]
|
||||
@@ -36,28 +36,22 @@ pub enum DirectSecp256k1HdWalletError {
|
||||
}
|
||||
|
||||
// TODO: maybe lock this one behind feature flag?
|
||||
#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct DirectSecp256k1HdWallet {
|
||||
/// Base secret
|
||||
secret: bip39::Mnemonic,
|
||||
|
||||
/// BIP39 seed
|
||||
seed: [u8; 64],
|
||||
|
||||
// An unfortunate result of immature rust async story is that async traits (only available in the separate package)
|
||||
// can't yet figure out everything and if we stored our derived account data on the struct,
|
||||
// that would include the secret key which is a dyn EcdsaSigner and hence not Sync making the wallet
|
||||
// not Sync and if used on the signing client in an async trait, it wouldn't be Send
|
||||
/// Derivation instructions
|
||||
/// Derived accounts
|
||||
#[zeroize(skip)]
|
||||
accounts: Vec<Secp256k1Derivation>,
|
||||
// unfortunately `dyn EcdsaSigner` does not guarantee Zeroize
|
||||
accounts: Vec<AccountData>,
|
||||
}
|
||||
|
||||
impl OfflineSigner for DirectSecp256k1HdWallet {
|
||||
type Error = DirectSecp256k1HdWalletError;
|
||||
|
||||
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
|
||||
self.try_derive_accounts()
|
||||
fn get_accounts(&self) -> &[AccountData] {
|
||||
&self.accounts
|
||||
}
|
||||
|
||||
fn sign_direct_with_account(
|
||||
@@ -77,55 +71,27 @@ impl DirectSecp256k1HdWallet {
|
||||
}
|
||||
|
||||
/// Restores a wallet from the given BIP39 mnemonic using default options.
|
||||
#[deprecated(
|
||||
note = "this function can potentially panic if accounts can't be derived correctly. please use .checked_from_mnemonic() instead"
|
||||
)]
|
||||
pub fn from_mnemonic(prefix: &str, mnemonic: bip39::Mnemonic) -> Self {
|
||||
// unfortunately due to backwards compatibility requirements,
|
||||
// we can't change signature of this method
|
||||
#[allow(deprecated)]
|
||||
DirectSecp256k1HdWalletBuilder::new(prefix).build(mnemonic)
|
||||
}
|
||||
|
||||
/// Restores a wallet from the given BIP39 mnemonic using default options.
|
||||
pub fn checked_from_mnemonic(
|
||||
prefix: &str,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> Result<Self, DirectSecp256k1HdWalletError> {
|
||||
DirectSecp256k1HdWalletBuilder::new(prefix).try_build(mnemonic)
|
||||
}
|
||||
|
||||
pub fn generate(prefix: &str, word_count: usize) -> Result<Self, DirectSecp256k1HdWalletError> {
|
||||
let mneomonic = bip39::Mnemonic::generate(word_count)?;
|
||||
Ok(Self::from_mnemonic(prefix, mneomonic))
|
||||
}
|
||||
|
||||
fn derive_keypair(
|
||||
&self,
|
||||
hd_path: &DerivationPath,
|
||||
) -> Result<Secp256k1Keypair, DirectSecp256k1HdWalletError> {
|
||||
let extended_private_key = XPrv::derive_from_path(self.seed, hd_path)?;
|
||||
|
||||
let private_key: SigningKey = extended_private_key.into();
|
||||
let public_key = private_key.public_key();
|
||||
|
||||
Ok((private_key, public_key))
|
||||
}
|
||||
|
||||
pub fn derive_extended_private_key(
|
||||
&self,
|
||||
hd_path: &DerivationPath,
|
||||
) -> Result<XPrv, DirectSecp256k1HdWalletError> {
|
||||
Ok(XPrv::derive_from_path(self.seed, hd_path)?)
|
||||
}
|
||||
|
||||
pub fn try_derive_accounts(&self) -> Result<Vec<AccountData>, DirectSecp256k1HdWalletError> {
|
||||
let mut accounts = Vec::with_capacity(self.accounts.len());
|
||||
for derivation_info in &self.accounts {
|
||||
let keypair = self.derive_keypair(&derivation_info.hd_path)?;
|
||||
|
||||
// it seems this can only fail if the provided account prefix is invalid
|
||||
let address = keypair
|
||||
.1
|
||||
.account_id(&derivation_info.prefix)
|
||||
.map_err(
|
||||
|source| DirectSecp256k1HdWalletError::AccountDerivationError { source },
|
||||
)?;
|
||||
|
||||
accounts.push(AccountData {
|
||||
address,
|
||||
public_key: keypair.1,
|
||||
private_key: keypair.0,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(accounts)
|
||||
Self::checked_from_mnemonic(prefix, mneomonic)
|
||||
}
|
||||
|
||||
pub fn secret(&self) -> &bip39::Mnemonic {
|
||||
@@ -142,6 +108,43 @@ impl DirectSecp256k1HdWallet {
|
||||
pub fn mnemonic_string(&self) -> Zeroizing<String> {
|
||||
Zeroizing::new(self.secret.to_string())
|
||||
}
|
||||
|
||||
pub fn account_seed<'a, P: Into<Cow<'a, str>>>(
|
||||
&self,
|
||||
bip39_password: P,
|
||||
) -> Zeroizing<[u8; 64]> {
|
||||
Zeroizing::new(self.secret.to_seed(bip39_password))
|
||||
}
|
||||
|
||||
/// Derive an extended private key from the stored account secret assuming no bip39 password
|
||||
#[deprecated(
|
||||
note = "use derive_extended_private_key_with_password to ensure correct derivation if used bip39 password"
|
||||
)]
|
||||
pub fn derive_extended_private_key(
|
||||
&self,
|
||||
hd_path: &DerivationPath,
|
||||
) -> Result<XPrv, DirectSecp256k1HdWalletError> {
|
||||
let seed = self.account_seed("");
|
||||
derive_extended_private_key(seed, hd_path)
|
||||
}
|
||||
|
||||
pub fn derive_keypair<'a, P: Into<Cow<'a, str>>>(
|
||||
&self,
|
||||
hd_path: &DerivationPath,
|
||||
bip39_password: P,
|
||||
) -> Result<Secp256k1Keypair, DirectSecp256k1HdWalletError> {
|
||||
let seed = self.account_seed(bip39_password);
|
||||
derive_keypair(seed, hd_path)
|
||||
}
|
||||
|
||||
pub fn derive_extended_private_key_with_password<'a, P: Into<Cow<'a, str>>>(
|
||||
&self,
|
||||
hd_path: &DerivationPath,
|
||||
bip39_password: P,
|
||||
) -> Result<XPrv, DirectSecp256k1HdWalletError> {
|
||||
let seed = self.account_seed(bip39_password);
|
||||
derive_extended_private_key(seed, hd_path)
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -188,23 +191,39 @@ impl DirectSecp256k1HdWalletBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
note = "this function can potentially panic if accounts can't be derived correctly. please use .try_build() instead"
|
||||
)]
|
||||
pub fn build(self, mnemonic: bip39::Mnemonic) -> DirectSecp256k1HdWallet {
|
||||
let seed = mnemonic.to_seed(&self.bip39_password);
|
||||
// unfortunately due to backwards compatibility requirements,
|
||||
// we can't change signature of this method
|
||||
#[allow(clippy::expect_used)]
|
||||
self.try_build(mnemonic)
|
||||
.expect("account derivation failure")
|
||||
}
|
||||
|
||||
pub fn try_build(
|
||||
self,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
) -> Result<DirectSecp256k1HdWallet, DirectSecp256k1HdWalletError> {
|
||||
let seed = Zeroizing::new(mnemonic.to_seed(&self.bip39_password));
|
||||
let prefix = self.prefix.clone();
|
||||
let accounts = self
|
||||
.hd_paths
|
||||
.iter()
|
||||
.map(|hd_path| Secp256k1Derivation {
|
||||
hd_path: hd_path.clone(),
|
||||
prefix: prefix.clone(),
|
||||
.map(|hd_path| {
|
||||
Secp256k1Derivation {
|
||||
hd_path: hd_path.clone(),
|
||||
prefix: prefix.clone(),
|
||||
}
|
||||
.try_derive_account(&seed)
|
||||
})
|
||||
.collect();
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
DirectSecp256k1HdWallet {
|
||||
Ok(DirectSecp256k1HdWallet {
|
||||
accounts,
|
||||
seed,
|
||||
secret: mnemonic,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +234,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn generating_account_addresses() {
|
||||
fn generating_account_addresses() -> anyhow::Result<()> {
|
||||
// test vectors produced from our js wallet
|
||||
let mnemonics = ["crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove",
|
||||
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
|
||||
@@ -230,11 +249,10 @@ mod tests {
|
||||
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
|
||||
];
|
||||
for (idx, mnemonic) in mnemonics.iter().enumerate() {
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(&prefix, mnemonic.parse().unwrap());
|
||||
assert_eq!(
|
||||
wallet.try_derive_accounts().unwrap()[0].address,
|
||||
addrs[idx].parse().unwrap()
|
||||
)
|
||||
let wallet =
|
||||
DirectSecp256k1HdWallet::checked_from_mnemonic(&prefix, mnemonic.parse()?)?;
|
||||
assert_eq!(wallet.signer_addresses()[0], addrs[idx].parse().unwrap());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWalletError;
|
||||
use bip32::XPrv;
|
||||
use cosmrs::bip32::DerivationPath;
|
||||
use cosmrs::crypto::secp256k1::SigningKey;
|
||||
use cosmrs::crypto::PublicKey;
|
||||
@@ -12,14 +14,64 @@ pub mod direct_wallet;
|
||||
pub mod signer;
|
||||
pub mod tx_signer;
|
||||
|
||||
pub(crate) type Secp256k1Keypair = (SigningKey, PublicKey);
|
||||
|
||||
/// Derivation information required to derive a keypair and an address from a mnemonic.
|
||||
#[derive(Debug, Clone)]
|
||||
struct Secp256k1Derivation {
|
||||
pub(crate) struct Secp256k1Derivation {
|
||||
hd_path: DerivationPath,
|
||||
prefix: String,
|
||||
}
|
||||
|
||||
// TODO: is this struct going to be derivable with other signer types?
|
||||
impl Secp256k1Derivation {
|
||||
pub(crate) fn try_derive_account<S>(
|
||||
&self,
|
||||
seed: S,
|
||||
) -> Result<AccountData, DirectSecp256k1HdWalletError>
|
||||
where
|
||||
S: AsRef<[u8]>,
|
||||
{
|
||||
let keypair = derive_keypair(seed, &self.hd_path)?;
|
||||
|
||||
// it seems this can only fail if the provided account prefix is invalid
|
||||
let address = keypair
|
||||
.1
|
||||
.account_id(&self.prefix)
|
||||
.map_err(|source| DirectSecp256k1HdWalletError::AccountDerivationError { source })?;
|
||||
|
||||
Ok(AccountData {
|
||||
address,
|
||||
public_key: keypair.1,
|
||||
private_key: keypair.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derive_keypair<S>(
|
||||
seed: S,
|
||||
hd_path: &DerivationPath,
|
||||
) -> Result<Secp256k1Keypair, DirectSecp256k1HdWalletError>
|
||||
where
|
||||
S: AsRef<[u8]>,
|
||||
{
|
||||
let extended_private_key = derive_extended_private_key(seed, hd_path)?;
|
||||
|
||||
let private_key: SigningKey = extended_private_key.into();
|
||||
let public_key = private_key.public_key();
|
||||
|
||||
Ok((private_key, public_key))
|
||||
}
|
||||
|
||||
pub fn derive_extended_private_key<S>(
|
||||
seed: S,
|
||||
hd_path: &DerivationPath,
|
||||
) -> Result<XPrv, DirectSecp256k1HdWalletError>
|
||||
where
|
||||
S: AsRef<[u8]>,
|
||||
{
|
||||
Ok(XPrv::derive_from_path(seed, hd_path)?)
|
||||
}
|
||||
|
||||
pub struct AccountData {
|
||||
pub address: AccountId,
|
||||
|
||||
|
||||
@@ -33,23 +33,18 @@ pub enum SignerType {
|
||||
pub trait OfflineSigner {
|
||||
type Error: From<SigningError>;
|
||||
|
||||
// I really dislike existence of this function because it makes you re-derive your key **twice** for each contract transaction
|
||||
fn signer_addresses(&self) -> Result<Vec<AccountId>, Self::Error> {
|
||||
let derived_addresses = self
|
||||
.get_accounts()?
|
||||
.into_iter()
|
||||
.map(|account| account.address)
|
||||
.collect();
|
||||
Ok(derived_addresses)
|
||||
fn signer_addresses(&self) -> Vec<AccountId> {
|
||||
self.get_accounts()
|
||||
.iter()
|
||||
.map(|account| account.address.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error>;
|
||||
fn get_accounts(&self) -> &[AccountData];
|
||||
|
||||
fn find_account(&self, signer_address: &AccountId) -> Result<AccountData, Self::Error> {
|
||||
// TODO: we could really use some zeroize action here
|
||||
let accounts = self.get_accounts()?;
|
||||
accounts
|
||||
.into_iter()
|
||||
fn find_account(&self, signer_address: &AccountId) -> Result<&AccountData, Self::Error> {
|
||||
self.get_accounts()
|
||||
.iter()
|
||||
.find(|account| &account.address == signer_address)
|
||||
.ok_or_else(|| {
|
||||
SigningError::AccountNotFound {
|
||||
@@ -76,7 +71,7 @@ pub trait OfflineSigner {
|
||||
message: M,
|
||||
) -> Result<Signature, Self::Error> {
|
||||
let signer = self.find_account(signer_address)?;
|
||||
self.sign_raw_with_account(&signer, message)
|
||||
self.sign_raw_with_account(signer, message)
|
||||
}
|
||||
|
||||
fn sign_direct(
|
||||
@@ -85,7 +80,7 @@ pub trait OfflineSigner {
|
||||
sign_doc: SignDoc,
|
||||
) -> Result<tx::Raw, Self::Error> {
|
||||
let signer = self.find_account(signer_address)?;
|
||||
self.sign_direct_with_account(&signer, sign_doc)
|
||||
self.sign_direct_with_account(signer, sign_doc)
|
||||
}
|
||||
|
||||
// unless explicitly defined, each signing method is unsupported
|
||||
@@ -122,7 +117,7 @@ pub struct NoSigner;
|
||||
// impl OfflineSigner for NoSigner {
|
||||
// type Error = SignerUnavailable;
|
||||
//
|
||||
// fn get_accounts(&self) -> Result<Vec<AccountData>, Self::Error> {
|
||||
// fn get_accounts(&self) -> &[AccountData] {
|
||||
// return Err(SignerUnavailable);
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -50,7 +50,7 @@ pub trait TxSigner: OfflineSigner {
|
||||
)
|
||||
.map_err(|source| SigningError::SignDocFailure { source })?;
|
||||
|
||||
self.sign_direct_with_account(&account_from_signer, sign_doc)
|
||||
self.sign_direct_with_account(account_from_signer, sign_doc)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use clap::Parser;
|
||||
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use nym_validator_client::signing::signer::OfflineSigner;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -15,9 +16,10 @@ pub fn create_account(args: Args, prefix: &str) {
|
||||
let word_count = args.word_count.unwrap_or(24);
|
||||
let mnemonic = bip39::Mnemonic::generate(word_count).expect("failed to generate mnemonic!");
|
||||
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)
|
||||
.expect("failed to derive accounts!");
|
||||
|
||||
// Output address and mnemonics into separate lines for easier parsing
|
||||
println!("{}", wallet.mnemonic_string().as_str());
|
||||
println!("{}", wallet.try_derive_accounts().unwrap()[0].address());
|
||||
println!("{}", wallet.signer_addresses()[0]);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::show_error;
|
||||
use clap::Parser;
|
||||
use log::{error, info};
|
||||
use nym_validator_client::nyxd::{AccountId, CosmWasmClient};
|
||||
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::show_error;
|
||||
use nym_validator_client::signing::signer::OfflineSigner;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
@@ -50,20 +50,19 @@ pub async fn get_pubkey(
|
||||
}
|
||||
|
||||
pub fn get_pubkey_from_mnemonic(address: AccountId, prefix: &str, mnemonic: bip39::Mnemonic) {
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
match wallet.try_derive_accounts() {
|
||||
Ok(accounts) => match accounts.iter().find(|a| *a.address() == address) {
|
||||
Some(account) => {
|
||||
println!("{}", account.public_key().to_string());
|
||||
}
|
||||
None => {
|
||||
error!("Could not derive key that matches {address}")
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to derive accounts. {e}");
|
||||
let wallet = match DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic) {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
error!("Failed to derive accounts. {err}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(account) = wallet.find_account(&address) else {
|
||||
error!("Could not derive key that matches {address}");
|
||||
return;
|
||||
};
|
||||
println!("{}", account.public_key().to_string());
|
||||
}
|
||||
|
||||
pub async fn get_pubkey_from_chain(address: AccountId, client: &QueryClient) {
|
||||
|
||||
@@ -36,32 +36,35 @@ pub fn sign(args: Args, prefix: &str, mnemonic: Option<bip39::Mnemonic>) {
|
||||
return;
|
||||
}
|
||||
|
||||
let wallet =
|
||||
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.expect("mnemonic not set"));
|
||||
match wallet.try_derive_accounts() {
|
||||
Ok(accounts) => match accounts.first() {
|
||||
Some(account) => {
|
||||
let msg = args.message.into_bytes();
|
||||
match wallet.sign_raw_with_account(account, msg) {
|
||||
Ok(signature) => {
|
||||
let output = SignatureOutputJson {
|
||||
account_id: account.address().to_string(),
|
||||
public_key: account.public_key(),
|
||||
signature_as_hex: signature.to_string(),
|
||||
};
|
||||
println!("{}", json!(output));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to sign message. {e}");
|
||||
}
|
||||
let wallet = match DirectSecp256k1HdWallet::checked_from_mnemonic(
|
||||
prefix,
|
||||
mnemonic.expect("mnemonic not set"),
|
||||
) {
|
||||
Ok(wallet) => wallet,
|
||||
Err(err) => {
|
||||
error!("Could not derive an account key from the mnemonic: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
match wallet.get_accounts().first() {
|
||||
Some(account) => {
|
||||
let msg = args.message.into_bytes();
|
||||
match wallet.sign_raw_with_account(account, msg) {
|
||||
Ok(signature) => {
|
||||
let output = SignatureOutputJson {
|
||||
account_id: account.address().to_string(),
|
||||
public_key: account.public_key(),
|
||||
signature_as_hex: signature.to_string(),
|
||||
};
|
||||
println!("{}", json!(output));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to sign message. {e}");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("Could not derive an account key from the mnemonic",)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to derive accounts. {e}");
|
||||
}
|
||||
None => {
|
||||
error!("Could not derive an account key from the mnemonic",)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
time = { workspace = true }
|
||||
time = { workspace = true, features = ["serde"] }
|
||||
tokio = { workspace = true }
|
||||
si-scale = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::Date;
|
||||
|
||||
const KIND: &str = "vpn_client_stats_report";
|
||||
const VERSION: &str = "v1";
|
||||
const BASIC_REPORT_KIND: &str = "vpn_client_stats_report";
|
||||
const SESSION_REPORT_KIND: &str = "vpn_client_session_report";
|
||||
const VERSION_1: &str = "v1";
|
||||
const VERSION_2: &str = "v2";
|
||||
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -13,15 +16,14 @@ pub struct VpnClientStatsReport {
|
||||
pub api_version: String,
|
||||
pub stats_id: String,
|
||||
pub static_information: StaticInformationReport,
|
||||
//SW called it basic so we can swap it easily down the line for more data
|
||||
pub basic_usage: Option<UsageReport>,
|
||||
}
|
||||
|
||||
impl VpnClientStatsReport {
|
||||
pub fn new(stats_id: String, static_information: StaticInformationReport) -> Self {
|
||||
VpnClientStatsReport {
|
||||
kind: KIND.into(),
|
||||
api_version: VERSION.into(),
|
||||
kind: BASIC_REPORT_KIND.into(),
|
||||
api_version: VERSION_1.into(),
|
||||
stats_id,
|
||||
static_information,
|
||||
basic_usage: None,
|
||||
@@ -34,6 +36,34 @@ impl VpnClientStatsReport {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VpnClientStatsReportV2 {
|
||||
pub kind: String,
|
||||
pub api_version: String,
|
||||
pub stats_id: String,
|
||||
|
||||
pub static_information: StaticInformationReport,
|
||||
pub session_report: SessionReport,
|
||||
}
|
||||
|
||||
impl VpnClientStatsReportV2 {
|
||||
pub fn new(
|
||||
stats_id: String,
|
||||
static_information: StaticInformationReport,
|
||||
session_report: SessionReport,
|
||||
) -> Self {
|
||||
VpnClientStatsReportV2 {
|
||||
kind: SESSION_REPORT_KIND.into(),
|
||||
api_version: VERSION_2.into(),
|
||||
stats_id,
|
||||
static_information,
|
||||
session_report,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StaticInformationReport {
|
||||
@@ -49,3 +79,17 @@ pub struct UsageReport {
|
||||
pub connection_time_ms: Option<i32>,
|
||||
pub two_hop: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SessionReport {
|
||||
pub start_day_utc: Date,
|
||||
pub connection_time_ms: i32,
|
||||
pub tunnel_type: String,
|
||||
pub retry_attempt: i32,
|
||||
pub session_duration_min: i32,
|
||||
pub disconnection_time_ms: i32,
|
||||
pub exit_id: String,
|
||||
pub follow_up_id: Option<String>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use nym_mixnet_contract_common::ContractsCommonError;
|
||||
use nym_validator_client::error::TendermintRpcError;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWalletError;
|
||||
use nym_validator_client::{nyxd::error::NyxdError, ValidatorClientError};
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::io;
|
||||
@@ -59,6 +60,8 @@ pub enum TypesError {
|
||||
#[from]
|
||||
source: cosmwasm_std::DecimalRangeExceeded,
|
||||
},
|
||||
#[error(transparent)]
|
||||
AccountDerivationFailure(#[from] DirectSecp256k1HdWalletError),
|
||||
#[error("No nym API URL configured")]
|
||||
NoNymApiUrlConfigured,
|
||||
#[error("{0} is not a valid amount string")]
|
||||
@@ -113,6 +116,7 @@ impl From<ValidatorClientError> for TypesError {
|
||||
ValidatorClientError::InconsistentPagedMetadata => {
|
||||
TypesError::InconsistentPagedMetadata
|
||||
}
|
||||
ValidatorClientError::AccountDerivationFailure { source } => source.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,12 +255,10 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(deps: DepsMut<'_>, env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
set_build_information!(deps.storage)?;
|
||||
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
|
||||
crate::queued_migrations::introduce_historical_epochs(deps, env)?;
|
||||
|
||||
Ok(Response::new())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,2 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::epoch_state::storage::HISTORICAL_EPOCH;
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::{DepsMut, Env};
|
||||
|
||||
pub fn introduce_historical_epochs(deps: DepsMut, env: Env) -> Result<(), ContractError> {
|
||||
if HISTORICAL_EPOCH.may_load(deps.storage)?.is_some() {
|
||||
return Err(ContractError::FailedMigration {
|
||||
comment: "this migration has already been run before".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let current = crate::epoch_state::storage::CURRENT_EPOCH.load(deps.storage)?;
|
||||
// we won't have information on intermediate states prior to now, but that's not the end of the world
|
||||
HISTORICAL_EPOCH.save(deps.storage, ¤t, env.block.height)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+4
@@ -32,6 +32,10 @@ chmod +x quic_bridge_deployment.sh
|
||||
```sh
|
||||
./quic_bridge_deployment.sh full_bridge_setup
|
||||
```
|
||||
- If you prefer a non-interactive mode, run the command with this variable (and skip next step):
|
||||
```sh
|
||||
NONINTERACTIVE=1 quick_bridge_deployment.sh full_bridge_setup
|
||||
```
|
||||
|
||||
###### 3. Follow the interactive prompts
|
||||
- Make sure you don't just press enter to insert default values if your setup is different, for example in case of path to the config file
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
import { Tabs } from 'nextra/components';
|
||||
import { Steps } from 'nextra/components';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
|
||||
export const ManagerIPOutput = () => (
|
||||
<div>
|
||||
Correct <code>./network-tunnel-manager.sh fetch_and_display_ipv6</code> output
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ManagerTablesOutput = () => (
|
||||
<div>
|
||||
Correct <code>./network-tunnel-manager.sh check_nymtun_iptables</code> output
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ShowTun = () => (
|
||||
<div>
|
||||
Correct <code>ip addr show nymtun0</code> output
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
<Callout>
|
||||
We recommend operators to configure their `nym-node` with the full routing configuration.
|
||||
|
||||
However, most of the time the packets sent through the Mixnet are IPv4 based. The IPv6 packets are still pretty rare and therefore it's not mandatory from operational point of view to have this configuration implemented if you running only `mixnode` mode.
|
||||
|
||||
If you preparing to run a `nym-node` with all modes enabled in the future, this setup is required.
|
||||
</Callout>
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
Networking configuration across different ISPs and various operation systems does not have a generic solution. If the provided configuration setup doesn't solve your problem check out [IPv6 troubleshooting](/operators/troubleshooting/vps-isp.mdx#ipv6-troubleshooting) page. Be aware that you may have to do more research, customised adjustments or contact your ISP to change settings for your VPS.
|
||||
</ Callout>
|
||||
|
||||
**Network Tunnel Manager ([`network-tunnel-manager.sh`](https://github.com/nymtech/nym/blob/develop/scripts/network_tunnel_manager.sh), NTM) is currently the one tool hadling the configuration of `nym-node` hosting server, according to the required design (node's [functionality](/operators/nodes/nym-node/setup#functionality-mode), WireGuard setup etc).**
|
||||
|
||||
**NTM cand administrate these areas:**
|
||||
|
||||
* IPv4 and IPv6 routing to the internet
|
||||
|
||||
* The `nymtun0` interface (Mixnet / 5-hop): dynamically managed by the `exit-gateway` service. When the service is stopped, `nymtun0` disappears, and when started, `nymtun0` is recreated.
|
||||
|
||||
* The `nymwg` interface (WG / 2-hop): used for creating a secure wireguard tunnel as part of the Nym Network configuration.
|
||||
|
||||
* `iptables` rules specific to `nymwg` to ensure proper routing and forwarding through the wireguard tunnel. The `nymwg` interface needs to be correctly configured and active for the related commands to function properly. This includes applying or removing iptables rules and running connectivity tests through the `nymwg` tunnel.
|
||||
|
||||
* WireGuard exit policy: Mixnet uses a common [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt), to apply the same for WG, the operators need to set that one up on their server using `iptables` rules.
|
||||
|
||||
* Testing and validating all above
|
||||
|
||||
**Before starting with the following configuration, make sure you have the [latest `nym-node` binary](https://github.com/nymtech/nym/releases) installed and your [VPS setup](../preliminary-steps/vps-setup.mdx) finished properly!**
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
**Run the following steps as root!**
|
||||
</ Callout>
|
||||
|
||||
**Choose configuration command according your setup**
|
||||
|
||||
<div>
|
||||
<Tabs items={[
|
||||
<strong>New <code>nym-node</ code> full configuration</strong>,
|
||||
<strong>Existing <code>nym-node</ code> full configuration</strong>,
|
||||
<strong>Step-by-step or Partial configuration</strong>
|
||||
]} defaultIndex={0}>
|
||||
<Tabs.Tab>
|
||||
This design is meant for operators setting up a new node on a fresh machine and it will result with a complete server readiness for routing as Entry Gateway and Exit Gateway in both Mixnet and WireGuard mode.
|
||||
|
||||
<Steps>
|
||||
###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
|
||||
|
||||
```sh
|
||||
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
|
||||
chmod +x network-tunnel-manager.sh && \
|
||||
./network-tunnel-manager.sh --help
|
||||
```
|
||||
|
||||
###### 2. Make sure your `nym-node` service is up and running and bonded
|
||||
|
||||
- **If you setting up a new node and not upgrading an existing one, keep it running and [bond](/operators/nodes/nym-node/bonding.mdx) your node now! Then come back here and follow the rest of the configuration.**
|
||||
|
||||
###### 3. Run command for configuration:
|
||||
- Nodes with **WireGuard enabled**: Configures interfaces (`nymtun0` and `nymwg`), IPv4 and IPv6 routing, WireGuard exit policy and does validation tests
|
||||
```sh
|
||||
./network-tunnel-manager.sh complete_networking_configuration
|
||||
```
|
||||
- Nodes with **WireGuard disabled**: Does everything like the command above *without WireGuard exit policy*
|
||||
```sh
|
||||
./network-tunnel-manager.sh nym_tunnel_setup
|
||||
```
|
||||
</ Steps>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
This is meant for operators configuring an existing and bonded node and it will result with a complete server readiness for routing as Entry Gateway and Exit Gateway in both Mixnet and WireGuard mode.
|
||||
|
||||
<Steps>
|
||||
###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
|
||||
|
||||
```sh
|
||||
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
|
||||
chmod +x network-tunnel-manager.sh && \
|
||||
./network-tunnel-manager.sh --help
|
||||
```
|
||||
|
||||
###### 2. Run command for configuration:
|
||||
- Nodes with **WireGuard enabled**: Configures interfaces (`nymtun0` and `nymwg`), IPv4 and IPv6 routing, WireGuard exit policy and does validation tests
|
||||
```sh
|
||||
./network-tunnel-manager.sh complete_networking_configuration
|
||||
```
|
||||
- Nodes with **WireGuard disabled**: Does everything like the command above *without WireGuard exit policy*
|
||||
```sh
|
||||
./network-tunnel-manager.sh nym_tunnel_setup
|
||||
```
|
||||
</ Steps>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
<Steps>
|
||||
This design is meant for operators who want to do their server configuration step by step or choose only some parts of the setup.
|
||||
|
||||
###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
|
||||
|
||||
```sh
|
||||
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
|
||||
chmod +x network-tunnel-manager.sh && \
|
||||
./network-tunnel-manager.sh --help
|
||||
```
|
||||
|
||||
###### 2. Make sure your `nym-node` service is up and running and bonded
|
||||
|
||||
- **If you setting up a new node and not upgrading an existing one, keep it running and [bond](/operators/nodes/nym-node/bonding.mdx) your node now! Then come back here and follow the rest of the configuration.**
|
||||
|
||||
###### 3. Choose steps according your need
|
||||
|
||||
> You should be certain in your selection when configuring only various parts of the server.
|
||||
|
||||
###### Setup IP tables rules
|
||||
|
||||
- Delete IP tables rules for IPv4 and IPv6 and apply new ones:
|
||||
```sh
|
||||
./network-tunnel-manager.sh remove_duplicate_rules nymtun0
|
||||
|
||||
./network-tunnel-manager.sh apply_iptables_rules
|
||||
```
|
||||
|
||||
- The process may prompt you if you want to save current IPv4 and IPv6 rules, choose yes.
|
||||
|
||||

|
||||
|
||||
- At this point you should see a `global ipv6` address.
|
||||
```sh
|
||||
./network-tunnel-manager.sh fetch_and_display_ipv6
|
||||
```
|
||||
<br />
|
||||
<AccordionTemplate name={<ManagerTablesOutput/>}>
|
||||
```sh
|
||||
iptables-persistent is already installed.
|
||||
Using IPv6 address: 2001:db8:a160::1/112 #the address will be different for you
|
||||
operation fetch_ipv6_address_nym_tun completed successfully.
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
###### Check Nymtun IP tables:
|
||||
|
||||
```sh
|
||||
./network-tunnel-manager.sh check_nymtun_iptables
|
||||
```
|
||||
|
||||
- If there's no process running it wouldn't return anything.
|
||||
- In case you see `nymtun0` but not active, this is probably because you are setting up a new (never bonded) node and not upgrading an existing one.
|
||||
|
||||
<br />
|
||||
<AccordionTemplate name={<ManagerIPOutput/>}>
|
||||
```sh
|
||||
iptables-persistent is already installed.
|
||||
network Device: eth0
|
||||
---------------------------------------
|
||||
|
||||
inspecting IPv4 firewall rules...
|
||||
Chain FORWARD (policy DROP 0 packets, 0 bytes)
|
||||
0 0 ufw-reject-forward all -- * * 0.0.0.0/0 0.0.0.0/0
|
||||
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
|
||||
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
|
||||
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
|
||||
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
|
||||
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
|
||||
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
|
||||
---------------------------------------
|
||||
|
||||
inspecting IPv6 firewall rules...
|
||||
Chain FORWARD (policy DROP 0 packets, 0 bytes)
|
||||
0 0 ufw6-reject-forward all * * ::/0 ::/0
|
||||
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
|
||||
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
|
||||
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
|
||||
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
|
||||
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
|
||||
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
|
||||
operation check_nymtun_iptables completed successfully.
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
###### Remove old and apply new rules for wireguad routing
|
||||
|
||||
```sh
|
||||
../network-tunnel-manager.sh remove_duplicate_rules nymwg
|
||||
|
||||
./network-tunnel-manager.sh apply_iptables_rules_wg
|
||||
```
|
||||
|
||||
###### Apply rules to configure DNS routing and allow ICMP piung test for node probing (network testing)
|
||||
|
||||
```sh
|
||||
./network-tunnel-manager.sh configure_dns_and_icmp_wg
|
||||
```
|
||||
###### Adjust and validate IP forwarding
|
||||
|
||||
```sh
|
||||
./network-tunnel-manager.sh adjust_ip_forwarding
|
||||
|
||||
./network-tunnel-manager.sh check_ipv6_ipv4_forwarding
|
||||
```
|
||||
|
||||
###### Check `nymtun0` interface and test routing configuration
|
||||
|
||||
```sh
|
||||
ip addr show nymtun0
|
||||
```
|
||||
|
||||
<br />
|
||||
<AccordionTemplate name={<ShowTun/>}>
|
||||
```sh
|
||||
# your addresses will be different
|
||||
8: nymtun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1420 qdisc fq_codel state UNKNOWN group default qlen 500
|
||||
link/none
|
||||
inet 10.0.0.1/16 scope global nymtun0
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 fc00::1/112 scope global
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 fe80::ad08:d167:5700:8c7c/64 scope link stable-privacy
|
||||
valid_lft forever preferred_lft forever`
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
- Validate your IPv6 and IPv4 networking by running a joke test via Mixnet:
|
||||
```sh
|
||||
./network-tunnel-manager.sh joke_through_the_mixnet
|
||||
```
|
||||
|
||||
- Validate your tunneling by running a joke test via WG:
|
||||
```sh
|
||||
../network-tunnel-manager.sh joke_through_wg_tunnel
|
||||
```
|
||||
|
||||
###### Enable wireguard
|
||||
|
||||
Now you can run your node with the `--wireguard-enabled true` flag or add it to your [systemd service config](#systemd). Restart your `nym-node` or [systemd](#2-following-steps-for-nym-nodes-running-as-systemd-service) service (recommended):
|
||||
|
||||
```sh
|
||||
systemctl daemon-reload && service nym-node restart
|
||||
```
|
||||
- Optionally, you can check if the node is running correctly by monitoring the service logs:
|
||||
|
||||
```sh
|
||||
journalctl -u nym-node.service -f -n 100
|
||||
```
|
||||
</ Steps>
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
Note that the functionality the node runs in is decided by [arguments on the node itself / in node's `config.toml`](/operators/nodes/nym-node/setup#functionality-mode), this tool only prepares the server.
|
||||
</ Callout>
|
||||
|
||||
Make sure that you get the validation of all connectivity. If there are still any problems, please refer to [troubleshooting section](/operators/troubleshooting/vps-isp.mdx#incorrect-gateway-network-check).
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Callout } from 'nextra/components';
|
||||
import { Tabs } from 'nextra/components';
|
||||
import { Steps } from 'nextra/components';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
import ExitPolicyInstallOutput from 'components/operators/snippets/wg-exit-policy-install-output.mdx';
|
||||
import ExitPolicyStatusOutput from 'components/operators/snippets/wg-exit-policy-status-output.mdx';
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
**In case you had used `network-tunnel-manager.sh` with the command `complete_networking_setup`, your WireGuard exit policy is already setup. You can test it in the next chapter.**
|
||||
</ Callout>
|
||||
|
||||
Nym Node running as Exit Gateway has contains multiple modules, one of them is Nym Network Requester(NR), routing TCP traffic to the internet. To make sure that the node is not just an open proxy, NR checks agains [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) following these conditions (in this exact order):
|
||||
|
||||
1. Do we explicitly block those IP addresses regardless of ports?
|
||||
2. Do we allow those specific ports regardless of IPs?
|
||||
3. Do block EVERYTHING else!
|
||||
|
||||
The exit policy is same for all NRs, the content is shaped by an offchain governance of Nym Node operators on our [forum](https://forum.nym.com/t/poll-a-new-nym-exit-policy-for-exit-gateways-and-the-nym-mixnet-is-inbound/464).
|
||||
|
||||
There is a caveat though. NR is only routing TCP streams and therefore any other type of routing than Mixnet is *not* filtered thorugh the exit policy. To ensure that Nym Nodes follow the same exit policy when routing IP packets through WireGuard and don't act as open proxies, the operators have to set up these rules via IP tables rules.
|
||||
|
||||
**For all routing configuration we provide one tool [`network-tunnel-manager.sh` (NTM)](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh). This tool manages WireGuard exit policy as well.**
|
||||
|
||||
In case you haven't run `network-tunnel-manager.sh` with the command `complete_networking_setup` you need to use NTM for WireGuard exit policy configuration.
|
||||
|
||||
**Folow these steps**
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
**Run the following steps as root!**
|
||||
</ Callout>
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
|
||||
|
||||
```sh
|
||||
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
|
||||
chmod +x network-tunnel-manager.sh && \
|
||||
./network-tunnel-manager.sh --help
|
||||
```
|
||||
|
||||
###### 2. Install exit policy
|
||||
|
||||
- Clear old rules and configure new ones:
|
||||
```sh
|
||||
./network-tunnel-manager.sh exit_policy_clear
|
||||
./network-tunnel-manager.sh exit_policy_install
|
||||
```
|
||||
- The output should look like this:
|
||||
<AccordionTemplate name="Cosole output">
|
||||
<ExitPolicyInstallOutput />
|
||||
</ AccordionTemplate>
|
||||
|
||||
|
||||
###### 3. Check status of your configuration
|
||||
```sh
|
||||
./network-tunnel-manager.sh exit_policy_status
|
||||
```
|
||||
|
||||
- The output should look like this:
|
||||
<AccordionTemplate name="Cosole output">
|
||||
<ExitPolicyStatusOutput />
|
||||
</ AccordionTemplate>
|
||||
</ Steps>
|
||||
|
||||
Now your WireGuard routing (2-hop) should have same rotuing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet) mode of NymVPN.
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Tabs } from 'nextra/components';
|
||||
import { Steps } from 'nextra/components';
|
||||
|
||||
import ExitPolicyTestServer from 'components/operators/snippets/wg-exit-policy-testing-from-server.mdx';
|
||||
import ExitPolicyTestOutside from 'components/operators/snippets/wg-exit-policy-testing-from-outside.mdx';
|
||||
|
||||
**For all routing configuration we provide one tool [`network-tunnel-manager.sh` (NTM)](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh). This tool manages WireGuard tests all configurations, including WireGuard exit policy as well.**
|
||||
|
||||
|
||||
You can use NTM to validate the application of the IP tables routes on your `nym-node` by checking it from the server side as well as from the outside.
|
||||
|
||||
<div>
|
||||
<Tabs items={[
|
||||
<strong>From the server</strong>,
|
||||
<strong>From the outside - using NymVPN</strong>
|
||||
]} defaultIndex={0}>
|
||||
<Tabs.Tab><ExitPolicyTestServer /></Tabs.Tab>
|
||||
<Tabs.Tab><ExitPolicyTestOutside /></Tabs.Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
|
||||
If all works , your node has successfully implemented WireGuard exit policy with the same routing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet) for TCP routing.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+22
-2
@@ -1,9 +1,29 @@
|
||||
import { Steps } from 'nextra/components';
|
||||
|
||||
Here are a few ways you can ensre your WireGuard exit policy is working correctly from the outside.
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Using NymVPN
|
||||
|
||||
- Connect to NymVPN and use your node as an Exit Gateway in dVPN (2-hop) mode
|
||||
|
||||
- While connected to NymVPN, navigate to [`portquiz.net:12345`](http://portquiz.net:12345) and see if you can load the page
|
||||
|
||||
- It shouldn't load, but if you navigate to some of the accepted ports, like[`portquiz.net:443`](http://portquiz.net:443) it should all work
|
||||
|
||||
###### 2. Testing from your local terminal
|
||||
|
||||
- Install these dependencies on your local machine:
|
||||
```shell
|
||||
sudo apt install tcpdump
|
||||
|
||||
sudo tcpdump -i nymwg -n
|
||||
```
|
||||
- Connect to [NymVPN](https://nym.com) and select your node as an Exit Gateway (after running the exit policy [manager script](#wireguard-exit-policy-configuration))
|
||||
- Connect to [NymVPN](https://nym.com) and select your node as an Exit Gateway
|
||||
|
||||
- Run the `tcpdump` command before registering
|
||||
- Have the output of the `echo $BLOCKED_IP` from your node and try to go into your browser or a registered client and try to connect - It will not resolve.
|
||||
|
||||
- Have the output of the `echo $BLOCKED_IP` from your node and try to go into your browser or a registered client and try to connect - It should not resolve
|
||||
|
||||
</ Steps>
|
||||
+31
-3
@@ -1,11 +1,39 @@
|
||||
Run this command to define variable `BLOCKED_IP` and try to `ping` it:
|
||||
import { Steps } from 'nextra/components';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
import ExitPolicyTestOutput from 'components/operators/snippets/wg-exit-policy-test-output.mdx';
|
||||
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Make sure to have the latest NTM:
|
||||
```sh
|
||||
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
|
||||
chmod +x network-tunnel-manager.sh && \
|
||||
./network-tunnel-manager.sh --help
|
||||
```
|
||||
|
||||
###### 2. Run tests with NTM
|
||||
```sh
|
||||
./network-tunnel-manager.sh exit_policy_test_connectivity
|
||||
./network-tunnel-manager.sh exit_policy_tests
|
||||
```
|
||||
|
||||
- The output should look like this:
|
||||
<AccordionTemplate name="Cosole output">
|
||||
<ExitPolicyTestOutput />
|
||||
</ AccordionTemplate>
|
||||
|
||||
###### 3. Optionally you can do some manual sanity checks
|
||||
- Run this command to define variable `BLOCKED_IP` and try to `ping` it:
|
||||
```shell
|
||||
BLOCKED_IP=$(grep "ExitPolicy reject" /etc/nym/exit-policy.txt | head -1 | sed -E 's/ExitPolicy reject ([^:]+):.*/\1/' | sed 's/\/.*$//')
|
||||
|
||||
ping -c 3 $BLOCKED_IP
|
||||
```
|
||||
You should see `100% packet loss` as an outcome.
|
||||
- You should see `100% packet loss` as an outcome.
|
||||
```shell
|
||||
telnet $BLOCKED_IP 80
|
||||
```
|
||||
You should see `telnet: Unable to connect to remote host: Connection timed out`.
|
||||
- You should see `telnet: Unable to connect to remote host: Connection timed out`.
|
||||
|
||||
</ Steps>
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.77%
|
||||
0.79%
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
37.305
|
||||
36.446
|
||||
|
||||
@@ -1 +1 @@
|
||||
Thursday, November 13th 2025, 11:23:33 UTC
|
||||
Wednesday, November 26th 2025, 12:13:56 UTC
|
||||
|
||||
@@ -11,7 +11,7 @@ options:
|
||||
--no_routing_history Display node stats without routing history
|
||||
--no_verloc_metrics Display node stats without verloc metrics
|
||||
-m, --markdown Display results in markdown format
|
||||
-o, --output [OUTPUT]
|
||||
-o [OUTPUT], --output [OUTPUT]
|
||||
Save results to file (in current dir or supply with
|
||||
path without filename)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
```sh
|
||||
usage: nym-node-cli install [-h] [-V] [-d BRANCH] [-v]
|
||||
[--mode {mixnode,entry-gateway,exit-gateway}]
|
||||
[--wireguard-enabled {true,false}]
|
||||
[--hostname HOSTNAME] [--location LOCATION]
|
||||
[--email EMAIL] [--moniker MONIKER]
|
||||
[--description DESCRIPTION]
|
||||
[--public-ip PUBLIC_IP]
|
||||
[--nym-node-binary NYM_NODE_BINARY]
|
||||
[--uplink-dev UPLINK_DEV] [--env KEY=VALUE]
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-V, --version show program's version number and exit
|
||||
-d BRANCH, --dev BRANCH
|
||||
Define github branch (default: develop)
|
||||
-v, --verbose Show full error tracebacks
|
||||
--mode {mixnode,entry-gateway,exit-gateway}
|
||||
Node mode: 'mixnode', 'entry-gateway', or 'exit-
|
||||
gateway'
|
||||
--wireguard-enabled {true,false}
|
||||
WireGuard functionality switch: true / false
|
||||
--hostname HOSTNAME Node domain / hostname
|
||||
--location LOCATION Node location (country code or name)
|
||||
--email EMAIL Contact email for the node operator
|
||||
--moniker MONIKER Public moniker displayed in explorer & NymVPN app
|
||||
--description DESCRIPTION
|
||||
Short public description of the node
|
||||
--public-ip PUBLIC_IP
|
||||
External IPv4 address (autodetected if omitted)
|
||||
--nym-node-binary NYM_NODE_BINARY
|
||||
URL for nym-node binary (autodetected if omitted)
|
||||
--uplink-dev UPLINK_DEV
|
||||
Override uplink interface used for NAT/FORWARD (e.g.,
|
||||
'eth0'; autodetected if omitted)
|
||||
--env KEY=VALUE (Optional) Extra ENV VARS, e.g. --env CUSTOM_KEY=value
|
||||
```
|
||||
@@ -49,6 +49,71 @@ This page displays a full list of all the changes during our release cycle from
|
||||
|
||||
<VarInfo />
|
||||
|
||||
## `v2025.21-mozzarella`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.21-mozzarella)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.22.0`
|
||||
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-11-25T14:26:29.627763948Z
|
||||
Build Version: 1.22.0
|
||||
Commit SHA: 22793bc45ea21561671d6670497ff42bc36b9d76
|
||||
Commit Date: 2025-11-25T15:16:42.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.88.0
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
### Operators Updates & Tools
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
**We are introducing new `network-tunnel-manager.sh` (NTM), with improved server configuration (see all changes here: [\#6186](https://github.com/nymtech/nym/pull/6186/)). We ask operators to re-run their routing configuration using the new NTM, following [these steps](/operators/nodes/nym-node/configuration#routing-configuration).**
|
||||
|
||||
**NOTE THAT THIS IS A NEW TOOL HANDLING VARIOUS COMPLEX SERVER SETTINGS, PLEASE RUN IT ON A FEW NODES AT A TIME, DO THE TESTING, MONITOR YOUR NODES AND [REPORT ANY ISSUES](https://matrix.to/#/#operators:nymtech.chat).**
|
||||
|
||||
We will keep `nym-node v1.21.1` as the latest version on API side for another 3 days to not penalize operators taking time for a proper implementation.
|
||||
</ Callout>
|
||||
|
||||
We are proud to announce that there are several improvements on tools aiming to make node operators life easier. Please let us know how do these programs work for you.
|
||||
|
||||
- [**New Nym Node CLI version**](/operators/tools#nym-node-cli): This version introduces arguments, allowing the operator to pass all needed variables (ie. hostname, mode etcetra) and let the program setup everything required for `nym-node` installation, server configuration, nginx, routing, tunnels and bridges, without further prompts for these values. This version also installs [QUIC bridge](/operators/nodes/nym-node/configuration#quic-transport-bridge-deployment).
|
||||
|
||||
- [**New Network Tunnel Manager**](/operators/nodes/nym-node/configuration#routing-configuration): NTM went through an big overhaul ([\#6186](https://github.com/nymtech/nym/pull/6186/)) - this new version combines old NTM with WG scripts. If run with `complete_networking_configuration` the tool does entire routing configuration, creates interfaces, and configures WireGuard exit policy.
|
||||
|
||||
- [**Updated QUIC Deployment Tool**](/operators/nodes/nym-node/configuration#quic-transport-bridge-deployment): The program is lighter, stripped of redundant functions reworking iptables rules.
|
||||
|
||||
### Features
|
||||
|
||||
- [HTTP API resilience enable & domain rotation conditions](https://github.com/nymtech/nym/pull/6200): Changes to make sure fallback domains are updated / enabled only for relevant
|
||||
|
||||
- [Typescript SDK 1.4.1](https://github.com/nymtech/nym/pull/6146): This PR is a new release of the Typescript SDK, `mixFetch` and `WASM` client. It also removes the Harbour Master client from `mixFetch`, replacing it with the Nym API's described endpoint for nym-nodes.
|
||||
|
||||
- [Enable URL rotation and retries for mixnet gateway init](https://github.com/nymtech/nym/pull/6126):
|
||||
- Multiple URL fallback with configurable retries (defaults to 3)
|
||||
- Infallible URL conversion per feedback (`Url::from()` instead of `parse().ok()`)
|
||||
- Non-breaking builder pattern for `BuilderConfig` per feedback
|
||||
- Reverted redundant node filtering per clarification that API already filters by `supported_roles.entry`
|
||||
|
||||
- [Credential proxy jwt](https://github.com/nymtech/nym/pull/5957): This PR is part of the 'Upgrade Mode' that should allow usage of the network in a situation where ecash credentials are unissuable, because, for example, we have lost signing quorum (i.e. we have fewer than the required number of threshold signers responding to requests). This version is more naive. Instead requesting actual 'emergency credentials' that would have been issued by a subset of ecash signers, the credentials proxy creates a JWT, signed with its key, attesting the upgrade mode has been enabled.
|
||||
|
||||
### Bugfix
|
||||
|
||||
- [Tunnel not waiting on `MixnetClient` to shut down cleanly](https://github.com/nymtech/nym/pull/6225): When there is a wireguard registration error, the mixnet client is signalled to shut down, but the tunnel doesn't wait. Now on errors, the registration client doesn't return until everything is properly stopped
|
||||
|
||||
- [Fix credential proxy upgrade mode attestation url arg](https://github.com/nymtech/nym/pull/6202): This includes bringing over changes introduced in [\#6174](https://github.com/nymtech/nym/pull/6174)
|
||||
|
||||
- [Remove debug feature from `http-macro` spec in gateway probe](https://github.com/nymtech/nym/pull/6195)
|
||||
|
||||
- [DNS reliability and troubleshooting ](https://github.com/nymtech/nym/pull/6179)
|
||||
|
||||
- [Distinguish authenticator errors by credential spent](https://github.com/nymtech/nym/pull/6176): This PR introduces a distinction between authenticator client errors that are happening before a credential was taken out of storage, and after
|
||||
|
||||
- [Disconnect mixnet client if registration fails](https://github.com/nymtech/nym/pull/6158): Currently the registration client does its job and then handles everything to the `vpn-client`, who then takes care on the mixnet client. However, if the registration fails, nothing is disconnecting the mixnet client, which can lead to errors of the kind of `There is already an open connection to this client`. This PR addresses that
|
||||
|
||||
|
||||
## `v2025.20-leerdammer`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.20-leerdammer)
|
||||
|
||||
@@ -3,34 +3,11 @@ import { Tabs } from 'nextra/components';
|
||||
import { VarInfo } from 'components/variable-info.tsx';
|
||||
import { Steps } from 'nextra/components';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
import ExitPolicyInstallOutput from 'components/operators/snippets/wg-exit-policy-install-output.mdx';
|
||||
import ExitPolicyStatusOutput from 'components/operators/snippets/wg-exit-policy-status-output.mdx';
|
||||
import ExitPolicyTestOutput from 'components/operators/snippets/wg-exit-policy-test-output.mdx';
|
||||
import ExitPolicyTestServer from 'components/operators/snippets/wg-exit-policy-testing-from-server.mdx';
|
||||
import ExitPolicyTestOutside from 'components/operators/snippets/wg-exit-policy-testing-from-outside.mdx';
|
||||
import WGExitPolicyConf from 'components/operators/snippets/wg-exit-policy-conf.mdx';
|
||||
import WGExitPolicyTest from 'components/operators/snippets/wg-exit-policy-test.mdx';
|
||||
import RoutingConf from 'components/operators/snippets/routing-conf.mdx';
|
||||
import QuicDeploymentSteps from 'components/operators/snippets/quic-bridge-deployment-script-setup.mdx';
|
||||
|
||||
|
||||
export const ManagerIPOutput = () => (
|
||||
<div>
|
||||
Correct <code>./network_tunnel_manager.sh fetch_and_display_ipv6</code> output
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ManagerTablesOutput = () => (
|
||||
<div>
|
||||
Correct <code>./network_tunnel_manager.sh check_nymtun_iptables</code> output
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ShowTun = () => (
|
||||
<div>
|
||||
Correct <code>ip addr show nymtun0</code> output
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
|
||||
# Nym Node Configuration
|
||||
|
||||
<VarInfo />
|
||||
@@ -222,9 +199,11 @@ This lets your operating system know it's ok to reload the service configuration
|
||||
</Steps>
|
||||
|
||||
|
||||
## Connectivity Test and Configuration
|
||||
## Routing Configuration
|
||||
|
||||
During our ongoing testing events we found out, that after introducing IP Packet Router (IPR) and [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) on embedded Network Requester (NR) by default, only a fragment of Gateways routes correctly through IPv4 and IPv6. We built a useful monitor to check out your Gateway (`nym-node --mode exit-gateway`) at [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net/).
|
||||
<RoutingConf />
|
||||
|
||||
### Quick IPv6 Check
|
||||
|
||||
IPv6 routing is not only a case for gateways. Imagine a rare occasion when you run a `mixnode` without IPv6 enabled and a client will sent IPv6 packets through the Mixnet through such route:
|
||||
```ascii
|
||||
@@ -232,19 +211,6 @@ IPv6 routing is not only a case for gateways. Imagine a rare occasion when you r
|
||||
```
|
||||
In this (unusual) case your `mixnode` will not be able to route the packets. The node will drop the packets and its performance would go down. For that reason it's beneficial to have IPv6 enabled when running a `mixnode` functionality.
|
||||
|
||||
<Callout>
|
||||
We recommend operators to configure their `nym-node` with the full routing configuration.
|
||||
|
||||
However, most of the time the packets sent through the Mixnet are IPv4 based. The IPv6 packets are still pretty rare and therefore it's not mandatory from operational point of view to have this configuration implemented if you running only `mixnode` mode.
|
||||
|
||||
If you preparing to run a `nym-node` with all modes enabled in the future, this setup is required.
|
||||
</Callout>
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
For everyone participating in Delegation Program or Service Grant program, this setup is a requirement!
|
||||
</Callout>
|
||||
|
||||
### Quick IPv6 Check
|
||||
|
||||
You can always check IPv6 address and connectivity by using some of these methods:
|
||||
<br />
|
||||
@@ -273,267 +239,13 @@ telnet -6 ipv6.telnetmyip.com
|
||||
Make sure to keep your IPv4 address enabled while setting up IPv6, as the majority of routing goes through that one!
|
||||
</Callout>
|
||||
|
||||
### Routing Configuration
|
||||
|
||||
While we're working on Rust implementation to have these settings as a part of the binary build, to solve these connectivity requirements in the meantime we wrote a script [`network_tunnel_manager.sh`](https://github.com/nymtech/nym/blob/develop/scripts/network_tunnel_manager.sh) to support operators to configure their servers and address all the connectivity requirements.
|
||||
|
||||
Networking configuration across different ISPs and various operation systems does not have a generic solution. If the provided configuration setup doesn't solve your problem check out [IPv6 troubleshooting](../../troubleshooting/vps-isp.mdx#ipv6-troubleshooting) page. Be aware that you may have to do more research, customised adjustments or contact your ISP to change settings for your VPS.
|
||||
|
||||
The `nymtun0` interface is dynamically managed by the `exit-gateway` service. When the service is stopped, `nymtun0` disappears, and when started, `nymtun0` is recreated.
|
||||
|
||||
The `nymwg` interface is used for creating a secure wireguard tunnel as part of the Nym Network configuration. Similar to `nymtun0`, the script manages iptables rules specific to `nymwg` to ensure proper routing and forwarding through the wireguard tunnel. The `nymwg` interface needs to be correctly configured and active for the related commands to function properly. This includes applying or removing iptables rules and running connectivity tests through the `nymwg` tunnel.
|
||||
|
||||
The script should be used in a context where `nym-node` is running to fully utilise its capabilities, particularly for fetching IPv6 addresses or applying network rules that depend on the `nymtun0` and `nymwg` interfaces and to establish a WireGuard tunnel.
|
||||
|
||||
**Before starting with the following configuration, make sure you have the [latest `nym-node` binary](https://github.com/nymtech/nym/releases) installed and your [VPS setup](../preliminary-steps/vps-setup.mdx) finished properly!**
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Download `network_tunnel_manager.sh`, make executable and run:
|
||||
|
||||
```sh
|
||||
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/network_tunnel_manager.sh -o network_tunnel_manager.sh && \
|
||||
chmod +x network_tunnel_manager.sh && \
|
||||
./network_tunnel_manager.sh
|
||||
```
|
||||
|
||||
###### 2. Make sure your `nym-node` service is up and running and bond
|
||||
|
||||
- **If you setting up a new node and not upgrading an existing one, keep it running and [bond](bonding.mdx) your node now**. Then come back here and follow the rest of the configuration.
|
||||
|
||||
<Callout type="warning" emoji="⚠️">
|
||||
**Run the following steps as root or with `sudo` prefix!**
|
||||
</Callout>
|
||||
|
||||
|
||||
###### 3. Setup IP tables rules
|
||||
|
||||
- Delete IP tables rules for IPv4 and IPv6 and apply new ones:
|
||||
```sh
|
||||
./network_tunnel_manager.sh remove_duplicate_rules nymtun0
|
||||
|
||||
./network_tunnel_manager.sh apply_iptables_rules
|
||||
```
|
||||
|
||||
- The process may prompt you if you want to save current IPv4 and IPv6 rules, choose yes.
|
||||
|
||||

|
||||
|
||||
- At this point you should see a `global ipv6` address.
|
||||
```sh
|
||||
./network_tunnel_manager.sh fetch_and_display_ipv6
|
||||
```
|
||||
<br />
|
||||
<AccordionTemplate name={<ManagerTablesOutput/>}>
|
||||
```sh
|
||||
iptables-persistent is already installed.
|
||||
Using IPv6 address: 2001:db8:a160::1/112 #the address will be different for you
|
||||
operation fetch_ipv6_address_nym_tun completed successfully.
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
###### 4. Check Nymtun IP tables:
|
||||
|
||||
```sh
|
||||
./network_tunnel_manager.sh check_nymtun_iptables
|
||||
```
|
||||
|
||||
- If there's no process running it wouldn't return anything.
|
||||
- In case you see `nymtun0` but not active, this is probably because you are setting up a new (never bonded) node and not upgrading an existing one.
|
||||
|
||||
<br />
|
||||
<AccordionTemplate name={<ManagerIPOutput/>}>
|
||||
```sh
|
||||
iptables-persistent is already installed.
|
||||
network Device: eth0
|
||||
---------------------------------------
|
||||
|
||||
inspecting IPv4 firewall rules...
|
||||
Chain FORWARD (policy DROP 0 packets, 0 bytes)
|
||||
0 0 ufw-reject-forward all -- * * 0.0.0.0/0 0.0.0.0/0
|
||||
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
|
||||
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
|
||||
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
|
||||
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
|
||||
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
|
||||
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
|
||||
---------------------------------------
|
||||
|
||||
inspecting IPv6 firewall rules...
|
||||
Chain FORWARD (policy DROP 0 packets, 0 bytes)
|
||||
0 0 ufw6-reject-forward all * * ::/0 ::/0
|
||||
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
|
||||
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
|
||||
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
|
||||
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
|
||||
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
|
||||
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
|
||||
operation check_nymtun_iptables completed successfully.
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
###### 5. Remove old and apply new rules for wireguad routing
|
||||
|
||||
```sh
|
||||
/network_tunnel_manager.sh remove_duplicate_rules nymwg
|
||||
|
||||
./network_tunnel_manager.sh apply_iptables_rules_wg
|
||||
```
|
||||
|
||||
###### 6. Apply rules to configure DNS routing and allow ICMP piung test for node probing (network testing)
|
||||
|
||||
```sh
|
||||
./network_tunnel_manager.sh configure_dns_and_icmp_wg
|
||||
```
|
||||
###### 7. Adjust and validate IP forwarding
|
||||
|
||||
```sh
|
||||
./network_tunnel_manager.sh adjust_ip_forwarding
|
||||
|
||||
./network_tunnel_manager.sh check_ipv6_ipv4_forwarding
|
||||
```
|
||||
|
||||
###### 8. Check `nymtun0` interface and test routing configuration
|
||||
|
||||
```sh
|
||||
ip addr show nymtun0
|
||||
```
|
||||
|
||||
<br />
|
||||
<AccordionTemplate name={<ShowTun/>}>
|
||||
```sh
|
||||
# your addresses will be different
|
||||
8: nymtun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1420 qdisc fq_codel state UNKNOWN group default qlen 500
|
||||
link/none
|
||||
inet 10.0.0.1/16 scope global nymtun0
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 fc00::1/112 scope global
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 fe80::ad08:d167:5700:8c7c/64 scope link stable-privacy
|
||||
valid_lft forever preferred_lft forever`
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
- Validate your IPv6 and IPv4 networking by running a joke test via Mixnet:
|
||||
```sh
|
||||
./network_tunnel_manager.sh joke_through_the_mixnet
|
||||
```
|
||||
|
||||
- Validate your tunneling by running a joke test via WG:
|
||||
```sh
|
||||
./network_tunnel_manager.sh joke_through_wg_tunnel
|
||||
```
|
||||
|
||||
- **Note:** WireGuard will return only IPv4 joke, not IPv6. WG IPv6 is under development. Running IPR joke through the mixnet with `./network_tunnel_manager.sh joke_through_the_mixnet` should work with both IPv4 and IPv6!
|
||||
|
||||
|
||||
###### 9. Enable wireguard
|
||||
|
||||
Now you can run your node with the `--wireguard-enabled true` flag or add it to your [systemd service config](#systemd). Restart your `nym-node` or [systemd](#2-following-steps-for-nym-nodes-running-as-systemd-service) service (recommended):
|
||||
|
||||
```sh
|
||||
systemctl daemon-reload && service nym-node restart
|
||||
```
|
||||
- Optionally, you can check if the node is running correctly by monitoring the service logs:
|
||||
|
||||
```sh
|
||||
journalctl -u nym-node.service -f -n 100
|
||||
```
|
||||
</Steps>
|
||||
|
||||
Make sure that you get the validation of all connectivity. If there are still any problems, please refer to [troubleshooting section](../../troubleshooting/vps-isp.mdx#incorrect-gateway-network-check).
|
||||
|
||||
## Wireguard Exit Policy Configuration
|
||||
|
||||
Nym Node running as Exit Gateway has contains multiple modules, one of them is Nym Network Requester(NR), routing TCP traffic to the internet. To make sure that the node is not just an open proxy, NR checks agains [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) following these conditions (in this exact order):
|
||||
|
||||
1. Do we explicitly block those IP addresses regardless of ports?
|
||||
2. Do we allow those specific ports regardless of IPs?
|
||||
3. Do block EVERYTHING else!
|
||||
|
||||
The exit policy is same for all NRs, the content is shaped by an offchain governance of Nym Node operators on our [forum](https://forum.nym.com/t/poll-a-new-nym-exit-policy-for-exit-gateways-and-the-nym-mixnet-is-inbound/464).
|
||||
|
||||
There is a caveat though. NR is only routing TCP streams and therefore any other type of routing is *not* filtered thorugh the exit policy. To ensure that Nym Nodes follow the same exit policy when routing IP packets through wireguard and don't act as open proxies, the operators have to set up these rules via IP tables rules.
|
||||
|
||||
**Follow these steps, using a [setup script](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh) and [testing scripts](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh) written by Nym quality assurance team, to setup exit policy for wireguard:**
|
||||
|
||||
<Steps>
|
||||
|
||||
###### 1. Download the scripts and make executable
|
||||
|
||||
- SSH to your node
|
||||
- Create a folder `~/nym-binaries` and navigate there
|
||||
```sh
|
||||
mkdir $HOME/nym-binaries
|
||||
cd $HOME/nym-binaries
|
||||
```
|
||||
- Download the scripts
|
||||
```sh
|
||||
wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh
|
||||
|
||||
wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh
|
||||
```
|
||||
- Make executable
|
||||
```sh
|
||||
chmod +x wireguard-exit-policy-manager.sh exit-policy-tests.sh
|
||||
```
|
||||
|
||||
###### 2. Install `wireguard-exit-policy-manager.sh`
|
||||
```sh
|
||||
./wireguard-exit-policy-manager.sh install
|
||||
```
|
||||
- The output should look like this:
|
||||
<AccordionTemplate name="Cosole output">
|
||||
<ExitPolicyInstallOutput />
|
||||
</ AccordionTemplate>
|
||||
|
||||
|
||||
###### 3. Run `wireguard-exit-policy-manager.sh`
|
||||
```sh
|
||||
./wireguard-exit-policy-manager.sh status
|
||||
```
|
||||
|
||||
- The output should look like this:
|
||||
<AccordionTemplate name="Cosole output">
|
||||
<ExitPolicyStatusOutput />
|
||||
</ AccordionTemplate>
|
||||
|
||||
###### 4. Test with `exit-policy-tests.sh`
|
||||
|
||||
```sh
|
||||
./exit-policy-tests.sh
|
||||
```
|
||||
|
||||
- The output should look like this:
|
||||
<AccordionTemplate name="Cosole output">
|
||||
<ExitPolicyTestOutput />
|
||||
</ AccordionTemplate>
|
||||
|
||||
###### 5. In case of problems, you can clear the exit policy rule
|
||||
```sh
|
||||
./wireguard-exit-policy-manager.sh clear
|
||||
|
||||
./wireguard-exit-policy-manager.sh status
|
||||
```
|
||||
</ Steps>
|
||||
|
||||
Now your wireguart routing should have same rotuing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet) mode of NymVPN.
|
||||
<WGExitPolicyConf />
|
||||
|
||||
### Testing Wireguard Exit Policy
|
||||
|
||||
You can validate the application of the IP tables routes on your `nym-node` by checking it from the server side as well as from the outside.
|
||||
|
||||
<div>
|
||||
<Tabs items={[
|
||||
<strong>From the server</strong>,
|
||||
<strong>From the outside - using NymVPN</strong>
|
||||
]} defaultIndex={0}>
|
||||
<Tabs.Tab><ExitPolicyTestServer /></Tabs.Tab>
|
||||
<Tabs.Tab><ExitPolicyTestOutside /></Tabs.Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
Your node has successfully implemented wireguard exit policy with the same routing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet).
|
||||
<WGExitPolicyTest />
|
||||
|
||||
## QUIC Transport Bridge Deployment
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-11-12T08:19:33.288341371Z
|
||||
Build Version: 1.21.0
|
||||
Commit SHA: babf113fe5d396fa8a84fa939ad4b1b5b4d38b83
|
||||
Commit Date: 2025-11-12T08:39:48.000000000+01:00
|
||||
Build Timestamp: 2025-11-25T14:26:29.627763948Z
|
||||
Build Version: 1.22.0
|
||||
Commit SHA: 22793bc45ea21561671d6670497ff42bc36b9d76
|
||||
Commit Date: 2025-11-25T15:16:42.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.88.0
|
||||
rustc Channel: stable
|
||||
|
||||
@@ -5,7 +5,13 @@ import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
|
||||
import { VarInfo } from 'components/variable-info.tsx';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
import { Steps } from 'nextra/components';
|
||||
import NymNodeCliInstallHelp from 'components/outputs/command-outputs/nym-node-cli-install-help.md';
|
||||
|
||||
export const NymNodeCliCommand = () => (
|
||||
<div>
|
||||
Arguments and options: <code>./nym-node-cli.py install --help</code>
|
||||
</div>
|
||||
);
|
||||
|
||||
# Tools
|
||||
|
||||
@@ -21,9 +27,11 @@ Nym Network stats can be humanly read on some of the explorers and dashboards.
|
||||
|
||||
- **[SpectreDAO Explorer](https://explorer.nym.spectredao.net/):** By operators for operators - currently the most used Nym explorer
|
||||
|
||||
- **[Nymesis](https://nymesis.vercel.app/):** A slick dashboard by operator community
|
||||
- **[Nym Node Status Observatory](https://harbourmaster.nymtech.net/):** An explorer with a list of Nym Nodes, their properformance and per node data
|
||||
|
||||
- **[Nym Harbourmaster](https://harbourmaster.nymtech.net/):** A dashboard showing results of Gateway probes and more (*in development*)
|
||||
- **[Nym Node Status UI](https://node-status.nym.com/):** A dashboard displaying results of Gateway probes and much more in one table
|
||||
|
||||
- **[Nymesis](https://nymesis.vercel.app/):** A slick dashboard by operator community
|
||||
|
||||
## Nym Node CLI
|
||||
|
||||
@@ -45,10 +53,50 @@ chmod +x ./nym-node-cli.py
|
||||
```
|
||||
|
||||
###### 3. Run the program
|
||||
|
||||
There are two ways how to run the program. fully interactive or with provided arguments. The former has a shorter initial command but then operators must fill the values on the go, the latter requires more complex command, but then there is a minimum prompts during the process.
|
||||
|
||||
1. Fully interactive mode - just run:
|
||||
```sh
|
||||
./nym-node-cli.py install
|
||||
```
|
||||
./nym-node-cli.py
|
||||
|
||||
2. Use arguments - run this command to see all options:
|
||||
```sh
|
||||
./nym-node-cli.py install --help
|
||||
```
|
||||
<AccordionTemplate name={<NymNodeCliCommand/>}>
|
||||
<NymNodeCliInstallHelp />
|
||||
</ AccordionTemplate>
|
||||
- An example can look like this:
|
||||
```sh
|
||||
# substitute with your real values:
|
||||
./nym-node-cli.py install
|
||||
--hostname node-install.devrel.nymte.ch
|
||||
--moniker MainnetGW-DE
|
||||
--description "This node is installed with nym-node-cli v1.2.0"
|
||||
--wireguard-enabled true
|
||||
--location DE
|
||||
--mode exit-gateway
|
||||
--email kawa_hesinkar@example.ku
|
||||
```
|
||||
|
||||
###### 4. Read and follow the prompts
|
||||
|
||||
There ware a few required confirmations and in case of running the program without the arguments, you will be prompted for values neccessary to setup a `nym-node` and configure your server.
|
||||
|
||||
**Read these prompts carefully!**
|
||||
|
||||
###### 5. Setup finished
|
||||
|
||||
Congratulation, your node is installed.
|
||||
|
||||
This version of the program does not prompt an operator for the local node moniker (`--id <ID>`) therefore it asigns your node an automatic one `default-nym-node`.
|
||||
|
||||
All configuration and data of your node can be found in this location:
|
||||
```sh
|
||||
ls $HOME/.nym/nym-nodes/default-nym-node/
|
||||
```
|
||||
</ Steps>
|
||||
|
||||
## CMD Reward Tracker
|
||||
|
||||
@@ -113,7 +113,7 @@ There are a few community explorers as well.
|
||||
|
||||
Enter your **identity key** to find your node. Check the contents of the `Mixnode stats` and `Routing score` sections.
|
||||
|
||||
[Here](https://github.com/cosmos/chain-registry/blob/master/nyx/chain.json#L158-L187) is a dictionary with Nyx chain registry entry regarding all explorers.
|
||||
[Here](https://github.com/cosmos/chain-registry/blob/master/nyx/chain.json#L183-L225) is a dictionary with Nyx chain registry entry regarding all explorers.
|
||||
|
||||
If you want more information, or if your node isn't showing up on the explorer of your choice and you want to double-check, here are some examples on how to check if the node is configured properly.
|
||||
|
||||
|
||||
@@ -59,4 +59,14 @@ echo '```sh' > ../../documentation/docs/components/outputs/command-outputs/nym-a
|
||||
./nym-api --help >> ../../documentation/docs/components/outputs/command-outputs/nym-api-help.md &&
|
||||
echo '```' >> ../../documentation/docs/components/outputs/command-outputs/nym-api-help.md &&
|
||||
|
||||
cd ../../scripts/nym-node-setup
|
||||
|
||||
echo '```sh' > ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
|
||||
python ./nym-node-cli.py install --help >> ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
|
||||
echo '```' >> ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
|
||||
|
||||
echo '```sh' > ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
|
||||
python ./nym-node-cli.py install --help >> ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
|
||||
echo '```' >> ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
|
||||
|
||||
echo "prebuild finished"
|
||||
|
||||
+83
-53
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@jsdevtools/rehype-url-inspector": "^2.0.2",
|
||||
"glob": "^10.3.4",
|
||||
"glob": "^10.5.0",
|
||||
"rehype-parse": "^9.0.0",
|
||||
"rehype-stringify": "^10.0.0",
|
||||
"to-vfile": "^8.0.0",
|
||||
@@ -20,6 +20,7 @@
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
@@ -48,6 +49,7 @@
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -80,9 +82,10 @@
|
||||
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -91,9 +94,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -113,12 +117,14 @@
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@@ -154,6 +160,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
@@ -164,7 +171,8 @@
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/comma-separated-tokens": {
|
||||
"version": "2.0.3",
|
||||
@@ -211,12 +219,14 @@
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
@@ -245,21 +255,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.3.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.4.tgz",
|
||||
"integrity": "sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==",
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^2.0.3",
|
||||
"minimatch": "^9.0.1",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
|
||||
"path-scurry": "^1.10.1"
|
||||
"jackspeak": "^3.1.2",
|
||||
"minimatch": "^9.0.4",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^1.11.1"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/cjs/src/bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -527,6 +536,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -548,15 +558,13 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.3.tgz",
|
||||
"integrity": "sha512-R2bUw+kVZFS/h1AZqBKrSgDmdmjApzgY0AlCPumopFiAlbUxE2gf+SCuBzQ0cP5hHmUmFYF5yw55T97Th5Kstg==",
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
@@ -565,12 +573,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz",
|
||||
"integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==",
|
||||
"engines": {
|
||||
"node": "14 || >=16.14"
|
||||
}
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/mdast-util-to-hast": {
|
||||
"version": "13.0.2",
|
||||
@@ -676,9 +682,10 @@
|
||||
]
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
|
||||
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
@@ -690,13 +697,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz",
|
||||
"integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
|
||||
@@ -717,15 +731,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
|
||||
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": "^9.1.1 || ^10.0.0",
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
"node": ">=16 || 14 >=14.18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -811,6 +826,7 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
@@ -828,6 +844,7 @@
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
@@ -841,6 +858,7 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -848,12 +866,14 @@
|
||||
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
@@ -875,9 +895,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
@@ -893,6 +914,7 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
@@ -904,6 +926,7 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -1220,6 +1243,7 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
@@ -1237,6 +1261,7 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
@@ -1253,6 +1278,7 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -1261,6 +1287,7 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
@@ -1274,12 +1301,14 @@
|
||||
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
@@ -1293,6 +1322,7 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@jsdevtools/rehype-url-inspector": "^2.0.2",
|
||||
"glob": "^10.3.4",
|
||||
"glob": "^10.5.0",
|
||||
"rehype-parse": "^9.0.0",
|
||||
"rehype-stringify": "^10.0.0",
|
||||
"to-vfile": "^8.0.0",
|
||||
|
||||
@@ -7,7 +7,6 @@ use nym_network_defaults::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::time::Duration;
|
||||
|
||||
pub use nym_client_core::config::Config as BaseClientConfig;
|
||||
pub use persistence::AuthenticatorPaths;
|
||||
@@ -27,6 +26,7 @@ pub struct Config {
|
||||
|
||||
impl Config {
|
||||
pub fn validate(&self) -> bool {
|
||||
// no other sections have explicit requirements (yet)
|
||||
self.base.validate()
|
||||
}
|
||||
}
|
||||
@@ -57,12 +57,6 @@ pub struct Authenticator {
|
||||
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv6.
|
||||
/// The maximum value for IPv6 is 128
|
||||
pub private_network_prefix_v6: u8,
|
||||
|
||||
/// Timeout to wait for responses from the peer controller before failing.
|
||||
/// Helps the authenticator recover from suspend/resume scenarios where the peer controller
|
||||
/// process/task can get stuck and never respond to oneshot RPC responses, which previously
|
||||
/// caused the authenticator to block forever waiting on the oneshot channel.
|
||||
pub peer_interaction_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Default for Authenticator {
|
||||
@@ -74,7 +68,6 @@ impl Default for Authenticator {
|
||||
tunnel_announced_port: WG_TUNNEL_PORT,
|
||||
private_network_prefix_v4: WG_TUN_DEVICE_NETMASK_V4,
|
||||
private_network_prefix_v6: WG_TUN_DEVICE_NETMASK_V6,
|
||||
peer_interaction_timeout: default_peer_interaction_timeout(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +85,3 @@ impl From<Authenticator> for nym_wireguard_types::Config {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_peer_interaction_timeout() -> Duration {
|
||||
Duration::from_millis(5_000)
|
||||
}
|
||||
|
||||
@@ -85,9 +85,6 @@ pub enum AuthenticatorError {
|
||||
#[error("peers can't be interacted with anymore")]
|
||||
PeerInteractionStopped,
|
||||
|
||||
#[error("peers interaction timed out while attempting to {operation}")]
|
||||
PeerInteractionTimeout { operation: &'static str },
|
||||
|
||||
#[error("unknown version number")]
|
||||
UnknownVersion,
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio_stream::wrappers::IntervalStream;
|
||||
|
||||
type AuthenticatorHandleResult = Result<(Vec<u8>, Option<Recipient>), AuthenticatorError>;
|
||||
@@ -73,7 +74,7 @@ pub(crate) struct MixnetListener {
|
||||
pub(crate) mixnet_client: nym_sdk::mixnet::MixnetClient,
|
||||
|
||||
// Registrations awaiting confirmation
|
||||
pub(crate) registered_and_free: RegisteredAndFree,
|
||||
pub(crate) registered_and_free: RwLock<RegisteredAndFree>,
|
||||
|
||||
pub(crate) peer_manager: PeerManager,
|
||||
|
||||
@@ -94,15 +95,14 @@ impl MixnetListener {
|
||||
mixnet_client: nym_sdk::mixnet::MixnetClient,
|
||||
upgrade_mode: UpgradeModeDetails,
|
||||
ecash_verifier: Arc<dyn EcashManager + Send + Sync>,
|
||||
peer_interaction_timeout: Duration,
|
||||
) -> Self {
|
||||
let timeout_check_interval =
|
||||
IntervalStream::new(tokio::time::interval(DEFAULT_REGISTRATION_TIMEOUT_CHECK));
|
||||
MixnetListener {
|
||||
config,
|
||||
mixnet_client,
|
||||
registered_and_free: RegisteredAndFree::new(free_private_network_ips),
|
||||
peer_manager: PeerManager::new(wireguard_gateway_data, peer_interaction_timeout),
|
||||
registered_and_free: RwLock::new(RegisteredAndFree::new(free_private_network_ips)),
|
||||
peer_manager: PeerManager::new(wireguard_gateway_data),
|
||||
upgrade_mode,
|
||||
ecash_verifier,
|
||||
timeout_check_interval,
|
||||
@@ -131,8 +131,8 @@ impl MixnetListener {
|
||||
))
|
||||
}
|
||||
|
||||
async fn remove_stale_registrations(&mut self) -> Result<(), AuthenticatorError> {
|
||||
let registered_and_free = &mut self.registered_and_free;
|
||||
async fn remove_stale_registrations(&self) -> Result<(), AuthenticatorError> {
|
||||
let mut registered_and_free = self.registered_and_free.write().await;
|
||||
let registered_values: Vec<_> = registered_and_free
|
||||
.registration_in_progres
|
||||
.values()
|
||||
@@ -185,9 +185,8 @@ impl MixnetListener {
|
||||
) -> AuthenticatorHandleResult {
|
||||
let remote_public = init_message.pub_key();
|
||||
let nonce: u64 = fastrand::u64(..);
|
||||
|
||||
if let Some(registration_data) = self
|
||||
.registered_and_free
|
||||
let mut registered_and_free = self.registered_and_free.write().await;
|
||||
if let Some(registration_data) = registered_and_free
|
||||
.registration_in_progres
|
||||
.get(&remote_public)
|
||||
{
|
||||
@@ -293,17 +292,7 @@ impl MixnetListener {
|
||||
return Ok((bytes, reply_to));
|
||||
}
|
||||
|
||||
let peer = match self.peer_manager.query_peer(remote_public).await {
|
||||
Ok(peer) => peer,
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
"Failed to query peer {}: {err}. Continuing with fresh registration",
|
||||
remote_public
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let peer = self.peer_manager.query_peer(remote_public).await?;
|
||||
if let Some(peer) = peer {
|
||||
let allowed_ipv4 = peer
|
||||
.allowed_ips
|
||||
@@ -394,21 +383,19 @@ impl MixnetListener {
|
||||
return Ok((bytes, reply_to));
|
||||
}
|
||||
|
||||
let private_ip = self
|
||||
.registered_and_free
|
||||
let private_ip_ref = registered_and_free
|
||||
.free_private_network_ips
|
||||
.iter_mut()
|
||||
.filter(|r| r.1.is_none())
|
||||
.choose(&mut thread_rng())
|
||||
.ok_or(AuthenticatorError::NoFreeIp)?;
|
||||
let private_ips = *private_ip.0;
|
||||
let private_ips = *private_ip_ref.0;
|
||||
// mark it as used, even though it's not final
|
||||
*private_ip.1 = Some(SystemTime::now());
|
||||
|
||||
*private_ip_ref.1 = Some(SystemTime::now());
|
||||
let gateway_data = GatewayClient::new(
|
||||
self.keypair().private_key(),
|
||||
remote_public.inner(),
|
||||
private_ips,
|
||||
*private_ip_ref.0,
|
||||
nonce,
|
||||
);
|
||||
let registration_data = latest::registration::RegistrationData {
|
||||
@@ -416,8 +403,7 @@ impl MixnetListener {
|
||||
gateway_data: gateway_data.clone(),
|
||||
wg_port: self.config.authenticator.tunnel_announced_port,
|
||||
};
|
||||
|
||||
self.registered_and_free
|
||||
registered_and_free
|
||||
.registration_in_progres
|
||||
.insert(remote_public, registration_data.clone());
|
||||
let bytes = match AuthenticatorVersion::from(protocol) {
|
||||
@@ -553,12 +539,12 @@ impl MixnetListener {
|
||||
request_id: u64,
|
||||
reply_to: Option<Recipient>,
|
||||
) -> AuthenticatorHandleResult {
|
||||
let registration_data = self
|
||||
.registered_and_free
|
||||
let mut registered_and_free = self.registered_and_free.write().await;
|
||||
let registration_data = registered_and_free
|
||||
.registration_in_progres
|
||||
.get(&final_message.gateway_client_pub_key())
|
||||
.cloned()
|
||||
.ok_or(AuthenticatorError::RegistrationNotInProgress)?;
|
||||
.ok_or(AuthenticatorError::RegistrationNotInProgress)?
|
||||
.clone();
|
||||
|
||||
if final_message
|
||||
.verify(self.keypair().private_key(), registration_data.nonce)
|
||||
@@ -609,7 +595,7 @@ impl MixnetListener {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
self.registered_and_free
|
||||
registered_and_free
|
||||
.registration_in_progres
|
||||
.remove(&final_message.gateway_client_pub_key());
|
||||
|
||||
@@ -832,7 +818,7 @@ impl MixnetListener {
|
||||
.to_bytes()
|
||||
.map_err(AuthenticatorError::response_serialisation)?,
|
||||
AuthenticatorVersion::V1 | AuthenticatorVersion::V2 | AuthenticatorVersion::UNKNOWN => {
|
||||
return Err(AuthenticatorError::UnknownVersion);
|
||||
return Err(AuthenticatorError::UnknownVersion)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -151,7 +151,6 @@ impl Authenticator {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let peer_timeout = self.config.authenticator.peer_interaction_timeout;
|
||||
let mixnet_listener = crate::node::internal_service_providers::authenticator::mixnet_listener::MixnetListener::new(
|
||||
self.config,
|
||||
free_private_network_ips,
|
||||
@@ -159,7 +158,6 @@ impl Authenticator {
|
||||
mixnet_client,
|
||||
self.upgrade_mode_state,
|
||||
self.ecash_verifier,
|
||||
peer_timeout,
|
||||
);
|
||||
|
||||
tracing::info!("The address of this client is: {self_address}");
|
||||
|
||||
@@ -8,19 +8,15 @@ use nym_credential_verification::{ClientBandwidth, TicketVerifier};
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_wireguard::{peer_controller::PeerControlRequest, WireguardGatewayData};
|
||||
use nym_wireguard_types::PeerPublicKey;
|
||||
use std::time::Duration;
|
||||
use tokio::time::timeout;
|
||||
|
||||
pub struct PeerManager {
|
||||
pub(crate) wireguard_gateway_data: WireguardGatewayData,
|
||||
response_timeout: Duration,
|
||||
}
|
||||
|
||||
impl PeerManager {
|
||||
pub fn new(wireguard_gateway_data: WireguardGatewayData, response_timeout: Duration) -> Self {
|
||||
pub fn new(wireguard_gateway_data: WireguardGatewayData) -> Self {
|
||||
PeerManager {
|
||||
wireguard_gateway_data,
|
||||
response_timeout,
|
||||
}
|
||||
}
|
||||
pub async fn add_peer(&self, peer: Peer) -> Result<(), AuthenticatorError> {
|
||||
@@ -32,8 +28,9 @@ impl PeerManager {
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::PeerInteractionStopped)?;
|
||||
|
||||
recv_with_timeout(response_rx, "add peer", self.response_timeout)
|
||||
.await?
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::InternalError("no response for add peer".to_string()))?
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!(
|
||||
"adding peer could not be performed: {err:?}"
|
||||
@@ -51,8 +48,11 @@ impl PeerManager {
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::PeerInteractionStopped)?;
|
||||
|
||||
recv_with_timeout(response_rx, "remove peer", self.response_timeout)
|
||||
.await?
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| {
|
||||
AuthenticatorError::InternalError("no response for remove peer".to_string())
|
||||
})?
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!(
|
||||
"removing peer could not be performed: {err:?}"
|
||||
@@ -73,8 +73,11 @@ impl PeerManager {
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::PeerInteractionStopped)?;
|
||||
|
||||
recv_with_timeout(response_rx, "query peer", self.response_timeout)
|
||||
.await?
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| {
|
||||
AuthenticatorError::InternalError("no response for query peer".to_string())
|
||||
})?
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!(
|
||||
"querying peer could not be performed: {err:?}"
|
||||
@@ -103,8 +106,13 @@ impl PeerManager {
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::PeerInteractionStopped)?;
|
||||
|
||||
recv_with_timeout(response_rx, "query client bandwidth", self.response_timeout)
|
||||
.await?
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| {
|
||||
AuthenticatorError::InternalError(
|
||||
"no response for query client bandwidth".to_string(),
|
||||
)
|
||||
})?
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!(
|
||||
"querying client bandwidth could not be performed: {err:?}"
|
||||
@@ -130,8 +138,11 @@ impl PeerManager {
|
||||
.await
|
||||
.map_err(|_| AuthenticatorError::PeerInteractionStopped)?;
|
||||
|
||||
recv_with_timeout(response_rx, "query verifier", self.response_timeout)
|
||||
.await?
|
||||
response_rx
|
||||
.await
|
||||
.map_err(|_| {
|
||||
AuthenticatorError::InternalError("no response for query verifier".to_string())
|
||||
})?
|
||||
.map_err(|err| {
|
||||
AuthenticatorError::InternalError(format!(
|
||||
"querying verifier could not be performed: {err:?}"
|
||||
@@ -140,31 +151,10 @@ impl PeerManager {
|
||||
}
|
||||
}
|
||||
|
||||
async fn recv_with_timeout<T>(
|
||||
response_rx: oneshot::Receiver<T>,
|
||||
operation: &'static str,
|
||||
timeout_duration: Duration,
|
||||
) -> Result<T, AuthenticatorError> {
|
||||
// Suspend/resume can wedge the peer controller, so we bound the wait to avoid deadlocking
|
||||
// authenticator responses on a stuck oneshot channel.
|
||||
match timeout(timeout_duration, response_rx).await {
|
||||
Ok(Ok(value)) => Ok(value),
|
||||
Ok(Err(_)) => Err(AuthenticatorError::PeerInteractionStopped),
|
||||
Err(_) => {
|
||||
tracing::warn!(
|
||||
"peer controller response timed out while attempting to {operation} after {:?}",
|
||||
timeout_duration
|
||||
);
|
||||
Err(AuthenticatorError::PeerInteractionTimeout { operation })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use nym_credential_verification::{
|
||||
bandwidth_storage_manager::BandwidthStorageManager, ecash::MockEcashManager,
|
||||
};
|
||||
@@ -173,8 +163,7 @@ mod tests {
|
||||
use nym_gateway_storage::traits::{mock::MockGatewayStorage, BandwidthGatewayStorage};
|
||||
use nym_wireguard::peer_controller::{start_controller, stop_controller};
|
||||
use rand::rngs::OsRng;
|
||||
use std::time::Duration;
|
||||
use time::{Duration as TimeDuration, OffsetDateTime};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::nym_authenticator::{
|
||||
@@ -254,7 +243,7 @@ mod tests {
|
||||
Authenticator::default().into(),
|
||||
Arc::new(KeyPair::new(&mut OsRng)),
|
||||
);
|
||||
let peer_manager = PeerManager::new(wireguard_data, Duration::from_secs(5));
|
||||
let peer_manager = PeerManager::new(wireguard_data);
|
||||
let (storage, task_manager) = start_controller(
|
||||
peer_manager.wireguard_gateway_data.peer_tx().clone(),
|
||||
request_rx,
|
||||
@@ -302,7 +291,7 @@ mod tests {
|
||||
Authenticator::default().into(),
|
||||
Arc::new(KeyPair::new(&mut OsRng)),
|
||||
);
|
||||
let mut peer_manager = PeerManager::new(wireguard_data, Duration::from_secs(5));
|
||||
let mut peer_manager = PeerManager::new(wireguard_data);
|
||||
let key = Key::default();
|
||||
let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap();
|
||||
let (storage, task_manager) = start_controller(
|
||||
@@ -322,7 +311,7 @@ mod tests {
|
||||
Authenticator::default().into(),
|
||||
Arc::new(KeyPair::new(&mut OsRng)),
|
||||
);
|
||||
let mut peer_manager = PeerManager::new(wireguard_data, Duration::from_secs(5));
|
||||
let mut peer_manager = PeerManager::new(wireguard_data);
|
||||
let key = Key::default();
|
||||
let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap();
|
||||
let (storage, task_manager) = start_controller(
|
||||
@@ -345,7 +334,7 @@ mod tests {
|
||||
Authenticator::default().into(),
|
||||
Arc::new(KeyPair::new(&mut OsRng)),
|
||||
);
|
||||
let mut peer_manager = PeerManager::new(wireguard_data, Duration::from_secs(5));
|
||||
let mut peer_manager = PeerManager::new(wireguard_data);
|
||||
let key = Key::default();
|
||||
let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap();
|
||||
let (storage, task_manager) = start_controller(
|
||||
@@ -368,7 +357,7 @@ mod tests {
|
||||
Authenticator::default().into(),
|
||||
Arc::new(KeyPair::new(&mut OsRng)),
|
||||
);
|
||||
let mut peer_manager = PeerManager::new(wireguard_data, Duration::from_secs(5));
|
||||
let mut peer_manager = PeerManager::new(wireguard_data);
|
||||
let key = Key::default();
|
||||
let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap();
|
||||
let (storage, task_manager) = start_controller(
|
||||
@@ -399,7 +388,7 @@ mod tests {
|
||||
Authenticator::default().into(),
|
||||
Arc::new(KeyPair::new(&mut OsRng)),
|
||||
);
|
||||
let mut peer_manager = PeerManager::new(wireguard_data, Duration::from_secs(5));
|
||||
let mut peer_manager = PeerManager::new(wireguard_data);
|
||||
let key = Key::default();
|
||||
let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap();
|
||||
let (storage, task_manager) = start_controller(
|
||||
@@ -428,7 +417,7 @@ mod tests {
|
||||
Authenticator::default().into(),
|
||||
Arc::new(KeyPair::new(&mut OsRng)),
|
||||
);
|
||||
let mut peer_manager = PeerManager::new(wireguard_data, Duration::from_secs(5));
|
||||
let mut peer_manager = PeerManager::new(wireguard_data);
|
||||
let key = Key::default();
|
||||
let public_key = PeerPublicKey::from_str(&key.to_string()).unwrap();
|
||||
let top_up = 42;
|
||||
@@ -455,7 +444,7 @@ mod tests {
|
||||
.increase_bandwidth(
|
||||
Bandwidth::new_unchecked(top_up as u64),
|
||||
OffsetDateTime::now_utc()
|
||||
.checked_add(TimeDuration::minutes(1))
|
||||
.checked_add(Duration::minutes(1))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
@@ -477,28 +466,4 @@ mod tests {
|
||||
|
||||
stop_controller(task_manager).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn recv_with_timeout_errors_after_deadline() {
|
||||
let (_tx, rx) = oneshot::channel::<()>();
|
||||
let err = super::recv_with_timeout(rx, "unit-test", Duration::from_millis(10))
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
AuthenticatorError::PeerInteractionTimeout {
|
||||
operation: "unit-test"
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn recv_with_timeout_succeeds_before_deadline() {
|
||||
let (tx, rx) = oneshot::channel::<u8>();
|
||||
tx.send(42).unwrap();
|
||||
let value = super::recv_with_timeout(rx, "unit-test", Duration::from_secs(1))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(value, 42);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.69"
|
||||
version = "1.1.71"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
|
||||
@@ -4,15 +4,15 @@ go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.13
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/net v0.47.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/tevino/abool v1.2.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20250611222258-0fe9a4bf489c // indirect
|
||||
|
||||
@@ -12,12 +12,12 @@ github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
|
||||
@@ -130,12 +130,20 @@ pub enum TestedNode {
|
||||
SameAsEntry,
|
||||
Custom {
|
||||
identity: NodeIdentity,
|
||||
shares_entry: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl TestedNode {
|
||||
pub fn is_same_as_entry(&self) -> bool {
|
||||
matches!(self, TestedNode::SameAsEntry)
|
||||
matches!(
|
||||
self,
|
||||
TestedNode::SameAsEntry
|
||||
| TestedNode::Custom {
|
||||
shares_entry: true,
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +317,20 @@ impl Probe {
|
||||
let entry_gateway = directory.entry_gateway(&self.entrypoint)?;
|
||||
|
||||
let node_info: TestedNodeDetails = match self.tested_node {
|
||||
TestedNode::Custom { identity } => {
|
||||
TestedNode::Custom {
|
||||
identity: _,
|
||||
shares_entry: true,
|
||||
} => {
|
||||
debug!(
|
||||
"testing node {} as both entry and exit",
|
||||
entry_gateway.identity()
|
||||
);
|
||||
entry_gateway.to_testable_node()?
|
||||
}
|
||||
TestedNode::Custom {
|
||||
identity,
|
||||
shares_entry: false,
|
||||
} => {
|
||||
let node = directory.get_nym_node(identity)?;
|
||||
info!(
|
||||
"testing node {} (via entry {})",
|
||||
@@ -421,12 +442,17 @@ impl Probe {
|
||||
storage.credential_store().clone(),
|
||||
client,
|
||||
);
|
||||
let credential = bw_controller
|
||||
.prepare_ecash_ticket(
|
||||
let (wg_ticket_type, credential_provider) = if tested_entry {
|
||||
(
|
||||
TicketType::V1WireguardEntry,
|
||||
nym_address.gateway().to_bytes(),
|
||||
1,
|
||||
)
|
||||
} else {
|
||||
(TicketType::V1WireguardExit, node_info.identity.to_bytes())
|
||||
};
|
||||
|
||||
let credential = bw_controller
|
||||
.prepare_ecash_ticket(wg_ticket_type, credential_provider, 1)
|
||||
.await?
|
||||
.data;
|
||||
|
||||
@@ -711,11 +737,7 @@ async fn do_ping_entry(
|
||||
}
|
||||
info!("Successfully mixnet pinged ourselves");
|
||||
|
||||
if tested_entry {
|
||||
Entry::success()
|
||||
} else {
|
||||
Entry::NotTested
|
||||
}
|
||||
Entry::success()
|
||||
}
|
||||
|
||||
async fn connect_exit(
|
||||
|
||||
@@ -199,6 +199,16 @@ impl NymApiDirectory {
|
||||
.map(|(id, _)| *id)
|
||||
}
|
||||
|
||||
pub fn random_entry_gateway(&self) -> anyhow::Result<NodeIdentity> {
|
||||
info!("Selecting random entry gateway");
|
||||
self.nodes
|
||||
.iter()
|
||||
.filter(|(_, n)| n.described.description.declared_role.entry)
|
||||
.choose(&mut rand::thread_rng())
|
||||
.ok_or(anyhow!("no entry gateways available"))
|
||||
.map(|(id, _)| *id)
|
||||
}
|
||||
|
||||
pub fn get_nym_node(&self, identity: NodeIdentity) -> anyhow::Result<DirectoryNode> {
|
||||
self.nodes
|
||||
.get(&identity)
|
||||
|
||||
@@ -124,16 +124,31 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
|
||||
let directory = NymApiDirectory::new(api_url).await?;
|
||||
|
||||
let entry = if let Some(gateway) = &args.entry_gateway {
|
||||
NodeIdentity::from_base58_string(gateway)?
|
||||
let node_override = args.node;
|
||||
let entry_override = if let Some(gateway) = &args.entry_gateway {
|
||||
Some(NodeIdentity::from_base58_string(gateway)?)
|
||||
} else {
|
||||
directory.random_exit_with_ipr()?
|
||||
None
|
||||
};
|
||||
|
||||
let test_point = if let Some(node) = args.node {
|
||||
TestedNode::Custom { identity: node }
|
||||
let entry = if let Some(entry) = entry_override {
|
||||
entry
|
||||
} else if let Some(node) = node_override {
|
||||
node
|
||||
} else {
|
||||
TestedNode::SameAsEntry
|
||||
directory.random_entry_gateway()?
|
||||
};
|
||||
|
||||
let test_point = match (node_override, entry_override) {
|
||||
(Some(node), Some(_)) => TestedNode::Custom {
|
||||
identity: node,
|
||||
shares_entry: false,
|
||||
},
|
||||
(Some(node), None) => TestedNode::Custom {
|
||||
identity: node,
|
||||
shares_entry: true,
|
||||
},
|
||||
(None, _) => TestedNode::SameAsEntry,
|
||||
};
|
||||
|
||||
let mut trial =
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-agent"
|
||||
version = "1.0.7"
|
||||
version = "1.0.7-niolo"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-api"
|
||||
version = "4.0.11-rc1"
|
||||
version = "4.0.12"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -372,6 +372,21 @@ impl DVpnGateway {
|
||||
}
|
||||
}
|
||||
|
||||
struct NodeScore {
|
||||
download_speed_score: f64,
|
||||
ping_ips_score: f64,
|
||||
mixnet_performance: f64,
|
||||
}
|
||||
|
||||
impl NodeScore {
|
||||
// Weighted scoring: mixnet performance (40%), download speed (30%), ping performance (30%)
|
||||
fn calculate_weighted_score(&self) -> f64 {
|
||||
(self.mixnet_performance * 0.4)
|
||||
+ (self.download_speed_score * 0.3)
|
||||
+ (self.ping_ips_score * 0.3)
|
||||
}
|
||||
}
|
||||
|
||||
/// calculates the gateway probe score for mixnet mode
|
||||
fn calculate_mixnet_score(gateway: &Gateway) -> ScoreValue {
|
||||
let mixnet_performance = gateway.performance as f64 / 100.0;
|
||||
@@ -387,13 +402,14 @@ fn calculate_mixnet_score(gateway: &Gateway) -> ScoreValue {
|
||||
}
|
||||
}
|
||||
|
||||
/// calculates a visual score for the gateway
|
||||
/// calculates a visual score for the gateway using weighted metrics
|
||||
fn calculate_score(gateway: &Gateway, probe_outcome: &LastProbeResult) -> ScoreValue {
|
||||
let mixnet_performance = gateway.performance as f64 / 100.0;
|
||||
let ping_ips_performance = probe_outcome
|
||||
|
||||
let node_score = probe_outcome
|
||||
.outcome
|
||||
.wg
|
||||
.clone()
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
let ping_ips_performance = p.ping_ips_performance_v4 as f64;
|
||||
|
||||
@@ -419,18 +435,25 @@ fn calculate_score(gateway: &Gateway, probe_outcome: &LastProbeResult) -> ScoreV
|
||||
0.1
|
||||
};
|
||||
|
||||
// combine the scores
|
||||
file_download_score * ping_ips_performance
|
||||
NodeScore {
|
||||
download_speed_score: file_download_score,
|
||||
ping_ips_score: ping_ips_performance,
|
||||
mixnet_performance,
|
||||
}
|
||||
})
|
||||
.unwrap_or(0f64);
|
||||
.unwrap_or(NodeScore {
|
||||
download_speed_score: 0.0,
|
||||
ping_ips_score: 0.0,
|
||||
mixnet_performance,
|
||||
});
|
||||
|
||||
let score = mixnet_performance * ping_ips_performance;
|
||||
let weighted_score = node_score.calculate_weighted_score();
|
||||
|
||||
if score > 0.75 {
|
||||
if weighted_score > 0.75 {
|
||||
ScoreValue::High
|
||||
} else if score > 0.5 {
|
||||
} else if weighted_score > 0.5 {
|
||||
ScoreValue::Medium
|
||||
} else if score > 0.1 {
|
||||
} else if weighted_score > 0.1 {
|
||||
ScoreValue::Low
|
||||
} else {
|
||||
ScoreValue::Offline
|
||||
@@ -733,6 +756,130 @@ mod test {
|
||||
assert!(service.mixnet_websockets.is_none());
|
||||
assert!(service.last_successful_ping_utc.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weighted_score_calculation() {
|
||||
use crate::http::models::directory_gw_probe_outcome::EntryTestResult;
|
||||
use crate::http::models::wg_outcome_versions::ProbeOutcomeV1;
|
||||
|
||||
// Helper function to create a test gateway
|
||||
fn create_test_gateway(performance: u8) -> Gateway {
|
||||
Gateway {
|
||||
gateway_identity_key: "test_key".to_string(),
|
||||
bonded: true,
|
||||
performance,
|
||||
self_described: None,
|
||||
explorer_pretty_bond: None,
|
||||
description: nym_node_requests::api::v1::node::models::NodeDescription {
|
||||
moniker: "test".to_string(),
|
||||
details: "test".to_string(),
|
||||
security_contact: "test@example.com".to_string(),
|
||||
website: "https://example.com".to_string(),
|
||||
},
|
||||
last_probe_result: None,
|
||||
last_probe_log: None,
|
||||
last_testrun_utc: None,
|
||||
last_updated_utc: "2025-10-10T00:00:00Z".to_string(),
|
||||
routing_score: 0.0,
|
||||
config_score: 0,
|
||||
bridges: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a test probe outcome
|
||||
fn create_test_probe_outcome(
|
||||
download_speed_mbps: f64,
|
||||
ping_ips_performance: f32,
|
||||
) -> LastProbeResult {
|
||||
let duration_sec = 1.0;
|
||||
let file_size_mb = download_speed_mbps;
|
||||
|
||||
LastProbeResult {
|
||||
node: "test_node".to_string(),
|
||||
used_entry: "test_entry".to_string(),
|
||||
outcome: ProbeOutcome {
|
||||
as_entry: directory_gw_probe_outcome::Entry::Tested(EntryTestResult {
|
||||
can_connect: true,
|
||||
can_route: true,
|
||||
}),
|
||||
as_exit: None,
|
||||
wg: Some(ProbeOutcomeV1 {
|
||||
can_register: true,
|
||||
can_handshake: Some(true),
|
||||
can_resolve_dns: Some(true),
|
||||
ping_hosts_performance: Some(ping_ips_performance),
|
||||
ping_ips_performance: Some(ping_ips_performance),
|
||||
can_query_metadata_v4: Some(true),
|
||||
can_handshake_v4: true,
|
||||
can_resolve_dns_v4: true,
|
||||
ping_hosts_performance_v4: ping_ips_performance,
|
||||
ping_ips_performance_v4: ping_ips_performance,
|
||||
can_handshake_v6: true,
|
||||
can_resolve_dns_v6: true,
|
||||
ping_hosts_performance_v6: ping_ips_performance,
|
||||
ping_ips_performance_v6: ping_ips_performance,
|
||||
download_duration_sec_v4: (duration_sec * 1000.0) as u64,
|
||||
download_duration_milliseconds_v4: Some((duration_sec * 1000.0) as u64),
|
||||
downloaded_file_size_bytes_v4: Some(
|
||||
(file_size_mb * 1024.0 * 1024.0) as u64,
|
||||
),
|
||||
downloaded_file_v4: "test".to_string(),
|
||||
download_error_v4: "".to_string(),
|
||||
download_duration_sec_v6: 0,
|
||||
download_duration_milliseconds_v6: Some(0),
|
||||
downloaded_file_size_bytes_v6: Some(0),
|
||||
downloaded_file_v6: "".to_string(),
|
||||
download_error_v6: "".to_string(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Test case 1: Excellent node (should be High)
|
||||
let gateway = create_test_gateway(90); // 90% mixnet performance
|
||||
let probe = create_test_probe_outcome(6.0, 1.0); // 6 Mbps, 100% ping
|
||||
let score = calculate_score(&gateway, &probe);
|
||||
assert_eq!(score, ScoreValue::High, "Excellent node should be High");
|
||||
|
||||
// Test case 2: Good node (should be High with weighted scoring)
|
||||
let gateway = create_test_gateway(90); // 90% mixnet performance
|
||||
let probe = create_test_probe_outcome(3.0, 0.9); // 3 Mbps (0.75 score), 90% ping
|
||||
let score = calculate_score(&gateway, &probe);
|
||||
assert_eq!(
|
||||
score,
|
||||
ScoreValue::High,
|
||||
"Good node should be High with weighted scoring"
|
||||
);
|
||||
|
||||
// Test case 3: Medium node
|
||||
let gateway = create_test_gateway(80); // 80% mixnet performance
|
||||
let probe = create_test_probe_outcome(1.5, 0.8); // 1.5 Mbps (0.5 score), 80% ping
|
||||
let score = calculate_score(&gateway, &probe);
|
||||
assert_eq!(score, ScoreValue::Medium, "Medium node should be Medium");
|
||||
|
||||
// Test case 4: Poor node
|
||||
let gateway = create_test_gateway(60); // 60% mixnet performance
|
||||
let probe = create_test_probe_outcome(0.3, 0.3); // 0.3 Mbps (0.1 score), 30% ping
|
||||
let score = calculate_score(&gateway, &probe);
|
||||
assert_eq!(score, ScoreValue::Low, "Poor node should be Low");
|
||||
|
||||
// Test case 5: Failed node
|
||||
let gateway = create_test_gateway(10); // 10% mixnet performance
|
||||
let probe = create_test_probe_outcome(0.1, 0.0); // 0.1 Mbps (0.1 score), 0% ping
|
||||
let score = calculate_score(&gateway, &probe);
|
||||
assert_eq!(score, ScoreValue::Offline, "Failed node should be Offline");
|
||||
|
||||
// Test case 6: Edge case - just above threshold
|
||||
let gateway = create_test_gateway(76); // 76% mixnet performance
|
||||
let probe = create_test_probe_outcome(2.1, 0.75); // 2.1 Mbps (0.75 score), 75% ping
|
||||
let score = calculate_score(&gateway, &probe);
|
||||
// Weighted: (0.76 * 0.4) + (0.75 * 0.3) + (0.75 * 0.3) = 0.304 + 0.225 + 0.225 = 0.754
|
||||
assert_eq!(
|
||||
score,
|
||||
ScoreValue::High,
|
||||
"Edge case just above 0.75 threshold should be High"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"d3-array": "^3.2.4",
|
||||
"dayjs": "^1.11.13",
|
||||
"material-react-table": "^3.2.1",
|
||||
"next": "^15.4.1",
|
||||
"next": "^15.4.7",
|
||||
"openapi-typescript": "^7.5.2",
|
||||
"react": "^19.0.0",
|
||||
"react-country-flag": "^3.1.0",
|
||||
|
||||
+194
-214
@@ -22,7 +22,7 @@ importers:
|
||||
version: 7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@mui/material-nextjs':
|
||||
specifier: ^7.2.0
|
||||
version: 7.2.0(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(next@15.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
|
||||
version: 7.2.0(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(next@15.4.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
|
||||
'@mui/x-charts':
|
||||
specifier: ^8.8.0
|
||||
version: 8.8.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@mui/material@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@mui/system@7.2.0(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
@@ -42,8 +42,8 @@ importers:
|
||||
specifier: ^3.2.1
|
||||
version: 3.2.1(e4be24f02074e47c0e16001efb50991b)
|
||||
next:
|
||||
specifier: ^15.4.1
|
||||
version: 15.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
specifier: ^15.4.7
|
||||
version: 15.4.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
openapi-typescript:
|
||||
specifier: ^7.5.2
|
||||
version: 7.5.2(typescript@5.7.3)
|
||||
@@ -179,8 +179,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@emnapi/runtime@1.4.4':
|
||||
resolution: {integrity: sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==}
|
||||
'@emnapi/runtime@1.7.0':
|
||||
resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==}
|
||||
|
||||
'@emotion/babel-plugin@11.13.5':
|
||||
resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
|
||||
@@ -236,124 +236,139 @@ packages:
|
||||
'@emotion/weak-memoize@0.4.0':
|
||||
resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.34.3':
|
||||
resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==}
|
||||
'@img/colour@1.0.0':
|
||||
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-darwin-x64@0.34.3':
|
||||
resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==}
|
||||
'@img/sharp-darwin-x64@0.34.5':
|
||||
resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64@1.2.0':
|
||||
resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==}
|
||||
'@img/sharp-libvips-darwin-arm64@1.2.4':
|
||||
resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-darwin-x64@1.2.0':
|
||||
resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==}
|
||||
'@img/sharp-libvips-darwin-x64@1.2.4':
|
||||
resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@img/sharp-libvips-linux-arm64@1.2.0':
|
||||
resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==}
|
||||
'@img/sharp-libvips-linux-arm64@1.2.4':
|
||||
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.0':
|
||||
resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==}
|
||||
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.0':
|
||||
resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==}
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.4':
|
||||
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.2.0':
|
||||
resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==}
|
||||
'@img/sharp-libvips-linux-riscv64@1.2.4':
|
||||
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.2.4':
|
||||
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.0':
|
||||
resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==}
|
||||
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.0':
|
||||
resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==}
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.0':
|
||||
resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==}
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.3':
|
||||
resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==}
|
||||
'@img/sharp-linux-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linux-arm@0.34.3':
|
||||
resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==}
|
||||
'@img/sharp-linux-arm@0.34.5':
|
||||
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linux-ppc64@0.34.3':
|
||||
resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==}
|
||||
'@img/sharp-linux-ppc64@0.34.5':
|
||||
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.3':
|
||||
resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==}
|
||||
'@img/sharp-linux-riscv64@0.34.5':
|
||||
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.5':
|
||||
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linux-x64@0.34.3':
|
||||
resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==}
|
||||
'@img/sharp-linux-x64@0.34.5':
|
||||
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.3':
|
||||
resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==}
|
||||
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.3':
|
||||
resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==}
|
||||
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@img/sharp-wasm32@0.34.3':
|
||||
resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==}
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [wasm32]
|
||||
|
||||
'@img/sharp-win32-arm64@0.34.3':
|
||||
resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==}
|
||||
'@img/sharp-win32-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@img/sharp-win32-ia32@0.34.3':
|
||||
resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==}
|
||||
'@img/sharp-win32-ia32@0.34.5':
|
||||
resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@img/sharp-win32-x64@0.34.3':
|
||||
resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==}
|
||||
'@img/sharp-win32-x64@0.34.5':
|
||||
resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -557,53 +572,53 @@ packages:
|
||||
'@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0
|
||||
react: ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@next/env@15.4.1':
|
||||
resolution: {integrity: sha512-DXQwFGAE2VH+f2TJsKepRXpODPU+scf5fDbKOME8MMyeyswe4XwgRdiiIYmBfkXU+2ssliLYznajTrOQdnLR5A==}
|
||||
'@next/env@15.4.7':
|
||||
resolution: {integrity: sha512-PrBIpO8oljZGTOe9HH0miix1w5MUiGJ/q83Jge03mHEE0E3pyqzAy2+l5G6aJDbXoobmxPJTVhbCuwlLtjSHwg==}
|
||||
|
||||
'@next/swc-darwin-arm64@15.4.1':
|
||||
resolution: {integrity: sha512-L+81yMsiHq82VRXS2RVq6OgDwjvA4kDksGU8hfiDHEXP+ncKIUhUsadAVB+MRIp2FErs/5hpXR0u2eluWPAhig==}
|
||||
'@next/swc-darwin-arm64@15.4.7':
|
||||
resolution: {integrity: sha512-2Dkb+VUTp9kHHkSqtws4fDl2Oxms29HcZBwFIda1X7Ztudzy7M6XF9HDS2dq85TmdN47VpuhjE+i6wgnIboVzQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@15.4.1':
|
||||
resolution: {integrity: sha512-jfz1RXu6SzL14lFl05/MNkcN35lTLMJWPbqt7Xaj35+ZWAX342aePIJrN6xBdGeKl6jPXJm0Yqo3Xvh3Gpo3Uw==}
|
||||
'@next/swc-darwin-x64@15.4.7':
|
||||
resolution: {integrity: sha512-qaMnEozKdWezlmh1OGDVFueFv2z9lWTcLvt7e39QA3YOvZHNpN2rLs/IQLwZaUiw2jSvxW07LxMCWtOqsWFNQg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.4.1':
|
||||
resolution: {integrity: sha512-k0tOFn3dsnkaGfs6iQz8Ms6f1CyQe4GacXF979sL8PNQxjYS1swx9VsOyUQYaPoGV8nAZ7OX8cYaeiXGq9ahPQ==}
|
||||
'@next/swc-linux-arm64-gnu@15.4.7':
|
||||
resolution: {integrity: sha512-ny7lODPE7a15Qms8LZiN9wjNWIeI+iAZOFDOnv2pcHStncUr7cr9lD5XF81mdhrBXLUP9yT9RzlmSWKIazWoDw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.4.1':
|
||||
resolution: {integrity: sha512-4ogGQ/3qDzbbK3IwV88ltihHFbQVq6Qr+uEapzXHXBH1KsVBZOB50sn6BWHPcFjwSoMX2Tj9eH/fZvQnSIgc3g==}
|
||||
'@next/swc-linux-arm64-musl@15.4.7':
|
||||
resolution: {integrity: sha512-4SaCjlFR/2hGJqZLLWycccy1t+wBrE/vyJWnYaZJhUVHccpGLG5q0C+Xkw4iRzUIkE+/dr90MJRUym3s1+vO8A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.4.1':
|
||||
resolution: {integrity: sha512-Jj0Rfw3wIgp+eahMz/tOGwlcYYEFjlBPKU7NqoOkTX0LY45i5W0WcDpgiDWSLrN8KFQq/LW7fZq46gxGCiOYlQ==}
|
||||
'@next/swc-linux-x64-gnu@15.4.7':
|
||||
resolution: {integrity: sha512-2uNXjxvONyRidg00VwvlTYDwC9EgCGNzPAPYbttIATZRxmOZ3hllk/YYESzHZb65eyZfBR5g9xgCZjRAl9YYGg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-musl@15.4.1':
|
||||
resolution: {integrity: sha512-9WlEZfnw1vFqkWsTMzZDgNL7AUI1aiBHi0S2m8jvycPyCq/fbZjtE/nDkhJRYbSjXbtRHYLDBlmP95kpjEmJbw==}
|
||||
'@next/swc-linux-x64-musl@15.4.7':
|
||||
resolution: {integrity: sha512-ceNbPjsFgLscYNGKSu4I6LYaadq2B8tcK116nVuInpHHdAWLWSwVK6CHNvCi0wVS9+TTArIFKJGsEyVD1H+4Kg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.4.1':
|
||||
resolution: {integrity: sha512-WodRbZ9g6CQLRZsG3gtrA9w7Qfa9BwDzhFVdlI6sV0OCPq9JrOrJSp9/ioLsezbV8w9RCJ8v55uzJuJ5RgWLZg==}
|
||||
'@next/swc-win32-arm64-msvc@15.4.7':
|
||||
resolution: {integrity: sha512-pZyxmY1iHlZJ04LUL7Css8bNvsYAMYOY9JRwFA3HZgpaNKsJSowD09Vg2R9734GxAcLJc2KDQHSCR91uD6/AAw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@15.4.1':
|
||||
resolution: {integrity: sha512-y+wTBxelk2xiNofmDOVU7O5WxTHcvOoL3srOM0kxTzKDjQ57kPU0tpnPJ/BWrRnsOwXEv0+3QSbGR7hY4n9LkQ==}
|
||||
'@next/swc-win32-x64-msvc@15.4.7':
|
||||
resolution: {integrity: sha512-HjuwPJ7BeRzgl3KrjKqD2iDng0eQIpIReyhpF5r4yeAHFwWRuAhfW92rWv/r3qeQHEwHsLRzFDvMqRjyM5DI6A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -742,8 +757,8 @@ packages:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
caniuse-lite@1.0.30001695:
|
||||
resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==}
|
||||
caniuse-lite@1.0.30001754:
|
||||
resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==}
|
||||
|
||||
change-case@5.4.4:
|
||||
resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==}
|
||||
@@ -755,20 +770,6 @@ packages:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
||||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
color-string@1.9.1:
|
||||
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
||||
|
||||
color@4.2.3:
|
||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||
engines: {node: '>=12.5.0'}
|
||||
|
||||
colorette@1.4.0:
|
||||
resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
|
||||
|
||||
@@ -841,8 +842,8 @@ packages:
|
||||
delaunator@5.0.1:
|
||||
resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
|
||||
|
||||
detect-libc@2.0.4:
|
||||
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
@@ -898,9 +899,6 @@ packages:
|
||||
is-arrayish@0.2.1:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
|
||||
is-arrayish@0.3.2:
|
||||
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||
|
||||
is-core-module@2.16.1:
|
||||
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -953,13 +951,13 @@ packages:
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
nanoid@3.3.8:
|
||||
resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
next@15.4.1:
|
||||
resolution: {integrity: sha512-eNKB1q8C7o9zXF8+jgJs2CzSLIU3T6bQtX6DcTnCq1sIR1CJ0GlSyRs1BubQi3/JgCnr9Vr+rS5mOMI38FFyQw==}
|
||||
next@15.4.7:
|
||||
resolution: {integrity: sha512-OcqRugwF7n7mC8OSYjvsZhhG1AYSvulor1EIUsIkbbEbf1qoE5EbH36Swj8WhF4cHqmDgkiam3z1c1W0J1Wifg==}
|
||||
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -1086,18 +1084,15 @@ packages:
|
||||
scheduler@0.25.0:
|
||||
resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==}
|
||||
|
||||
semver@7.7.2:
|
||||
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
|
||||
semver@7.7.3:
|
||||
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
sharp@0.34.3:
|
||||
resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==}
|
||||
sharp@0.34.5:
|
||||
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
|
||||
simple-swizzle@0.2.2:
|
||||
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1268,7 +1263,7 @@ snapshots:
|
||||
'@biomejs/cli-win32-x64@1.9.4':
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.4.4':
|
||||
'@emnapi/runtime@1.7.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
@@ -1356,90 +1351,101 @@ snapshots:
|
||||
|
||||
'@emotion/weak-memoize@0.4.0': {}
|
||||
|
||||
'@img/sharp-darwin-arm64@0.34.3':
|
||||
'@img/colour@1.0.0':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-darwin-arm64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-arm64': 1.2.0
|
||||
'@img/sharp-libvips-darwin-arm64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-darwin-x64@0.34.3':
|
||||
'@img/sharp-darwin-x64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-darwin-x64': 1.2.0
|
||||
'@img/sharp-libvips-darwin-x64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64@1.2.0':
|
||||
'@img/sharp-libvips-darwin-arm64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-darwin-x64@1.2.0':
|
||||
'@img/sharp-libvips-darwin-x64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm64@1.2.0':
|
||||
'@img/sharp-libvips-linux-arm64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.0':
|
||||
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.0':
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.2.0':
|
||||
'@img/sharp-libvips-linux-riscv64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.0':
|
||||
'@img/sharp-libvips-linux-s390x@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.0':
|
||||
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.0':
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.3':
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm64': 1.2.0
|
||||
'@img/sharp-libvips-linux-arm64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-arm@0.34.3':
|
||||
'@img/sharp-linux-arm@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-arm': 1.2.0
|
||||
'@img/sharp-libvips-linux-arm': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-ppc64@0.34.3':
|
||||
'@img/sharp-linux-ppc64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-ppc64': 1.2.0
|
||||
'@img/sharp-libvips-linux-ppc64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.3':
|
||||
'@img/sharp-linux-riscv64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-s390x': 1.2.0
|
||||
'@img/sharp-libvips-linux-riscv64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linux-x64@0.34.3':
|
||||
'@img/sharp-linux-s390x@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linux-x64': 1.2.0
|
||||
'@img/sharp-libvips-linux-s390x': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.3':
|
||||
'@img/sharp-linux-x64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.2.0
|
||||
'@img/sharp-libvips-linux-x64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.3':
|
||||
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.2.0
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-wasm32@0.34.3':
|
||||
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||
optionalDependencies:
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
|
||||
optional: true
|
||||
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
dependencies:
|
||||
'@emnapi/runtime': 1.4.4
|
||||
'@emnapi/runtime': 1.7.0
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-arm64@0.34.3':
|
||||
'@img/sharp-win32-arm64@0.34.5':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-ia32@0.34.3':
|
||||
'@img/sharp-win32-ia32@0.34.5':
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-x64@0.34.3':
|
||||
'@img/sharp-win32-x64@0.34.5':
|
||||
optional: true
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.8':
|
||||
@@ -1469,11 +1475,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.7
|
||||
|
||||
'@mui/material-nextjs@7.2.0(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(next@15.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)':
|
||||
'@mui/material-nextjs@7.2.0(@emotion/cache@11.14.0)(@emotion/react@11.14.0(@types/react@19.0.7)(react@19.0.0))(@types/react@19.0.7)(next@15.4.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
'@emotion/react': 11.14.0(@types/react@19.0.7)(react@19.0.0)
|
||||
next: 15.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
next: 15.4.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
react: 19.0.0
|
||||
optionalDependencies:
|
||||
'@emotion/cache': 11.14.0
|
||||
@@ -1640,30 +1646,30 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
'@next/env@15.4.1': {}
|
||||
'@next/env@15.4.7': {}
|
||||
|
||||
'@next/swc-darwin-arm64@15.4.1':
|
||||
'@next/swc-darwin-arm64@15.4.7':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@15.4.1':
|
||||
'@next/swc-darwin-x64@15.4.7':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.4.1':
|
||||
'@next/swc-linux-arm64-gnu@15.4.7':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.4.1':
|
||||
'@next/swc-linux-arm64-musl@15.4.7':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.4.1':
|
||||
'@next/swc-linux-x64-gnu@15.4.7':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@15.4.1':
|
||||
'@next/swc-linux-x64-musl@15.4.7':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.4.1':
|
||||
'@next/swc-win32-arm64-msvc@15.4.7':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@15.4.1':
|
||||
'@next/swc-win32-x64-msvc@15.4.7':
|
||||
optional: true
|
||||
|
||||
'@popperjs/core@2.11.8': {}
|
||||
@@ -1798,7 +1804,7 @@ snapshots:
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
caniuse-lite@1.0.30001695: {}
|
||||
caniuse-lite@1.0.30001754: {}
|
||||
|
||||
change-case@5.4.4: {}
|
||||
|
||||
@@ -1806,26 +1812,6 @@ snapshots:
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
optional: true
|
||||
|
||||
color-name@1.1.4:
|
||||
optional: true
|
||||
|
||||
color-string@1.9.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
simple-swizzle: 0.2.2
|
||||
optional: true
|
||||
|
||||
color@4.2.3:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
color-string: 1.9.1
|
||||
optional: true
|
||||
|
||||
colorette@1.4.0: {}
|
||||
|
||||
convert-source-map@1.9.0: {}
|
||||
@@ -1892,7 +1878,7 @@ snapshots:
|
||||
dependencies:
|
||||
robust-predicates: 3.0.2
|
||||
|
||||
detect-libc@2.0.4:
|
||||
detect-libc@2.1.2:
|
||||
optional: true
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
@@ -1942,9 +1928,6 @@ snapshots:
|
||||
|
||||
is-arrayish@0.2.1: {}
|
||||
|
||||
is-arrayish@0.3.2:
|
||||
optional: true
|
||||
|
||||
is-core-module@2.16.1:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
@@ -1989,27 +1972,27 @@ snapshots:
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
nanoid@3.3.8: {}
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
next@15.4.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
next@15.4.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
'@next/env': 15.4.1
|
||||
'@next/env': 15.4.7
|
||||
'@swc/helpers': 0.5.15
|
||||
caniuse-lite: 1.0.30001695
|
||||
caniuse-lite: 1.0.30001754
|
||||
postcss: 8.4.31
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
styled-jsx: 5.1.6(react@19.0.0)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 15.4.1
|
||||
'@next/swc-darwin-x64': 15.4.1
|
||||
'@next/swc-linux-arm64-gnu': 15.4.1
|
||||
'@next/swc-linux-arm64-musl': 15.4.1
|
||||
'@next/swc-linux-x64-gnu': 15.4.1
|
||||
'@next/swc-linux-x64-musl': 15.4.1
|
||||
'@next/swc-win32-arm64-msvc': 15.4.1
|
||||
'@next/swc-win32-x64-msvc': 15.4.1
|
||||
sharp: 0.34.3
|
||||
'@next/swc-darwin-arm64': 15.4.7
|
||||
'@next/swc-darwin-x64': 15.4.7
|
||||
'@next/swc-linux-arm64-gnu': 15.4.7
|
||||
'@next/swc-linux-arm64-musl': 15.4.7
|
||||
'@next/swc-linux-x64-gnu': 15.4.7
|
||||
'@next/swc-linux-x64-musl': 15.4.7
|
||||
'@next/swc-win32-arm64-msvc': 15.4.7
|
||||
'@next/swc-win32-x64-msvc': 15.4.7
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
@@ -2059,7 +2042,7 @@ snapshots:
|
||||
|
||||
postcss@8.4.31:
|
||||
dependencies:
|
||||
nanoid: 3.3.8
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
@@ -2113,42 +2096,39 @@ snapshots:
|
||||
|
||||
scheduler@0.25.0: {}
|
||||
|
||||
semver@7.7.2:
|
||||
semver@7.7.3:
|
||||
optional: true
|
||||
|
||||
sharp@0.34.3:
|
||||
sharp@0.34.5:
|
||||
dependencies:
|
||||
color: 4.2.3
|
||||
detect-libc: 2.0.4
|
||||
semver: 7.7.2
|
||||
'@img/colour': 1.0.0
|
||||
detect-libc: 2.1.2
|
||||
semver: 7.7.3
|
||||
optionalDependencies:
|
||||
'@img/sharp-darwin-arm64': 0.34.3
|
||||
'@img/sharp-darwin-x64': 0.34.3
|
||||
'@img/sharp-libvips-darwin-arm64': 1.2.0
|
||||
'@img/sharp-libvips-darwin-x64': 1.2.0
|
||||
'@img/sharp-libvips-linux-arm': 1.2.0
|
||||
'@img/sharp-libvips-linux-arm64': 1.2.0
|
||||
'@img/sharp-libvips-linux-ppc64': 1.2.0
|
||||
'@img/sharp-libvips-linux-s390x': 1.2.0
|
||||
'@img/sharp-libvips-linux-x64': 1.2.0
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.2.0
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.2.0
|
||||
'@img/sharp-linux-arm': 0.34.3
|
||||
'@img/sharp-linux-arm64': 0.34.3
|
||||
'@img/sharp-linux-ppc64': 0.34.3
|
||||
'@img/sharp-linux-s390x': 0.34.3
|
||||
'@img/sharp-linux-x64': 0.34.3
|
||||
'@img/sharp-linuxmusl-arm64': 0.34.3
|
||||
'@img/sharp-linuxmusl-x64': 0.34.3
|
||||
'@img/sharp-wasm32': 0.34.3
|
||||
'@img/sharp-win32-arm64': 0.34.3
|
||||
'@img/sharp-win32-ia32': 0.34.3
|
||||
'@img/sharp-win32-x64': 0.34.3
|
||||
optional: true
|
||||
|
||||
simple-swizzle@0.2.2:
|
||||
dependencies:
|
||||
is-arrayish: 0.3.2
|
||||
'@img/sharp-darwin-arm64': 0.34.5
|
||||
'@img/sharp-darwin-x64': 0.34.5
|
||||
'@img/sharp-libvips-darwin-arm64': 1.2.4
|
||||
'@img/sharp-libvips-darwin-x64': 1.2.4
|
||||
'@img/sharp-libvips-linux-arm': 1.2.4
|
||||
'@img/sharp-libvips-linux-arm64': 1.2.4
|
||||
'@img/sharp-libvips-linux-ppc64': 1.2.4
|
||||
'@img/sharp-libvips-linux-riscv64': 1.2.4
|
||||
'@img/sharp-libvips-linux-s390x': 1.2.4
|
||||
'@img/sharp-libvips-linux-x64': 1.2.4
|
||||
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
|
||||
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
|
||||
'@img/sharp-linux-arm': 0.34.5
|
||||
'@img/sharp-linux-arm64': 0.34.5
|
||||
'@img/sharp-linux-ppc64': 0.34.5
|
||||
'@img/sharp-linux-riscv64': 0.34.5
|
||||
'@img/sharp-linux-s390x': 0.34.5
|
||||
'@img/sharp-linux-x64': 0.34.5
|
||||
'@img/sharp-linuxmusl-arm64': 0.34.5
|
||||
'@img/sharp-linuxmusl-x64': 0.34.5
|
||||
'@img/sharp-wasm32': 0.34.5
|
||||
'@img/sharp-win32-arm64': 0.34.5
|
||||
'@img/sharp-win32-ia32': 0.34.5
|
||||
'@img/sharp-win32-x64': 0.34.5
|
||||
optional: true
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
+1
-2
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node"
|
||||
version = "1.21.0"
|
||||
version = "1.23.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
@@ -27,7 +27,6 @@ console-subscriber = { workspace = true, optional = true }
|
||||
csv = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "env"] }
|
||||
futures = { workspace = true }
|
||||
humantime = { workspace = true }
|
||||
humantime-serde = { workspace = true }
|
||||
human-repr = { workspace = true }
|
||||
ipnetwork = { workspace = true }
|
||||
|
||||
@@ -944,7 +944,6 @@ pub struct Wireguard {
|
||||
|
||||
/// Tunnel port announced to external clients wishing to connect to the wireguard interface.
|
||||
/// Useful in the instances where the node is behind a proxy.
|
||||
#[serde(alias = "announced_port")]
|
||||
pub announced_tunnel_port: u16,
|
||||
|
||||
/// Metadata port announced to external clients wishing to connect to the metadata endpoint.
|
||||
@@ -1002,7 +1001,6 @@ impl From<Wireguard> for nym_authenticator::config::Authenticator {
|
||||
tunnel_announced_port: value.announced_tunnel_port,
|
||||
private_network_prefix_v4: value.private_network_prefix_v4,
|
||||
private_network_prefix_v6: value.private_network_prefix_v6,
|
||||
peer_interaction_timeout: nym_authenticator::config::default_peer_interaction_timeout(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
mod old_config_v1;
|
||||
mod old_config_v10;
|
||||
mod old_config_v11;
|
||||
mod old_config_v2;
|
||||
mod old_config_v3;
|
||||
mod old_config_v4;
|
||||
@@ -22,3 +23,4 @@ pub use old_config_v7::try_upgrade_config_v7;
|
||||
pub use old_config_v8::try_upgrade_config_v8;
|
||||
pub use old_config_v9::try_upgrade_config_v9;
|
||||
pub use old_config_v10::try_upgrade_config_v10;
|
||||
pub use old_config_v11::try_upgrade_config_v11;
|
||||
|
||||
@@ -1,26 +1,11 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::authenticator::{Authenticator, AuthenticatorDebug};
|
||||
use crate::config::gateway_tasks::{
|
||||
ClientBandwidthDebug, StaleMessageDebug, UpgradeModeWatcher, ZkNymTicketHandlerDebug,
|
||||
};
|
||||
use crate::config::persistence::{
|
||||
AuthenticatorPaths, GatewayTasksPaths, IpPacketRouterPaths, KeysPaths, NetworkRequesterPaths,
|
||||
NymNodePaths, ReplayProtectionPaths, ServiceProvidersPaths, WireguardPaths,
|
||||
};
|
||||
use crate::config::service_providers::{
|
||||
IpPacketRouter, IpPacketRouterDebug, NetworkRequester, NetworkRequesterDebug,
|
||||
};
|
||||
use crate::config::{
|
||||
Config, DEFAULT_HTTP_PORT, GatewayTasksConfig, Host, Http, KeyRotation, KeyRotationDebug,
|
||||
Mixnet, MixnetDebug, NodeModes, ReplayProtection, ReplayProtectionDebug,
|
||||
ServiceProvidersConfig, Verloc, VerlocDebug, Wireguard, gateway_tasks, service_providers,
|
||||
};
|
||||
use crate::config::old_configs::old_config_v11::{ConfigV11, WireguardV11};
|
||||
use crate::config::{DEFAULT_HTTP_PORT, NodeModes};
|
||||
use crate::error::NymNodeError;
|
||||
use celes::Country;
|
||||
use clap::ValueEnum;
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core_config_types::DebugConfig as ClientDebugConfig;
|
||||
use nym_config::defaults::{DEFAULT_VERLOC_LISTENING_PORT, WG_METADATA_PORT};
|
||||
use nym_config::helpers::{in6addr_any_init, inaddr_any};
|
||||
@@ -33,7 +18,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
use tracing::{debug, error, instrument};
|
||||
use tracing::{debug, instrument};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
@@ -63,7 +48,6 @@ pub struct WireguardV10 {
|
||||
|
||||
/// Port announced to external clients wishing to connect to the wireguard interface.
|
||||
/// Useful in the instances where the node is behind a proxy.
|
||||
#[serde(alias = "announced_tunnel_port")]
|
||||
pub announced_port: u16,
|
||||
|
||||
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
|
||||
@@ -1170,7 +1154,7 @@ impl ConfigV10 {
|
||||
pub async fn try_upgrade_config_v10<P: AsRef<Path>>(
|
||||
path: P,
|
||||
prev_config: Option<ConfigV10>,
|
||||
) -> Result<Config, NymNodeError> {
|
||||
) -> Result<ConfigV11, NymNodeError> {
|
||||
debug!("attempting to load v10 config...");
|
||||
|
||||
let old_cfg = if let Some(prev_config) = prev_config {
|
||||
@@ -1179,398 +1163,37 @@ pub async fn try_upgrade_config_v10<P: AsRef<Path>>(
|
||||
ConfigV10::read_from_path(&path)?
|
||||
};
|
||||
|
||||
let cfg = Config {
|
||||
let cfg = ConfigV11 {
|
||||
save_path: old_cfg.save_path,
|
||||
id: old_cfg.id,
|
||||
modes: NodeModes {
|
||||
mixnode: old_cfg.modes.mixnode,
|
||||
entry: old_cfg.modes.entry,
|
||||
exit: old_cfg.modes.exit,
|
||||
},
|
||||
host: Host {
|
||||
public_ips: old_cfg.host.public_ips,
|
||||
hostname: old_cfg.host.hostname,
|
||||
location: old_cfg.host.location,
|
||||
},
|
||||
mixnet: Mixnet {
|
||||
bind_address: old_cfg.mixnet.bind_address,
|
||||
announce_port: old_cfg.mixnet.announce_port,
|
||||
nym_api_urls: old_cfg.mixnet.nym_api_urls,
|
||||
nyxd_urls: old_cfg.mixnet.nyxd_urls,
|
||||
replay_protection: ReplayProtection {
|
||||
storage_paths: ReplayProtectionPaths {
|
||||
current_bloomfilters_directory: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.storage_paths
|
||||
.current_bloomfilters_directory,
|
||||
},
|
||||
debug: ReplayProtectionDebug {
|
||||
unsafe_disabled: old_cfg.mixnet.replay_protection.debug.unsafe_disabled,
|
||||
maximum_replay_detection_deferral: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.maximum_replay_detection_deferral,
|
||||
maximum_replay_detection_pending_packets: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.maximum_replay_detection_pending_packets,
|
||||
false_positive_rate: old_cfg.mixnet.replay_protection.debug.false_positive_rate,
|
||||
initial_expected_packets_per_second: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.initial_expected_packets_per_second,
|
||||
bloomfilter_minimum_packets_per_second_size: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.bloomfilter_minimum_packets_per_second_size,
|
||||
bloomfilter_size_multiplier: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.bloomfilter_size_multiplier,
|
||||
bloomfilter_disk_flushing_rate: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.bloomfilter_disk_flushing_rate,
|
||||
},
|
||||
},
|
||||
key_rotation: KeyRotation {
|
||||
debug: KeyRotationDebug {
|
||||
rotation_state_poling_interval: old_cfg
|
||||
.mixnet
|
||||
.key_rotation
|
||||
.debug
|
||||
.rotation_state_poling_interval,
|
||||
},
|
||||
},
|
||||
debug: MixnetDebug {
|
||||
maximum_forward_packet_delay: old_cfg.mixnet.debug.maximum_forward_packet_delay,
|
||||
packet_forwarding_initial_backoff: old_cfg
|
||||
.mixnet
|
||||
.debug
|
||||
.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: old_cfg
|
||||
.mixnet
|
||||
.debug
|
||||
.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: old_cfg.mixnet.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: old_cfg.mixnet.debug.maximum_connection_buffer_size,
|
||||
unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise,
|
||||
use_legacy_packet_encoding: old_cfg.mixnet.debug.use_legacy_packet_encoding,
|
||||
},
|
||||
},
|
||||
storage_paths: NymNodePaths {
|
||||
keys: KeysPaths {
|
||||
private_ed25519_identity_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.private_ed25519_identity_key_file,
|
||||
public_ed25519_identity_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.public_ed25519_identity_key_file,
|
||||
primary_x25519_sphinx_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.primary_x25519_sphinx_key_file,
|
||||
private_x25519_noise_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.private_x25519_noise_key_file,
|
||||
public_x25519_noise_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.public_x25519_noise_key_file,
|
||||
secondary_x25519_sphinx_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.secondary_x25519_sphinx_key_file,
|
||||
},
|
||||
description: old_cfg.storage_paths.description,
|
||||
},
|
||||
http: Http {
|
||||
bind_address: old_cfg.http.bind_address,
|
||||
landing_page_assets_path: old_cfg.http.landing_page_assets_path,
|
||||
access_token: old_cfg.http.access_token,
|
||||
expose_system_info: old_cfg.http.expose_system_info,
|
||||
expose_system_hardware: old_cfg.http.expose_system_hardware,
|
||||
expose_crypto_hardware: old_cfg.http.expose_crypto_hardware,
|
||||
node_load_cache_ttl: old_cfg.http.node_load_cache_ttl,
|
||||
},
|
||||
verloc: Verloc {
|
||||
bind_address: old_cfg.verloc.bind_address,
|
||||
announce_port: old_cfg.verloc.announce_port,
|
||||
debug: VerlocDebug {
|
||||
packets_per_node: old_cfg.verloc.debug.packets_per_node,
|
||||
connection_timeout: old_cfg.verloc.debug.connection_timeout,
|
||||
packet_timeout: old_cfg.verloc.debug.packet_timeout,
|
||||
delay_between_packets: old_cfg.verloc.debug.delay_between_packets,
|
||||
tested_nodes_batch_size: old_cfg.verloc.debug.tested_nodes_batch_size,
|
||||
testing_interval: old_cfg.verloc.debug.testing_interval,
|
||||
retry_timeout: old_cfg.verloc.debug.retry_timeout,
|
||||
},
|
||||
},
|
||||
wireguard: Wireguard {
|
||||
modes: old_cfg.modes,
|
||||
host: old_cfg.host,
|
||||
mixnet: old_cfg.mixnet,
|
||||
storage_paths: old_cfg.storage_paths,
|
||||
http: old_cfg.http,
|
||||
verloc: old_cfg.verloc,
|
||||
wireguard: WireguardV11 {
|
||||
enabled: old_cfg.wireguard.enabled,
|
||||
bind_address: old_cfg.wireguard.bind_address,
|
||||
private_ipv4: old_cfg.wireguard.private_ipv4,
|
||||
private_ipv6: old_cfg.wireguard.private_ipv6,
|
||||
|
||||
// \/ CHANGED
|
||||
announced_tunnel_port: old_cfg.wireguard.announced_port,
|
||||
// /\ CHANGED
|
||||
|
||||
// \/ ADDED
|
||||
announced_metadata_port: WG_METADATA_PORT,
|
||||
// /\ ADDED
|
||||
private_network_prefix_v4: old_cfg.wireguard.private_network_prefix_v4,
|
||||
private_network_prefix_v6: old_cfg.wireguard.private_network_prefix_v6,
|
||||
storage_paths: WireguardPaths {
|
||||
private_diffie_hellman_key_file: old_cfg
|
||||
.wireguard
|
||||
.storage_paths
|
||||
.private_diffie_hellman_key_file,
|
||||
public_diffie_hellman_key_file: old_cfg
|
||||
.wireguard
|
||||
.storage_paths
|
||||
.public_diffie_hellman_key_file,
|
||||
},
|
||||
storage_paths: old_cfg.wireguard.storage_paths,
|
||||
},
|
||||
gateway_tasks: GatewayTasksConfig {
|
||||
storage_paths: GatewayTasksPaths {
|
||||
clients_storage: old_cfg.gateway_tasks.storage_paths.clients_storage,
|
||||
stats_storage: old_cfg.gateway_tasks.storage_paths.stats_storage,
|
||||
cosmos_mnemonic: old_cfg.gateway_tasks.storage_paths.cosmos_mnemonic,
|
||||
bridge_client_params: old_cfg.gateway_tasks.storage_paths.bridge_client_params,
|
||||
},
|
||||
enforce_zk_nyms: old_cfg.gateway_tasks.enforce_zk_nyms,
|
||||
ws_bind_address: old_cfg.gateway_tasks.ws_bind_address,
|
||||
announce_ws_port: old_cfg.gateway_tasks.announce_ws_port,
|
||||
announce_wss_port: old_cfg.gateway_tasks.announce_wss_port,
|
||||
upgrade_mode: UpgradeModeWatcher::new()
|
||||
.inspect_err(|_| {
|
||||
error!(
|
||||
"failed to set custom upgrade mode configuration - falling back to mainnet"
|
||||
)
|
||||
})
|
||||
.unwrap_or(UpgradeModeWatcher::new_mainnet()),
|
||||
debug: gateway_tasks::Debug {
|
||||
message_retrieval_limit: old_cfg.gateway_tasks.debug.message_retrieval_limit,
|
||||
maximum_open_connections: old_cfg.gateway_tasks.debug.maximum_open_connections,
|
||||
minimum_mix_performance: old_cfg.gateway_tasks.debug.minimum_mix_performance,
|
||||
max_request_timestamp_skew: old_cfg.gateway_tasks.debug.max_request_timestamp_skew,
|
||||
stale_messages: StaleMessageDebug {
|
||||
cleaner_run_interval: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.stale_messages
|
||||
.cleaner_run_interval,
|
||||
max_age: old_cfg.gateway_tasks.debug.stale_messages.max_age,
|
||||
},
|
||||
client_bandwidth: ClientBandwidthDebug {
|
||||
max_flushing_rate: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.client_bandwidth
|
||||
.max_flushing_rate,
|
||||
max_delta_flushing_amount: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.client_bandwidth
|
||||
.max_delta_flushing_amount,
|
||||
},
|
||||
zk_nym_tickets: ZkNymTicketHandlerDebug {
|
||||
revocation_bandwidth_penalty: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.zk_nym_tickets
|
||||
.revocation_bandwidth_penalty,
|
||||
pending_poller: old_cfg.gateway_tasks.debug.zk_nym_tickets.pending_poller,
|
||||
minimum_api_quorum: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.zk_nym_tickets
|
||||
.minimum_api_quorum,
|
||||
minimum_redemption_tickets: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.zk_nym_tickets
|
||||
.minimum_redemption_tickets,
|
||||
maximum_time_between_redemption: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.zk_nym_tickets
|
||||
.maximum_time_between_redemption,
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
service_providers: ServiceProvidersConfig {
|
||||
storage_paths: ServiceProvidersPaths {
|
||||
clients_storage: old_cfg.service_providers.storage_paths.clients_storage,
|
||||
stats_storage: old_cfg.service_providers.storage_paths.stats_storage,
|
||||
network_requester: NetworkRequesterPaths {
|
||||
private_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.private_ed25519_identity_key_file,
|
||||
public_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.public_ed25519_identity_key_file,
|
||||
private_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.private_x25519_diffie_hellman_key_file,
|
||||
public_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.public_x25519_diffie_hellman_key_file,
|
||||
ack_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.ack_key_file,
|
||||
reply_surb_database: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.reply_surb_database,
|
||||
gateway_registrations: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.gateway_registrations,
|
||||
},
|
||||
ip_packet_router: IpPacketRouterPaths {
|
||||
private_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.private_ed25519_identity_key_file,
|
||||
public_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.public_ed25519_identity_key_file,
|
||||
private_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.private_x25519_diffie_hellman_key_file,
|
||||
public_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.public_x25519_diffie_hellman_key_file,
|
||||
ack_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.ack_key_file,
|
||||
reply_surb_database: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.reply_surb_database,
|
||||
gateway_registrations: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.gateway_registrations,
|
||||
},
|
||||
authenticator: AuthenticatorPaths {
|
||||
private_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.private_ed25519_identity_key_file,
|
||||
public_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.public_ed25519_identity_key_file,
|
||||
private_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.private_x25519_diffie_hellman_key_file,
|
||||
public_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.public_x25519_diffie_hellman_key_file,
|
||||
ack_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.ack_key_file,
|
||||
reply_surb_database: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.reply_surb_database,
|
||||
gateway_registrations: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.gateway_registrations,
|
||||
},
|
||||
},
|
||||
open_proxy: old_cfg.service_providers.open_proxy,
|
||||
upstream_exit_policy_url: old_cfg.service_providers.upstream_exit_policy_url,
|
||||
network_requester: NetworkRequester {
|
||||
debug: NetworkRequesterDebug {
|
||||
enabled: old_cfg.service_providers.network_requester.debug.enabled,
|
||||
disable_poisson_rate: old_cfg
|
||||
.service_providers
|
||||
.network_requester
|
||||
.debug
|
||||
.disable_poisson_rate,
|
||||
client_debug: old_cfg
|
||||
.service_providers
|
||||
.network_requester
|
||||
.debug
|
||||
.client_debug,
|
||||
},
|
||||
},
|
||||
ip_packet_router: IpPacketRouter {
|
||||
debug: IpPacketRouterDebug {
|
||||
enabled: old_cfg.service_providers.ip_packet_router.debug.enabled,
|
||||
disable_poisson_rate: old_cfg
|
||||
.service_providers
|
||||
.ip_packet_router
|
||||
.debug
|
||||
.disable_poisson_rate,
|
||||
client_debug: old_cfg
|
||||
.service_providers
|
||||
.ip_packet_router
|
||||
.debug
|
||||
.client_debug,
|
||||
},
|
||||
},
|
||||
authenticator: Authenticator {
|
||||
debug: AuthenticatorDebug {
|
||||
enabled: old_cfg.service_providers.authenticator.debug.enabled,
|
||||
disable_poisson_rate: old_cfg
|
||||
.service_providers
|
||||
.authenticator
|
||||
.debug
|
||||
.disable_poisson_rate,
|
||||
client_debug: old_cfg.service_providers.authenticator.debug.client_debug,
|
||||
},
|
||||
},
|
||||
debug: service_providers::Debug {
|
||||
message_retrieval_limit: old_cfg.service_providers.debug.message_retrieval_limit,
|
||||
},
|
||||
},
|
||||
metrics: Default::default(),
|
||||
logging: LoggingSettings {},
|
||||
debug: Default::default(),
|
||||
gateway_tasks: old_cfg.gateway_tasks,
|
||||
service_providers: old_cfg.service_providers,
|
||||
metrics: old_cfg.metrics,
|
||||
logging: old_cfg.logging,
|
||||
debug: old_cfg.debug,
|
||||
};
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,599 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::authenticator::{Authenticator, AuthenticatorDebug};
|
||||
use crate::config::gateway_tasks::{
|
||||
ClientBandwidthDebug, StaleMessageDebug, UpgradeModeWatcher, ZkNymTicketHandlerDebug,
|
||||
};
|
||||
use crate::config::persistence::{
|
||||
AuthenticatorPaths, GatewayTasksPaths, IpPacketRouterPaths, KeysPaths, NetworkRequesterPaths,
|
||||
NymNodePaths, ReplayProtectionPaths, ServiceProvidersPaths, WireguardPaths,
|
||||
};
|
||||
use crate::config::service_providers::{
|
||||
IpPacketRouter, IpPacketRouterDebug, NetworkRequester, NetworkRequesterDebug,
|
||||
};
|
||||
use crate::config::{
|
||||
Config, GatewayTasksConfig, Host, Http, KeyRotation, KeyRotationDebug, Mixnet, MixnetDebug,
|
||||
NodeModes, ReplayProtection, ReplayProtectionDebug, ServiceProvidersConfig, Verloc,
|
||||
VerlocDebug, Wireguard, gateway_tasks, service_providers,
|
||||
};
|
||||
use crate::error::NymNodeError;
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_config::read_config_from_toml_file;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::{debug, error, instrument};
|
||||
|
||||
pub use unchanged_v11_types::*;
|
||||
|
||||
// (while some of those are technically unused, they might be needed in future migrations,
|
||||
// thus allow them to exist)
|
||||
#[allow(dead_code)]
|
||||
pub mod unchanged_v11_types {
|
||||
use crate::config::old_configs::old_config_v10::{
|
||||
AuthenticatorDebugV10, AuthenticatorPathsV10, AuthenticatorV10, ClientBandwidthDebugV10,
|
||||
DebugV10, GatewayTasksConfigDebugV10, GatewayTasksConfigV10, GatewayTasksPathsV10, HostV10,
|
||||
HttpV10, IpPacketRouterDebugV10, IpPacketRouterPathsV10, IpPacketRouterV10,
|
||||
KeyRotationDebugV10, KeyRotationV10, KeysPathsV10, LoggingSettingsV10, MetricsConfigV10,
|
||||
MetricsDebugV10, MixnetDebugV10, MixnetV10, NetworkRequesterDebugV10,
|
||||
NetworkRequesterPathsV10, NetworkRequesterV10, NodeModeV10, NodeModesV10, NymNodePathsV10,
|
||||
ReplayProtectionDebugV10, ReplayProtectionPathsV10, ReplayProtectionV10,
|
||||
ServiceProvidersConfigDebugV10, ServiceProvidersConfigV10, ServiceProvidersPathsV10,
|
||||
StaleMessageDebugV10, VerlocDebugV10, VerlocV10, WireguardPathsV10,
|
||||
ZkNymTicketHandlerDebugV10,
|
||||
};
|
||||
|
||||
pub type WireguardPathsV11 = WireguardPathsV10;
|
||||
pub type NodeModeV11 = NodeModeV10;
|
||||
pub type NodeModesV11 = NodeModesV10;
|
||||
pub type HostV11 = HostV10;
|
||||
pub type KeyRotationDebugV11 = KeyRotationDebugV10;
|
||||
pub type KeyRotationV11 = KeyRotationV10;
|
||||
pub type MixnetDebugV11 = MixnetDebugV10;
|
||||
pub type MixnetV11 = MixnetV10;
|
||||
pub type ReplayProtectionV11 = ReplayProtectionV10;
|
||||
pub type ReplayProtectionPathsV11 = ReplayProtectionPathsV10;
|
||||
pub type ReplayProtectionDebugV11 = ReplayProtectionDebugV10;
|
||||
pub type KeysPathsV11 = KeysPathsV10;
|
||||
pub type NymNodePathsV11 = NymNodePathsV10;
|
||||
pub type HttpV11 = HttpV10;
|
||||
pub type VerlocDebugV11 = VerlocDebugV10;
|
||||
pub type VerlocV11 = VerlocV10;
|
||||
pub type DebugV11 = DebugV10;
|
||||
pub type ZkNymTicketHandlerDebugV11 = ZkNymTicketHandlerDebugV10;
|
||||
pub type NetworkRequesterPathsV11 = NetworkRequesterPathsV10;
|
||||
pub type IpPacketRouterPathsV11 = IpPacketRouterPathsV10;
|
||||
pub type AuthenticatorPathsV11 = AuthenticatorPathsV10;
|
||||
pub type AuthenticatorV11 = AuthenticatorV10;
|
||||
pub type AuthenticatorDebugV11 = AuthenticatorDebugV10;
|
||||
pub type IpPacketRouterDebugV11 = IpPacketRouterDebugV10;
|
||||
pub type IpPacketRouterV11 = IpPacketRouterV10;
|
||||
pub type NetworkRequesterDebugV11 = NetworkRequesterDebugV10;
|
||||
pub type NetworkRequesterV11 = NetworkRequesterV10;
|
||||
pub type GatewayTasksPathsV11 = GatewayTasksPathsV10;
|
||||
pub type StaleMessageDebugV11 = StaleMessageDebugV10;
|
||||
pub type ClientBandwidthDebugV11 = ClientBandwidthDebugV10;
|
||||
pub type GatewayTasksConfigDebugV11 = GatewayTasksConfigDebugV10;
|
||||
pub type GatewayTasksConfigV11 = GatewayTasksConfigV10;
|
||||
pub type ServiceProvidersPathsV11 = ServiceProvidersPathsV10;
|
||||
pub type ServiceProvidersConfigDebugV11 = ServiceProvidersConfigDebugV10;
|
||||
pub type ServiceProvidersConfigV11 = ServiceProvidersConfigV10;
|
||||
pub type MetricsConfigV11 = MetricsConfigV10;
|
||||
pub type MetricsDebugV11 = MetricsDebugV10;
|
||||
pub type LoggingSettingsV11 = LoggingSettingsV10;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WireguardV11 {
|
||||
/// Specifies whether the wireguard service is enabled on this node.
|
||||
pub enabled: bool,
|
||||
|
||||
/// Socket address this node will use for binding its wireguard interface.
|
||||
/// default: `[::]:51822`
|
||||
pub bind_address: SocketAddr,
|
||||
|
||||
/// Private IPv4 address of the wireguard gateway.
|
||||
/// default: `10.1.0.1`
|
||||
pub private_ipv4: Ipv4Addr,
|
||||
|
||||
/// Private IPv6 address of the wireguard gateway.
|
||||
/// default: `fc01::1`
|
||||
pub private_ipv6: Ipv6Addr,
|
||||
|
||||
/// Tunnel port announced to external clients wishing to connect to the wireguard interface.
|
||||
/// Useful in the instances where the node is behind a proxy.
|
||||
pub announced_tunnel_port: u16,
|
||||
|
||||
/// Metadata port announced to external clients wishing to connect to the metadata endpoint.
|
||||
/// Useful in the instances where the node is behind a proxy.
|
||||
pub announced_metadata_port: u16,
|
||||
|
||||
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv4.
|
||||
/// The maximum value for IPv4 is 32
|
||||
pub private_network_prefix_v4: u8,
|
||||
|
||||
/// The prefix denoting the maximum number of the clients that can be connected via Wireguard using IPv6.
|
||||
/// The maximum value for IPv6 is 128
|
||||
pub private_network_prefix_v6: u8,
|
||||
|
||||
/// Paths for wireguard keys, client registries, etc.
|
||||
pub storage_paths: WireguardPathsV11,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV11 {
|
||||
// additional metadata holding on-disk location of this config file
|
||||
#[serde(skip)]
|
||||
pub(crate) save_path: Option<PathBuf>,
|
||||
|
||||
/// Human-readable ID of this particular node.
|
||||
pub id: String,
|
||||
|
||||
/// Current modes of this nym-node.
|
||||
pub modes: NodeModesV11,
|
||||
|
||||
pub host: HostV11,
|
||||
|
||||
pub mixnet: MixnetV11,
|
||||
|
||||
/// Storage paths to persistent nym-node data, such as its long term keys.
|
||||
pub storage_paths: NymNodePathsV11,
|
||||
|
||||
#[serde(default)]
|
||||
pub http: HttpV11,
|
||||
|
||||
#[serde(default)]
|
||||
pub verloc: VerlocV11,
|
||||
|
||||
pub wireguard: WireguardV11,
|
||||
|
||||
#[serde(alias = "entry_gateway")]
|
||||
pub gateway_tasks: GatewayTasksConfigV11,
|
||||
|
||||
#[serde(alias = "exit_gateway")]
|
||||
pub service_providers: ServiceProvidersConfigV11,
|
||||
|
||||
#[serde(default)]
|
||||
pub metrics: MetricsConfigV11,
|
||||
|
||||
#[serde(default)]
|
||||
pub logging: LoggingSettingsV11,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: DebugV11,
|
||||
}
|
||||
|
||||
impl ConfigV11 {
|
||||
// simple wrapper that reads config file and assigns path location
|
||||
fn read_from_path<P: AsRef<Path>>(path: P) -> Result<Self, NymNodeError> {
|
||||
let path = path.as_ref();
|
||||
let mut loaded: ConfigV11 =
|
||||
read_config_from_toml_file(path).map_err(|source| NymNodeError::ConfigLoadFailure {
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
})?;
|
||||
loaded.save_path = Some(path.to_path_buf());
|
||||
debug!("loaded config file from {}", path.display());
|
||||
Ok(loaded)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn try_upgrade_config_v11<P: AsRef<Path>>(
|
||||
path: P,
|
||||
prev_config: Option<ConfigV11>,
|
||||
) -> Result<Config, NymNodeError> {
|
||||
debug!("attempting to load v11 config...");
|
||||
|
||||
let old_cfg = if let Some(prev_config) = prev_config {
|
||||
prev_config
|
||||
} else {
|
||||
ConfigV11::read_from_path(&path)?
|
||||
};
|
||||
|
||||
// for future reference: when creating v12 migration,
|
||||
// look at how v10 -> v11 is implemented
|
||||
// you might be able to create a bunch of type aliases again to save you some headache
|
||||
let cfg = Config {
|
||||
save_path: old_cfg.save_path,
|
||||
id: old_cfg.id,
|
||||
modes: NodeModes {
|
||||
mixnode: old_cfg.modes.mixnode,
|
||||
entry: old_cfg.modes.entry,
|
||||
exit: old_cfg.modes.exit,
|
||||
},
|
||||
host: Host {
|
||||
public_ips: old_cfg.host.public_ips,
|
||||
hostname: old_cfg.host.hostname,
|
||||
location: old_cfg.host.location,
|
||||
},
|
||||
mixnet: Mixnet {
|
||||
bind_address: old_cfg.mixnet.bind_address,
|
||||
announce_port: old_cfg.mixnet.announce_port,
|
||||
nym_api_urls: old_cfg.mixnet.nym_api_urls,
|
||||
nyxd_urls: old_cfg.mixnet.nyxd_urls,
|
||||
replay_protection: ReplayProtection {
|
||||
storage_paths: ReplayProtectionPaths {
|
||||
current_bloomfilters_directory: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.storage_paths
|
||||
.current_bloomfilters_directory,
|
||||
},
|
||||
debug: ReplayProtectionDebug {
|
||||
unsafe_disabled: old_cfg.mixnet.replay_protection.debug.unsafe_disabled,
|
||||
maximum_replay_detection_deferral: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.maximum_replay_detection_deferral,
|
||||
maximum_replay_detection_pending_packets: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.maximum_replay_detection_pending_packets,
|
||||
false_positive_rate: old_cfg.mixnet.replay_protection.debug.false_positive_rate,
|
||||
initial_expected_packets_per_second: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.initial_expected_packets_per_second,
|
||||
bloomfilter_minimum_packets_per_second_size: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.bloomfilter_minimum_packets_per_second_size,
|
||||
bloomfilter_size_multiplier: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.bloomfilter_size_multiplier,
|
||||
bloomfilter_disk_flushing_rate: old_cfg
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.debug
|
||||
.bloomfilter_disk_flushing_rate,
|
||||
},
|
||||
},
|
||||
key_rotation: KeyRotation {
|
||||
debug: KeyRotationDebug {
|
||||
rotation_state_poling_interval: old_cfg
|
||||
.mixnet
|
||||
.key_rotation
|
||||
.debug
|
||||
.rotation_state_poling_interval,
|
||||
},
|
||||
},
|
||||
debug: MixnetDebug {
|
||||
maximum_forward_packet_delay: old_cfg.mixnet.debug.maximum_forward_packet_delay,
|
||||
packet_forwarding_initial_backoff: old_cfg
|
||||
.mixnet
|
||||
.debug
|
||||
.packet_forwarding_initial_backoff,
|
||||
packet_forwarding_maximum_backoff: old_cfg
|
||||
.mixnet
|
||||
.debug
|
||||
.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: old_cfg.mixnet.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: old_cfg.mixnet.debug.maximum_connection_buffer_size,
|
||||
unsafe_disable_noise: old_cfg.mixnet.debug.unsafe_disable_noise,
|
||||
use_legacy_packet_encoding: old_cfg.mixnet.debug.use_legacy_packet_encoding,
|
||||
},
|
||||
},
|
||||
storage_paths: NymNodePaths {
|
||||
keys: KeysPaths {
|
||||
private_ed25519_identity_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.private_ed25519_identity_key_file,
|
||||
public_ed25519_identity_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.public_ed25519_identity_key_file,
|
||||
primary_x25519_sphinx_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.primary_x25519_sphinx_key_file,
|
||||
private_x25519_noise_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.private_x25519_noise_key_file,
|
||||
public_x25519_noise_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.public_x25519_noise_key_file,
|
||||
secondary_x25519_sphinx_key_file: old_cfg
|
||||
.storage_paths
|
||||
.keys
|
||||
.secondary_x25519_sphinx_key_file,
|
||||
},
|
||||
description: old_cfg.storage_paths.description,
|
||||
},
|
||||
http: Http {
|
||||
bind_address: old_cfg.http.bind_address,
|
||||
landing_page_assets_path: old_cfg.http.landing_page_assets_path,
|
||||
access_token: old_cfg.http.access_token,
|
||||
expose_system_info: old_cfg.http.expose_system_info,
|
||||
expose_system_hardware: old_cfg.http.expose_system_hardware,
|
||||
expose_crypto_hardware: old_cfg.http.expose_crypto_hardware,
|
||||
node_load_cache_ttl: old_cfg.http.node_load_cache_ttl,
|
||||
},
|
||||
verloc: Verloc {
|
||||
bind_address: old_cfg.verloc.bind_address,
|
||||
announce_port: old_cfg.verloc.announce_port,
|
||||
debug: VerlocDebug {
|
||||
packets_per_node: old_cfg.verloc.debug.packets_per_node,
|
||||
connection_timeout: old_cfg.verloc.debug.connection_timeout,
|
||||
packet_timeout: old_cfg.verloc.debug.packet_timeout,
|
||||
delay_between_packets: old_cfg.verloc.debug.delay_between_packets,
|
||||
tested_nodes_batch_size: old_cfg.verloc.debug.tested_nodes_batch_size,
|
||||
testing_interval: old_cfg.verloc.debug.testing_interval,
|
||||
retry_timeout: old_cfg.verloc.debug.retry_timeout,
|
||||
},
|
||||
},
|
||||
wireguard: Wireguard {
|
||||
enabled: old_cfg.wireguard.enabled,
|
||||
bind_address: old_cfg.wireguard.bind_address,
|
||||
private_ipv4: old_cfg.wireguard.private_ipv4,
|
||||
private_ipv6: old_cfg.wireguard.private_ipv6,
|
||||
announced_tunnel_port: old_cfg.wireguard.announced_tunnel_port,
|
||||
announced_metadata_port: old_cfg.wireguard.announced_metadata_port,
|
||||
private_network_prefix_v4: old_cfg.wireguard.private_network_prefix_v4,
|
||||
private_network_prefix_v6: old_cfg.wireguard.private_network_prefix_v6,
|
||||
storage_paths: WireguardPaths {
|
||||
private_diffie_hellman_key_file: old_cfg
|
||||
.wireguard
|
||||
.storage_paths
|
||||
.private_diffie_hellman_key_file,
|
||||
public_diffie_hellman_key_file: old_cfg
|
||||
.wireguard
|
||||
.storage_paths
|
||||
.public_diffie_hellman_key_file,
|
||||
},
|
||||
},
|
||||
gateway_tasks: GatewayTasksConfig {
|
||||
storage_paths: GatewayTasksPaths {
|
||||
clients_storage: old_cfg.gateway_tasks.storage_paths.clients_storage,
|
||||
stats_storage: old_cfg.gateway_tasks.storage_paths.stats_storage,
|
||||
cosmos_mnemonic: old_cfg.gateway_tasks.storage_paths.cosmos_mnemonic,
|
||||
bridge_client_params: old_cfg.gateway_tasks.storage_paths.bridge_client_params,
|
||||
},
|
||||
enforce_zk_nyms: old_cfg.gateway_tasks.enforce_zk_nyms,
|
||||
ws_bind_address: old_cfg.gateway_tasks.ws_bind_address,
|
||||
announce_ws_port: old_cfg.gateway_tasks.announce_ws_port,
|
||||
announce_wss_port: old_cfg.gateway_tasks.announce_wss_port,
|
||||
// \/ ADDED
|
||||
upgrade_mode: UpgradeModeWatcher::new()
|
||||
.inspect_err(|_| {
|
||||
error!(
|
||||
"failed to set custom upgrade mode configuration - falling back to mainnet"
|
||||
)
|
||||
})
|
||||
.unwrap_or(UpgradeModeWatcher::new_mainnet()),
|
||||
// /\ ADDED
|
||||
debug: gateway_tasks::Debug {
|
||||
message_retrieval_limit: old_cfg.gateway_tasks.debug.message_retrieval_limit,
|
||||
maximum_open_connections: old_cfg.gateway_tasks.debug.maximum_open_connections,
|
||||
minimum_mix_performance: old_cfg.gateway_tasks.debug.minimum_mix_performance,
|
||||
max_request_timestamp_skew: old_cfg.gateway_tasks.debug.max_request_timestamp_skew,
|
||||
stale_messages: StaleMessageDebug {
|
||||
cleaner_run_interval: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.stale_messages
|
||||
.cleaner_run_interval,
|
||||
max_age: old_cfg.gateway_tasks.debug.stale_messages.max_age,
|
||||
},
|
||||
client_bandwidth: ClientBandwidthDebug {
|
||||
max_flushing_rate: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.client_bandwidth
|
||||
.max_flushing_rate,
|
||||
max_delta_flushing_amount: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.client_bandwidth
|
||||
.max_delta_flushing_amount,
|
||||
},
|
||||
zk_nym_tickets: ZkNymTicketHandlerDebug {
|
||||
revocation_bandwidth_penalty: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.zk_nym_tickets
|
||||
.revocation_bandwidth_penalty,
|
||||
pending_poller: old_cfg.gateway_tasks.debug.zk_nym_tickets.pending_poller,
|
||||
minimum_api_quorum: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.zk_nym_tickets
|
||||
.minimum_api_quorum,
|
||||
minimum_redemption_tickets: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.zk_nym_tickets
|
||||
.minimum_redemption_tickets,
|
||||
maximum_time_between_redemption: old_cfg
|
||||
.gateway_tasks
|
||||
.debug
|
||||
.zk_nym_tickets
|
||||
.maximum_time_between_redemption,
|
||||
},
|
||||
// \/ ADDED (be explicit about the value rather than using ..Default::default()
|
||||
upgrade_mode_min_staleness_recheck: gateway_tasks::Debug::default()
|
||||
.upgrade_mode_min_staleness_recheck,
|
||||
// /\ ADDED
|
||||
},
|
||||
},
|
||||
service_providers: ServiceProvidersConfig {
|
||||
storage_paths: ServiceProvidersPaths {
|
||||
clients_storage: old_cfg.service_providers.storage_paths.clients_storage,
|
||||
stats_storage: old_cfg.service_providers.storage_paths.stats_storage,
|
||||
network_requester: NetworkRequesterPaths {
|
||||
private_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.private_ed25519_identity_key_file,
|
||||
public_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.public_ed25519_identity_key_file,
|
||||
private_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.private_x25519_diffie_hellman_key_file,
|
||||
public_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.public_x25519_diffie_hellman_key_file,
|
||||
ack_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.ack_key_file,
|
||||
reply_surb_database: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.reply_surb_database,
|
||||
gateway_registrations: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.network_requester
|
||||
.gateway_registrations,
|
||||
},
|
||||
ip_packet_router: IpPacketRouterPaths {
|
||||
private_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.private_ed25519_identity_key_file,
|
||||
public_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.public_ed25519_identity_key_file,
|
||||
private_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.private_x25519_diffie_hellman_key_file,
|
||||
public_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.public_x25519_diffie_hellman_key_file,
|
||||
ack_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.ack_key_file,
|
||||
reply_surb_database: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.reply_surb_database,
|
||||
gateway_registrations: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.ip_packet_router
|
||||
.gateway_registrations,
|
||||
},
|
||||
authenticator: AuthenticatorPaths {
|
||||
private_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.private_ed25519_identity_key_file,
|
||||
public_ed25519_identity_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.public_ed25519_identity_key_file,
|
||||
private_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.private_x25519_diffie_hellman_key_file,
|
||||
public_x25519_diffie_hellman_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.public_x25519_diffie_hellman_key_file,
|
||||
ack_key_file: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.ack_key_file,
|
||||
reply_surb_database: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.reply_surb_database,
|
||||
gateway_registrations: old_cfg
|
||||
.service_providers
|
||||
.storage_paths
|
||||
.authenticator
|
||||
.gateway_registrations,
|
||||
},
|
||||
},
|
||||
open_proxy: old_cfg.service_providers.open_proxy,
|
||||
upstream_exit_policy_url: old_cfg.service_providers.upstream_exit_policy_url,
|
||||
network_requester: NetworkRequester {
|
||||
debug: NetworkRequesterDebug {
|
||||
enabled: old_cfg.service_providers.network_requester.debug.enabled,
|
||||
disable_poisson_rate: old_cfg
|
||||
.service_providers
|
||||
.network_requester
|
||||
.debug
|
||||
.disable_poisson_rate,
|
||||
client_debug: old_cfg
|
||||
.service_providers
|
||||
.network_requester
|
||||
.debug
|
||||
.client_debug,
|
||||
},
|
||||
},
|
||||
ip_packet_router: IpPacketRouter {
|
||||
debug: IpPacketRouterDebug {
|
||||
enabled: old_cfg.service_providers.ip_packet_router.debug.enabled,
|
||||
disable_poisson_rate: old_cfg
|
||||
.service_providers
|
||||
.ip_packet_router
|
||||
.debug
|
||||
.disable_poisson_rate,
|
||||
client_debug: old_cfg
|
||||
.service_providers
|
||||
.ip_packet_router
|
||||
.debug
|
||||
.client_debug,
|
||||
},
|
||||
},
|
||||
authenticator: Authenticator {
|
||||
debug: AuthenticatorDebug {
|
||||
enabled: old_cfg.service_providers.authenticator.debug.enabled,
|
||||
disable_poisson_rate: old_cfg
|
||||
.service_providers
|
||||
.authenticator
|
||||
.debug
|
||||
.disable_poisson_rate,
|
||||
client_debug: old_cfg.service_providers.authenticator.debug.client_debug,
|
||||
},
|
||||
},
|
||||
debug: service_providers::Debug {
|
||||
message_retrieval_limit: old_cfg.service_providers.debug.message_retrieval_limit,
|
||||
},
|
||||
},
|
||||
metrics: Default::default(),
|
||||
logging: LoggingSettings {},
|
||||
debug: Default::default(),
|
||||
};
|
||||
Ok(cfg)
|
||||
}
|
||||
@@ -17,7 +17,8 @@ async fn try_upgrade_config(path: &Path) -> Result<(), NymNodeError> {
|
||||
let cfg = try_upgrade_config_v7(path, cfg).await.ok();
|
||||
let cfg = try_upgrade_config_v8(path, cfg).await.ok();
|
||||
let cfg = try_upgrade_config_v9(path, cfg).await.ok();
|
||||
match try_upgrade_config_v10(path, cfg).await {
|
||||
let cfg = try_upgrade_config_v10(path, cfg).await.ok();
|
||||
match try_upgrade_config_v11(path, cfg).await {
|
||||
Ok(cfg) => cfg.save(),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to finish upgrade: {e}");
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use nym_authenticator_client::{AuthClientMixnetListener, AuthenticatorClient};
|
||||
use nym_authenticator_client::{
|
||||
AuthClientMixnetListener, AuthClientMixnetListenerHandle, AuthenticatorClient,
|
||||
};
|
||||
use nym_bandwidth_controller::BandwidthTicketProvider;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_ip_packet_client::IprClientConnect;
|
||||
@@ -35,9 +37,22 @@ pub struct RegistrationClient {
|
||||
event_rx: EventReceiver,
|
||||
}
|
||||
|
||||
enum MixnetClientHandle {
|
||||
Authenticator(AuthClientMixnetListenerHandle),
|
||||
Sdk(Box<MixnetClient>),
|
||||
}
|
||||
|
||||
impl MixnetClientHandle {
|
||||
async fn stop(self) {
|
||||
match self {
|
||||
Self::Authenticator(handle) => handle.stop().await,
|
||||
Self::Sdk(handle) => handle.disconnect().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
// Bundle of an actual error and the underlying mixnet client so it can be shutdown correctly if needed
|
||||
struct RegistrationError {
|
||||
mixnet_client: Option<MixnetClient>,
|
||||
mixnet_client_handle: MixnetClientHandle,
|
||||
source: crate::RegistrationClientError,
|
||||
}
|
||||
|
||||
@@ -49,7 +64,7 @@ impl RegistrationClient {
|
||||
|
||||
let Some(ipr_address) = self.config.exit.node.ipr_address else {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client: Some(self.mixnet_client),
|
||||
mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)),
|
||||
source: RegistrationClientError::NoIpPacketRouterAddress {
|
||||
node_id: self.config.exit.node.identity.to_base58_string(),
|
||||
},
|
||||
@@ -67,13 +82,17 @@ impl RegistrationClient {
|
||||
Some(Ok(addr)) => addr,
|
||||
Some(Err(e)) => {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client: Some(ipr_client.into_mixnet_client()),
|
||||
mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(
|
||||
ipr_client.into_mixnet_client(),
|
||||
)),
|
||||
source: RegistrationClientError::ConnectToIpPacketRouter(e),
|
||||
});
|
||||
}
|
||||
None => {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client: Some(ipr_client.into_mixnet_client()),
|
||||
mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(
|
||||
ipr_client.into_mixnet_client(),
|
||||
)),
|
||||
source: RegistrationClientError::Cancelled,
|
||||
});
|
||||
}
|
||||
@@ -97,7 +116,7 @@ impl RegistrationClient {
|
||||
async fn register_wg(self) -> Result<RegistrationResult, RegistrationError> {
|
||||
let Some(entry_auth_address) = self.config.entry.node.authenticator_address else {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client: Some(self.mixnet_client),
|
||||
mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)),
|
||||
source: RegistrationClientError::AuthenticationNotPossible {
|
||||
node_id: self.config.entry.node.identity.to_base58_string(),
|
||||
},
|
||||
@@ -106,7 +125,7 @@ impl RegistrationClient {
|
||||
|
||||
let Some(exit_auth_address) = self.config.exit.node.authenticator_address else {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client: Some(self.mixnet_client),
|
||||
mixnet_client_handle: MixnetClientHandle::Sdk(Box::new(self.mixnet_client)),
|
||||
source: RegistrationClientError::AuthenticationNotPossible {
|
||||
node_id: self.config.exit.node.identity.to_base58_string(),
|
||||
},
|
||||
@@ -150,35 +169,50 @@ impl RegistrationClient {
|
||||
let exit_fut = exit_auth_client
|
||||
.register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardExit);
|
||||
|
||||
let (entry, exit) = Box::pin(
|
||||
let (entry, exit) = match Box::pin(
|
||||
self.cancel_token
|
||||
.run_until_cancelled(async { tokio::join!(entry_fut, exit_fut) }),
|
||||
)
|
||||
.await
|
||||
.ok_or(RegistrationError {
|
||||
mixnet_client: None,
|
||||
source: RegistrationClientError::Cancelled,
|
||||
})?;
|
||||
{
|
||||
Some((entry, exit)) => (entry, exit),
|
||||
None => {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener),
|
||||
source: RegistrationClientError::Cancelled,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let entry = entry.map_err(|source| RegistrationError {
|
||||
mixnet_client: None,
|
||||
source: RegistrationClientError::from_authenticator_error(
|
||||
source,
|
||||
self.config.entry.node.identity.to_base58_string(),
|
||||
entry_auth_address,
|
||||
true,
|
||||
),
|
||||
})?;
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(source) => {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener),
|
||||
source: RegistrationClientError::from_authenticator_error(
|
||||
source,
|
||||
self.config.entry.node.identity.to_base58_string(),
|
||||
entry_auth_address,
|
||||
true,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let exit = exit.map_err(|source| RegistrationError {
|
||||
mixnet_client: None,
|
||||
source: RegistrationClientError::from_authenticator_error(
|
||||
source,
|
||||
self.config.exit.node.identity.to_base58_string(),
|
||||
exit_auth_address,
|
||||
false,
|
||||
),
|
||||
})?;
|
||||
let exit = match exit {
|
||||
Ok(exit) => exit,
|
||||
Err(source) => {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client_handle: MixnetClientHandle::Authenticator(mixnet_listener),
|
||||
source: RegistrationClientError::from_authenticator_error(
|
||||
source,
|
||||
self.config.exit.node.identity.to_base58_string(),
|
||||
exit_auth_address,
|
||||
false,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Ok(RegistrationResult::Wireguard(Box::new(
|
||||
WireguardRegistrationResult {
|
||||
@@ -199,15 +233,14 @@ impl RegistrationClient {
|
||||
self.register_mix_exit().await
|
||||
};
|
||||
|
||||
// If we failed to register, and we were the owner of the mixnet client, shut it down
|
||||
// If we failed to register, shut down the mixnet client and wait for it to exit
|
||||
match registration_result {
|
||||
Ok(result) => Ok(result),
|
||||
Err(error) => {
|
||||
debug!("Registration failed");
|
||||
if let Some(mixnet_client) = error.mixnet_client {
|
||||
debug!("Shutting down mixnet client");
|
||||
mixnet_client.disconnect().await;
|
||||
}
|
||||
debug!("Shutting down mixnet client");
|
||||
error.mixnet_client_handle.stop().await;
|
||||
debug!("Mixnet client stopped");
|
||||
Err(error.source)
|
||||
}
|
||||
}
|
||||
|
||||
nym-statistics-api/.sqlx/query-14d75cdd34201313e34ae7f0b931c9df43603232e3be42b0573013cd74226518.json
Generated
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO report_v2(\n received_at,\n source_ip,\n from_mixnet,\n country_code,\n report_version,\n device_id,\n os_type,\n os_version,\n architecture,\n app_version,\n user_agent,\n start_day_utc,\n connection_time_ms,\n tunnel_type,\n retry_attempt,\n session_duration_min,\n disconnection_time_ms,\n exit_id,\n follow_up_id,\n error)\n VALUES ($1::timestamptz, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Timestamptz",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Date",
|
||||
"Int4",
|
||||
"Text",
|
||||
"Int4",
|
||||
"Int4",
|
||||
"Int4",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "14d75cdd34201313e34ae7f0b931c9df43603232e3be42b0573013cd74226518"
|
||||
}
|
||||
nym-statistics-api/.sqlx/query-dce9f3dae7ae0dc5953d1f69e843bea9553fb05a2f656cfff6598f12a21c99ba.json
Generated
-19
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO connection_stats (\n received_at,\n connection_time_ms,\n two_hop,\n source_ip,\n country_code,\n from_mixnet) VALUES ($1::timestamptz, $2, $3, $4, $5, $6)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Timestamptz",
|
||||
"Int4",
|
||||
"Bool",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "dce9f3dae7ae0dc5953d1f69e843bea9553fb05a2f656cfff6598f12a21c99ba"
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-statistics-api"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
@@ -20,14 +20,13 @@ axum-client-ip.workspace = true
|
||||
axum-extra = { workspace = true, features = ["typed-header"] }
|
||||
celes.workspace = true
|
||||
clap = { workspace = true, features = ["cargo", "derive", "env", "string"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"postgres",
|
||||
"time",
|
||||
] }
|
||||
time.workspace = true
|
||||
time = { workspace = true, features = ["serde-human-readable"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||
tokio-util.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
CREATE TABLE report_v2 (
|
||||
-- some info about the report, inferred from when/from where we got it
|
||||
received_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
source_ip TEXT,
|
||||
from_mixnet BOOLEAN,
|
||||
country_code TEXT,
|
||||
report_version TEXT,
|
||||
|
||||
-- some infos about the device sending the report
|
||||
device_id TEXT NOT NULL,
|
||||
os_type TEXT,
|
||||
os_version TEXT,
|
||||
architecture TEXT,
|
||||
app_version TEXT,
|
||||
user_agent TEXT,
|
||||
|
||||
-- session info
|
||||
start_day_utc DATE,
|
||||
connection_time_ms INTEGER,
|
||||
tunnel_type TEXT,
|
||||
retry_attempt INTEGER,
|
||||
session_duration_min INTEGER,
|
||||
disconnection_time_ms INTEGER,
|
||||
exit_id TEXT,
|
||||
follow_up_id TEXT,
|
||||
error TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_report_v2_received_at ON report_v2 (received_at);
|
||||
@@ -24,7 +24,7 @@ impl RouterBuilder {
|
||||
)
|
||||
.route(
|
||||
"/",
|
||||
axum::routing::get(|| async { Redirect::permanent("/swagger") }), // SW let's redirect to a blogpost explaining the stats collection process once it exists
|
||||
axum::routing::get(|| async { Redirect::permanent("/swagger") }),
|
||||
)
|
||||
.nest("/v1", Router::new().nest("/stats", stats::routes()));
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use axum::{Json, Router, extract::State};
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use axum_extra::{TypedHeader, headers::UserAgent};
|
||||
use nym_statistics_common::report::vpn_client::VpnClientStatsReport;
|
||||
use nym_statistics_common::report::vpn_client::{VpnClientStatsReport, VpnClientStatsReportV2};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
@@ -9,11 +9,13 @@ use crate::{
|
||||
error::{HttpError, HttpResult},
|
||||
state::AppState,
|
||||
},
|
||||
storage::models::{ConnectionInfoDto, DailyActiveDeviceDto, StatsReportV1Dto},
|
||||
storage::models::{DailyActiveDeviceDto, StatsReportV1Dto, StatsReportV2Dto},
|
||||
};
|
||||
|
||||
pub(crate) fn routes() -> Router<AppState> {
|
||||
Router::new().route("/report", axum::routing::post(submit_stats_report))
|
||||
Router::new()
|
||||
.route("/report", axum::routing::post(submit_stats_report))
|
||||
.route("/session", axum::routing::post(submit_session_report))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -44,19 +46,11 @@ async fn submit_stats_report(
|
||||
let maybe_location = gateway_record.unwrap_or_default();
|
||||
|
||||
if from_mixnet {
|
||||
debug!("Received a report from the network");
|
||||
debug!("Received a V1 report from the network");
|
||||
} else {
|
||||
debug!("Received a report from outside of the network");
|
||||
debug!("Received a V1 report from outside of the network");
|
||||
}
|
||||
|
||||
let active_device = DailyActiveDeviceDto::new(now, &report, user_agent.clone(), from_mixnet);
|
||||
let maybe_connection_info = ConnectionInfoDto::maybe_new(
|
||||
now,
|
||||
&report,
|
||||
insecure_ip_addr.0,
|
||||
maybe_location,
|
||||
from_mixnet,
|
||||
);
|
||||
|
||||
let stats_report = StatsReportV1Dto::new(
|
||||
now,
|
||||
@@ -75,7 +69,64 @@ async fn submit_stats_report(
|
||||
|
||||
state
|
||||
.storage()
|
||||
.store_legacy_vpn_client_report(active_device, maybe_connection_info)
|
||||
.store_active_device(active_device)
|
||||
.await
|
||||
.map_err(HttpError::internal_with_logging)?;
|
||||
|
||||
Ok(Json(()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
request_body = VpnClientStatsReportV2,
|
||||
tag = "Stats",
|
||||
path = "/session",
|
||||
context_path = "/v1/stats",
|
||||
responses(
|
||||
(status = 200)
|
||||
)
|
||||
)]
|
||||
#[tracing::instrument(level = "info", skip_all)]
|
||||
async fn submit_session_report(
|
||||
State(mut state): State<AppState>,
|
||||
TypedHeader(user_agent): TypedHeader<UserAgent>,
|
||||
insecure_ip_addr: InsecureClientIp, // This is the reverse proxy IP for now, but maybe in the future?
|
||||
Json(report): Json<VpnClientStatsReportV2>,
|
||||
) -> HttpResult<Json<()>> {
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
let gateway_record = state
|
||||
.network_view()
|
||||
.get_country_by_id(&report.session_report.exit_id)
|
||||
.await;
|
||||
|
||||
let from_mixnet = gateway_record.is_some();
|
||||
let maybe_location = gateway_record.unwrap_or_default();
|
||||
|
||||
if from_mixnet {
|
||||
debug!("Received a V2 report from the network");
|
||||
} else {
|
||||
debug!("Received a V2 report from outside of the network");
|
||||
}
|
||||
let active_device = DailyActiveDeviceDto::new_v2(now, &report, user_agent.clone(), from_mixnet);
|
||||
|
||||
let stats_report = StatsReportV2Dto::new(
|
||||
now,
|
||||
&report,
|
||||
user_agent,
|
||||
from_mixnet,
|
||||
insecure_ip_addr.0,
|
||||
maybe_location,
|
||||
);
|
||||
|
||||
state
|
||||
.storage()
|
||||
.store_active_device(active_device)
|
||||
.await
|
||||
.map_err(HttpError::internal_with_logging)?;
|
||||
|
||||
state
|
||||
.storage()
|
||||
.store_vpn_client_report_v2(stats_report)
|
||||
.await
|
||||
.map_err(HttpError::internal_with_logging)?;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ use tracing::{error, info, trace, warn};
|
||||
const NETWORK_CACHE_TTL: Duration = Duration::from_secs(600);
|
||||
|
||||
type IpToCountryMap = HashMap<IpAddr, Option<Country>>;
|
||||
type IdToCountryMap = HashMap<String, Option<Country>>;
|
||||
|
||||
// SW this should use a proper NS API client once it exists
|
||||
struct NodesQuerier {
|
||||
@@ -45,19 +46,24 @@ impl NetworkView {
|
||||
fn new_empty() -> Self {
|
||||
NetworkView {
|
||||
inner: Arc::new(RwLock::new(NetworkViewInner {
|
||||
network_nodes: HashMap::new(),
|
||||
ip_to_country: HashMap::new(),
|
||||
id_to_country: HashMap::new(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_country_by_ip(&self, ip_addr: &IpAddr) -> Option<Option<Country>> {
|
||||
self.inner.read().await.network_nodes.get(ip_addr).copied()
|
||||
self.inner.read().await.ip_to_country.get(ip_addr).copied()
|
||||
}
|
||||
pub(crate) async fn get_country_by_id(&self, id_key: &str) -> Option<Option<Country>> {
|
||||
self.inner.read().await.id_to_country.get(id_key).copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NetworkViewInner {
|
||||
network_nodes: IpToCountryMap,
|
||||
ip_to_country: IpToCountryMap,
|
||||
id_to_country: IdToCountryMap,
|
||||
}
|
||||
|
||||
pub struct NetworkRefresher {
|
||||
@@ -112,7 +118,7 @@ impl NetworkRefresher {
|
||||
let nodes = querier.current_nymnodes().await?;
|
||||
|
||||
// collect all known/allowed nodes information
|
||||
let known_nodes = nodes
|
||||
let ip_to_country = nodes
|
||||
.iter()
|
||||
.flat_map(|n| {
|
||||
n.description
|
||||
@@ -124,8 +130,19 @@ impl NetworkRefresher {
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let id_to_country = nodes
|
||||
.iter()
|
||||
.map(|n| {
|
||||
(
|
||||
n.ed25519_identity_key().to_base58_string(),
|
||||
n.description.auxiliary_details.location,
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut network_guard = self.network.inner.write().await;
|
||||
network_guard.network_nodes = known_nodes;
|
||||
network_guard.ip_to_country = ip_to_country;
|
||||
network_guard.id_to_country = id_to_country;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use anyhow::{Result, anyhow};
|
||||
use models::{ConnectionInfoDto, DailyActiveDeviceDto};
|
||||
use sqlx::{
|
||||
Executor,
|
||||
migrate::Migrator,
|
||||
@@ -7,7 +6,7 @@ use sqlx::{
|
||||
};
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use crate::storage::models::StatsReportV1Dto;
|
||||
use crate::storage::models::{DailyActiveDeviceDto, StatsReportV1Dto, StatsReportV2Dto};
|
||||
|
||||
pub(crate) mod models;
|
||||
|
||||
@@ -62,19 +61,10 @@ impl StatisticsStorage {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn store_legacy_vpn_client_report(
|
||||
&mut self,
|
||||
pub(crate) async fn store_active_device(
|
||||
&self,
|
||||
active_device: DailyActiveDeviceDto,
|
||||
connection_info: Option<ConnectionInfoDto>,
|
||||
) -> Result<()> {
|
||||
self.store_device(active_device).await?;
|
||||
if let Some(connection_info) = connection_info {
|
||||
self.store_connection_stats(connection_info).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn store_device(&self, active_device: DailyActiveDeviceDto) -> Result<()> {
|
||||
sqlx::query!(
|
||||
r#"INSERT INTO active_device (
|
||||
day,
|
||||
@@ -101,27 +91,6 @@ impl StatisticsStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn store_connection_stats(&self, connection_info: ConnectionInfoDto) -> Result<()> {
|
||||
sqlx::query!(
|
||||
r#"INSERT INTO connection_stats (
|
||||
received_at,
|
||||
connection_time_ms,
|
||||
two_hop,
|
||||
source_ip,
|
||||
country_code,
|
||||
from_mixnet) VALUES ($1::timestamptz, $2, $3, $4, $5, $6)"#,
|
||||
connection_info.received_at as time::OffsetDateTime,
|
||||
connection_info.connection_time_ms,
|
||||
connection_info.two_hop,
|
||||
connection_info.received_from,
|
||||
connection_info.country_code,
|
||||
connection_info.from_mixnet
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn store_vpn_client_report(
|
||||
&mut self,
|
||||
report_v1: StatsReportV1Dto,
|
||||
@@ -158,4 +127,57 @@ impl StatisticsStorage {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn store_vpn_client_report_v2(
|
||||
&self,
|
||||
report_v2: StatsReportV2Dto,
|
||||
) -> Result<()> {
|
||||
sqlx::query!(
|
||||
r#"INSERT INTO report_v2(
|
||||
received_at,
|
||||
source_ip,
|
||||
from_mixnet,
|
||||
country_code,
|
||||
report_version,
|
||||
device_id,
|
||||
os_type,
|
||||
os_version,
|
||||
architecture,
|
||||
app_version,
|
||||
user_agent,
|
||||
start_day_utc,
|
||||
connection_time_ms,
|
||||
tunnel_type,
|
||||
retry_attempt,
|
||||
session_duration_min,
|
||||
disconnection_time_ms,
|
||||
exit_id,
|
||||
follow_up_id,
|
||||
error)
|
||||
VALUES ($1::timestamptz, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)"#,
|
||||
report_v2.received_at as time::OffsetDateTime,
|
||||
report_v2.received_from,
|
||||
report_v2.from_mixnet,
|
||||
report_v2.country_code,
|
||||
report_v2.report_version,
|
||||
report_v2.stats_id,
|
||||
report_v2.os_type,
|
||||
report_v2.os_version,
|
||||
report_v2.os_arch,
|
||||
report_v2.app_version,
|
||||
report_v2.user_agent,
|
||||
report_v2.start_day_utc,
|
||||
report_v2.connection_time_ms,
|
||||
report_v2.tunnel_type,
|
||||
report_v2.retry_attempt,
|
||||
report_v2.session_duration_min,
|
||||
report_v2.disconnection_time_ms,
|
||||
report_v2.exit_id,
|
||||
report_v2.follow_up_id,
|
||||
report_v2.error
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::net::IpAddr;
|
||||
|
||||
use axum_extra::headers::UserAgent;
|
||||
use celes::Country;
|
||||
use nym_statistics_common::report::vpn_client::VpnClientStatsReport;
|
||||
use nym_statistics_common::report::vpn_client::{VpnClientStatsReport, VpnClientStatsReportV2};
|
||||
use time::{Date, OffsetDateTime};
|
||||
|
||||
pub type StatsId = String;
|
||||
@@ -37,38 +37,25 @@ impl DailyActiveDeviceDto {
|
||||
from_mixnet,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
pub(crate) struct ConnectionInfoDto {
|
||||
pub(crate) received_at: OffsetDateTime,
|
||||
pub(crate) received_from: String,
|
||||
pub(crate) connection_time_ms: Option<i32>,
|
||||
pub(crate) two_hop: bool,
|
||||
pub(crate) country_code: Option<String>,
|
||||
pub(crate) from_mixnet: bool,
|
||||
}
|
||||
|
||||
impl ConnectionInfoDto {
|
||||
pub(crate) fn maybe_new(
|
||||
pub(crate) fn new_v2(
|
||||
received_at: OffsetDateTime,
|
||||
stats_report: &VpnClientStatsReport,
|
||||
received_from: IpAddr,
|
||||
maybe_country: Option<Country>,
|
||||
stats_report: &VpnClientStatsReportV2,
|
||||
user_agent: UserAgent,
|
||||
from_mixnet: bool,
|
||||
) -> Option<Self> {
|
||||
stats_report.basic_usage.as_ref().map(|usage_report| Self {
|
||||
received_at,
|
||||
received_from: received_from.to_string(),
|
||||
connection_time_ms: usage_report.connection_time_ms,
|
||||
two_hop: usage_report.two_hop,
|
||||
country_code: maybe_country.map(|c| c.alpha2.into()),
|
||||
) -> Self {
|
||||
Self {
|
||||
day: received_at.date(),
|
||||
stats_id: stats_report.stats_id.clone(),
|
||||
os_type: stats_report.static_information.os_type.clone(),
|
||||
os_version: stats_report.static_information.os_version.clone(),
|
||||
os_arch: stats_report.static_information.os_arch.clone(),
|
||||
app_version: stats_report.static_information.app_version.clone(),
|
||||
user_agent: user_agent.to_string(),
|
||||
from_mixnet,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New structure. The two above will be removed when it is confirmed to work
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
pub(crate) struct StatsReportV1Dto {
|
||||
pub(crate) received_at: OffsetDateTime,
|
||||
@@ -116,3 +103,66 @@ impl StatsReportV1Dto {
|
||||
report
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
pub(crate) struct StatsReportV2Dto {
|
||||
// Report metadata
|
||||
pub(crate) received_at: OffsetDateTime,
|
||||
pub(crate) received_from: String,
|
||||
pub(crate) from_mixnet: bool,
|
||||
pub(crate) country_code: Option<String>,
|
||||
pub(crate) report_version: String,
|
||||
|
||||
// Device info
|
||||
pub(crate) stats_id: StatsId,
|
||||
pub(crate) os_type: String,
|
||||
pub(crate) os_version: Option<String>,
|
||||
pub(crate) os_arch: String,
|
||||
pub(crate) app_version: String,
|
||||
pub(crate) user_agent: String,
|
||||
|
||||
// session info
|
||||
pub(crate) start_day_utc: Date,
|
||||
pub(crate) connection_time_ms: i32,
|
||||
pub(crate) tunnel_type: String,
|
||||
pub(crate) retry_attempt: i32,
|
||||
pub(crate) session_duration_min: i32,
|
||||
pub(crate) disconnection_time_ms: i32,
|
||||
pub(crate) exit_id: String,
|
||||
pub(crate) follow_up_id: Option<String>,
|
||||
pub(crate) error: Option<String>,
|
||||
}
|
||||
|
||||
impl StatsReportV2Dto {
|
||||
pub(crate) fn new(
|
||||
received_at: OffsetDateTime,
|
||||
stats_report: &VpnClientStatsReportV2,
|
||||
user_agent: UserAgent,
|
||||
from_mixnet: bool,
|
||||
received_from: IpAddr,
|
||||
maybe_country: Option<Country>,
|
||||
) -> Self {
|
||||
Self {
|
||||
received_at,
|
||||
received_from: received_from.to_string(),
|
||||
from_mixnet,
|
||||
country_code: maybe_country.map(|c| c.alpha2.into()),
|
||||
report_version: stats_report.api_version.clone(),
|
||||
stats_id: stats_report.stats_id.clone(),
|
||||
os_type: stats_report.static_information.os_type.clone(),
|
||||
os_version: stats_report.static_information.os_version.clone(),
|
||||
os_arch: stats_report.static_information.os_arch.clone(),
|
||||
app_version: stats_report.static_information.app_version.clone(),
|
||||
user_agent: user_agent.to_string(),
|
||||
start_day_utc: stats_report.session_report.start_day_utc,
|
||||
connection_time_ms: stats_report.session_report.connection_time_ms,
|
||||
tunnel_type: stats_report.session_report.tunnel_type.clone(),
|
||||
retry_attempt: stats_report.session_report.retry_attempt,
|
||||
session_duration_min: stats_report.session_report.session_duration_min,
|
||||
disconnection_time_ms: stats_report.session_report.disconnection_time_ms,
|
||||
exit_id: stats_report.session_report.exit_id.clone(),
|
||||
follow_up_id: stats_report.session_report.follow_up_id.clone(),
|
||||
error: stats_report.session_report.error.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+12
-34
@@ -3860,11 +3860,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4051,12 +4051,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
version = "0.50.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5061,12 +5060,6 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
version = "0.13.2"
|
||||
@@ -5953,17 +5946,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5974,15 +5958,9 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
@@ -7951,14 +7929,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
|
||||
@@ -11,7 +11,7 @@ use nym_config::defaults::{NymNetworkDetails, COSMOS_DERIVATION_PATH};
|
||||
use nym_types::account::{Account, AccountEntry, Balance};
|
||||
use nym_validator_client::nyxd::CosmWasmClient;
|
||||
use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use nym_validator_client::signing::AccountData;
|
||||
use nym_validator_client::signing::signer::OfflineSigner;
|
||||
use nym_validator_client::DirectSigningHttpRpcValidatorClient;
|
||||
use nym_wallet_types::network::Network as WalletNetwork;
|
||||
use std::collections::HashMap;
|
||||
@@ -575,10 +575,10 @@ fn derive_address(
|
||||
prefix: &str,
|
||||
) -> Result<cosmrs::AccountId, BackendError> {
|
||||
// note: the ephemeral wallet will zeroize the mnemonic on drop
|
||||
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)
|
||||
.try_derive_accounts()?
|
||||
DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)
|
||||
.map_err(|_| BackendError::FailedToDeriveAddress)?
|
||||
.signer_addresses()
|
||||
.first()
|
||||
.map(AccountData::address)
|
||||
.cloned()
|
||||
.ok_or(BackendError::FailedToDeriveAddress)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ pub async fn sign(
|
||||
) -> Result<String, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let derived_accounts = client.nyxd.get_accounts()?;
|
||||
let derived_accounts = client.nyxd.get_accounts();
|
||||
let account = derived_accounts.first().ok_or_else(|| {
|
||||
log::error!(">>> Unable to derive account");
|
||||
BackendError::SignatureError("unable to derive account".to_string())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user